main
nico 12 months ago
parent c1df23a5a4
commit 0a3862a5b2
  1. 59
      .coveragerc
  2. 13
      .dockerignore
  3. 9
      .flake8
  4. 94
      .gitignore
  5. 26
      .pre-commit-config.yaml
  6. 76
      CODE_OF_CONDUCT.md
  7. 98
      CONTRIBUTING.md
  8. 33
      DATA_COLLECTION.md
  9. 146
      DOCKER.md
  10. 76
      Dockerfile
  11. 201
      LICENSE
  12. 51
      Makefile
  13. 138
      README.md
  14. BIN
      assets/Pangolin-logo.png
  15. BIN
      assets/altmarkets_logo1.png
  16. BIN
      assets/ascendex-logo.jpg
  17. BIN
      assets/balancer-logo.jpg
  18. BIN
      assets/bamboorelay-logo.jpg
  19. BIN
      assets/beaxy-logo.png
  20. BIN
      assets/binance-logo.jpg
  21. BIN
      assets/binance_futures-logo.jpg
  22. BIN
      assets/binance_us-logo.jpg
  23. BIN
      assets/bitfinex-logo.jpg
  24. BIN
      assets/bitget-logo.png
  25. BIN
      assets/bitmart-logo.jpg
  26. BIN
      assets/bitmex-logo.png
  27. BIN
      assets/bittrex_global-logo.jpg
  28. BIN
      assets/btcmarkets-logo.jpg
  29. BIN
      assets/bybit-logo.jpg
  30. BIN
      assets/celo-logo.jpg
  31. BIN
      assets/coinbase_pro-logo.jpg
  32. BIN
      assets/coinzoom-logo.jpg
  33. BIN
      assets/cryptocom-logo.jpg
  34. BIN
      assets/digifinex-logo.jpg
  35. BIN
      assets/dolomite-logo.jpg
  36. BIN
      assets/dydx-logo.jpg
  37. BIN
      assets/eve_exchange_logo.png
  38. BIN
      assets/foxbit-exchange-logo.png
  39. BIN
      assets/ftx-logo.jpg
  40. BIN
      assets/gate-io-logo.jpg
  41. BIN
      assets/himalaya_exchange-logo.jpg
  42. BIN
      assets/hitbtc-logo.jpg
  43. BIN
      assets/huobi_global-logo.jpg
  44. BIN
      assets/injective.jpg
  45. BIN
      assets/kraken-logo.jpg
  46. BIN
      assets/kucoin-logo.jpg
  47. BIN
      assets/latoken-logo.png
  48. BIN
      assets/lbank.jpg
  49. BIN
      assets/lbank.png
  50. BIN
      assets/liquid-logo.jpg
  51. BIN
      assets/loopring-logo.jpg
  52. BIN
      assets/mexc.jpg
  53. BIN
      assets/mm-finance-logo.png
  54. BIN
      assets/ndax-logo.jpg
  55. BIN
      assets/okex-logo.jpg
  56. BIN
      assets/pancakeswap-logo.png
  57. BIN
      assets/perpetual_protocol-logo.jpg
  58. BIN
      assets/probit-logo.jpg
  59. BIN
      assets/probit_kr-logo.jpg
  60. BIN
      assets/quickswap-logo.png
  61. BIN
      assets/radar_logo.png
  62. BIN
      assets/ref-finance-logo.png
  63. BIN
      assets/serum-logo.jpg
  64. BIN
      assets/sushiswap-logo.jpg
  65. BIN
      assets/terra-logo.jpg
  66. BIN
      assets/traderjoe-logo.png
  67. BIN
      assets/uniswap-logo.jpg
  68. BIN
      assets/uniswap_v3-logo.jpg
  69. BIN
      assets/vvs-finance-logo.png
  70. BIN
      assets/wazirX-logo.jpg
  71. BIN
      assets/white-bit.png
  72. 1
      bin/.gitignore
  73. 0
      bin/__init__.py
  74. 13
      bin/conf_migration_script.py
  75. 99
      bin/hummingbot.py
  76. 161
      bin/hummingbot_quickstart.py
  77. 16
      bin/path_util.py
  78. 11
      clean
  79. 5
      compile
  80. 3
      compile.bat
  81. 3
      conf/.gitignore
  82. 106
      conf/__init__.py
  83. 1
      conf/connectors/.gitignore
  84. 0
      conf/connectors/__init__.py
  85. 1
      conf/strategies/.gitignore
  86. 0
      conf/strategies/__init__.py
  87. 47
      docker-compose.yml
  88. 1
      docker/etc/sudoers.d/hummingbot
  89. 5
      hooks/README.md
  90. 6
      hooks/build
  91. 29
      hummingbot/README.md
  92. 1
      hummingbot/VERSION
  93. 182
      hummingbot/__init__.py
  94. 26
      hummingbot/client/__init__.py
  95. 43
      hummingbot/client/command/__init__.py
  96. 219
      hummingbot/client/command/balance_command.py
  97. 488
      hummingbot/client/command/config_command.py
  98. 151
      hummingbot/client/command/connect_command.py
  99. 257
      hummingbot/client/command/create_command.py
  100. 39
      hummingbot/client/command/exit_command.py
  101. Some files were not shown because too many files have changed in this diff Show More

@ -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

@ -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

@ -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

94
.gitignore vendored

@ -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

@ -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]

@ -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

@ -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!

@ -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.

@ -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 <files/folders>
```
### Attach to the container
```
docker attach <container-name>
```
### Detach from the container and return to command line
Press keys <kbd>Ctrl</kbd> + <kbd>P</kbd> then <kbd>Ctrl</kbd> + <kbd>Q</kbd>
### 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 <container-name>
```
### Remove a container
```
docker rm <container-name>
```

@ -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 <federico@hummingbot.org>"
# 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

@ -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.

@ -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

@ -1,3 +1,137 @@
# hummingbot
![Hummingbot](https://i.ibb.co/X5zNkKw/blacklogo-with-text.png)
Custom HummingBot for Whitebit
----
[![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).

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 168 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 104 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 119 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 153 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 962 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 143 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 163 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

1
bin/.gitignore vendored

@ -0,0 +1 @@
dev.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_)

@ -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()

@ -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()

@ -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__, "../../")))

11
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!"

@ -0,0 +1,5 @@
#!/bin/bash
cd $(dirname "$0")
python setup.py build_ext --inplace

@ -0,0 +1,3 @@
@echo off
python setup.py build_ext --inplace -j 8

3
conf/.gitignore vendored

@ -0,0 +1,3 @@
*.yml
*.json
.password_verification

@ -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***"
"""

@ -0,0 +1 @@
*.yml

@ -0,0 +1 @@
*.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

@ -0,0 +1 @@
hummingbot ALL=(ALL:ALL) NOPASSWD: /bin/chmod, /bin/chown

@ -0,0 +1,5 @@
# Docker Autobuild Hooks
This folder containers hooks for docker autobuild.
[Hummingbot builds](https://hub.docker.com/r/coinalpha/hummingbot/builds)

@ -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 .

@ -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
│ └── <market_name> # 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
```

@ -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 []

@ -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)

@ -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,
]

@ -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

@ -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,
)

@ -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)

@ -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]

@ -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()

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save