├── .coveragerc ├── .dependabot └── config.yml ├── .dockerignore ├── .github ├── ISSUE_TEMPLATE.md └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── .pylintrc ├── .readthedocs.yml ├── .travis.yml ├── AI-Scalpel-Trading-Bot ├── LICENSE └── README.md ├── CONTRIBUTING.md ├── Dockerfile ├── Dockerfile.develop ├── Dockerfile.pi ├── Dockerfile.technical ├── LICENSE ├── MANIFEST.in ├── README.md ├── bin └── freqtrade ├── build_helpers ├── install_ta-lib.sh ├── publish_docker.sh └── ta-lib-0.4.0-src.tar.gz ├── config.json.example ├── config_binance.json.example ├── config_full.json.example ├── config_kraken.json.example ├── docs ├── assets │ ├── freqtrade-screenshot.png │ ├── plot-dataframe.png │ └── plot-profit.png ├── backtesting.md ├── bot-usage.md ├── configuration.md ├── data-analysis.md ├── deprecated.md ├── developer.md ├── docker.md ├── edge.md ├── faq.md ├── hyperopt.md ├── images │ └── logo.png ├── index.md ├── installation.md ├── partials │ └── header.html ├── plotting.md ├── requirements-docs.txt ├── rest-api.md ├── sandbox-testing.md ├── sql_cheatsheet.md ├── stoploss.md ├── strategy-customization.md ├── telegram-usage.md └── webhook-config.md ├── environment.yml ├── freqtrade.service ├── freqtrade.service.watchdog ├── freqtrade ├── __init__.py ├── __main__.py ├── configuration │ ├── __init__.py │ ├── arguments.py │ ├── check_exchange.py │ ├── cli_options.py │ ├── config_validation.py │ ├── configuration.py │ ├── directory_operations.py │ ├── load_config.py │ └── timerange.py ├── constants.py ├── data │ ├── __init__.py │ ├── btanalysis.py │ ├── converter.py │ ├── dataprovider.py │ └── history.py ├── edge │ └── __init__.py ├── exchange │ ├── __init__.py │ ├── binance.py │ ├── exchange.py │ └── kraken.py ├── freqtradebot.py ├── indicator_helpers.py ├── loggers.py ├── main.py ├── misc.py ├── optimize │ ├── __init__.py │ ├── backtesting.py │ ├── default_hyperopt.py │ ├── default_hyperopt_loss.py │ ├── edge_cli.py │ ├── hyperopt.py │ ├── hyperopt_interface.py │ ├── hyperopt_loss_interface.py │ ├── hyperopt_loss_onlyprofit.py │ └── hyperopt_loss_sharpe.py ├── pairlist │ ├── IPairList.py │ ├── StaticPairList.py │ ├── VolumePairList.py │ └── __init__.py ├── persistence.py ├── plot │ ├── __init__.py │ ├── plot_utils.py │ └── plotting.py ├── resolvers │ ├── __init__.py │ ├── exchange_resolver.py │ ├── hyperopt_resolver.py │ ├── iresolver.py │ ├── pairlist_resolver.py │ └── strategy_resolver.py ├── rpc │ ├── __init__.py │ ├── api_server.py │ ├── fiat_convert.py │ ├── rpc.py │ ├── rpc_manager.py │ ├── telegram.py │ └── webhook.py ├── state.py ├── strategy │ ├── __init__.py │ ├── default_strategy.py │ └── interface.py ├── tests │ ├── __init__.py │ ├── config_test_comments.json │ ├── conftest.py │ ├── data │ │ ├── __init__.py │ │ ├── test_btanalysis.py │ │ ├── test_converter.py │ │ ├── test_dataprovider.py │ │ └── test_history.py │ ├── edge │ │ ├── __init__.py │ │ └── test_edge.py │ ├── exchange │ │ ├── __init__.py │ │ ├── test_binance.py │ │ ├── test_exchange.py │ │ └── test_kraken.py │ ├── optimize │ │ ├── __init__.py │ │ ├── test_backtest_detail.py │ │ ├── test_backtesting.py │ │ ├── test_edge_cli.py │ │ └── test_hyperopt.py │ ├── pairlist │ │ ├── __init__.py │ │ └── test_pairlist.py │ ├── rpc │ │ ├── __init__.py │ │ ├── test_fiat_convert.py │ │ ├── test_rpc.py │ │ ├── test_rpc_apiserver.py │ │ ├── test_rpc_manager.py │ │ ├── test_rpc_telegram.py │ │ └── test_rpc_webhook.py │ ├── strategy │ │ ├── __init__.py │ │ ├── legacy_strategy.py │ │ ├── test_default_strategy.py │ │ ├── test_interface.py │ │ └── test_strategy.py │ ├── test_arguments.py │ ├── test_configuration.py │ ├── test_freqtradebot.py │ ├── test_indicator_helpers.py │ ├── test_main.py │ ├── test_misc.py │ ├── test_persistence.py │ ├── test_plotting.py │ ├── test_talib.py │ ├── test_timerange.py │ ├── test_utils.py │ ├── test_wallets.py │ └── testdata │ │ ├── ADA_BTC-1m.json │ │ ├── ADA_BTC-5m.json │ │ ├── DASH_BTC-1m.json │ │ ├── DASH_BTC-5m.json │ │ ├── ETC_BTC-1m.json │ │ ├── ETC_BTC-5m.json │ │ ├── ETH_BTC-1m.json │ │ ├── ETH_BTC-5m.json │ │ ├── LTC_BTC-1m.json │ │ ├── LTC_BTC-5m.json │ │ ├── NXT_BTC-1m.json │ │ ├── NXT_BTC-5m.json │ │ ├── POWR_BTC-1m.json │ │ ├── POWR_BTC-5m.json │ │ ├── UNITTEST_BTC-1m.json │ │ ├── UNITTEST_BTC-30m.json │ │ ├── UNITTEST_BTC-5m.json │ │ ├── UNITTEST_BTC-8m.json │ │ ├── UNITTEST_BTC-8m.json.gz │ │ ├── XLM_BTC-1m.json │ │ ├── XLM_BTC-5m.json │ │ ├── XMR_BTC-1m.json │ │ ├── XMR_BTC-5m.json │ │ ├── ZEC_BTC-1m.json │ │ ├── ZEC_BTC-5m.json │ │ ├── backtest-result_test.json │ │ └── pairs.json ├── utils.py ├── vendor │ ├── __init__.py │ └── qtpylib │ │ ├── __init__.py │ │ └── indicators.py ├── wallets.py └── worker.py ├── mkdocs.yml ├── requirements-common.txt ├── requirements-dev.txt ├── requirements-plot.txt ├── requirements.txt ├── scripts ├── download_backtest_data.py ├── get_market_pairs.py ├── plot_dataframe.py ├── plot_profit.py └── rest_client.py ├── setup.cfg ├── setup.py ├── setup.sh └── user_data ├── backtest_data └── .gitkeep ├── backtest_results └── .gitkeep ├── data └── .gitkeep ├── hyperopts ├── __init__.py ├── sample_hyperopt.py ├── sample_hyperopt_advanced.py └── sample_hyperopt_loss.py ├── notebooks ├── .gitkeep └── strategy_analysis_example.ipynb └── strategies ├── __init__.py └── sample_strategy.py /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | omit = 3 | scripts/* 4 | freqtrade/tests/* 5 | freqtrade/vendor/* 6 | freqtrade/__main__.py 7 | -------------------------------------------------------------------------------- /.dependabot/config.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | 3 | update_configs: 4 | - package_manager: "python" 5 | directory: "/" 6 | update_schedule: "weekly" 7 | allowed_updates: 8 | - match: 9 | update_type: "all" 10 | target_branch: "develop" 11 | 12 | - package_manager: "docker" 13 | directory: "/" 14 | update_schedule: "daily" 15 | allowed_updates: 16 | - match: 17 | update_type: "all" 18 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | Dockerfile 4 | .dockerignore 5 | config.json* 6 | *.sqlite 7 | .coveragerc 8 | .eggs 9 | .github 10 | .pylintrc 11 | .travis.yml 12 | CONTRIBUTING.md 13 | MANIFEST.in 14 | README.md 15 | freqtrade.service 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Step 1: Have you search for this issue before posting it? 2 | 3 | If you have discovered a bug in the bot, please [search our issue tracker](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue). 4 | If it hasn't been reported, please create a new issue. 5 | 6 | ## Step 2: Describe your environment 7 | 8 | * Operating system: ____ 9 | * Python Version: _____ (`python -V`) 10 | * CCXT version: _____ (`pip freeze | grep ccxt`) 11 | * Branch: Master | Develop 12 | * Last Commit ID: _____ (`git log --format="%H" -n 1`) 13 | 14 | ## Step 3: Describe the problem: 15 | 16 | *Explain the problem you have encountered* 17 | 18 | ### Steps to reproduce: 19 | 20 | 1. _____ 21 | 2. _____ 22 | 3. _____ 23 | 24 | ### Observed Results: 25 | 26 | * What happened? 27 | * What did you expect to happen? 28 | 29 | ### Relevant code exceptions or logs: 30 | 31 | ``` 32 | // paste your log here 33 | ``` 34 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | Thank you for sending your pull request. But first, have you included 2 | unit tests, and is your code PEP8 conformant? [More details](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md) 3 | 4 | ## Summary 5 | Explain in one sentence the goal of this PR 6 | 7 | Solve the issue: #___ 8 | 9 | ## Quick changelog 10 | 11 | - 12 | - 13 | 14 | ## What's new? 15 | *Explain in details what this PR solve or improve. You can include visuals.* 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Freqtrade rules 2 | freqtrade/tests/testdata/*.json 3 | hyperopt_conf.py 4 | config*.json 5 | *.sqlite 6 | .hyperopt 7 | logfile.txt 8 | hyperopt_trials.pickle 9 | user_data/* 10 | !user_data/notebooks 11 | user_data/notebooks/* 12 | !user_data/notebooks/*example.ipynb 13 | freqtrade-plot.html 14 | freqtrade-profit-plot.html 15 | 16 | # Byte-compiled / optimized / DLL files 17 | __pycache__/ 18 | *.py[cod] 19 | *$py.class 20 | 21 | # C extensions 22 | *.so 23 | 24 | # Distribution / packaging 25 | .Python 26 | env/ 27 | build/ 28 | develop-eggs/ 29 | dist/ 30 | downloads/ 31 | eggs/ 32 | .eggs/ 33 | lib/ 34 | lib64/ 35 | parts/ 36 | sdist/ 37 | var/ 38 | wheels/ 39 | *.egg-info/ 40 | .installed.cfg 41 | *.egg 42 | 43 | # PyInstaller 44 | # Usually these files are written by a python script from a template 45 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 46 | *.manifest 47 | *.spec 48 | 49 | # Installer logs 50 | pip-log.txt 51 | pip-delete-this-directory.txt 52 | 53 | # Unit test / coverage reports 54 | htmlcov/ 55 | .tox/ 56 | .coverage 57 | .coverage.* 58 | .cache 59 | nosetests.xml 60 | coverage.xml 61 | *.cover 62 | .hypothesis/ 63 | 64 | # Translations 65 | *.mo 66 | *.pot 67 | 68 | # Django stuff: 69 | *.log 70 | local_settings.py 71 | 72 | # Flask stuff: 73 | instance/ 74 | .webassets-cache 75 | 76 | # Scrapy stuff: 77 | .scrapy 78 | 79 | # Sphinx documentation 80 | docs/_build/ 81 | 82 | # PyBuilder 83 | target/ 84 | 85 | # Jupyter Notebook 86 | *.ipynb_checkpoints 87 | 88 | # pyenv 89 | .python-version 90 | 91 | .env 92 | .venv 93 | .idea 94 | .vscode 95 | 96 | .pytest_cache/ 97 | .mypy_cache/ 98 | 99 | #exceptions 100 | !*.gitkeep 101 | -------------------------------------------------------------------------------- /.pylintrc: -------------------------------------------------------------------------------- 1 | [MASTER] 2 | extension-pkg-whitelist=numpy,talib,talib.abstract 3 | 4 | [BASIC] 5 | good-names=logger 6 | ignore=vendor 7 | 8 | [TYPECHECK] 9 | ignored-modules=numpy,talib,talib.abstract 10 | 11 | -------------------------------------------------------------------------------- /.readthedocs.yml: -------------------------------------------------------------------------------- 1 | # .readthedocs.yml 2 | 3 | build: 4 | image: latest 5 | 6 | python: 7 | version: 3.6 8 | setup_py_install: false -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: true 2 | os: 3 | - linux 4 | dist: xenial 5 | language: python 6 | python: 7 | - 3.6 8 | services: 9 | - docker 10 | env: 11 | global: 12 | - IMAGE_NAME=freqtradeorg/freqtrade 13 | install: 14 | - cd build_helpers && ./install_ta-lib.sh ${HOME}/dependencies/; cd .. 15 | - export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH 16 | - export TA_LIBRARY_PATH=${HOME}/dependencies/lib 17 | - export TA_INCLUDE_PATH=${HOME}/dependencies/lib/include 18 | - pip install -r requirements-dev.txt 19 | - pip install -e . 20 | jobs: 21 | 22 | include: 23 | - stage: tests 24 | script: 25 | - pytest --random-order --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/ 26 | # Allow failure for coveralls 27 | - coveralls || true 28 | name: pytest 29 | - script: 30 | - cp config.json.example config.json 31 | - freqtrade --datadir freqtrade/tests/testdata backtesting 32 | name: backtest 33 | - script: 34 | - cp config.json.example config.json 35 | - freqtrade --datadir freqtrade/tests/testdata hyperopt -e 5 36 | name: hyperopt 37 | - script: flake8 freqtrade scripts 38 | name: flake8 39 | - script: 40 | # Test Documentation boxes - 41 | # !!! : is not allowed! 42 | - grep -Er '^!{3}\s\S+:' docs/*; test $? -ne 0 43 | name: doc syntax 44 | - script: mypy freqtrade scripts 45 | name: mypy 46 | 47 | - stage: docker 48 | if: branch in (master, develop, feat/improve_travis) AND (type in (push, cron)) 49 | script: 50 | - build_helpers/publish_docker.sh 51 | name: "Build and test and push docker image" 52 | 53 | notifications: 54 | slack: 55 | secure: bKLXmOrx8e2aPZl7W8DA5BdPAXWGpI5UzST33oc1G/thegXcDVmHBTJrBs4sZak6bgAclQQrdZIsRd2eFYzHLalJEaw6pk7hoAw8SvLnZO0ZurWboz7qg2+aZZXfK4eKl/VUe4sM9M4e/qxjkK+yWG7Marg69c4v1ypF7ezUi1fPYILYw8u0paaiX0N5UX8XNlXy+PBlga2MxDjUY70MuajSZhPsY2pDUvYnMY1D/7XN3cFW0g+3O8zXjF0IF4q1Z/1ASQe+eYjKwPQacE+O8KDD+ZJYoTOFBAPllrtpO1jnOPFjNGf3JIbVMZw4bFjIL0mSQaiSUaUErbU3sFZ5Or79rF93XZ81V7uEZ55vD8KMfR2CB1cQJcZcj0v50BxLo0InkFqa0Y8Nra3sbpV4fV5Oe8pDmomPJrNFJnX6ULQhQ1gTCe0M5beKgVms5SITEpt4/Y0CmLUr6iHDT0CUiyMIRWAXdIgbGh1jfaWOMksybeRevlgDsIsNBjXmYI1Sw2ZZR2Eo2u4R6zyfyjOMLwYJ3vgq9IrACv2w5nmf0+oguMWHf6iWi2hiOqhlAN1W74+3HsYQcqnuM3LGOmuCnPprV1oGBqkPXjIFGpy21gNx4vHfO1noLUyJnMnlu2L7SSuN1CdLsnjJ1hVjpJjPfqB4nn8g12x87TqM1bOm+3Q= 56 | cache: 57 | pip: True 58 | directories: 59 | - $HOME/dependencies 60 | -------------------------------------------------------------------------------- /AI-Scalpel-Trading-Bot/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jacob S. Ortiz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /AI-Scalpel-Trading-Bot/README.md: -------------------------------------------------------------------------------- 1 | # AI-Scalpel-Trading-Bot 2 | A python bot that lets you trade in most crypto exchanges and allows you to optimize your strategies with machine learning. 3 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Contribute to freqtrade 4 | 5 | Feel like our bot is missing a feature? We welcome your pull requests! 6 | 7 | Issues labeled [good first issue](https://github.com/freqtrade/freqtrade/labels/good%20first%20issue) can be good first contributions, and will help get you familiar with the codebase. 8 | 9 | Few pointers for contributions: 10 | 11 | - Create your PR against the `develop` branch, not `master`. 12 | - New features need to contain unit tests and must be PEP8 conformant (max-line-length = 100). 13 | 14 | If you are unsure, discuss the feature on our [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LWEyODBiNzkzNzcyNzU0MWYyYzE5NjIyOTQxMzBmMGUxOTIzM2YyN2Y4NWY1YTEwZDgwYTRmMzE2NmM5ZmY2MTg) 15 | or in a [issue](https://github.com/freqtrade/freqtrade/issues) before a PR. 16 | 17 | ## Getting started 18 | 19 | Best start by reading the [documentation](https://www.freqtrade.io/) to get a feel for what is possible with the bot, or head straight to the [Developer-documentation](https://www.freqtrade.io/en/latest/developer/) (WIP) which should help you getting started. 20 | 21 | ## Before sending the PR: 22 | 23 | ### 1. Run unit tests 24 | 25 | All unit tests must pass. If a unit test is broken, change your code to 26 | make it pass. It means you have introduced a regression. 27 | 28 | #### Test the whole project 29 | 30 | ```bash 31 | pytest freqtrade 32 | ``` 33 | 34 | #### Test only one file 35 | 36 | ```bash 37 | pytest freqtrade/tests/test_.py 38 | ``` 39 | 40 | #### Test only one method from one file 41 | 42 | ```bash 43 | pytest freqtrade/tests/test_.py::test_ 44 | ``` 45 | 46 | ### 2. Test if your code is PEP8 compliant 47 | 48 | #### Run Flake8 49 | 50 | ```bash 51 | flake8 freqtrade 52 | ``` 53 | 54 | We receive a lot of code that fails the `flake8` checks. 55 | To help with that, we encourage you to install the git pre-commit 56 | hook that will warn you when you try to commit code that fails these checks. 57 | Guide for installing them is [here](http://flake8.pycqa.org/en/latest/user/using-hooks.html). 58 | 59 | ### 3. Test if all type-hints are correct 60 | 61 | #### Run mypy 62 | 63 | ``` bash 64 | mypy freqtrade 65 | ``` 66 | 67 | ## (Core)-Committer Guide 68 | 69 | ### Process: Pull Requests 70 | 71 | How to prioritize pull requests, from most to least important: 72 | 73 | 1. Fixes for broken tests. Broken means broken on any supported platform or Python version. 74 | 1. Extra tests to cover corner cases. 75 | 1. Minor edits to docs. 76 | 1. Bug fixes. 77 | 1. Major edits to docs. 78 | 1. Features. 79 | 80 | Ensure that each pull request meets all requirements in the Contributing document. 81 | 82 | ### Process: Issues 83 | 84 | If an issue is a bug that needs an urgent fix, mark it for the next patch release. 85 | Then either fix it or mark as please-help. 86 | 87 | For other issues: encourage friendly discussion, moderate debate, offer your thoughts. 88 | 89 | ### Process: Your own code changes 90 | 91 | All code changes, regardless of who does them, need to be reviewed and merged by someone else. 92 | This rule applies to all the core committers. 93 | 94 | Exceptions: 95 | 96 | - Minor corrections and fixes to pull requests submitted by others. 97 | - While making a formal release, the release manager can make necessary, appropriate changes. 98 | - Small documentation changes that reinforce existing subject matter. Most commonly being, but not limited to spelling and grammar corrections. 99 | 100 | ### Responsibilities 101 | 102 | - Ensure cross-platform compatibility for every change that's accepted. Windows, Mac & Linux. 103 | - Ensure no malicious code is introduced into the core code. 104 | - Create issues for any major changes and enhancements that you wish to make. Discuss things transparently and get community feedback. 105 | - Keep feature versions as small as possible, preferably one new feature per version. 106 | - Be welcoming to newcomers and encourage diverse new contributors from all backgrounds. See the Python Community Code of Conduct (https://www.python.org/psf/codeofconduct/). 107 | 108 | ### Becoming a Committer 109 | 110 | Contributors may be given commit privileges. Preference will be given to those with: 111 | 112 | 1. Past contributions to FreqTrade and other related open-source projects. Contributions to FreqTrade include both code (both accepted and pending) and friendly participation in the issue tracker and Pull request reviews. Quantity and quality are considered. 113 | 1. A coding style that the other core committers find simple, minimal, and clean. 114 | 1. Access to resources for cross-platform development and testing. 115 | 1. Time to devote to the project regularly. 116 | 117 | Beeing a Committer does not grant write permission on `develop` or `master` for security reasons (Users trust FreqTrade with their Exchange API keys). 118 | 119 | After beeing Committer for some time, a Committer may be named Core Committer and given full repository access. 120 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.7.4-slim-stretch 2 | 3 | RUN apt-get update \ 4 | && apt-get -y install curl build-essential libssl-dev \ 5 | && apt-get clean \ 6 | && pip install --upgrade pip 7 | 8 | # Prepare environment 9 | RUN mkdir /freqtrade 10 | WORKDIR /freqtrade 11 | 12 | # Install TA-lib 13 | COPY build_helpers/* /tmp/ 14 | RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib* 15 | 16 | ENV LD_LIBRARY_PATH /usr/local/lib 17 | 18 | # Install dependencies 19 | COPY requirements.txt requirements-common.txt /freqtrade/ 20 | RUN pip install numpy --no-cache-dir \ 21 | && pip install -r requirements.txt --no-cache-dir 22 | 23 | # Install and execute 24 | COPY . /freqtrade/ 25 | RUN pip install -e . --no-cache-dir 26 | ENTRYPOINT ["freqtrade"] 27 | -------------------------------------------------------------------------------- /Dockerfile.develop: -------------------------------------------------------------------------------- 1 | FROM freqtradeorg/freqtrade:develop 2 | 3 | # Install dependencies 4 | COPY requirements-dev.txt /freqtrade/ 5 | RUN pip install numpy --no-cache-dir \ 6 | && pip install -r requirements-dev.txt --no-cache-dir 7 | 8 | # Empty the ENTRYPOINT to allow all commands 9 | ENTRYPOINT [] 10 | -------------------------------------------------------------------------------- /Dockerfile.pi: -------------------------------------------------------------------------------- 1 | FROM balenalib/raspberrypi3-debian:stretch 2 | 3 | RUN [ "cross-build-start" ] 4 | 5 | RUN apt-get update \ 6 | && apt-get -y install wget curl build-essential libssl-dev libffi-dev \ 7 | && apt-get clean 8 | 9 | # Prepare environment 10 | RUN mkdir /freqtrade 11 | WORKDIR /freqtrade 12 | 13 | # Install TA-lib 14 | COPY build_helpers/ta-lib-0.4.0-src.tar.gz /freqtrade/ 15 | RUN tar -xzf /freqtrade/ta-lib-0.4.0-src.tar.gz \ 16 | && cd /freqtrade/ta-lib/ \ 17 | && ./configure \ 18 | && make \ 19 | && make install \ 20 | && rm /freqtrade/ta-lib-0.4.0-src.tar.gz 21 | 22 | ENV LD_LIBRARY_PATH /usr/local/lib 23 | 24 | # Install berryconda 25 | RUN wget https://github.com/jjhelmus/berryconda/releases/download/v2.0.0/Berryconda3-2.0.0-Linux-armv7l.sh \ 26 | && bash ./Berryconda3-2.0.0-Linux-armv7l.sh -b \ 27 | && rm Berryconda3-2.0.0-Linux-armv7l.sh 28 | 29 | # Install dependencies 30 | COPY requirements-common.txt /freqtrade/ 31 | RUN ~/berryconda3/bin/conda install -y numpy pandas scipy \ 32 | && ~/berryconda3/bin/pip install -r requirements-common.txt --no-cache-dir 33 | 34 | # Install and execute 35 | COPY . /freqtrade/ 36 | RUN ~/berryconda3/bin/pip install -e . --no-cache-dir 37 | 38 | RUN [ "cross-build-end" ] 39 | 40 | ENTRYPOINT ["/root/berryconda3/bin/python","./freqtrade/main.py"] 41 | -------------------------------------------------------------------------------- /Dockerfile.technical: -------------------------------------------------------------------------------- 1 | FROM freqtradeorg/freqtrade:develop 2 | 3 | RUN apt-get update \ 4 | && apt-get -y install git \ 5 | && apt-get clean \ 6 | && pip install git+https://github.com/freqtrade/technical 7 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include LICENSE 2 | include README.md 3 | include config.json.example 4 | recursive-include freqtrade *.py 5 | include freqtrade/tests/testdata/*.json 6 | -------------------------------------------------------------------------------- /bin/freqtrade: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import warnings 5 | 6 | from freqtrade.main import main 7 | 8 | warnings.warn( 9 | "Deprecated - To continue to run the bot like this, please run `pip install -e .` again.", 10 | DeprecationWarning) 11 | main(sys.argv[1:]) 12 | -------------------------------------------------------------------------------- /build_helpers/install_ta-lib.sh: -------------------------------------------------------------------------------- 1 | if [ -z "$1" ]; then 2 | INSTALL_LOC=/usr/local 3 | else 4 | INSTALL_LOC=${1} 5 | fi 6 | echo "Installing to ${INSTALL_LOC}" 7 | if [ ! -f "${INSTALL_LOC}/lib/libta_lib.a" ]; then 8 | tar zxvf ta-lib-0.4.0-src.tar.gz 9 | cd ta-lib \ 10 | && sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h \ 11 | && ./configure --prefix=${INSTALL_LOC}/ \ 12 | && make \ 13 | && which sudo && sudo make install || make install \ 14 | && cd .. 15 | else 16 | echo "TA-lib already installed, skipping installation" 17 | fi 18 | -------------------------------------------------------------------------------- /build_helpers/publish_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # - export TAG=`if [ "$TRAVIS_BRANCH" == "develop" ]; then echo "latest"; else echo $TRAVIS_BRANCH ; fi` 3 | # Replace / with _ to create a valid tag 4 | TAG=$(echo "${TRAVIS_BRANCH}" | sed -e "s/\//_/") 5 | 6 | 7 | # Add commit and commit_message to docker container 8 | echo "${TRAVIS_COMMIT} ${TRAVIS_COMMIT_MESSAGE}" > freqtrade_commit 9 | 10 | if [ "${TRAVIS_EVENT_TYPE}" = "cron" ]; then 11 | echo "event ${TRAVIS_EVENT_TYPE}: full rebuild - skipping cache" 12 | docker build -t freqtrade:${TAG} . 13 | else 14 | echo "event ${TRAVIS_EVENT_TYPE}: building with cache" 15 | # Pull last build to avoid rebuilding the whole image 16 | docker pull ${IMAGE_NAME}:${TAG} 17 | docker build --cache-from ${IMAGE_NAME}:${TAG} -t freqtrade:${TAG} . 18 | fi 19 | 20 | if [ $? -ne 0 ]; then 21 | echo "failed building image" 22 | return 1 23 | fi 24 | 25 | # Run backtest 26 | docker run --rm -it -v $(pwd)/config.json.example:/freqtrade/config.json:ro freqtrade:${TAG} --datadir freqtrade/tests/testdata backtesting 27 | 28 | if [ $? -ne 0 ]; then 29 | echo "failed running backtest" 30 | return 1 31 | fi 32 | 33 | # Tag image for upload 34 | docker tag freqtrade:$TAG ${IMAGE_NAME}:$TAG 35 | if [ $? -ne 0 ]; then 36 | echo "failed tagging image" 37 | return 1 38 | fi 39 | 40 | # Tag as latest for develop builds 41 | if [ "${TRAVIS_BRANCH}" = "develop" ]; then 42 | docker tag freqtrade:$TAG ${IMAGE_NAME}:latest 43 | fi 44 | 45 | # Login 46 | echo "$DOCKER_PASS" | docker login -u $DOCKER_USER --password-stdin 47 | 48 | if [ $? -ne 0 ]; then 49 | echo "failed login" 50 | return 1 51 | fi 52 | 53 | # Show all available images 54 | docker images 55 | 56 | docker push ${IMAGE_NAME} 57 | if [ $? -ne 0 ]; then 58 | echo "failed pushing repo" 59 | return 1 60 | fi 61 | -------------------------------------------------------------------------------- /build_helpers/ta-lib-0.4.0-src.tar.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/build_helpers/ta-lib-0.4.0-src.tar.gz -------------------------------------------------------------------------------- /config.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "max_open_trades": 3, 3 | "stake_currency": "BTC", 4 | "stake_amount": 0.05, 5 | "fiat_display_currency": "USD", 6 | "ticker_interval" : "5m", 7 | "dry_run": false, 8 | "trailing_stop": false, 9 | "unfilledtimeout": { 10 | "buy": 10, 11 | "sell": 30 12 | }, 13 | "bid_strategy": { 14 | "ask_last_balance": 0.0, 15 | "use_order_book": false, 16 | "order_book_top": 1, 17 | "check_depth_of_market": { 18 | "enabled": false, 19 | "bids_to_ask_delta": 1 20 | } 21 | }, 22 | "ask_strategy":{ 23 | "use_order_book": false, 24 | "order_book_min": 1, 25 | "order_book_max": 9 26 | }, 27 | "exchange": { 28 | "name": "bittrex", 29 | "key": "your_exchange_key", 30 | "secret": "your_exchange_secret", 31 | "ccxt_config": {"enableRateLimit": true}, 32 | "ccxt_async_config": { 33 | "enableRateLimit": true, 34 | "rateLimit": 500 35 | }, 36 | "pair_whitelist": [ 37 | "ETH/BTC", 38 | "LTC/BTC", 39 | "ETC/BTC", 40 | "DASH/BTC", 41 | "ZEC/BTC", 42 | "XLM/BTC", 43 | "NXT/BTC", 44 | "POWR/BTC", 45 | "ADA/BTC", 46 | "XMR/BTC" 47 | ], 48 | "pair_blacklist": [ 49 | "DOGE/BTC" 50 | ] 51 | }, 52 | "experimental": { 53 | "use_sell_signal": false, 54 | "sell_profit_only": false, 55 | "ignore_roi_if_buy_signal": false 56 | }, 57 | "edge": { 58 | "enabled": false, 59 | "process_throttle_secs": 3600, 60 | "calculate_since_number_of_days": 7, 61 | "capital_available_percentage": 0.5, 62 | "allowed_risk": 0.01, 63 | "stoploss_range_min": -0.01, 64 | "stoploss_range_max": -0.1, 65 | "stoploss_range_step": -0.01, 66 | "minimum_winrate": 0.60, 67 | "minimum_expectancy": 0.20, 68 | "min_trade_number": 10, 69 | "max_trade_duration_minute": 1440, 70 | "remove_pumps": false 71 | }, 72 | "telegram": { 73 | "enabled": true, 74 | "token": "your_telegram_token", 75 | "chat_id": "your_telegram_chat_id" 76 | }, 77 | "initial_state": "running", 78 | "forcebuy_enable": false, 79 | "internals": { 80 | "process_throttle_secs": 5 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /config_binance.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "max_open_trades": 3, 3 | "stake_currency": "BTC", 4 | "stake_amount": 0.05, 5 | "fiat_display_currency": "USD", 6 | "ticker_interval" : "5m", 7 | "dry_run": true, 8 | "trailing_stop": false, 9 | "unfilledtimeout": { 10 | "buy": 10, 11 | "sell": 30 12 | }, 13 | "bid_strategy": { 14 | "use_order_book": false, 15 | "ask_last_balance": 0.0, 16 | "order_book_top": 1, 17 | "check_depth_of_market": { 18 | "enabled": false, 19 | "bids_to_ask_delta": 1 20 | } 21 | }, 22 | "ask_strategy":{ 23 | "use_order_book": false, 24 | "order_book_min": 1, 25 | "order_book_max": 9 26 | }, 27 | "exchange": { 28 | "name": "binance", 29 | "key": "your_exchange_key", 30 | "secret": "your_exchange_secret", 31 | "ccxt_config": {"enableRateLimit": true}, 32 | "ccxt_async_config": { 33 | "enableRateLimit": true, 34 | "rateLimit": 200 35 | }, 36 | "pair_whitelist": [ 37 | "AST/BTC", 38 | "ETC/BTC", 39 | "ETH/BTC", 40 | "EOS/BTC", 41 | "IOTA/BTC", 42 | "LTC/BTC", 43 | "MTH/BTC", 44 | "NCASH/BTC", 45 | "TNT/BTC", 46 | "XMR/BTC", 47 | "XLM/BTC", 48 | "XRP/BTC" 49 | ], 50 | "pair_blacklist": [ 51 | "BNB/BTC" 52 | ] 53 | }, 54 | "experimental": { 55 | "use_sell_signal": false, 56 | "sell_profit_only": false, 57 | "ignore_roi_if_buy_signal": false 58 | }, 59 | "edge": { 60 | "enabled": false, 61 | "process_throttle_secs": 3600, 62 | "calculate_since_number_of_days": 7, 63 | "capital_available_percentage": 0.5, 64 | "allowed_risk": 0.01, 65 | "stoploss_range_min": -0.01, 66 | "stoploss_range_max": -0.1, 67 | "stoploss_range_step": -0.01, 68 | "minimum_winrate": 0.60, 69 | "minimum_expectancy": 0.20, 70 | "min_trade_number": 10, 71 | "max_trade_duration_minute": 1440, 72 | "remove_pumps": false 73 | }, 74 | "telegram": { 75 | "enabled": false, 76 | "token": "your_telegram_token", 77 | "chat_id": "your_telegram_chat_id" 78 | }, 79 | "initial_state": "running", 80 | "forcebuy_enable": false, 81 | "internals": { 82 | "process_throttle_secs": 5 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /config_full.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "max_open_trades": 3, 3 | "stake_currency": "BTC", 4 | "stake_amount": 0.05, 5 | "fiat_display_currency": "USD", 6 | "amount_reserve_percent" : 0.05, 7 | "dry_run": false, 8 | "ticker_interval": "5m", 9 | "trailing_stop": false, 10 | "trailing_stop_positive": 0.005, 11 | "trailing_stop_positive_offset": 0.0051, 12 | "trailing_only_offset_is_reached": false, 13 | "minimal_roi": { 14 | "40": 0.0, 15 | "30": 0.01, 16 | "20": 0.02, 17 | "0": 0.04 18 | }, 19 | "stoploss": -0.10, 20 | "unfilledtimeout": { 21 | "buy": 10, 22 | "sell": 30 23 | }, 24 | "bid_strategy": { 25 | "use_order_book": false, 26 | "ask_last_balance": 0.0, 27 | "order_book_top": 1, 28 | "check_depth_of_market": { 29 | "enabled": false, 30 | "bids_to_ask_delta": 1 31 | } 32 | }, 33 | "ask_strategy":{ 34 | "use_order_book": false, 35 | "order_book_min": 1, 36 | "order_book_max": 9 37 | }, 38 | "order_types": { 39 | "buy": "limit", 40 | "sell": "limit", 41 | "emergencysell": "market", 42 | "stoploss": "market", 43 | "stoploss_on_exchange": false, 44 | "stoploss_on_exchange_interval": 60 45 | }, 46 | "order_time_in_force": { 47 | "buy": "gtc", 48 | "sell": "gtc" 49 | }, 50 | "pairlist": { 51 | "method": "VolumePairList", 52 | "config": { 53 | "number_assets": 20, 54 | "sort_key": "quoteVolume", 55 | "precision_filter": false 56 | } 57 | }, 58 | "exchange": { 59 | "name": "bittrex", 60 | "sandbox": false, 61 | "key": "your_exchange_key", 62 | "secret": "your_exchange_secret", 63 | "password": "", 64 | "ccxt_config": {"enableRateLimit": true}, 65 | "ccxt_async_config": { 66 | "enableRateLimit": false, 67 | "rateLimit": 500, 68 | "aiohttp_trust_env": false 69 | }, 70 | "pair_whitelist": [ 71 | "ETH/BTC", 72 | "LTC/BTC", 73 | "ETC/BTC", 74 | "DASH/BTC", 75 | "ZEC/BTC", 76 | "XLM/BTC", 77 | "NXT/BTC", 78 | "POWR/BTC", 79 | "ADA/BTC", 80 | "XMR/BTC" 81 | ], 82 | "pair_blacklist": [ 83 | "DOGE/BTC" 84 | ], 85 | "outdated_offset": 5, 86 | "markets_refresh_interval": 60 87 | }, 88 | "edge": { 89 | "enabled": false, 90 | "process_throttle_secs": 3600, 91 | "calculate_since_number_of_days": 7, 92 | "capital_available_percentage": 0.5, 93 | "allowed_risk": 0.01, 94 | "stoploss_range_min": -0.01, 95 | "stoploss_range_max": -0.1, 96 | "stoploss_range_step": -0.01, 97 | "minimum_winrate": 0.60, 98 | "minimum_expectancy": 0.20, 99 | "min_trade_number": 10, 100 | "max_trade_duration_minute": 1440, 101 | "remove_pumps": false 102 | }, 103 | "experimental": { 104 | "use_sell_signal": false, 105 | "sell_profit_only": false, 106 | "ignore_roi_if_buy_signal": false 107 | }, 108 | "telegram": { 109 | "enabled": true, 110 | "token": "your_telegram_token", 111 | "chat_id": "your_telegram_chat_id" 112 | }, 113 | "api_server": { 114 | "enabled": false, 115 | "listen_ip_address": "127.0.0.1", 116 | "listen_port": 8080, 117 | "username": "freqtrader", 118 | "password": "SuperSecurePassword" 119 | }, 120 | "db_url": "sqlite:///tradesv3.sqlite", 121 | "initial_state": "running", 122 | "forcebuy_enable": false, 123 | "internals": { 124 | "process_throttle_secs": 5 125 | }, 126 | "strategy": "DefaultStrategy", 127 | "strategy_path": "user_data/strategies/" 128 | } 129 | -------------------------------------------------------------------------------- /config_kraken.json.example: -------------------------------------------------------------------------------- 1 | { 2 | "max_open_trades": 5, 3 | "stake_currency": "EUR", 4 | "stake_amount": 10, 5 | "fiat_display_currency": "EUR", 6 | "ticker_interval" : "5m", 7 | "dry_run": true, 8 | "trailing_stop": false, 9 | "unfilledtimeout": { 10 | "buy": 10, 11 | "sell": 30 12 | }, 13 | "bid_strategy": { 14 | "use_order_book": false, 15 | "ask_last_balance": 0.0, 16 | "order_book_top": 1, 17 | "check_depth_of_market": { 18 | "enabled": false, 19 | "bids_to_ask_delta": 1 20 | } 21 | }, 22 | "ask_strategy":{ 23 | "use_order_book": false, 24 | "order_book_min": 1, 25 | "order_book_max": 9 26 | }, 27 | "exchange": { 28 | "name": "kraken", 29 | "key": "", 30 | "secret": "", 31 | "ccxt_config": {"enableRateLimit": true}, 32 | "ccxt_async_config": { 33 | "enableRateLimit": true, 34 | "rateLimit": 1000 35 | }, 36 | "pair_whitelist": [ 37 | "ETH/EUR", 38 | "BTC/EUR", 39 | "BCH/EUR" 40 | ], 41 | "pair_blacklist": [ 42 | 43 | ] 44 | }, 45 | "edge": { 46 | "enabled": false, 47 | "process_throttle_secs": 3600, 48 | "calculate_since_number_of_days": 7, 49 | "capital_available_percentage": 0.5, 50 | "allowed_risk": 0.01, 51 | "stoploss_range_min": -0.01, 52 | "stoploss_range_max": -0.1, 53 | "stoploss_range_step": -0.01, 54 | "minimum_winrate": 0.60, 55 | "minimum_expectancy": 0.20, 56 | "min_trade_number": 10, 57 | "max_trade_duration_minute": 1440, 58 | "remove_pumps": false 59 | }, 60 | "telegram": { 61 | "enabled": false, 62 | "token": "your_telegram_token", 63 | "chat_id": "your_telegram_chat_id" 64 | }, 65 | "initial_state": "running", 66 | "forcebuy_enable": false, 67 | "internals": { 68 | "process_throttle_secs": 5 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /docs/assets/freqtrade-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/docs/assets/freqtrade-screenshot.png -------------------------------------------------------------------------------- /docs/assets/plot-dataframe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/docs/assets/plot-dataframe.png -------------------------------------------------------------------------------- /docs/assets/plot-profit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/docs/assets/plot-profit.png -------------------------------------------------------------------------------- /docs/deprecated.md: -------------------------------------------------------------------------------- 1 | # Deprecated features 2 | 3 | This page contains description of the command line arguments, configuration parameters 4 | and the bot features that were declared as DEPRECATED by the bot development team 5 | and are no longer supported. Please avoid their usage in your configuration. 6 | 7 | ## Deprecated 8 | 9 | ### the `--refresh-pairs-cached` command line option 10 | 11 | `--refresh-pairs-cached` in the context of backtesting, hyperopt and edge allows to refresh candle data for backtesting. 12 | Since this leads to much confusion, and slows down backtesting (while not being part of backtesting) this has been singled out 13 | as a seperate freqtrade subcommand `freqtrade download-data`. 14 | 15 | This command line option was deprecated in `2019.7-dev` and will be removed after the next release. 16 | 17 | ## Removed features 18 | 19 | ### The **--dynamic-whitelist** command line option 20 | 21 | This command line option was deprecated in 2018 and removed freqtrade 2019.6-dev (develop branch) 22 | and in freqtrade 2019.7 (master branch). 23 | 24 | ### the `--live` command line option 25 | 26 | `--live` in the context of backtesting allowed to download the latest tick data for backtesting. 27 | Did only download the latest 500 candles, so was ineffective in getting good backtest data. 28 | Removed in 2019-7-dev (develop branch) and in freqtrade 2019-8 (master branch) 29 | -------------------------------------------------------------------------------- /docs/faq.md: -------------------------------------------------------------------------------- 1 | # Freqtrade FAQ 2 | 3 | ## Freqtrade common issues 4 | 5 | ### The bot does not start 6 | 7 | Running the bot with `freqtrade --config config.json` does show the output `freqtrade: command not found`. 8 | 9 | This could have the following reasons: 10 | 11 | * The virtual environment is not active 12 | * run `source .env/bin/activate` to activate the virtual environment 13 | * The installation did not work correctly. 14 | * Please check the [Installation documentation](installation.md). 15 | 16 | ### I have waited 5 minutes, why hasn't the bot made any trades yet?! 17 | 18 | Depending on the buy strategy, the amount of whitelisted coins, the 19 | situation of the market etc, it can take up to hours to find good entry 20 | position for a trade. Be patient! 21 | 22 | ### I have made 12 trades already, why is my total profit negative?! 23 | 24 | I understand your disappointment but unfortunately 12 trades is just 25 | not enough to say anything. If you run backtesting, you can see that our 26 | current algorithm does leave you on the plus side, but that is after 27 | thousands of trades and even there, you will be left with losses on 28 | specific coins that you have traded tens if not hundreds of times. We 29 | of course constantly aim to improve the bot but it will _always_ be a 30 | gamble, which should leave you with modest wins on monthly basis but 31 | you can't say much from few trades. 32 | 33 | ### I’d like to change the stake amount. Can I just stop the bot with /stop and then change the config.json and run it again? 34 | 35 | Not quite. Trades are persisted to a database but the configuration is 36 | currently only read when the bot is killed and restarted. `/stop` more 37 | like pauses. You can stop your bot, adjust settings and start it again. 38 | 39 | ### I want to improve the bot with a new strategy 40 | 41 | That's great. We have a nice backtesting and hyperoptimizing setup. See 42 | the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-commands). 43 | 44 | ### Is there a setting to only SELL the coins being held and not perform anymore BUYS? 45 | 46 | You can use the `/forcesell all` command from Telegram. 47 | 48 | ### I get the message "RESTRICTED_MARKET" 49 | 50 | Currently known to happen for US Bittrex users. 51 | Bittrex split its exchange into US and International versions. 52 | The International version has more pairs available, however the API always returns all pairs, so there is currently no automated way to detect if you're affected by the restriction. 53 | 54 | If you have restricted pairs in your whitelist, you'll get a warning message in the log on FreqTrade startup for each restricted pair. 55 | If you're an "International" Customer on the Bittrex exchange, then this warning will probably not impact you. 56 | If you're a US customer, the bot will fail to create orders for these pairs, and you should remove them from your Whitelist. 57 | 58 | ## Hyperopt module 59 | 60 | ### How many epoch do I need to get a good Hyperopt result? 61 | 62 | Per default Hyperopts without `-e` or `--epochs` parameter will only 63 | run 100 epochs, means 100 evals of your triggers, guards, ... Too few 64 | to find a great result (unless if you are very lucky), so you probably 65 | have to run it for 10.000 or more. But it will take an eternity to 66 | compute. 67 | 68 | We recommend you to run it at least 10.000 epochs: 69 | 70 | ```bash 71 | freqtrade hyperopt -e 10000 72 | ``` 73 | 74 | or if you want intermediate result to see 75 | 76 | ```bash 77 | for i in {1..100}; do freqtrade hyperopt -e 100; done 78 | ``` 79 | 80 | ### Why it is so long to run hyperopt? 81 | 82 | Finding a great Hyperopt results takes time. 83 | 84 | If you wonder why it takes a while to find great hyperopt results 85 | 86 | This answer was written during the under the release 0.15.1, when we had: 87 | 88 | - 8 triggers 89 | - 9 guards: let's say we evaluate even 10 values from each 90 | - 1 stoploss calculation: let's say we want 10 values from that too to be evaluated 91 | 92 | The following calculation is still very rough and not very precise 93 | but it will give the idea. With only these triggers and guards there is 94 | already 8\*10^9\*10 evaluations. A roughly total of 80 billion evals. 95 | Did you run 100 000 evals? Congrats, you've done roughly 1 / 100 000 th 96 | of the search space. 97 | 98 | ## Edge module 99 | 100 | ### Edge implements interesting approach for controlling position size, is there any theory behind it? 101 | 102 | The Edge module is mostly a result of brainstorming of [@mishaker](https://github.com/mishaker) and [@creslinux](https://github.com/creslinux) freqtrade team members. 103 | 104 | You can find further info on expectancy, winrate, risk management and position size in the following sources: 105 | 106 | - https://www.tradeciety.com/ultimate-math-guide-for-traders/ 107 | - http://www.vantharp.com/tharp-concepts/expectancy.asp 108 | - https://samuraitradingacademy.com/trading-expectancy/ 109 | - https://www.learningmarkets.com/determining-expectancy-in-your-trading/ 110 | - http://www.lonestocktrader.com/make-money-trading-positive-expectancy/ 111 | - https://www.babypips.com/trading/trade-expectancy-matter 112 | -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/docs/images/logo.png -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | # Freqtrade 2 | [![Build Status](https://travis-ci.org/freqtrade/freqtrade.svg?branch=develop)](https://travis-ci.org/freqtrade/freqtrade) 3 | [![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop) 4 | [![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](https://codeclimate.com/github/freqtrade/freqtrade/maintainability) 5 | 6 | 7 | Star 8 | 9 | Fork 10 | 11 | Download 12 | 13 | Follow @freqtrade 14 | ## Introduction 15 | Freqtrade is a cryptocurrency trading bot written in Python. 16 | 17 | !!! Danger "DISCLAIMER" 18 | This software is for educational purposes only. Do not risk money which you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS. 19 | 20 | Always start by running a trading bot in Dry-run and do not engage money before you understand how it works and what profit/loss you should expect. 21 | 22 | We strongly recommend you to have basic coding skills and Python knowledge. Do not hesitate to read the source code and understand the mechanisms of this bot, algorithms and techniques implemented in it. 23 | 24 | ## Features 25 | 26 | - Based on Python 3.6+: For botting on any operating system — Windows, macOS and Linux. 27 | - Persistence: Persistence is achieved through sqlite database. 28 | - Dry-run mode: Run the bot without playing money. 29 | - Backtesting: Run a simulation of your buy/sell strategy with historical data. 30 | - Strategy Optimization by machine learning: Use machine learning to optimize your buy/sell strategy parameters with real exchange data. 31 | - Edge position sizing: Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. 32 | - Whitelist crypto-currencies: Select which crypto-currency you want to trade or use dynamic whitelists based on market (pair) trade volume. 33 | - Blacklist crypto-currencies: Select which crypto-currency you want to avoid. 34 | - Manageable via Telegram or REST APi: Manage the bot with Telegram or via the builtin REST API. 35 | - Display profit/loss in fiat: Display your profit/loss in any of 33 fiat currencies supported. 36 | - Daily summary of profit/loss: Receive the daily summary of your profit/loss. 37 | - Performance status report: Receive the performance status of your current trades. 38 | 39 | ## Requirements 40 | 41 | ### Up to date clock 42 | 43 | The clock on the system running the bot must be accurate, synchronized to a NTP server frequently enough to avoid problems with communication to the exchanges. 44 | 45 | ### Hardware requirements 46 | 47 | To run this bot we recommend you a cloud instance with a minimum of: 48 | 49 | - 2GB RAM 50 | - 1GB disk space 51 | - 2vCPU 52 | 53 | ### Software requirements 54 | 55 | - Python 3.6.x 56 | - pip (pip3) 57 | - git 58 | - TA-Lib 59 | - virtualenv (Recommended) 60 | - Docker (Recommended) 61 | 62 | ## Support 63 | 64 | Help / Slack 65 | For any questions not covered by the documentation or for further information about the bot, we encourage you to join our Slack channel. 66 | 67 | Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LWEyODBiNzkzNzcyNzU0MWYyYzE5NjIyOTQxMzBmMGUxOTIzM2YyN2Y4NWY1YTEwZDgwYTRmMzE2NmM5ZmY2MTg) to join Slack channel. 68 | 69 | ## Ready to try? 70 | 71 | Begin by reading our installation guide [here](installation). 72 | -------------------------------------------------------------------------------- /docs/partials/header.html: -------------------------------------------------------------------------------- 1 |
2 | 50 | 51 | 52 |
-------------------------------------------------------------------------------- /docs/requirements-docs.txt: -------------------------------------------------------------------------------- 1 | mkdocs-material==4.4.2 -------------------------------------------------------------------------------- /docs/sandbox-testing.md: -------------------------------------------------------------------------------- 1 | # Sandbox API testing 2 | 3 | Where an exchange provides a sandbox for risk-free integration, or end-to-end, testing CCXT provides access to these. 4 | 5 | This document is a *light overview of configuring Freqtrade and GDAX sandbox. 6 | This can be useful to developers and trader alike as Freqtrade is quite customisable. 7 | 8 | When testing your API connectivity, make sure to use the following URLs. 9 | ***Website** 10 | https://public.sandbox.gdax.com 11 | ***REST API** 12 | https://api-public.sandbox.gdax.com 13 | 14 | --- 15 | 16 | # Configure a Sandbox account on Gdax 17 | 18 | Aim of this document section 19 | 20 | - An sanbox account 21 | - create 2FA (needed to create an API) 22 | - Add test 50BTC to account 23 | - Create : 24 | - - API-KEY 25 | - - API-Secret 26 | - - API Password 27 | 28 | ## Acccount 29 | 30 | This link will redirect to the sandbox main page to login / create account dialogues: 31 | https://public.sandbox.pro.coinbase.com/orders/ 32 | 33 | After registration and Email confimation you wil be redirected into your sanbox account. It is easy to verify you're in sandbox by checking the URL bar. 34 | > https://public.sandbox.pro.coinbase.com/ 35 | 36 | ## Enable 2Fa (a prerequisite to creating sandbox API Keys) 37 | 38 | From within sand box site select your profile, top right. 39 | >Or as a direct link: https://public.sandbox.pro.coinbase.com/profile 40 | 41 | From the menu panel to the left of the screen select 42 | 43 | > Security: "*View or Update*" 44 | 45 | In the new site select "enable authenticator" as typical google Authenticator. 46 | 47 | - open Google Authenticator on your phone 48 | - scan barcode 49 | - enter your generated 2fa 50 | 51 | ## Enable API Access 52 | 53 | From within sandbox select profile>api>create api-keys 54 | >or as a direct link: https://public.sandbox.pro.coinbase.com/profile/api 55 | 56 | Click on "create one" and ensure **view** and **trade** are "checked" and sumbit your 2FA 57 | 58 | - **Copy and paste the Passphase** into a notepade this will be needed later 59 | - **Copy and paste the API Secret** popup into a notepad this will needed later 60 | - **Copy and paste the API Key** into a notepad this will needed later 61 | 62 | ## Add 50 BTC test funds 63 | 64 | To add funds, use the web interface deposit and withdraw buttons. 65 | 66 | To begin select 'Wallets' from the top menu. 67 | > Or as a direct link: https://public.sandbox.pro.coinbase.com/wallets 68 | 69 | - Deposits (bottom left of screen) 70 | - - Deposit Funds Bitcoin 71 | - - - Coinbase BTC Wallet 72 | - - - - Max (50 BTC) 73 | - - - - - Deposit 74 | 75 | *This process may be repeated for other currencies, ETH as example* 76 | 77 | --- 78 | 79 | # Configure Freqtrade to use Gax Sandbox 80 | 81 | The aim of this document section 82 | 83 | - Enable sandbox URLs in Freqtrade 84 | - Configure API 85 | - - secret 86 | - - key 87 | - - passphrase 88 | 89 | ## Sandbox URLs 90 | 91 | Freqtrade makes use of CCXT which in turn provides a list of URLs to Freqtrade. 92 | These include `['test']` and `['api']`. 93 | 94 | - `[Test]` if available will point to an Exchanges sandbox. 95 | - `[Api]` normally used, and resolves to live API target on the exchange 96 | 97 | To make use of sandbox / test add "sandbox": true, to your config.json 98 | 99 | ```json 100 | "exchange": { 101 | "name": "gdax", 102 | "sandbox": true, 103 | "key": "5wowfxemogxeowo;heiohgmd", 104 | "secret": "/ZMH1P62rCVmwefewrgcewX8nh4gob+lywxfwfxwwfxwfNsH1ySgvWCUR/w==", 105 | "password": "1bkjfkhfhfu6sr", 106 | "outdated_offset": 5 107 | "pair_whitelist": [ 108 | "BTC/USD" 109 | ``` 110 | 111 | Also insert your 112 | 113 | - api-key (noted earlier) 114 | - api-secret (noted earlier) 115 | - password (the passphrase - noted earlier) 116 | 117 | --- 118 | 119 | ## You should now be ready to test your sandbox 120 | 121 | Ensure Freqtrade logs show the sandbox URL, and trades made are shown in sandbox. 122 | ** Typically the BTC/USD has the most activity in sandbox to test against. 123 | 124 | ## GDAX - Old Candles problem 125 | 126 | It is my experience that GDAX sandbox candles may be 20+- minutes out of date. This can cause trades to fail as one of Freqtrades safety checks. 127 | 128 | To disable this check, add / change the `"outdated_offset"` parameter in the exchange section of your configuration to adjust for this delay. 129 | Example based on the above configuration: 130 | 131 | ```json 132 | "exchange": { 133 | "name": "gdax", 134 | "sandbox": true, 135 | "key": "5wowfxemogxeowo;heiohgmd", 136 | "secret": "/ZMH1P62rCVmwefewrgcewX8nh4gob+lywxfwfxwwfxwfNsH1ySgvWCUR/w==", 137 | "password": "1bkjfkhfhfu6sr", 138 | "outdated_offset": 30 139 | "pair_whitelist": [ 140 | "BTC/USD" 141 | ``` 142 | -------------------------------------------------------------------------------- /docs/sql_cheatsheet.md: -------------------------------------------------------------------------------- 1 | # SQL Helper 2 | This page contains some help if you want to edit your sqlite db. 3 | 4 | ## Install sqlite3 5 | **Ubuntu/Debian installation** 6 | ```bash 7 | sudo apt-get install sqlite3 8 | ``` 9 | 10 | ## Open the DB 11 | ```bash 12 | sqlite3 13 | .open 14 | ``` 15 | 16 | ## Table structure 17 | 18 | ### List tables 19 | ```bash 20 | .tables 21 | ``` 22 | 23 | ### Display table structure 24 | ```bash 25 | .schema 26 | ``` 27 | 28 | ### Trade table structure 29 | ```sql 30 | CREATE TABLE trades ( 31 | id INTEGER NOT NULL, 32 | exchange VARCHAR NOT NULL, 33 | pair VARCHAR NOT NULL, 34 | is_open BOOLEAN NOT NULL, 35 | fee_open FLOAT NOT NULL, 36 | fee_close FLOAT NOT NULL, 37 | open_rate FLOAT, 38 | open_rate_requested FLOAT, 39 | close_rate FLOAT, 40 | close_rate_requested FLOAT, 41 | close_profit FLOAT, 42 | stake_amount FLOAT NOT NULL, 43 | amount FLOAT, 44 | open_date DATETIME NOT NULL, 45 | close_date DATETIME, 46 | open_order_id VARCHAR, 47 | stop_loss FLOAT, 48 | initial_stop_loss FLOAT, 49 | stoploss_order_id VARCHAR, 50 | stoploss_last_update DATETIME, 51 | max_rate FLOAT, 52 | sell_reason VARCHAR, 53 | strategy VARCHAR, 54 | ticker_interval INTEGER, 55 | PRIMARY KEY (id), 56 | CHECK (is_open IN (0, 1)) 57 | ); 58 | ``` 59 | 60 | ## Get all trades in the table 61 | 62 | ```sql 63 | SELECT * FROM trades; 64 | ``` 65 | 66 | ## Fix trade still open after a manual sell on the exchange 67 | 68 | !!! Warning 69 | Manually selling a pair on the exchange will not be detected by the bot and it will try to sell anyway. Whenever possible, forcesell should be used to accomplish the same thing. 70 | It is strongly advised to backup your database file before making any manual changes. 71 | 72 | !!! Note 73 | This should not be necessary after /forcesell, as forcesell orders are closed automatically by the bot on the next iteration. 74 | 75 | ```sql 76 | UPDATE trades 77 | SET is_open=0, close_date=, close_rate=, close_profit=close_rate/open_rate-1, sell_reason= 78 | WHERE id=; 79 | ``` 80 | 81 | ##### Example 82 | 83 | ```sql 84 | UPDATE trades 85 | SET is_open=0, close_date='2017-12-20 03:08:45.103418', close_rate=0.19638016, close_profit=0.0496, sell_reason='force_sell' 86 | WHERE id=31; 87 | ``` 88 | 89 | ## Insert manually a new trade 90 | 91 | ```sql 92 | INSERT INTO trades (exchange, pair, is_open, fee_open, fee_close, open_rate, stake_amount, amount, open_date) 93 | VALUES ('bittrex', 'ETH/BTC', 1, 0.0025, 0.0025, , , , '') 94 | ``` 95 | 96 | ##### Example: 97 | 98 | ```sql 99 | INSERT INTO trades (exchange, pair, is_open, fee_open, fee_close, open_rate, stake_amount, amount, open_date) 100 | VALUES ('bittrex', 'ETH/BTC', 1, 0.0025, 0.0025, 0.00258580, 0.002, 0.7715262081, '2017-11-28 12:44:24.000000') 101 | ``` 102 | 103 | ## Fix wrong fees in the table 104 | If your DB was created before [PR#200](https://github.com/freqtrade/freqtrade/pull/200) was merged (before 12/23/17). 105 | 106 | ```sql 107 | UPDATE trades SET fee=0.0025 WHERE fee=0.005; 108 | ``` 109 | -------------------------------------------------------------------------------- /docs/stoploss.md: -------------------------------------------------------------------------------- 1 | # Stop Loss 2 | 3 | The `stoploss` configuration parameter is loss in percentage that should trigger a sale. 4 | For example, value `-0.10` will cause immediate sell if the profit dips below -10% for a given trade. This parameter is optional. 5 | 6 | Most of the strategy files already include the optimal `stoploss` 7 | value. This parameter is optional. If you use it in the configuration file, it will take over the 8 | `stoploss` value from the strategy file. 9 | 10 | ## Stop Loss support 11 | 12 | At this stage the bot contains the following stoploss support modes: 13 | 14 | 1. static stop loss, defined in either the strategy or configuration. 15 | 2. trailing stop loss, defined in the configuration. 16 | 3. trailing stop loss, custom positive loss, defined in configuration. 17 | 18 | !!! Note 19 | All stoploss properties can be configured in either Strategy or configuration. Configuration values override strategy values. 20 | 21 | Those stoploss modes can be *on exchange* or *off exchange*. If the stoploss is *on exchange* it means a stoploss limit order is placed on the exchange immediately after buy order happens successfuly. This will protect you against sudden crashes in market as the order will be in the queue immediately and if market goes down then the order has more chance of being fulfilled. 22 | 23 | In case of stoploss on exchange there is another parameter called `stoploss_on_exchange_interval`. This configures the interval in seconds at which the bot will check the stoploss and update it if necessary. As an example in case of trailing stoploss if the order is on the exchange and the market is going up then the bot automatically cancels the previous stoploss order and put a new one with a stop value higher than previous one. It is clear that the bot cannot do it every 5 seconds otherwise it gets banned. So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute). 24 | 25 | !!! Note 26 | Stoploss on exchange is only supported for Binance as of now. 27 | 28 | ## Static Stop Loss 29 | 30 | This is very simple, basically you define a stop loss of x in your strategy file or alternative in the configuration, which 31 | will overwrite the strategy definition. This will basically try to sell your asset, the second the loss exceeds the defined loss. 32 | 33 | ## Trailing Stop Loss 34 | 35 | The initial value for this stop loss, is defined in your strategy or configuration. Just as you would define your Stop Loss normally. 36 | To enable this Feauture all you have to do is to define the configuration element: 37 | 38 | ``` json 39 | "trailing_stop" : True 40 | ``` 41 | 42 | This will now activate an algorithm, which automatically moves your stop loss up every time the price of your asset increases. 43 | 44 | For example, simplified math, 45 | 46 | * you buy an asset at a price of 100$ 47 | * your stop loss is defined at 2% 48 | * which means your stop loss, gets triggered once your asset dropped below 98$ 49 | * assuming your asset now increases to 102$ 50 | * your stop loss, will now be 2% of 102$ or 99.96$ 51 | * now your asset drops in value to 101$, your stop loss, will still be 99.96$ 52 | 53 | basically what this means is that your stop loss will be adjusted to be always be 2% of the highest observed price 54 | 55 | ### Custom positive loss 56 | 57 | Due to demand, it is possible to have a default stop loss, when you are in the red with your buy, but once your profit surpasses a certain percentage, 58 | the system will utilize a new stop loss, which can be a different value. For example your default stop loss is 5%, but once you have 1.1% profit, 59 | it will be changed to be only a 1% stop loss, which trails the green candles until it goes below them. 60 | 61 | Both values can be configured in the main configuration file and requires `"trailing_stop": true` to be set to true. 62 | 63 | ``` json 64 | "trailing_stop_positive": 0.01, 65 | "trailing_stop_positive_offset": 0.011, 66 | "trailing_only_offset_is_reached": false 67 | ``` 68 | 69 | The 0.01 would translate to a 1% stop loss, once you hit 1.1% profit. 70 | 71 | You should also make sure to have this value (`trailing_stop_positive_offset`) lower than your minimal ROI, otherwise minimal ROI will apply first and sell your trade. 72 | 73 | If `"trailing_only_offset_is_reached": true` then the trailing stoploss is only activated once the offset is reached. Until then, the stoploss remains at the configured`stoploss`. 74 | 75 | ## Changing stoploss on open trades 76 | 77 | A stoploss on an open trade can be changed by changing the value in the configuration or strategy and use the `/reload_conf` command (alternatively, completely stopping and restarting the bot also works). 78 | 79 | The new stoploss value will be applied to open trades (and corresponding log-messages will be generated). 80 | 81 | ### Limitations 82 | 83 | Stoploss values cannot be changed if `trailing_stop` is enabled and the stoploss has already been adjusted, or if [Edge](edge.md) is enabled (since Edge would recalculate stoploss based on the current market situation). 84 | -------------------------------------------------------------------------------- /docs/webhook-config.md: -------------------------------------------------------------------------------- 1 | # Webhook usage 2 | 3 | ## Configuration 4 | 5 | Enable webhooks by adding a webhook-section to your configuration file, and setting `webhook.enabled` to `true`. 6 | 7 | Sample configuration (tested using IFTTT). 8 | 9 | ```json 10 | "webhook": { 11 | "enabled": true, 12 | "url": "https://maker.ifttt.com/trigger//with/key//", 13 | "webhookbuy": { 14 | "value1": "Buying {pair}", 15 | "value2": "limit {limit:8f}", 16 | "value3": "{stake_amount:8f} {stake_currency}" 17 | }, 18 | "webhooksell": { 19 | "value1": "Selling {pair}", 20 | "value2": "limit {limit:8f}", 21 | "value3": "profit: {profit_amount:8f} {stake_currency}" 22 | }, 23 | "webhookstatus": { 24 | "value1": "Status: {status}", 25 | "value2": "", 26 | "value3": "" 27 | } 28 | }, 29 | ``` 30 | 31 | The url in `webhook.url` should point to the correct url for your webhook. If you're using [IFTTT](https://ifttt.com) (as shown in the sample above) please insert our event and key to the url. 32 | 33 | Different payloads can be configured for different events. Not all fields are necessary, but you should configure at least one of the dicts, otherwise the webhook will never be called. 34 | 35 | ### Webhookbuy 36 | 37 | The fields in `webhook.webhookbuy` are filled when the bot executes a buy. Parameters are filled using string.format. 38 | Possible parameters are: 39 | 40 | * `exchange` 41 | * `pair` 42 | * `limit` 43 | * `stake_amount` 44 | * `stake_currency` 45 | * `fiat_currency` 46 | * `order_type` 47 | 48 | ### Webhooksell 49 | 50 | The fields in `webhook.webhooksell` are filled when the bot sells a trade. Parameters are filled using string.format. 51 | Possible parameters are: 52 | 53 | * `exchange` 54 | * `pair` 55 | * `gain` 56 | * `limit` 57 | * `amount` 58 | * `open_rate` 59 | * `current_rate` 60 | * `profit_amount` 61 | * `profit_percent` 62 | * `stake_currency` 63 | * `fiat_currency` 64 | * `sell_reason` 65 | * `order_type` 66 | 67 | ### Webhookstatus 68 | 69 | The fields in `webhook.webhookstatus` are used for regular status messages (Started / Stopped / ...). Parameters are filled using string.format. 70 | 71 | The only possible value here is `{status}`. 72 | -------------------------------------------------------------------------------- /environment.yml: -------------------------------------------------------------------------------- 1 | name: freqtrade 2 | channels: 3 | - defaults 4 | - conda-forge 5 | dependencies: 6 | # Required for app 7 | - python>=3.6 8 | - pip 9 | - wheel 10 | - numpy 11 | - pandas 12 | - scipy 13 | - SQLAlchemy 14 | - scikit-learn 15 | - arrow 16 | - requests 17 | - urllib3 18 | - wrapt 19 | - joblib 20 | - jsonschema 21 | - tabulate 22 | - python-rapidjson 23 | - filelock 24 | - flask 25 | - python-dotenv 26 | - cachetools 27 | - scikit-optimize 28 | - python-telegram-bot 29 | # Optional for plotting 30 | - plotly 31 | # Optional for development 32 | - flake8 33 | - pytest 34 | - pytest-mock 35 | - pytest-asyncio 36 | - pytest-cov 37 | - coveralls 38 | - mypy 39 | # Useful for jupyter 40 | - jupyter 41 | - ipykernel 42 | - isort 43 | - yapf 44 | - pip: 45 | # Required for app 46 | - cython 47 | - coinmarketcap 48 | - ccxt 49 | - TA-Lib 50 | - py_find_1st 51 | - sdnotify 52 | # Optional for develpment 53 | - flake8-tidy-imports 54 | - flake8-type-annotations 55 | - pytest-random-order 56 | - -e . 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /freqtrade.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Freqtrade Daemon 3 | After=network.target 4 | 5 | [Service] 6 | # Set WorkingDirectory and ExecStart to your file paths accordingly 7 | # NOTE: %h will be resolved to /home/ 8 | WorkingDirectory=%h/freqtrade 9 | ExecStart=/usr/bin/freqtrade 10 | Restart=on-failure 11 | 12 | [Install] 13 | WantedBy=default.target 14 | 15 | -------------------------------------------------------------------------------- /freqtrade.service.watchdog: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Freqtrade Daemon 3 | After=network.target 4 | 5 | [Service] 6 | # Set WorkingDirectory and ExecStart to your file paths accordingly 7 | # NOTE: %h will be resolved to /home/ 8 | WorkingDirectory=%h/freqtrade 9 | ExecStart=/usr/bin/freqtrade --sd-notify 10 | 11 | Restart=always 12 | #Restart=on-failure 13 | 14 | # Note that we use Type=notify here 15 | Type=notify 16 | 17 | # Currently required if Type=notify 18 | NotifyAccess=all 19 | 20 | StartLimitInterval=1min 21 | StartLimitBurst=5 22 | 23 | TimeoutStartSec=1min 24 | 25 | # Use here (process_throttle_secs * 2) or longer time interval 26 | WatchdogSec=20 27 | 28 | [Install] 29 | WantedBy=default.target 30 | 31 | -------------------------------------------------------------------------------- /freqtrade/__init__.py: -------------------------------------------------------------------------------- 1 | """ FreqTrade bot """ 2 | __version__ = '2019.7-dev' 3 | 4 | 5 | class DependencyException(Exception): 6 | """ 7 | Indicates that an assumed dependency is not met. 8 | This could happen when there is currently not enough money on the account. 9 | """ 10 | 11 | 12 | class OperationalException(Exception): 13 | """ 14 | Requires manual intervention and will usually stop the bot. 15 | This happens when an exchange returns an unexpected error during runtime 16 | or given configuration is invalid. 17 | """ 18 | 19 | 20 | class InvalidOrderException(Exception): 21 | """ 22 | This is returned when the order is not valid. Example: 23 | If stoploss on exchange order is hit, then trying to cancel the order 24 | should return this exception. 25 | """ 26 | 27 | 28 | class TemporaryError(Exception): 29 | """ 30 | Temporary network or exchange related error. 31 | This could happen when an exchange is congested, unavailable, or the user 32 | has networking problems. Usually resolves itself after a time. 33 | """ 34 | -------------------------------------------------------------------------------- /freqtrade/__main__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | __main__.py for Freqtrade 4 | To launch Freqtrade as a module 5 | 6 | > python -m freqtrade (with Python >= 3.6) 7 | """ 8 | 9 | from freqtrade import main 10 | 11 | if __name__ == '__main__': 12 | main.main() 13 | -------------------------------------------------------------------------------- /freqtrade/configuration/__init__.py: -------------------------------------------------------------------------------- 1 | from freqtrade.configuration.arguments import Arguments # noqa: F401 2 | from freqtrade.configuration.timerange import TimeRange # noqa: F401 3 | from freqtrade.configuration.configuration import Configuration # noqa: F401 4 | from freqtrade.configuration.config_validation import validate_config_consistency # noqa: F401 5 | -------------------------------------------------------------------------------- /freqtrade/configuration/check_exchange.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Any, Dict 3 | 4 | from freqtrade import OperationalException 5 | from freqtrade.exchange import (available_exchanges, get_exchange_bad_reason, 6 | is_exchange_available, is_exchange_bad, 7 | is_exchange_officially_supported) 8 | from freqtrade.state import RunMode 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | def check_exchange(config: Dict[str, Any], check_for_bad: bool = True) -> bool: 14 | """ 15 | Check if the exchange name in the config file is supported by Freqtrade 16 | :param check_for_bad: if True, check the exchange against the list of known 'bad' 17 | exchanges 18 | :return: False if exchange is 'bad', i.e. is known to work with the bot with 19 | critical issues or does not work at all, crashes, etc. True otherwise. 20 | raises an exception if the exchange if not supported by ccxt 21 | and thus is not known for the Freqtrade at all. 22 | """ 23 | 24 | if config['runmode'] in [RunMode.PLOT] and not config.get('exchange', {}).get('name'): 25 | # Skip checking exchange in plot mode, since it requires no exchange 26 | return True 27 | logger.info("Checking exchange...") 28 | 29 | exchange = config.get('exchange', {}).get('name').lower() 30 | if not is_exchange_available(exchange): 31 | raise OperationalException( 32 | f'Exchange "{exchange}" is not supported by ccxt ' 33 | f'and therefore not available for the bot.\n' 34 | f'The following exchanges are supported by ccxt: ' 35 | f'{", ".join(available_exchanges())}' 36 | ) 37 | 38 | if check_for_bad and is_exchange_bad(exchange): 39 | raise OperationalException(f'Exchange "{exchange}" is known to not work with the bot yet. ' 40 | f'Reason: {get_exchange_bad_reason(exchange)}') 41 | 42 | if is_exchange_officially_supported(exchange): 43 | logger.info(f'Exchange "{exchange}" is officially supported ' 44 | f'by the Freqtrade development team.') 45 | else: 46 | logger.warning(f'Exchange "{exchange}" is supported by ccxt ' 47 | f'and therefore available for the bot but not officially supported ' 48 | f'by the Freqtrade development team. ' 49 | f'It may work flawlessly (please report back) or have serious issues. ' 50 | f'Use it at your own discretion.') 51 | 52 | return True 53 | -------------------------------------------------------------------------------- /freqtrade/configuration/config_validation.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Any, Dict 3 | 4 | from jsonschema import Draft4Validator, validators 5 | from jsonschema.exceptions import ValidationError, best_match 6 | 7 | from freqtrade import constants, OperationalException 8 | 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | def _extend_validator(validator_class): 14 | """ 15 | Extended validator for the Freqtrade configuration JSON Schema. 16 | Currently it only handles defaults for subschemas. 17 | """ 18 | validate_properties = validator_class.VALIDATORS['properties'] 19 | 20 | def set_defaults(validator, properties, instance, schema): 21 | for prop, subschema in properties.items(): 22 | if 'default' in subschema: 23 | instance.setdefault(prop, subschema['default']) 24 | 25 | for error in validate_properties( 26 | validator, properties, instance, schema, 27 | ): 28 | yield error 29 | 30 | return validators.extend( 31 | validator_class, {'properties': set_defaults} 32 | ) 33 | 34 | 35 | FreqtradeValidator = _extend_validator(Draft4Validator) 36 | 37 | 38 | def validate_config_schema(conf: Dict[str, Any]) -> Dict[str, Any]: 39 | """ 40 | Validate the configuration follow the Config Schema 41 | :param conf: Config in JSON format 42 | :return: Returns the config if valid, otherwise throw an exception 43 | """ 44 | try: 45 | FreqtradeValidator(constants.CONF_SCHEMA).validate(conf) 46 | return conf 47 | except ValidationError as e: 48 | logger.critical( 49 | f"Invalid configuration. See config.json.example. Reason: {e}" 50 | ) 51 | raise ValidationError( 52 | best_match(Draft4Validator(constants.CONF_SCHEMA).iter_errors(conf)).message 53 | ) 54 | 55 | 56 | def validate_config_consistency(conf: Dict[str, Any]) -> None: 57 | """ 58 | Validate the configuration consistency. 59 | Should be ran after loading both configuration and strategy, 60 | since strategies can set certain configuration settings too. 61 | :param conf: Config in JSON format 62 | :return: Returns None if everything is ok, otherwise throw an OperationalException 63 | """ 64 | # validating trailing stoploss 65 | _validate_trailing_stoploss(conf) 66 | _validate_edge(conf) 67 | 68 | 69 | def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None: 70 | 71 | if conf.get('stoploss') == 0.0: 72 | raise OperationalException( 73 | 'The config stoploss needs to be different from 0 to avoid problems with sell orders.' 74 | ) 75 | # Skip if trailing stoploss is not activated 76 | if not conf.get('trailing_stop', False): 77 | return 78 | 79 | tsl_positive = float(conf.get('trailing_stop_positive', 0)) 80 | tsl_offset = float(conf.get('trailing_stop_positive_offset', 0)) 81 | tsl_only_offset = conf.get('trailing_only_offset_is_reached', False) 82 | 83 | if tsl_only_offset: 84 | if tsl_positive == 0.0: 85 | raise OperationalException( 86 | 'The config trailing_only_offset_is_reached needs ' 87 | 'trailing_stop_positive_offset to be more than 0 in your config.') 88 | if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive: 89 | raise OperationalException( 90 | 'The config trailing_stop_positive_offset needs ' 91 | 'to be greater than trailing_stop_positive in your config.') 92 | 93 | # Fetch again without default 94 | if 'trailing_stop_positive' in conf and float(conf['trailing_stop_positive']) == 0.0: 95 | raise OperationalException( 96 | 'The config trailing_stop_positive needs to be different from 0 ' 97 | 'to avoid problems with sell orders.' 98 | ) 99 | 100 | 101 | def _validate_edge(conf: Dict[str, Any]) -> None: 102 | """ 103 | Edge and Dynamic whitelist should not both be enabled, since edge overrides dynamic whitelists. 104 | """ 105 | 106 | if not conf.get('edge', {}).get('enabled'): 107 | return 108 | 109 | if conf.get('pairlist', {}).get('method') == 'VolumePairList': 110 | raise OperationalException( 111 | "Edge and VolumePairList are incompatible, " 112 | "Edge will override whatever pairs VolumePairlist selects." 113 | ) 114 | -------------------------------------------------------------------------------- /freqtrade/configuration/directory_operations.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from typing import Any, Dict, Optional 3 | from pathlib import Path 4 | 5 | from freqtrade import OperationalException 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | def create_datadir(config: Dict[str, Any], datadir: Optional[str] = None) -> str: 11 | 12 | folder = Path(datadir) if datadir else Path(f"{config['user_data_dir']}/data") 13 | if not datadir: 14 | # set datadir 15 | exchange_name = config.get('exchange', {}).get('name').lower() 16 | folder = folder.joinpath(exchange_name) 17 | 18 | if not folder.is_dir(): 19 | folder.mkdir(parents=True) 20 | logger.info(f'Created data directory: {datadir}') 21 | return str(folder) 22 | 23 | 24 | def create_userdata_dir(directory: str, create_dir=False) -> Path: 25 | """ 26 | Create userdata directory structure. 27 | if create_dir is True, then the parent-directory will be created if it does not exist. 28 | Sub-directories will always be created if the parent directory exists. 29 | Raises OperationalException if given a non-existing directory. 30 | :param directory: Directory to check 31 | :param create_dir: Create directory if it does not exist. 32 | :return: Path object containing the directory 33 | """ 34 | sub_dirs = ["backtest_results", "data", "hyperopts", "hyperopt_results", "plot", "strategies", ] 35 | folder = Path(directory) 36 | if not folder.is_dir(): 37 | if create_dir: 38 | folder.mkdir(parents=True) 39 | logger.info(f'Created user-data directory: {folder}') 40 | else: 41 | raise OperationalException( 42 | f"Directory `{folder}` does not exist. " 43 | "Please use `freqtrade create-userdir` to create a user directory") 44 | 45 | # Create required subdirectories 46 | for f in sub_dirs: 47 | subfolder = folder / f 48 | if not subfolder.is_dir(): 49 | subfolder.mkdir(parents=False) 50 | return folder 51 | -------------------------------------------------------------------------------- /freqtrade/configuration/load_config.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contain functions to load the configuration file 3 | """ 4 | import rapidjson 5 | import logging 6 | import sys 7 | from typing import Any, Dict 8 | 9 | from freqtrade import OperationalException 10 | 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | CONFIG_PARSE_MODE = rapidjson.PM_COMMENTS | rapidjson.PM_TRAILING_COMMAS 16 | 17 | 18 | def load_config_file(path: str) -> Dict[str, Any]: 19 | """ 20 | Loads a config file from the given path 21 | :param path: path as str 22 | :return: configuration as dictionary 23 | """ 24 | try: 25 | # Read config from stdin if requested in the options 26 | with open(path) if path != '-' else sys.stdin as file: 27 | config = rapidjson.load(file, parse_mode=CONFIG_PARSE_MODE) 28 | except FileNotFoundError: 29 | raise OperationalException( 30 | f'Config file "{path}" not found!' 31 | ' Please create a config file or check whether it exists.') 32 | 33 | return config 34 | -------------------------------------------------------------------------------- /freqtrade/configuration/timerange.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains the argument manager class 3 | """ 4 | import re 5 | from typing import Optional 6 | 7 | import arrow 8 | 9 | 10 | class TimeRange(): 11 | """ 12 | object defining timerange inputs. 13 | [start/stop]type defines if [start/stop]ts shall be used. 14 | if *type is None, don't use corresponding startvalue. 15 | """ 16 | 17 | def __init__(self, starttype: Optional[str] = None, stoptype: Optional[str] = None, 18 | startts: int = 0, stopts: int = 0): 19 | 20 | self.starttype: Optional[str] = starttype 21 | self.stoptype: Optional[str] = stoptype 22 | self.startts: int = startts 23 | self.stopts: int = stopts 24 | 25 | def __eq__(self, other): 26 | """Override the default Equals behavior""" 27 | return (self.starttype == other.starttype and self.stoptype == other.stoptype 28 | and self.startts == other.startts and self.stopts == other.stopts) 29 | 30 | @staticmethod 31 | def parse_timerange(text: Optional[str]): 32 | """ 33 | Parse the value of the argument --timerange to determine what is the range desired 34 | :param text: value from --timerange 35 | :return: Start and End range period 36 | """ 37 | if text is None: 38 | return TimeRange(None, None, 0, 0) 39 | syntax = [(r'^-(\d{8})$', (None, 'date')), 40 | (r'^(\d{8})-$', ('date', None)), 41 | (r'^(\d{8})-(\d{8})$', ('date', 'date')), 42 | (r'^-(\d{10})$', (None, 'date')), 43 | (r'^(\d{10})-$', ('date', None)), 44 | (r'^(\d{10})-(\d{10})$', ('date', 'date')), 45 | (r'^(-\d+)$', (None, 'line')), 46 | (r'^(\d+)-$', ('line', None)), 47 | (r'^(\d+)-(\d+)$', ('index', 'index'))] 48 | for rex, stype in syntax: 49 | # Apply the regular expression to text 50 | match = re.match(rex, text) 51 | if match: # Regex has matched 52 | rvals = match.groups() 53 | index = 0 54 | start: int = 0 55 | stop: int = 0 56 | if stype[0]: 57 | starts = rvals[index] 58 | if stype[0] == 'date' and len(starts) == 8: 59 | start = arrow.get(starts, 'YYYYMMDD').timestamp 60 | else: 61 | start = int(starts) 62 | index += 1 63 | if stype[1]: 64 | stops = rvals[index] 65 | if stype[1] == 'date' and len(stops) == 8: 66 | stop = arrow.get(stops, 'YYYYMMDD').timestamp 67 | else: 68 | stop = int(stops) 69 | return TimeRange(stype[0], stype[1], start, stop) 70 | raise Exception('Incorrect syntax for timerange "%s"' % text) 71 | -------------------------------------------------------------------------------- /freqtrade/data/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Module to handle data operations for freqtrade 3 | """ 4 | 5 | # limit what's imported when using `from freqtrad.data import *`` 6 | __all__ = [ 7 | 'converter' 8 | ] 9 | -------------------------------------------------------------------------------- /freqtrade/data/converter.py: -------------------------------------------------------------------------------- 1 | """ 2 | Functions to convert data from one format to another 3 | """ 4 | import logging 5 | 6 | import pandas as pd 7 | from pandas import DataFrame, to_datetime 8 | 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | def parse_ticker_dataframe(ticker: list, ticker_interval: str, pair: str, *, 14 | fill_missing: bool = True, 15 | drop_incomplete: bool = True) -> DataFrame: 16 | """ 17 | Converts a ticker-list (format ccxt.fetch_ohlcv) to a Dataframe 18 | :param ticker: ticker list, as returned by exchange.async_get_candle_history 19 | :param ticker_interval: ticker_interval (e.g. 5m). Used to fill up eventual missing data 20 | :param pair: Pair this data is for (used to warn if fillup was necessary) 21 | :param fill_missing: fill up missing candles with 0 candles 22 | (see ohlcv_fill_up_missing_data for details) 23 | :param drop_incomplete: Drop the last candle of the dataframe, assuming it's incomplete 24 | :return: DataFrame 25 | """ 26 | logger.debug("Parsing tickerlist to dataframe") 27 | cols = ['date', 'open', 'high', 'low', 'close', 'volume'] 28 | frame = DataFrame(ticker, columns=cols) 29 | 30 | frame['date'] = to_datetime(frame['date'], 31 | unit='ms', 32 | utc=True, 33 | infer_datetime_format=True) 34 | 35 | # Some exchanges return int values for volume and even for ohlc. 36 | # Convert them since TA-LIB indicators used in the strategy assume floats 37 | # and fail with exception... 38 | frame = frame.astype(dtype={'open': 'float', 'high': 'float', 'low': 'float', 'close': 'float', 39 | 'volume': 'float'}) 40 | 41 | # group by index and aggregate results to eliminate duplicate ticks 42 | frame = frame.groupby(by='date', as_index=False, sort=True).agg({ 43 | 'open': 'first', 44 | 'high': 'max', 45 | 'low': 'min', 46 | 'close': 'last', 47 | 'volume': 'max', 48 | }) 49 | # eliminate partial candle 50 | if drop_incomplete: 51 | frame.drop(frame.tail(1).index, inplace=True) 52 | logger.debug('Dropping last candle') 53 | 54 | if fill_missing: 55 | return ohlcv_fill_up_missing_data(frame, ticker_interval, pair) 56 | else: 57 | return frame 58 | 59 | 60 | def ohlcv_fill_up_missing_data(dataframe: DataFrame, ticker_interval: str, pair: str) -> DataFrame: 61 | """ 62 | Fills up missing data with 0 volume rows, 63 | using the previous close as price for "open", "high" "low" and "close", volume is set to 0 64 | 65 | """ 66 | from freqtrade.exchange import timeframe_to_minutes 67 | 68 | ohlc_dict = { 69 | 'open': 'first', 70 | 'high': 'max', 71 | 'low': 'min', 72 | 'close': 'last', 73 | 'volume': 'sum' 74 | } 75 | ticker_minutes = timeframe_to_minutes(ticker_interval) 76 | # Resample to create "NAN" values 77 | df = dataframe.resample(f'{ticker_minutes}min', on='date').agg(ohlc_dict) 78 | 79 | # Forwardfill close for missing columns 80 | df['close'] = df['close'].fillna(method='ffill') 81 | # Use close for "open, high, low" 82 | df.loc[:, ['open', 'high', 'low']] = df[['open', 'high', 'low']].fillna( 83 | value={'open': df['close'], 84 | 'high': df['close'], 85 | 'low': df['close'], 86 | }) 87 | df.reset_index(inplace=True) 88 | len_before = len(dataframe) 89 | len_after = len(df) 90 | if len_before != len_after: 91 | logger.info(f"Missing data fillup for {pair}: before: {len_before} - after: {len_after}") 92 | return df 93 | 94 | 95 | def order_book_to_dataframe(bids: list, asks: list) -> DataFrame: 96 | """ 97 | Gets order book list, returns dataframe with below format per suggested by creslin 98 | ------------------------------------------------------------------- 99 | b_sum b_size bids asks a_size a_sum 100 | ------------------------------------------------------------------- 101 | """ 102 | cols = ['bids', 'b_size'] 103 | 104 | bids_frame = DataFrame(bids, columns=cols) 105 | # add cumulative sum column 106 | bids_frame['b_sum'] = bids_frame['b_size'].cumsum() 107 | cols2 = ['asks', 'a_size'] 108 | asks_frame = DataFrame(asks, columns=cols2) 109 | # add cumulative sum column 110 | asks_frame['a_sum'] = asks_frame['a_size'].cumsum() 111 | 112 | frame = pd.concat([bids_frame['b_sum'], bids_frame['b_size'], bids_frame['bids'], 113 | asks_frame['asks'], asks_frame['a_size'], asks_frame['a_sum']], axis=1, 114 | keys=['b_sum', 'b_size', 'bids', 'asks', 'a_size', 'a_sum']) 115 | # logger.info('order book %s', frame ) 116 | return frame 117 | -------------------------------------------------------------------------------- /freqtrade/data/dataprovider.py: -------------------------------------------------------------------------------- 1 | """ 2 | Dataprovider 3 | Responsible to provide data to the bot 4 | including Klines, tickers, historic data 5 | Common Interface for bot and strategy to access data. 6 | """ 7 | import logging 8 | from pathlib import Path 9 | from typing import List, Tuple 10 | 11 | from pandas import DataFrame 12 | 13 | from freqtrade.data.history import load_pair_history 14 | from freqtrade.exchange import Exchange 15 | from freqtrade.state import RunMode 16 | 17 | logger = logging.getLogger(__name__) 18 | 19 | 20 | class DataProvider(): 21 | 22 | def __init__(self, config: dict, exchange: Exchange) -> None: 23 | self._config = config 24 | self._exchange = exchange 25 | 26 | def refresh(self, 27 | pairlist: List[Tuple[str, str]], 28 | helping_pairs: List[Tuple[str, str]] = None) -> None: 29 | """ 30 | Refresh data, called with each cycle 31 | """ 32 | if helping_pairs: 33 | self._exchange.refresh_latest_ohlcv(pairlist + helping_pairs) 34 | else: 35 | self._exchange.refresh_latest_ohlcv(pairlist) 36 | 37 | @property 38 | def available_pairs(self) -> List[Tuple[str, str]]: 39 | """ 40 | Return a list of tuples containing pair, ticker_interval for which data is currently cached. 41 | Should be whitelist + open trades. 42 | """ 43 | return list(self._exchange._klines.keys()) 44 | 45 | def ohlcv(self, pair: str, ticker_interval: str = None, copy: bool = True) -> DataFrame: 46 | """ 47 | Get ohlcv data for the given pair as DataFrame 48 | Please use the `available_pairs` method to verify which pairs are currently cached. 49 | :param pair: pair to get the data for 50 | :param ticker_interval: ticker interval to get data for 51 | :param copy: copy dataframe before returning if True. 52 | Use False only for read-only operations (where the dataframe is not modified) 53 | """ 54 | if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE): 55 | return self._exchange.klines((pair, ticker_interval or self._config['ticker_interval']), 56 | copy=copy) 57 | else: 58 | return DataFrame() 59 | 60 | def historic_ohlcv(self, pair: str, ticker_interval: str = None) -> DataFrame: 61 | """ 62 | Get stored historic ohlcv data 63 | :param pair: pair to get the data for 64 | :param ticker_interval: ticker interval to get data for 65 | """ 66 | return load_pair_history(pair=pair, 67 | ticker_interval=ticker_interval or self._config['ticker_interval'], 68 | refresh_pairs=False, 69 | datadir=Path(self._config['datadir']) if self._config.get( 70 | 'datadir') else None 71 | ) 72 | 73 | def get_pair_dataframe(self, pair: str, ticker_interval: str = None) -> DataFrame: 74 | """ 75 | Return pair ohlcv data, either live or cached historical -- depending 76 | on the runmode. 77 | :param pair: pair to get the data for 78 | :param ticker_interval: ticker interval to get data for 79 | """ 80 | if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE): 81 | # Get live ohlcv data. 82 | data = self.ohlcv(pair=pair, ticker_interval=ticker_interval) 83 | else: 84 | # Get historic ohlcv data (cached on disk). 85 | data = self.historic_ohlcv(pair=pair, ticker_interval=ticker_interval) 86 | if len(data) == 0: 87 | logger.warning(f"No data found for ({pair}, {ticker_interval}).") 88 | return data 89 | 90 | def ticker(self, pair: str): 91 | """ 92 | Return last ticker data 93 | """ 94 | # TODO: Implement me 95 | pass 96 | 97 | def orderbook(self, pair: str, maximum: int): 98 | """ 99 | return latest orderbook data 100 | :param pair: pair to get the data for 101 | :param maximum: Maximum number of orderbook entries to query 102 | :return: dict including bids/asks with a total of `maximum` entries. 103 | """ 104 | return self._exchange.get_order_book(pair, maximum) 105 | 106 | @property 107 | def runmode(self) -> RunMode: 108 | """ 109 | Get runmode of the bot 110 | can be "live", "dry-run", "backtest", "edgecli", "hyperopt" or "other". 111 | """ 112 | return RunMode(self._config.get('runmode', RunMode.OTHER)) 113 | -------------------------------------------------------------------------------- /freqtrade/exchange/__init__.py: -------------------------------------------------------------------------------- 1 | from freqtrade.exchange.exchange import Exchange # noqa: F401 2 | from freqtrade.exchange.exchange import (get_exchange_bad_reason, # noqa: F401 3 | is_exchange_bad, 4 | is_exchange_available, 5 | is_exchange_officially_supported, 6 | available_exchanges) 7 | from freqtrade.exchange.exchange import (timeframe_to_seconds, # noqa: F401 8 | timeframe_to_minutes, 9 | timeframe_to_msecs, 10 | timeframe_to_next_date, 11 | timeframe_to_prev_date) 12 | from freqtrade.exchange.kraken import Kraken # noqa: F401 13 | from freqtrade.exchange.binance import Binance # noqa: F401 14 | -------------------------------------------------------------------------------- /freqtrade/exchange/binance.py: -------------------------------------------------------------------------------- 1 | """ Binance exchange subclass """ 2 | import logging 3 | from typing import Dict 4 | 5 | import ccxt 6 | 7 | from freqtrade import (DependencyException, InvalidOrderException, 8 | OperationalException, TemporaryError) 9 | from freqtrade.exchange import Exchange 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | class Binance(Exchange): 15 | 16 | _ft_has: Dict = { 17 | "stoploss_on_exchange": True, 18 | "order_time_in_force": ['gtc', 'fok', 'ioc'], 19 | } 20 | 21 | def get_order_book(self, pair: str, limit: int = 100) -> dict: 22 | """ 23 | get order book level 2 from exchange 24 | 25 | 20180619: binance support limits but only on specific range 26 | """ 27 | limit_range = [5, 10, 20, 50, 100, 500, 1000] 28 | # get next-higher step in the limit_range list 29 | limit = min(list(filter(lambda x: limit <= x, limit_range))) 30 | 31 | return super().get_order_book(pair, limit) 32 | 33 | def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict: 34 | """ 35 | creates a stoploss limit order. 36 | this stoploss-limit is binance-specific. 37 | It may work with a limited number of other exchanges, but this has not been tested yet. 38 | 39 | """ 40 | ordertype = "stop_loss_limit" 41 | 42 | stop_price = self.symbol_price_prec(pair, stop_price) 43 | 44 | # Ensure rate is less than stop price 45 | if stop_price <= rate: 46 | raise OperationalException( 47 | 'In stoploss limit order, stop price should be more than limit price') 48 | 49 | if self._config['dry_run']: 50 | dry_order = self.dry_run_order( 51 | pair, ordertype, "sell", amount, stop_price) 52 | return dry_order 53 | 54 | try: 55 | params = self._params.copy() 56 | params.update({'stopPrice': stop_price}) 57 | 58 | amount = self.symbol_amount_prec(pair, amount) 59 | 60 | rate = self.symbol_price_prec(pair, rate) 61 | 62 | order = self._api.create_order(pair, ordertype, 'sell', 63 | amount, rate, params) 64 | logger.info('stoploss limit order added for %s. ' 65 | 'stop price: %s. limit: %s', pair, stop_price, rate) 66 | return order 67 | except ccxt.InsufficientFunds as e: 68 | raise DependencyException( 69 | f'Insufficient funds to create {ordertype} sell order on market {pair}.' 70 | f'Tried to sell amount {amount} at rate {rate}. ' 71 | f'Message: {e}') from e 72 | except ccxt.InvalidOrder as e: 73 | # Errors: 74 | # `binance Order would trigger immediately.` 75 | raise InvalidOrderException( 76 | f'Could not create {ordertype} sell order on market {pair}. ' 77 | f'Tried to sell amount {amount} at rate {rate}. ' 78 | f'Message: {e}') from e 79 | except (ccxt.NetworkError, ccxt.ExchangeError) as e: 80 | raise TemporaryError( 81 | f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e 82 | except ccxt.BaseError as e: 83 | raise OperationalException(e) from e 84 | -------------------------------------------------------------------------------- /freqtrade/exchange/kraken.py: -------------------------------------------------------------------------------- 1 | """ Kraken exchange subclass """ 2 | import logging 3 | from typing import Dict 4 | 5 | from freqtrade.exchange import Exchange 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | class Kraken(Exchange): 11 | 12 | _params: Dict = {"trading_agreement": "agree"} 13 | -------------------------------------------------------------------------------- /freqtrade/indicator_helpers.py: -------------------------------------------------------------------------------- 1 | from math import cos, exp, pi, sqrt 2 | 3 | import numpy as np 4 | import talib as ta 5 | from pandas import Series 6 | 7 | 8 | def went_up(series: Series) -> bool: 9 | return series > series.shift(1) 10 | 11 | 12 | def went_down(series: Series) -> bool: 13 | return series < series.shift(1) 14 | 15 | 16 | def ehlers_super_smoother(series: Series, smoothing: float = 6) -> Series: 17 | magic = pi * sqrt(2) / smoothing 18 | a1 = exp(-magic) 19 | coeff2 = 2 * a1 * cos(magic) 20 | coeff3 = -a1 * a1 21 | coeff1 = (1 - coeff2 - coeff3) / 2 22 | 23 | filtered = series.copy() 24 | 25 | for i in range(2, len(series)): 26 | filtered.iloc[i] = coeff1 * (series.iloc[i] + series.iloc[i-1]) + \ 27 | coeff2 * filtered.iloc[i-1] + coeff3 * filtered.iloc[i-2] 28 | 29 | return filtered 30 | 31 | 32 | def fishers_inverse(series: Series, smoothing: float = 0) -> np.ndarray: 33 | """ Does a smoothed fishers inverse transformation. 34 | Can be used with any oscillator that goes from 0 to 100 like RSI or MFI """ 35 | v1 = 0.1 * (series - 50) 36 | if smoothing > 0: 37 | v2 = ta.WMA(v1.values, timeperiod=smoothing) 38 | else: 39 | v2 = v1 40 | return (np.exp(2 * v2)-1) / (np.exp(2 * v2) + 1) 41 | -------------------------------------------------------------------------------- /freqtrade/loggers.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | 4 | from logging.handlers import RotatingFileHandler 5 | from typing import Any, Dict, List 6 | 7 | 8 | logger = logging.getLogger(__name__) 9 | 10 | 11 | def _set_loggers(verbosity: int = 0) -> None: 12 | """ 13 | Set the logging level for third party libraries 14 | :return: None 15 | """ 16 | 17 | logging.getLogger('requests').setLevel( 18 | logging.INFO if verbosity <= 1 else logging.DEBUG 19 | ) 20 | logging.getLogger("urllib3").setLevel( 21 | logging.INFO if verbosity <= 1 else logging.DEBUG 22 | ) 23 | logging.getLogger('ccxt.base.exchange').setLevel( 24 | logging.INFO if verbosity <= 2 else logging.DEBUG 25 | ) 26 | logging.getLogger('telegram').setLevel(logging.INFO) 27 | 28 | 29 | def setup_logging(config: Dict[str, Any]) -> None: 30 | """ 31 | Process -v/--verbose, --logfile options 32 | """ 33 | # Log level 34 | verbosity = config['verbosity'] 35 | 36 | # Log to stdout, not stderr 37 | log_handlers: List[logging.Handler] = [logging.StreamHandler(sys.stdout)] 38 | 39 | if config.get('logfile'): 40 | log_handlers.append(RotatingFileHandler(config['logfile'], 41 | maxBytes=1024 * 1024, # 1Mb 42 | backupCount=10)) 43 | 44 | logging.basicConfig( 45 | level=logging.INFO if verbosity < 1 else logging.DEBUG, 46 | format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', 47 | handlers=log_handlers 48 | ) 49 | _set_loggers(verbosity) 50 | logger.info('Verbosity set to %s', verbosity) 51 | -------------------------------------------------------------------------------- /freqtrade/main.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Main Freqtrade bot script. 4 | Read the documentation to know what cli arguments you need. 5 | """ 6 | 7 | import sys 8 | # check min. python version 9 | if sys.version_info < (3, 6): 10 | sys.exit("Freqtrade requires Python version >= 3.6") 11 | 12 | # flake8: noqa E402 13 | import logging 14 | from argparse import Namespace 15 | from typing import Any, List 16 | 17 | from freqtrade import OperationalException 18 | from freqtrade.configuration import Arguments 19 | from freqtrade.worker import Worker 20 | 21 | 22 | logger = logging.getLogger('freqtrade') 23 | 24 | 25 | def main(sysargv: List[str] = None) -> None: 26 | """ 27 | This function will initiate the bot and start the trading loop. 28 | :return: None 29 | """ 30 | 31 | return_code: Any = 1 32 | worker = None 33 | try: 34 | arguments = Arguments(sysargv) 35 | args: Namespace = arguments.get_parsed_arg() 36 | 37 | # A subcommand has been issued. 38 | # Means if Backtesting or Hyperopt have been called we exit the bot 39 | if hasattr(args, 'func'): 40 | args.func(args) 41 | # TODO: fetch return_code as returned by the command function here 42 | return_code = 0 43 | else: 44 | # Load and run worker 45 | worker = Worker(args) 46 | worker.run() 47 | 48 | except SystemExit as e: 49 | return_code = e 50 | except KeyboardInterrupt: 51 | logger.info('SIGINT received, aborting ...') 52 | return_code = 0 53 | except OperationalException as e: 54 | logger.error(str(e)) 55 | return_code = 2 56 | except Exception: 57 | logger.exception('Fatal exception!') 58 | finally: 59 | if worker: 60 | worker.exit() 61 | sys.exit(return_code) 62 | 63 | 64 | if __name__ == '__main__': 65 | main() 66 | -------------------------------------------------------------------------------- /freqtrade/misc.py: -------------------------------------------------------------------------------- 1 | """ 2 | Various tool function for Freqtrade and scripts 3 | """ 4 | import gzip 5 | import logging 6 | import re 7 | from datetime import datetime 8 | from pathlib import Path 9 | from typing.io import IO 10 | 11 | import numpy as np 12 | import rapidjson 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | 17 | def shorten_date(_date: str) -> str: 18 | """ 19 | Trim the date so it fits on small screens 20 | """ 21 | new_date = re.sub('seconds?', 'sec', _date) 22 | new_date = re.sub('minutes?', 'min', new_date) 23 | new_date = re.sub('hours?', 'h', new_date) 24 | new_date = re.sub('days?', 'd', new_date) 25 | new_date = re.sub('^an?', '1', new_date) 26 | return new_date 27 | 28 | 29 | ############################################ 30 | # Used by scripts # 31 | # Matplotlib doesn't support ::datetime64, # 32 | # so we need to convert it into ::datetime # 33 | ############################################ 34 | def datesarray_to_datetimearray(dates: np.ndarray) -> np.ndarray: 35 | """ 36 | Convert an pandas-array of timestamps into 37 | An numpy-array of datetimes 38 | :return: numpy-array of datetime 39 | """ 40 | return dates.dt.to_pydatetime() 41 | 42 | 43 | def file_dump_json(filename: Path, data, is_zip=False) -> None: 44 | """ 45 | Dump JSON data into a file 46 | :param filename: file to create 47 | :param data: JSON Data to save 48 | :return: 49 | """ 50 | logger.info(f'dumping json to "{filename}"') 51 | 52 | if is_zip: 53 | if filename.suffix != '.gz': 54 | filename = filename.with_suffix('.gz') 55 | with gzip.open(filename, 'w') as fp: 56 | rapidjson.dump(data, fp, default=str, number_mode=rapidjson.NM_NATIVE) 57 | else: 58 | with open(filename, 'w') as fp: 59 | rapidjson.dump(data, fp, default=str, number_mode=rapidjson.NM_NATIVE) 60 | 61 | logger.debug(f'done json to "{filename}"') 62 | 63 | 64 | def json_load(datafile: IO): 65 | """ 66 | load data with rapidjson 67 | Use this to have a consistent experience, 68 | sete number_mode to "NM_NATIVE" for greatest speed 69 | """ 70 | return rapidjson.load(datafile, number_mode=rapidjson.NM_NATIVE) 71 | 72 | 73 | def file_load_json(file): 74 | 75 | gzipfile = file.with_suffix(file.suffix + '.gz') 76 | 77 | # Try gzip file first, otherwise regular json file. 78 | if gzipfile.is_file(): 79 | logger.debug('Loading ticker data from file %s', gzipfile) 80 | with gzip.open(gzipfile) as tickerdata: 81 | pairdata = json_load(tickerdata) 82 | elif file.is_file(): 83 | logger.debug('Loading ticker data from file %s', file) 84 | with open(file) as tickerdata: 85 | pairdata = json_load(tickerdata) 86 | else: 87 | return None 88 | return pairdata 89 | 90 | 91 | def format_ms_time(date: int) -> str: 92 | """ 93 | convert MS date to readable format. 94 | : epoch-string in ms 95 | """ 96 | return datetime.fromtimestamp(date/1000.0).strftime('%Y-%m-%dT%H:%M:%S') 97 | 98 | 99 | def deep_merge_dicts(source, destination): 100 | """ 101 | Values from Source override destination, destination is returned (and modified!!) 102 | Sample: 103 | >>> a = { 'first' : { 'rows' : { 'pass' : 'dog', 'number' : '1' } } } 104 | >>> b = { 'first' : { 'rows' : { 'fail' : 'cat', 'number' : '5' } } } 105 | >>> merge(b, a) == { 'first' : { 'rows' : { 'pass' : 'dog', 'fail' : 'cat', 'number' : '5' } } } 106 | True 107 | """ 108 | for key, value in source.items(): 109 | if isinstance(value, dict): 110 | # get node or create one 111 | node = destination.setdefault(key, {}) 112 | deep_merge_dicts(value, node) 113 | else: 114 | destination[key] = value 115 | 116 | return destination 117 | 118 | 119 | def round_dict(d, n): 120 | """ 121 | Rounds float values in the dict to n digits after the decimal point. 122 | """ 123 | return {k: (round(v, n) if isinstance(v, float) else v) for k, v in d.items()} 124 | -------------------------------------------------------------------------------- /freqtrade/optimize/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from argparse import Namespace 3 | from typing import Any, Dict 4 | 5 | from filelock import FileLock, Timeout 6 | 7 | from freqtrade import DependencyException, constants 8 | from freqtrade.state import RunMode 9 | from freqtrade.utils import setup_utils_configuration 10 | 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | def setup_configuration(args: Namespace, method: RunMode) -> Dict[str, Any]: 16 | """ 17 | Prepare the configuration for the Hyperopt module 18 | :param args: Cli args from Arguments() 19 | :return: Configuration 20 | """ 21 | config = setup_utils_configuration(args, method) 22 | 23 | if method == RunMode.BACKTEST: 24 | if config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT: 25 | raise DependencyException('stake amount could not be "%s" for backtesting' % 26 | constants.UNLIMITED_STAKE_AMOUNT) 27 | 28 | if method == RunMode.HYPEROPT: 29 | # Special cases for Hyperopt 30 | if config.get('strategy') and config.get('strategy') != 'DefaultStrategy': 31 | logger.error("Please don't use --strategy for hyperopt.") 32 | logger.error( 33 | "Read the documentation at " 34 | "https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md " 35 | "to understand how to configure hyperopt.") 36 | raise DependencyException("--strategy configured but not supported for hyperopt") 37 | 38 | return config 39 | 40 | 41 | def start_backtesting(args: Namespace) -> None: 42 | """ 43 | Start Backtesting script 44 | :param args: Cli args from Arguments() 45 | :return: None 46 | """ 47 | # Import here to avoid loading backtesting module when it's not used 48 | from freqtrade.optimize.backtesting import Backtesting 49 | 50 | # Initialize configuration 51 | config = setup_configuration(args, RunMode.BACKTEST) 52 | 53 | logger.info('Starting freqtrade in Backtesting mode') 54 | 55 | # Initialize backtesting object 56 | backtesting = Backtesting(config) 57 | backtesting.start() 58 | 59 | 60 | def start_hyperopt(args: Namespace) -> None: 61 | """ 62 | Start hyperopt script 63 | :param args: Cli args from Arguments() 64 | :return: None 65 | """ 66 | # Import here to avoid loading hyperopt module when it's not used 67 | from freqtrade.optimize.hyperopt import Hyperopt 68 | 69 | # Initialize configuration 70 | config = setup_configuration(args, RunMode.HYPEROPT) 71 | 72 | logger.info('Starting freqtrade in Hyperopt mode') 73 | 74 | lock = FileLock(Hyperopt.get_lock_filename(config)) 75 | 76 | try: 77 | with lock.acquire(timeout=1): 78 | 79 | # Remove noisy log messages 80 | logging.getLogger('hyperopt.tpe').setLevel(logging.WARNING) 81 | logging.getLogger('filelock').setLevel(logging.WARNING) 82 | 83 | # Initialize backtesting object 84 | hyperopt = Hyperopt(config) 85 | hyperopt.start() 86 | 87 | except Timeout: 88 | logger.info("Another running instance of freqtrade Hyperopt detected.") 89 | logger.info("Simultaneous execution of multiple Hyperopt commands is not supported. " 90 | "Hyperopt module is resource hungry. Please run your Hyperopts sequentially " 91 | "or on separate machines.") 92 | logger.info("Quitting now.") 93 | # TODO: return False here in order to help freqtrade to exit 94 | # with non-zero exit code... 95 | # Same in Edge and Backtesting start() functions. 96 | 97 | 98 | def start_edge(args: Namespace) -> None: 99 | """ 100 | Start Edge script 101 | :param args: Cli args from Arguments() 102 | :return: None 103 | """ 104 | from freqtrade.optimize.edge_cli import EdgeCli 105 | # Initialize configuration 106 | config = setup_configuration(args, RunMode.EDGE) 107 | logger.info('Starting freqtrade in Edge mode') 108 | 109 | # Initialize Edge object 110 | edge_cli = EdgeCli(config) 111 | edge_cli.start() 112 | -------------------------------------------------------------------------------- /freqtrade/optimize/default_hyperopt_loss.py: -------------------------------------------------------------------------------- 1 | """ 2 | DefaultHyperOptLoss 3 | This module defines the default HyperoptLoss class which is being used for 4 | Hyperoptimization. 5 | """ 6 | from math import exp 7 | 8 | from pandas import DataFrame 9 | 10 | from freqtrade.optimize.hyperopt import IHyperOptLoss 11 | 12 | 13 | # Set TARGET_TRADES to suit your number concurrent trades so its realistic 14 | # to the number of days 15 | TARGET_TRADES = 600 16 | 17 | # This is assumed to be expected avg profit * expected trade count. 18 | # For example, for 0.35% avg per trade (or 0.0035 as ratio) and 1100 trades, 19 | # expected max profit = 3.85 20 | # Check that the reported Σ% values do not exceed this! 21 | # Note, this is ratio. 3.85 stated above means 385Σ%. 22 | EXPECTED_MAX_PROFIT = 3.0 23 | 24 | # Max average trade duration in minutes. 25 | # If eval ends with higher value, we consider it a failed eval. 26 | MAX_ACCEPTED_TRADE_DURATION = 300 27 | 28 | 29 | class DefaultHyperOptLoss(IHyperOptLoss): 30 | """ 31 | Defines the default loss function for hyperopt 32 | """ 33 | 34 | @staticmethod 35 | def hyperopt_loss_function(results: DataFrame, trade_count: int, 36 | *args, **kwargs) -> float: 37 | """ 38 | Objective function, returns smaller number for better results 39 | This is the Default algorithm 40 | Weights are distributed as follows: 41 | * 0.4 to trade duration 42 | * 0.25: Avoiding trade loss 43 | * 1.0 to total profit, compared to the expected value (`EXPECTED_MAX_PROFIT`) defined above 44 | """ 45 | total_profit = results.profit_percent.sum() 46 | trade_duration = results.trade_duration.mean() 47 | 48 | trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8) 49 | profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT) 50 | duration_loss = 0.4 * min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1) 51 | result = trade_loss + profit_loss + duration_loss 52 | return result 53 | -------------------------------------------------------------------------------- /freqtrade/optimize/edge_cli.py: -------------------------------------------------------------------------------- 1 | # pragma pylint: disable=missing-docstring, W0212, too-many-arguments 2 | 3 | """ 4 | This module contains the edge backtesting interface 5 | """ 6 | import logging 7 | from typing import Dict, Any 8 | from tabulate import tabulate 9 | from freqtrade import constants 10 | from freqtrade.edge import Edge 11 | 12 | from freqtrade.configuration import TimeRange 13 | from freqtrade.exchange import Exchange 14 | from freqtrade.resolvers import StrategyResolver 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | 19 | class EdgeCli(object): 20 | """ 21 | EdgeCli class, this class contains all the logic to run edge backtesting 22 | 23 | To run a edge backtest: 24 | edge = EdgeCli(config) 25 | edge.start() 26 | """ 27 | 28 | def __init__(self, config: Dict[str, Any]) -> None: 29 | self.config = config 30 | 31 | # Reset keys for edge 32 | self.config['exchange']['key'] = '' 33 | self.config['exchange']['secret'] = '' 34 | self.config['exchange']['password'] = '' 35 | self.config['exchange']['uid'] = '' 36 | self.config['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT 37 | self.config['dry_run'] = True 38 | self.exchange = Exchange(self.config) 39 | self.strategy = StrategyResolver(self.config).strategy 40 | 41 | self.edge = Edge(config, self.exchange, self.strategy) 42 | self.edge._refresh_pairs = self.config.get('refresh_pairs', False) 43 | 44 | self.timerange = TimeRange.parse_timerange(None if self.config.get( 45 | 'timerange') is None else str(self.config.get('timerange'))) 46 | 47 | self.edge._timerange = self.timerange 48 | 49 | def _generate_edge_table(self, results: dict) -> str: 50 | 51 | floatfmt = ('s', '.10g', '.2f', '.2f', '.2f', '.2f', 'd', '.d') 52 | tabular_data = [] 53 | headers = ['pair', 'stoploss', 'win rate', 'risk reward ratio', 54 | 'required risk reward', 'expectancy', 'total number of trades', 55 | 'average duration (min)'] 56 | 57 | for result in results.items(): 58 | if result[1].nb_trades > 0: 59 | tabular_data.append([ 60 | result[0], 61 | result[1].stoploss, 62 | result[1].winrate, 63 | result[1].risk_reward_ratio, 64 | result[1].required_risk_reward, 65 | result[1].expectancy, 66 | result[1].nb_trades, 67 | round(result[1].avg_trade_duration) 68 | ]) 69 | 70 | # Ignore type as floatfmt does allow tuples but mypy does not know that 71 | return tabulate(tabular_data, headers=headers, # type: ignore 72 | floatfmt=floatfmt, tablefmt="pipe") 73 | 74 | def start(self) -> None: 75 | result = self.edge.calculate() 76 | if result: 77 | print('') # blank line for readability 78 | print(self._generate_edge_table(self.edge._cached_pairs)) 79 | -------------------------------------------------------------------------------- /freqtrade/optimize/hyperopt_loss_interface.py: -------------------------------------------------------------------------------- 1 | """ 2 | IHyperOptLoss interface 3 | This module defines the interface for the loss-function for hyperopts 4 | """ 5 | 6 | from abc import ABC, abstractmethod 7 | from datetime import datetime 8 | 9 | from pandas import DataFrame 10 | 11 | 12 | class IHyperOptLoss(ABC): 13 | """ 14 | Interface for freqtrade hyperopts Loss functions. 15 | Defines the custom loss function (`hyperopt_loss_function()` which is evaluated every epoch.) 16 | """ 17 | ticker_interval: str 18 | 19 | @staticmethod 20 | @abstractmethod 21 | def hyperopt_loss_function(results: DataFrame, trade_count: int, 22 | min_date: datetime, max_date: datetime, *args, **kwargs) -> float: 23 | """ 24 | Objective function, returns smaller number for better results 25 | """ 26 | -------------------------------------------------------------------------------- /freqtrade/optimize/hyperopt_loss_onlyprofit.py: -------------------------------------------------------------------------------- 1 | """ 2 | OnlyProfitHyperOptLoss 3 | 4 | This module defines the alternative HyperOptLoss class which can be used for 5 | Hyperoptimization. 6 | """ 7 | from pandas import DataFrame 8 | 9 | from freqtrade.optimize.hyperopt import IHyperOptLoss 10 | 11 | 12 | # This is assumed to be expected avg profit * expected trade count. 13 | # For example, for 0.35% avg per trade (or 0.0035 as ratio) and 1100 trades, 14 | # expected max profit = 3.85 15 | # 16 | # Note, this is ratio. 3.85 stated above means 385Σ%, 3.0 means 300Σ%. 17 | # 18 | # In this implementation it's only used in calculation of the resulting value 19 | # of the objective function as a normalization coefficient and does not 20 | # represent any limit for profits as in the Freqtrade legacy default loss function. 21 | EXPECTED_MAX_PROFIT = 3.0 22 | 23 | 24 | class OnlyProfitHyperOptLoss(IHyperOptLoss): 25 | """ 26 | Defines the loss function for hyperopt. 27 | 28 | This implementation takes only profit into account. 29 | """ 30 | 31 | @staticmethod 32 | def hyperopt_loss_function(results: DataFrame, trade_count: int, 33 | *args, **kwargs) -> float: 34 | """ 35 | Objective function, returns smaller number for better results. 36 | """ 37 | total_profit = results.profit_percent.sum() 38 | return 1 - total_profit / EXPECTED_MAX_PROFIT 39 | -------------------------------------------------------------------------------- /freqtrade/optimize/hyperopt_loss_sharpe.py: -------------------------------------------------------------------------------- 1 | """ 2 | SharpeHyperOptLoss 3 | 4 | This module defines the alternative HyperOptLoss class which can be used for 5 | Hyperoptimization. 6 | """ 7 | from datetime import datetime 8 | 9 | from pandas import DataFrame 10 | import numpy as np 11 | 12 | from freqtrade.optimize.hyperopt import IHyperOptLoss 13 | 14 | 15 | class SharpeHyperOptLoss(IHyperOptLoss): 16 | """ 17 | Defines the loss function for hyperopt. 18 | 19 | This implementation uses the Sharpe Ratio calculation. 20 | """ 21 | 22 | @staticmethod 23 | def hyperopt_loss_function(results: DataFrame, trade_count: int, 24 | min_date: datetime, max_date: datetime, 25 | *args, **kwargs) -> float: 26 | """ 27 | Objective function, returns smaller number for more optimal results. 28 | 29 | Uses Sharpe Ratio calculation. 30 | """ 31 | total_profit = results.profit_percent 32 | days_period = (max_date - min_date).days 33 | 34 | # adding slippage of 0.1% per trade 35 | total_profit = total_profit - 0.0005 36 | expected_yearly_return = total_profit.sum() / days_period 37 | 38 | if (np.std(total_profit) != 0.): 39 | sharp_ratio = expected_yearly_return / np.std(total_profit) * np.sqrt(365) 40 | else: 41 | # Define high (negative) sharpe ratio to be clear that this is NOT optimal. 42 | sharp_ratio = -20. 43 | 44 | # print(expected_yearly_return, np.std(total_profit), sharp_ratio) 45 | return -sharp_ratio 46 | -------------------------------------------------------------------------------- /freqtrade/pairlist/IPairList.py: -------------------------------------------------------------------------------- 1 | """ 2 | Static List provider 3 | 4 | Provides lists as configured in config.json 5 | 6 | """ 7 | import logging 8 | from abc import ABC, abstractmethod 9 | from typing import List 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | class IPairList(ABC): 15 | 16 | def __init__(self, freqtrade, config: dict) -> None: 17 | self._freqtrade = freqtrade 18 | self._config = config 19 | self._whitelist = self._config['exchange']['pair_whitelist'] 20 | self._blacklist = self._config['exchange'].get('pair_blacklist', []) 21 | 22 | @property 23 | def name(self) -> str: 24 | """ 25 | Gets name of the class 26 | -> no need to overwrite in subclasses 27 | """ 28 | return self.__class__.__name__ 29 | 30 | @property 31 | def whitelist(self) -> List[str]: 32 | """ 33 | Has the current whitelist 34 | -> no need to overwrite in subclasses 35 | """ 36 | return self._whitelist 37 | 38 | @property 39 | def blacklist(self) -> List[str]: 40 | """ 41 | Has the current blacklist 42 | -> no need to overwrite in subclasses 43 | """ 44 | return self._blacklist 45 | 46 | @abstractmethod 47 | def short_desc(self) -> str: 48 | """ 49 | Short whitelist method description - used for startup-messages 50 | -> Please overwrite in subclasses 51 | """ 52 | 53 | @abstractmethod 54 | def refresh_pairlist(self) -> None: 55 | """ 56 | Refreshes pairlists and assigns them to self._whitelist and self._blacklist respectively 57 | -> Please overwrite in subclasses 58 | """ 59 | 60 | def _validate_whitelist(self, whitelist: List[str]) -> List[str]: 61 | """ 62 | Check available markets and remove pair from whitelist if necessary 63 | :param whitelist: the sorted list of pairs the user might want to trade 64 | :return: the list of pairs the user wants to trade without those unavailable or 65 | black_listed 66 | """ 67 | markets = self._freqtrade.exchange.markets 68 | 69 | sanitized_whitelist = set() 70 | for pair in whitelist: 71 | # pair is not in the generated dynamic market, or in the blacklist ... ignore it 72 | if (pair in self.blacklist or pair not in markets 73 | or not pair.endswith(self._config['stake_currency'])): 74 | logger.warning(f"Pair {pair} is not compatible with exchange " 75 | f"{self._freqtrade.exchange.name} or contained in " 76 | f"your blacklist. Removing it from whitelist..") 77 | continue 78 | # Check if market is active 79 | market = markets[pair] 80 | if not market['active']: 81 | logger.info(f"Ignoring {pair} from whitelist. Market is not active.") 82 | continue 83 | sanitized_whitelist.add(pair) 84 | 85 | # We need to remove pairs that are unknown 86 | return list(sanitized_whitelist) 87 | -------------------------------------------------------------------------------- /freqtrade/pairlist/StaticPairList.py: -------------------------------------------------------------------------------- 1 | """ 2 | Static List provider 3 | 4 | Provides lists as configured in config.json 5 | 6 | """ 7 | import logging 8 | 9 | from freqtrade.pairlist.IPairList import IPairList 10 | 11 | logger = logging.getLogger(__name__) 12 | 13 | 14 | class StaticPairList(IPairList): 15 | 16 | def __init__(self, freqtrade, config: dict) -> None: 17 | super().__init__(freqtrade, config) 18 | 19 | def short_desc(self) -> str: 20 | """ 21 | Short whitelist method description - used for startup-messages 22 | -> Please overwrite in subclasses 23 | """ 24 | return f"{self.name}: {self.whitelist}" 25 | 26 | def refresh_pairlist(self) -> None: 27 | """ 28 | Refreshes pairlists and assigns them to self._whitelist and self._blacklist respectively 29 | """ 30 | self._whitelist = self._validate_whitelist(self._config['exchange']['pair_whitelist']) 31 | -------------------------------------------------------------------------------- /freqtrade/pairlist/VolumePairList.py: -------------------------------------------------------------------------------- 1 | """ 2 | Volume PairList provider 3 | 4 | Provides lists as configured in config.json 5 | 6 | """ 7 | import logging 8 | from typing import List 9 | from cachetools import TTLCache, cached 10 | 11 | from freqtrade.pairlist.IPairList import IPairList 12 | from freqtrade import OperationalException 13 | logger = logging.getLogger(__name__) 14 | 15 | SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume'] 16 | 17 | 18 | class VolumePairList(IPairList): 19 | 20 | def __init__(self, freqtrade, config: dict) -> None: 21 | super().__init__(freqtrade, config) 22 | self._whitelistconf = self._config.get('pairlist', {}).get('config') 23 | if 'number_assets' not in self._whitelistconf: 24 | raise OperationalException( 25 | f'`number_assets` not specified. Please check your configuration ' 26 | 'for "pairlist.config.number_assets"') 27 | self._number_pairs = self._whitelistconf['number_assets'] 28 | self._sort_key = self._whitelistconf.get('sort_key', 'quoteVolume') 29 | self._precision_filter = self._whitelistconf.get('precision_filter', False) 30 | 31 | if not self._freqtrade.exchange.exchange_has('fetchTickers'): 32 | raise OperationalException( 33 | 'Exchange does not support dynamic whitelist.' 34 | 'Please edit your config and restart the bot' 35 | ) 36 | if not self._validate_keys(self._sort_key): 37 | raise OperationalException( 38 | f'key {self._sort_key} not in {SORT_VALUES}') 39 | 40 | def _validate_keys(self, key): 41 | return key in SORT_VALUES 42 | 43 | def short_desc(self) -> str: 44 | """ 45 | Short whitelist method description - used for startup-messages 46 | -> Please overwrite in subclasses 47 | """ 48 | return f"{self.name} - top {self._whitelistconf['number_assets']} volume pairs." 49 | 50 | def refresh_pairlist(self) -> None: 51 | """ 52 | Refreshes pairlists and assigns them to self._whitelist and self._blacklist respectively 53 | -> Please overwrite in subclasses 54 | """ 55 | # Generate dynamic whitelist 56 | self._whitelist = self._gen_pair_whitelist( 57 | self._config['stake_currency'], self._sort_key)[:self._number_pairs] 58 | 59 | @cached(TTLCache(maxsize=1, ttl=1800)) 60 | def _gen_pair_whitelist(self, base_currency: str, key: str) -> List[str]: 61 | """ 62 | Updates the whitelist with with a dynamically generated list 63 | :param base_currency: base currency as str 64 | :param key: sort key (defaults to 'quoteVolume') 65 | :return: List of pairs 66 | """ 67 | 68 | tickers = self._freqtrade.exchange.get_tickers() 69 | # check length so that we make sure that '/' is actually in the string 70 | tickers = [v for k, v in tickers.items() 71 | if (len(k.split('/')) == 2 and k.split('/')[1] == base_currency 72 | and v[key] is not None)] 73 | sorted_tickers = sorted(tickers, reverse=True, key=lambda t: t[key]) 74 | # Validate whitelist to only have active market pairs 75 | valid_pairs = self._validate_whitelist([s['symbol'] for s in sorted_tickers]) 76 | valid_tickers = [t for t in sorted_tickers if t["symbol"] in valid_pairs] 77 | 78 | if self._freqtrade.strategy.stoploss is not None and self._precision_filter: 79 | 80 | stop_prices = [self._freqtrade.get_target_bid(t["symbol"], t) 81 | * (1 - abs(self._freqtrade.strategy.stoploss)) for t in valid_tickers] 82 | rates = [sp * 0.99 for sp in stop_prices] 83 | logger.debug("\n".join([f"{sp} : {r}" for sp, r in zip(stop_prices[:10], rates[:10])])) 84 | for i, t in enumerate(valid_tickers): 85 | sp = self._freqtrade.exchange.symbol_price_prec(t["symbol"], stop_prices[i]) 86 | r = self._freqtrade.exchange.symbol_price_prec(t["symbol"], rates[i]) 87 | logger.debug(f"{t['symbol']} - {sp} : {r}") 88 | if sp <= r: 89 | logger.info(f"Removed {t['symbol']} from whitelist, " 90 | f"because stop price {sp} would be <= stop limit {r}") 91 | valid_tickers.remove(t) 92 | 93 | pairs = [s['symbol'] for s in valid_tickers] 94 | logger.info(f"Searching pairs: {self._whitelist}") 95 | 96 | return pairs 97 | -------------------------------------------------------------------------------- /freqtrade/pairlist/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/freqtrade/pairlist/__init__.py -------------------------------------------------------------------------------- /freqtrade/plot/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/freqtrade/plot/__init__.py -------------------------------------------------------------------------------- /freqtrade/plot/plot_utils.py: -------------------------------------------------------------------------------- 1 | from argparse import Namespace 2 | from freqtrade import OperationalException 3 | from freqtrade.state import RunMode 4 | from freqtrade.utils import setup_utils_configuration 5 | 6 | 7 | def validate_plot_args(args: Namespace): 8 | args_tmp = vars(args) 9 | if not args_tmp.get('datadir') and not args_tmp.get('config'): 10 | raise OperationalException( 11 | "You need to specify either `--datadir` or `--config` " 12 | "for plot-profit and plot-dataframe.") 13 | 14 | 15 | def start_plot_dataframe(args: Namespace) -> None: 16 | """ 17 | Entrypoint for dataframe plotting 18 | """ 19 | # Import here to avoid errors if plot-dependencies are not installed. 20 | from freqtrade.plot.plotting import analyse_and_plot_pairs 21 | validate_plot_args(args) 22 | config = setup_utils_configuration(args, RunMode.PLOT) 23 | 24 | analyse_and_plot_pairs(config) 25 | 26 | 27 | def start_plot_profit(args: Namespace) -> None: 28 | """ 29 | Entrypoint for plot_profit 30 | """ 31 | # Import here to avoid errors if plot-dependencies are not installed. 32 | from freqtrade.plot.plotting import plot_profit 33 | validate_plot_args(args) 34 | config = setup_utils_configuration(args, RunMode.PLOT) 35 | 36 | plot_profit(config) 37 | -------------------------------------------------------------------------------- /freqtrade/resolvers/__init__.py: -------------------------------------------------------------------------------- 1 | from freqtrade.resolvers.iresolver import IResolver # noqa: F401 2 | from freqtrade.resolvers.exchange_resolver import ExchangeResolver # noqa: F401 3 | # Don't import HyperoptResolver to avoid loading the whole Optimize tree 4 | # from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver # noqa: F401 5 | from freqtrade.resolvers.pairlist_resolver import PairListResolver # noqa: F401 6 | from freqtrade.resolvers.strategy_resolver import StrategyResolver # noqa: F401 7 | -------------------------------------------------------------------------------- /freqtrade/resolvers/exchange_resolver.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module loads custom exchanges 3 | """ 4 | import logging 5 | 6 | from freqtrade.exchange import Exchange 7 | import freqtrade.exchange as exchanges 8 | from freqtrade.resolvers import IResolver 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | class ExchangeResolver(IResolver): 14 | """ 15 | This class contains all the logic to load a custom exchange class 16 | """ 17 | 18 | __slots__ = ['exchange'] 19 | 20 | def __init__(self, exchange_name: str, config: dict) -> None: 21 | """ 22 | Load the custom class from config parameter 23 | :param config: configuration dictionary 24 | """ 25 | exchange_name = exchange_name.title() 26 | try: 27 | self.exchange = self._load_exchange(exchange_name, kwargs={'config': config}) 28 | except ImportError: 29 | logger.info( 30 | f"No {exchange_name} specific subclass found. Using the generic class instead.") 31 | if not hasattr(self, "exchange"): 32 | self.exchange = Exchange(config) 33 | 34 | def _load_exchange( 35 | self, exchange_name: str, kwargs: dict) -> Exchange: 36 | """ 37 | Loads the specified exchange. 38 | Only checks for exchanges exported in freqtrade.exchanges 39 | :param exchange_name: name of the module to import 40 | :return: Exchange instance or None 41 | """ 42 | 43 | try: 44 | ex_class = getattr(exchanges, exchange_name) 45 | 46 | exchange = ex_class(kwargs['config']) 47 | if exchange: 48 | logger.info(f"Using resolved exchange '{exchange_name}'...") 49 | return exchange 50 | except AttributeError: 51 | # Pass and raise ImportError instead 52 | pass 53 | 54 | raise ImportError( 55 | f"Impossible to load Exchange '{exchange_name}'. This class does not exist " 56 | "or contains Python code errors." 57 | ) 58 | -------------------------------------------------------------------------------- /freqtrade/resolvers/hyperopt_resolver.py: -------------------------------------------------------------------------------- 1 | # pragma pylint: disable=attribute-defined-outside-init 2 | 3 | """ 4 | This module load custom hyperopts 5 | """ 6 | import logging 7 | from pathlib import Path 8 | from typing import Optional, Dict 9 | 10 | from freqtrade import OperationalException 11 | from freqtrade.constants import DEFAULT_HYPEROPT, DEFAULT_HYPEROPT_LOSS 12 | from freqtrade.optimize.hyperopt_interface import IHyperOpt 13 | from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss 14 | from freqtrade.resolvers import IResolver 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | 19 | class HyperOptResolver(IResolver): 20 | """ 21 | This class contains all the logic to load custom hyperopt class 22 | """ 23 | 24 | __slots__ = ['hyperopt'] 25 | 26 | def __init__(self, config: Dict) -> None: 27 | """ 28 | Load the custom class from config parameter 29 | :param config: configuration dictionary 30 | """ 31 | 32 | # Verify the hyperopt is in the configuration, otherwise fallback to the default hyperopt 33 | hyperopt_name = config.get('hyperopt') or DEFAULT_HYPEROPT 34 | self.hyperopt = self._load_hyperopt(hyperopt_name, config, 35 | extra_dir=config.get('hyperopt_path')) 36 | 37 | # Assign ticker_interval to be used in hyperopt 38 | IHyperOpt.ticker_interval = str(config['ticker_interval']) 39 | 40 | if not hasattr(self.hyperopt, 'populate_buy_trend'): 41 | logger.warning("Custom Hyperopt does not provide populate_buy_trend. " 42 | "Using populate_buy_trend from DefaultStrategy.") 43 | if not hasattr(self.hyperopt, 'populate_sell_trend'): 44 | logger.warning("Custom Hyperopt does not provide populate_sell_trend. " 45 | "Using populate_sell_trend from DefaultStrategy.") 46 | 47 | def _load_hyperopt( 48 | self, hyperopt_name: str, config: Dict, extra_dir: Optional[str] = None) -> IHyperOpt: 49 | """ 50 | Search and loads the specified hyperopt. 51 | :param hyperopt_name: name of the module to import 52 | :param config: configuration dictionary 53 | :param extra_dir: additional directory to search for the given hyperopt 54 | :return: HyperOpt instance or None 55 | """ 56 | current_path = Path(__file__).parent.parent.joinpath('optimize').resolve() 57 | 58 | abs_paths = [ 59 | config['user_data_dir'].joinpath('hyperopts'), 60 | current_path, 61 | ] 62 | 63 | if extra_dir: 64 | # Add extra hyperopt directory on top of search paths 65 | abs_paths.insert(0, Path(extra_dir).resolve()) 66 | 67 | hyperopt = self._load_object(paths=abs_paths, object_type=IHyperOpt, 68 | object_name=hyperopt_name) 69 | if hyperopt: 70 | return hyperopt 71 | raise OperationalException( 72 | f"Impossible to load Hyperopt '{hyperopt_name}'. This class does not exist " 73 | "or contains Python code errors." 74 | ) 75 | 76 | 77 | class HyperOptLossResolver(IResolver): 78 | """ 79 | This class contains all the logic to load custom hyperopt loss class 80 | """ 81 | 82 | __slots__ = ['hyperoptloss'] 83 | 84 | def __init__(self, config: Dict = None) -> None: 85 | """ 86 | Load the custom class from config parameter 87 | :param config: configuration dictionary or None 88 | """ 89 | config = config or {} 90 | 91 | # Verify the hyperopt is in the configuration, otherwise fallback to the default hyperopt 92 | hyperopt_name = config.get('hyperopt_loss') or DEFAULT_HYPEROPT_LOSS 93 | self.hyperoptloss = self._load_hyperoptloss( 94 | hyperopt_name, config, extra_dir=config.get('hyperopt_path')) 95 | 96 | # Assign ticker_interval to be used in hyperopt 97 | self.hyperoptloss.__class__.ticker_interval = str(config['ticker_interval']) 98 | 99 | if not hasattr(self.hyperoptloss, 'hyperopt_loss_function'): 100 | raise OperationalException( 101 | f"Found hyperopt {hyperopt_name} does not implement `hyperopt_loss_function`.") 102 | 103 | def _load_hyperoptloss( 104 | self, hyper_loss_name: str, config: Dict, 105 | extra_dir: Optional[str] = None) -> IHyperOptLoss: 106 | """ 107 | Search and loads the specified hyperopt loss class. 108 | :param hyper_loss_name: name of the module to import 109 | :param config: configuration dictionary 110 | :param extra_dir: additional directory to search for the given hyperopt 111 | :return: HyperOptLoss instance or None 112 | """ 113 | current_path = Path(__file__).parent.parent.joinpath('optimize').resolve() 114 | 115 | abs_paths = [ 116 | config['user_data_dir'].joinpath('hyperopts'), 117 | current_path, 118 | ] 119 | 120 | if extra_dir: 121 | # Add extra hyperopt directory on top of search paths 122 | abs_paths.insert(0, Path(extra_dir).resolve()) 123 | 124 | hyperoptloss = self._load_object(paths=abs_paths, object_type=IHyperOptLoss, 125 | object_name=hyper_loss_name) 126 | if hyperoptloss: 127 | return hyperoptloss 128 | 129 | raise OperationalException( 130 | f"Impossible to load HyperoptLoss '{hyper_loss_name}'. This class does not exist " 131 | "or contains Python code errors." 132 | ) 133 | -------------------------------------------------------------------------------- /freqtrade/resolvers/iresolver.py: -------------------------------------------------------------------------------- 1 | # pragma pylint: disable=attribute-defined-outside-init 2 | 3 | """ 4 | This module load custom objects 5 | """ 6 | import importlib.util 7 | import inspect 8 | import logging 9 | from pathlib import Path 10 | from typing import Any, List, Optional, Tuple, Type, Union 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class IResolver(object): 16 | """ 17 | This class contains all the logic to load custom classes 18 | """ 19 | 20 | @staticmethod 21 | def _get_valid_object(object_type, module_path: Path, 22 | object_name: str) -> Optional[Type[Any]]: 23 | """ 24 | Returns the first object with matching object_type and object_name in the path given. 25 | :param object_type: object_type (class) 26 | :param module_path: absolute path to the module 27 | :param object_name: Class name of the object 28 | :return: class or None 29 | """ 30 | 31 | # Generate spec based on absolute path 32 | # Pass object_name as first argument to have logging print a reasonable name. 33 | spec = importlib.util.spec_from_file_location(object_name, str(module_path)) 34 | module = importlib.util.module_from_spec(spec) 35 | try: 36 | spec.loader.exec_module(module) # type: ignore # importlib does not use typehints 37 | except (ModuleNotFoundError, SyntaxError) as err: 38 | # Catch errors in case a specific module is not installed 39 | logger.warning(f"Could not import {module_path} due to '{err}'") 40 | 41 | valid_objects_gen = ( 42 | obj for name, obj in inspect.getmembers(module, inspect.isclass) 43 | if object_name == name and object_type in obj.__bases__ 44 | ) 45 | return next(valid_objects_gen, None) 46 | 47 | @staticmethod 48 | def _search_object(directory: Path, object_type, object_name: str, 49 | kwargs: dict = {}) -> Union[Tuple[Any, Path], Tuple[None, None]]: 50 | """ 51 | Search for the objectname in the given directory 52 | :param directory: relative or absolute directory path 53 | :return: object instance 54 | """ 55 | logger.debug("Searching for %s %s in '%s'", object_type.__name__, object_name, directory) 56 | for entry in directory.iterdir(): 57 | # Only consider python files 58 | if not str(entry).endswith('.py'): 59 | logger.debug('Ignoring %s', entry) 60 | continue 61 | module_path = entry.resolve() 62 | obj = IResolver._get_valid_object( 63 | object_type, module_path, object_name 64 | ) 65 | if obj: 66 | return (obj(**kwargs), module_path) 67 | return (None, None) 68 | 69 | @staticmethod 70 | def _load_object(paths: List[Path], object_type, object_name: str, 71 | kwargs: dict = {}) -> Optional[Any]: 72 | """ 73 | Try to load object from path list. 74 | """ 75 | 76 | for _path in paths: 77 | try: 78 | (module, module_path) = IResolver._search_object(directory=_path, 79 | object_type=object_type, 80 | object_name=object_name, 81 | kwargs=kwargs) 82 | if module: 83 | logger.info( 84 | f"Using resolved {object_type.__name__.lower()[1:]} {object_name} " 85 | f"from '{module_path}'...") 86 | return module 87 | except FileNotFoundError: 88 | logger.warning('Path "%s" does not exist.', _path.resolve()) 89 | 90 | return None 91 | -------------------------------------------------------------------------------- /freqtrade/resolvers/pairlist_resolver.py: -------------------------------------------------------------------------------- 1 | # pragma pylint: disable=attribute-defined-outside-init 2 | 3 | """ 4 | This module load custom hyperopts 5 | """ 6 | import logging 7 | from pathlib import Path 8 | 9 | from freqtrade import OperationalException 10 | from freqtrade.pairlist.IPairList import IPairList 11 | from freqtrade.resolvers import IResolver 12 | 13 | logger = logging.getLogger(__name__) 14 | 15 | 16 | class PairListResolver(IResolver): 17 | """ 18 | This class contains all the logic to load custom hyperopt class 19 | """ 20 | 21 | __slots__ = ['pairlist'] 22 | 23 | def __init__(self, pairlist_name: str, freqtrade, config: dict) -> None: 24 | """ 25 | Load the custom class from config parameter 26 | :param config: configuration dictionary or None 27 | """ 28 | self.pairlist = self._load_pairlist(pairlist_name, config, kwargs={'freqtrade': freqtrade, 29 | 'config': config}) 30 | 31 | def _load_pairlist( 32 | self, pairlist_name: str, config: dict, kwargs: dict) -> IPairList: 33 | """ 34 | Search and loads the specified pairlist. 35 | :param pairlist_name: name of the module to import 36 | :param config: configuration dictionary 37 | :param extra_dir: additional directory to search for the given pairlist 38 | :return: PairList instance or None 39 | """ 40 | current_path = Path(__file__).parent.parent.joinpath('pairlist').resolve() 41 | 42 | abs_paths = [ 43 | config['user_data_dir'].joinpath('pairlist'), 44 | current_path, 45 | ] 46 | 47 | pairlist = self._load_object(paths=abs_paths, object_type=IPairList, 48 | object_name=pairlist_name, kwargs=kwargs) 49 | if pairlist: 50 | return pairlist 51 | raise OperationalException( 52 | f"Impossible to load Pairlist '{pairlist_name}'. This class does not exist " 53 | "or contains Python code errors." 54 | ) 55 | -------------------------------------------------------------------------------- /freqtrade/rpc/__init__.py: -------------------------------------------------------------------------------- 1 | from .rpc import RPC, RPCMessageType, RPCException # noqa 2 | from .rpc_manager import RPCManager # noqa 3 | -------------------------------------------------------------------------------- /freqtrade/rpc/rpc_manager.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module contains class to manage RPC communications (Telegram, Slack, ...) 3 | """ 4 | import logging 5 | from typing import Any, Dict, List 6 | 7 | from freqtrade.rpc import RPC, RPCMessageType 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | class RPCManager(object): 13 | """ 14 | Class to manage RPC objects (Telegram, Slack, ...) 15 | """ 16 | def __init__(self, freqtrade) -> None: 17 | """ Initializes all enabled rpc modules """ 18 | self.registered_modules: List[RPC] = [] 19 | 20 | # Enable telegram 21 | if freqtrade.config['telegram'].get('enabled', False): 22 | logger.info('Enabling rpc.telegram ...') 23 | from freqtrade.rpc.telegram import Telegram 24 | self.registered_modules.append(Telegram(freqtrade)) 25 | 26 | # Enable Webhook 27 | if freqtrade.config.get('webhook', {}).get('enabled', False): 28 | logger.info('Enabling rpc.webhook ...') 29 | from freqtrade.rpc.webhook import Webhook 30 | self.registered_modules.append(Webhook(freqtrade)) 31 | 32 | # Enable local rest api server for cmd line control 33 | if freqtrade.config.get('api_server', {}).get('enabled', False): 34 | logger.info('Enabling rpc.api_server') 35 | from freqtrade.rpc.api_server import ApiServer 36 | self.registered_modules.append(ApiServer(freqtrade)) 37 | 38 | def cleanup(self) -> None: 39 | """ Stops all enabled rpc modules """ 40 | logger.info('Cleaning up rpc modules ...') 41 | while self.registered_modules: 42 | mod = self.registered_modules.pop() 43 | logger.debug('Cleaning up rpc.%s ...', mod.name) 44 | mod.cleanup() 45 | del mod 46 | 47 | def send_msg(self, msg: Dict[str, Any]) -> None: 48 | """ 49 | Send given message to all registered rpc modules. 50 | A message consists of one or more key value pairs of strings. 51 | e.g.: 52 | { 53 | 'status': 'stopping bot' 54 | } 55 | """ 56 | logger.info('Sending rpc message: %s', msg) 57 | for mod in self.registered_modules: 58 | logger.debug('Forwarding message to rpc.%s', mod.name) 59 | try: 60 | mod.send_msg(msg) 61 | except NotImplementedError: 62 | logger.error(f"Message type {msg['type']} not implemented by handler {mod.name}.") 63 | 64 | def startup_messages(self, config, pairlist) -> None: 65 | if config.get('dry_run', False): 66 | self.send_msg({ 67 | 'type': RPCMessageType.WARNING_NOTIFICATION, 68 | 'status': 'Dry run is enabled. All trades are simulated.' 69 | }) 70 | stake_currency = config['stake_currency'] 71 | stake_amount = config['stake_amount'] 72 | minimal_roi = config['minimal_roi'] 73 | stoploss = config['stoploss'] 74 | trailing_stop = config['trailing_stop'] 75 | ticker_interval = config['ticker_interval'] 76 | exchange_name = config['exchange']['name'] 77 | strategy_name = config.get('strategy', '') 78 | self.send_msg({ 79 | 'type': RPCMessageType.CUSTOM_NOTIFICATION, 80 | 'status': f'*Exchange:* `{exchange_name}`\n' 81 | f'*Stake per trade:* `{stake_amount} {stake_currency}`\n' 82 | f'*Minimum ROI:* `{minimal_roi}`\n' 83 | f'*{"Trailing " if trailing_stop else ""}Stoploss:* `{stoploss}`\n' 84 | f'*Ticker Interval:* `{ticker_interval}`\n' 85 | f'*Strategy:* `{strategy_name}`' 86 | }) 87 | self.send_msg({ 88 | 'type': RPCMessageType.STATUS_NOTIFICATION, 89 | 'status': f'Searching for {stake_currency} pairs to buy and sell ' 90 | f'based on {pairlist.short_desc()}' 91 | }) 92 | -------------------------------------------------------------------------------- /freqtrade/rpc/webhook.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module manages webhook communication 3 | """ 4 | import logging 5 | from typing import Any, Dict 6 | 7 | from requests import post, RequestException 8 | 9 | from freqtrade.rpc import RPC, RPCMessageType 10 | 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | logger.debug('Included module rpc.webhook ...') 15 | 16 | 17 | class Webhook(RPC): 18 | """ This class handles all webhook communication """ 19 | 20 | def __init__(self, freqtrade) -> None: 21 | """ 22 | Init the Webhook class, and init the super class RPC 23 | :param freqtrade: Instance of a freqtrade bot 24 | :return: None 25 | """ 26 | super().__init__(freqtrade) 27 | 28 | self._config = freqtrade.config 29 | self._url = self._config['webhook']['url'] 30 | 31 | def cleanup(self) -> None: 32 | """ 33 | Cleanup pending module resources. 34 | This will do nothing for webhooks, they will simply not be called anymore 35 | """ 36 | pass 37 | 38 | def send_msg(self, msg: Dict[str, Any]) -> None: 39 | """ Send a message to telegram channel """ 40 | try: 41 | 42 | if msg['type'] == RPCMessageType.BUY_NOTIFICATION: 43 | valuedict = self._config['webhook'].get('webhookbuy', None) 44 | elif msg['type'] == RPCMessageType.SELL_NOTIFICATION: 45 | valuedict = self._config['webhook'].get('webhooksell', None) 46 | elif msg['type'] in(RPCMessageType.STATUS_NOTIFICATION, 47 | RPCMessageType.CUSTOM_NOTIFICATION, 48 | RPCMessageType.WARNING_NOTIFICATION): 49 | valuedict = self._config['webhook'].get('webhookstatus', None) 50 | else: 51 | raise NotImplementedError('Unknown message type: {}'.format(msg['type'])) 52 | if not valuedict: 53 | logger.info("Message type %s not configured for webhooks", msg['type']) 54 | return 55 | 56 | payload = {key: value.format(**msg) for (key, value) in valuedict.items()} 57 | self._send_msg(payload) 58 | except KeyError as exc: 59 | logger.exception("Problem calling Webhook. Please check your webhook configuration. " 60 | "Exception: %s", exc) 61 | 62 | def _send_msg(self, payload: dict) -> None: 63 | """do the actual call to the webhook""" 64 | 65 | try: 66 | post(self._url, data=payload) 67 | except RequestException as exc: 68 | logger.warning("Could not call webhook url. Exception: %s", exc) 69 | -------------------------------------------------------------------------------- /freqtrade/state.py: -------------------------------------------------------------------------------- 1 | # pragma pylint: disable=too-few-public-methods 2 | 3 | """ 4 | Bot state constant 5 | """ 6 | from enum import Enum 7 | 8 | 9 | class State(Enum): 10 | """ 11 | Bot application states 12 | """ 13 | RUNNING = 1 14 | STOPPED = 2 15 | RELOAD_CONF = 3 16 | 17 | 18 | class RunMode(Enum): 19 | """ 20 | Bot running mode (backtest, hyperopt, ...) 21 | can be "live", "dry-run", "backtest", "edge", "hyperopt". 22 | """ 23 | LIVE = "live" 24 | DRY_RUN = "dry_run" 25 | BACKTEST = "backtest" 26 | EDGE = "edge" 27 | HYPEROPT = "hyperopt" 28 | PLOT = "plot" 29 | OTHER = "other" # Used for plotting scripts and test 30 | -------------------------------------------------------------------------------- /freqtrade/strategy/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | from copy import deepcopy 4 | 5 | from freqtrade.strategy.interface import IStrategy 6 | # Import Default-Strategy to have hyperopt correctly resolve 7 | from freqtrade.strategy.default_strategy import DefaultStrategy # noqa: F401 8 | 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | def import_strategy(strategy: IStrategy, config: dict) -> IStrategy: 14 | """ 15 | Imports given Strategy instance to global scope 16 | of freqtrade.strategy and returns an instance of it 17 | """ 18 | 19 | # Copy all attributes from base class and class 20 | comb = {**strategy.__class__.__dict__, **strategy.__dict__} 21 | 22 | # Delete '_abc_impl' from dict as deepcopy fails on 3.7 with 23 | # `TypeError: can't pickle _abc_data objects`` 24 | # This will only apply to python 3.7 25 | if sys.version_info.major == 3 and sys.version_info.minor == 7 and '_abc_impl' in comb: 26 | del comb['_abc_impl'] 27 | 28 | attr = deepcopy(comb) 29 | 30 | # Adjust module name 31 | attr['__module__'] = 'freqtrade.strategy' 32 | 33 | name = strategy.__class__.__name__ 34 | clazz = type(name, (IStrategy,), attr) 35 | 36 | logger.debug( 37 | 'Imported strategy %s.%s as %s.%s', 38 | strategy.__module__, strategy.__class__.__name__, 39 | clazz.__module__, strategy.__class__.__name__, 40 | ) 41 | 42 | # Modify global scope to declare class 43 | globals()[name] = clazz 44 | 45 | return clazz(config) 46 | -------------------------------------------------------------------------------- /freqtrade/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/freqtrade/tests/__init__.py -------------------------------------------------------------------------------- /freqtrade/tests/config_test_comments.json: -------------------------------------------------------------------------------- 1 | { 2 | /* Single-line C-style comment */ 3 | "max_open_trades": 3, 4 | /* 5 | * Multi-line C-style comment 6 | */ 7 | "stake_currency": "BTC", 8 | "stake_amount": 0.05, 9 | "fiat_display_currency": "USD", // C++-style comment 10 | "amount_reserve_percent" : 0.05, // And more, tabs before this comment 11 | "dry_run": false, 12 | "ticker_interval": "5m", 13 | "trailing_stop": false, 14 | "trailing_stop_positive": 0.005, 15 | "trailing_stop_positive_offset": 0.0051, 16 | "trailing_only_offset_is_reached": false, 17 | "minimal_roi": { 18 | "40": 0.0, 19 | "30": 0.01, 20 | "20": 0.02, 21 | "0": 0.04 22 | }, 23 | "stoploss": -0.10, 24 | "unfilledtimeout": { 25 | "buy": 10, 26 | "sell": 30, // Trailing comma should also be accepted now 27 | }, 28 | "bid_strategy": { 29 | "use_order_book": false, 30 | "ask_last_balance": 0.0, 31 | "order_book_top": 1, 32 | "check_depth_of_market": { 33 | "enabled": false, 34 | "bids_to_ask_delta": 1 35 | } 36 | }, 37 | "ask_strategy":{ 38 | "use_order_book": false, 39 | "order_book_min": 1, 40 | "order_book_max": 9 41 | }, 42 | "order_types": { 43 | "buy": "limit", 44 | "sell": "limit", 45 | "stoploss": "market", 46 | "stoploss_on_exchange": false, 47 | "stoploss_on_exchange_interval": 60 48 | }, 49 | "order_time_in_force": { 50 | "buy": "gtc", 51 | "sell": "gtc" 52 | }, 53 | "pairlist": { 54 | "method": "VolumePairList", 55 | "config": { 56 | "number_assets": 20, 57 | "sort_key": "quoteVolume", 58 | "precision_filter": false 59 | } 60 | }, 61 | "exchange": { 62 | "name": "bittrex", 63 | "sandbox": false, 64 | "key": "your_exchange_key", 65 | "secret": "your_exchange_secret", 66 | "password": "", 67 | "ccxt_config": {"enableRateLimit": true}, 68 | "ccxt_async_config": { 69 | "enableRateLimit": false, 70 | "rateLimit": 500, 71 | "aiohttp_trust_env": false 72 | }, 73 | "pair_whitelist": [ 74 | "ETH/BTC", 75 | "LTC/BTC", 76 | "ETC/BTC", 77 | "DASH/BTC", 78 | "ZEC/BTC", 79 | "XLM/BTC", 80 | "NXT/BTC", 81 | "POWR/BTC", 82 | "ADA/BTC", 83 | "XMR/BTC" 84 | ], 85 | "pair_blacklist": [ 86 | "DOGE/BTC" 87 | ], 88 | "outdated_offset": 5, 89 | "markets_refresh_interval": 60 90 | }, 91 | "edge": { 92 | "enabled": false, 93 | "process_throttle_secs": 3600, 94 | "calculate_since_number_of_days": 7, 95 | "capital_available_percentage": 0.5, 96 | "allowed_risk": 0.01, 97 | "stoploss_range_min": -0.01, 98 | "stoploss_range_max": -0.1, 99 | "stoploss_range_step": -0.01, 100 | "minimum_winrate": 0.60, 101 | "minimum_expectancy": 0.20, 102 | "min_trade_number": 10, 103 | "max_trade_duration_minute": 1440, 104 | "remove_pumps": false 105 | }, 106 | "experimental": { 107 | "use_sell_signal": false, 108 | "sell_profit_only": false, 109 | "ignore_roi_if_buy_signal": false 110 | }, 111 | "telegram": { 112 | // We can now comment out some settings 113 | // "enabled": true, 114 | "enabled": false, 115 | "token": "your_telegram_token", 116 | "chat_id": "your_telegram_chat_id" 117 | }, 118 | "api_server": { 119 | "enabled": false, 120 | "listen_ip_address": "127.0.0.1", 121 | "listen_port": 8080, 122 | "username": "freqtrader", 123 | "password": "SuperSecurePassword" 124 | }, 125 | "db_url": "sqlite:///tradesv3.sqlite", 126 | "initial_state": "running", 127 | "forcebuy_enable": false, 128 | "internals": { 129 | "process_throttle_secs": 5 130 | }, 131 | "strategy": "DefaultStrategy", 132 | "strategy_path": "user_data/strategies/" 133 | } 134 | -------------------------------------------------------------------------------- /freqtrade/tests/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/freqtrade/tests/data/__init__.py -------------------------------------------------------------------------------- /freqtrade/tests/data/test_btanalysis.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | import pytest 4 | from arrow import Arrow 5 | from pandas import DataFrame, to_datetime 6 | 7 | from freqtrade.configuration import TimeRange 8 | from freqtrade.data.btanalysis import (BT_DATA_COLUMNS, 9 | combine_tickers_with_mean, 10 | create_cum_profit, 11 | extract_trades_of_period, 12 | load_backtest_data, load_trades, 13 | load_trades_from_db) 14 | from freqtrade.data.history import (load_data, load_pair_history, 15 | make_testdata_path) 16 | from freqtrade.tests.test_persistence import create_mock_trades 17 | 18 | 19 | def test_load_backtest_data(): 20 | 21 | filename = make_testdata_path(None) / "backtest-result_test.json" 22 | bt_data = load_backtest_data(filename) 23 | assert isinstance(bt_data, DataFrame) 24 | assert list(bt_data.columns) == BT_DATA_COLUMNS + ["profitabs"] 25 | assert len(bt_data) == 179 26 | 27 | # Test loading from string (must yield same result) 28 | bt_data2 = load_backtest_data(str(filename)) 29 | assert bt_data.equals(bt_data2) 30 | 31 | with pytest.raises(ValueError, match=r"File .* does not exist\."): 32 | load_backtest_data(str("filename") + "nofile") 33 | 34 | 35 | @pytest.mark.usefixtures("init_persistence") 36 | def test_load_trades_db(default_conf, fee, mocker): 37 | 38 | create_mock_trades(fee) 39 | # remove init so it does not init again 40 | init_mock = mocker.patch('freqtrade.persistence.init', MagicMock()) 41 | 42 | trades = load_trades_from_db(db_url=default_conf['db_url']) 43 | assert init_mock.call_count == 1 44 | assert len(trades) == 3 45 | assert isinstance(trades, DataFrame) 46 | assert "pair" in trades.columns 47 | assert "open_time" in trades.columns 48 | assert "profitperc" in trades.columns 49 | 50 | for col in BT_DATA_COLUMNS: 51 | if col not in ['index', 'open_at_end']: 52 | assert col in trades.columns 53 | 54 | 55 | def test_extract_trades_of_period(): 56 | pair = "UNITTEST/BTC" 57 | timerange = TimeRange(None, 'line', 0, -1000) 58 | 59 | data = load_pair_history(pair=pair, ticker_interval='1m', 60 | datadir=None, timerange=timerange) 61 | 62 | # timerange = 2017-11-14 06:07 - 2017-11-14 22:58:00 63 | trades = DataFrame( 64 | {'pair': [pair, pair, pair, pair], 65 | 'profit_percent': [0.0, 0.1, -0.2, -0.5], 66 | 'profit_abs': [0.0, 1, -2, -5], 67 | 'open_time': to_datetime([Arrow(2017, 11, 13, 15, 40, 0).datetime, 68 | Arrow(2017, 11, 14, 9, 41, 0).datetime, 69 | Arrow(2017, 11, 14, 14, 20, 0).datetime, 70 | Arrow(2017, 11, 15, 3, 40, 0).datetime, 71 | ], utc=True 72 | ), 73 | 'close_time': to_datetime([Arrow(2017, 11, 13, 16, 40, 0).datetime, 74 | Arrow(2017, 11, 14, 10, 41, 0).datetime, 75 | Arrow(2017, 11, 14, 15, 25, 0).datetime, 76 | Arrow(2017, 11, 15, 3, 55, 0).datetime, 77 | ], utc=True) 78 | }) 79 | trades1 = extract_trades_of_period(data, trades) 80 | # First and last trade are dropped as they are out of range 81 | assert len(trades1) == 2 82 | assert trades1.iloc[0].open_time == Arrow(2017, 11, 14, 9, 41, 0).datetime 83 | assert trades1.iloc[0].close_time == Arrow(2017, 11, 14, 10, 41, 0).datetime 84 | assert trades1.iloc[-1].open_time == Arrow(2017, 11, 14, 14, 20, 0).datetime 85 | assert trades1.iloc[-1].close_time == Arrow(2017, 11, 14, 15, 25, 0).datetime 86 | 87 | 88 | def test_load_trades(default_conf, mocker): 89 | db_mock = mocker.patch("freqtrade.data.btanalysis.load_trades_from_db", MagicMock()) 90 | bt_mock = mocker.patch("freqtrade.data.btanalysis.load_backtest_data", MagicMock()) 91 | 92 | load_trades("DB", 93 | db_url=default_conf.get('db_url'), 94 | exportfilename=default_conf.get('exportfilename'), 95 | ) 96 | 97 | assert db_mock.call_count == 1 98 | assert bt_mock.call_count == 0 99 | 100 | db_mock.reset_mock() 101 | bt_mock.reset_mock() 102 | default_conf['exportfilename'] = "testfile.json" 103 | load_trades("file", 104 | db_url=default_conf.get('db_url'), 105 | exportfilename=default_conf.get('exportfilename'),) 106 | 107 | assert db_mock.call_count == 0 108 | assert bt_mock.call_count == 1 109 | 110 | 111 | def test_combine_tickers_with_mean(): 112 | pairs = ["ETH/BTC", "XLM/BTC"] 113 | tickers = load_data(datadir=None, 114 | pairs=pairs, 115 | ticker_interval='5m' 116 | ) 117 | df = combine_tickers_with_mean(tickers) 118 | assert isinstance(df, DataFrame) 119 | assert "ETH/BTC" in df.columns 120 | assert "XLM/BTC" in df.columns 121 | assert "mean" in df.columns 122 | 123 | 124 | def test_create_cum_profit(): 125 | filename = make_testdata_path(None) / "backtest-result_test.json" 126 | bt_data = load_backtest_data(filename) 127 | timerange = TimeRange.parse_timerange("20180110-20180112") 128 | 129 | df = load_pair_history(pair="POWR/BTC", ticker_interval='5m', 130 | datadir=None, timerange=timerange) 131 | 132 | cum_profits = create_cum_profit(df.set_index('date'), 133 | bt_data[bt_data["pair"] == 'POWR/BTC'], 134 | "cum_profits") 135 | assert "cum_profits" in cum_profits.columns 136 | assert cum_profits.iloc[0]['cum_profits'] == 0 137 | assert cum_profits.iloc[-1]['cum_profits'] == 0.0798005 138 | -------------------------------------------------------------------------------- /freqtrade/tests/data/test_converter.py: -------------------------------------------------------------------------------- 1 | # pragma pylint: disable=missing-docstring, C0103 2 | import logging 3 | 4 | from freqtrade.data.converter import parse_ticker_dataframe, ohlcv_fill_up_missing_data 5 | from freqtrade.data.history import load_pair_history, validate_backtest_data, get_timeframe 6 | from freqtrade.tests.conftest import log_has 7 | 8 | 9 | def test_dataframe_correct_columns(result): 10 | assert result.columns.tolist() == ['date', 'open', 'high', 'low', 'close', 'volume'] 11 | 12 | 13 | def test_parse_ticker_dataframe(ticker_history_list, caplog): 14 | columns = ['date', 'open', 'high', 'low', 'close', 'volume'] 15 | 16 | caplog.set_level(logging.DEBUG) 17 | # Test file with BV data 18 | dataframe = parse_ticker_dataframe(ticker_history_list, '5m', 19 | pair="UNITTEST/BTC", fill_missing=True) 20 | assert dataframe.columns.tolist() == columns 21 | assert log_has('Parsing tickerlist to dataframe', caplog) 22 | 23 | 24 | def test_ohlcv_fill_up_missing_data(caplog): 25 | data = load_pair_history(datadir=None, 26 | ticker_interval='1m', 27 | refresh_pairs=False, 28 | pair='UNITTEST/BTC', 29 | fill_up_missing=False) 30 | caplog.set_level(logging.DEBUG) 31 | data2 = ohlcv_fill_up_missing_data(data, '1m', 'UNITTEST/BTC') 32 | assert len(data2) > len(data) 33 | # Column names should not change 34 | assert (data.columns == data2.columns).all() 35 | 36 | assert log_has(f"Missing data fillup for UNITTEST/BTC: before: " 37 | f"{len(data)} - after: {len(data2)}", caplog) 38 | 39 | # Test fillup actually fixes invalid backtest data 40 | min_date, max_date = get_timeframe({'UNITTEST/BTC': data}) 41 | assert validate_backtest_data(data, 'UNITTEST/BTC', min_date, max_date, 1) 42 | assert not validate_backtest_data(data2, 'UNITTEST/BTC', min_date, max_date, 1) 43 | 44 | 45 | def test_ohlcv_fill_up_missing_data2(caplog): 46 | ticker_interval = '5m' 47 | ticks = [[ 48 | 1511686200000, # 8:50:00 49 | 8.794e-05, # open 50 | 8.948e-05, # high 51 | 8.794e-05, # low 52 | 8.88e-05, # close 53 | 2255, # volume (in quote currency) 54 | ], 55 | [ 56 | 1511686500000, # 8:55:00 57 | 8.88e-05, 58 | 8.942e-05, 59 | 8.88e-05, 60 | 8.893e-05, 61 | 9911, 62 | ], 63 | [ 64 | 1511687100000, # 9:05:00 65 | 8.891e-05, 66 | 8.893e-05, 67 | 8.875e-05, 68 | 8.877e-05, 69 | 2251 70 | ], 71 | [ 72 | 1511687400000, # 9:10:00 73 | 8.877e-05, 74 | 8.883e-05, 75 | 8.895e-05, 76 | 8.817e-05, 77 | 123551 78 | ] 79 | ] 80 | 81 | # Generate test-data without filling missing 82 | data = parse_ticker_dataframe(ticks, ticker_interval, pair="UNITTEST/BTC", fill_missing=False) 83 | assert len(data) == 3 84 | caplog.set_level(logging.DEBUG) 85 | data2 = ohlcv_fill_up_missing_data(data, ticker_interval, "UNITTEST/BTC") 86 | assert len(data2) == 4 87 | # 3rd candle has been filled 88 | row = data2.loc[2, :] 89 | assert row['volume'] == 0 90 | # close shoult match close of previous candle 91 | assert row['close'] == data.loc[1, 'close'] 92 | assert row['open'] == row['close'] 93 | assert row['high'] == row['close'] 94 | assert row['low'] == row['close'] 95 | # Column names should not change 96 | assert (data.columns == data2.columns).all() 97 | 98 | assert log_has(f"Missing data fillup for UNITTEST/BTC: before: " 99 | f"{len(data)} - after: {len(data2)}", caplog) 100 | 101 | 102 | def test_ohlcv_drop_incomplete(caplog): 103 | ticker_interval = '1d' 104 | ticks = [[ 105 | 1559750400000, # 2019-06-04 106 | 8.794e-05, # open 107 | 8.948e-05, # high 108 | 8.794e-05, # low 109 | 8.88e-05, # close 110 | 2255, # volume (in quote currency) 111 | ], 112 | [ 113 | 1559836800000, # 2019-06-05 114 | 8.88e-05, 115 | 8.942e-05, 116 | 8.88e-05, 117 | 8.893e-05, 118 | 9911, 119 | ], 120 | [ 121 | 1559923200000, # 2019-06-06 122 | 8.891e-05, 123 | 8.893e-05, 124 | 8.875e-05, 125 | 8.877e-05, 126 | 2251 127 | ], 128 | [ 129 | 1560009600000, # 2019-06-07 130 | 8.877e-05, 131 | 8.883e-05, 132 | 8.895e-05, 133 | 8.817e-05, 134 | 123551 135 | ] 136 | ] 137 | caplog.set_level(logging.DEBUG) 138 | data = parse_ticker_dataframe(ticks, ticker_interval, pair="UNITTEST/BTC", 139 | fill_missing=False, drop_incomplete=False) 140 | assert len(data) == 4 141 | assert not log_has("Dropping last candle", caplog) 142 | 143 | # Drop last candle 144 | data = parse_ticker_dataframe(ticks, ticker_interval, pair="UNITTEST/BTC", 145 | fill_missing=False, drop_incomplete=True) 146 | assert len(data) == 3 147 | 148 | assert log_has("Dropping last candle", caplog) 149 | -------------------------------------------------------------------------------- /freqtrade/tests/data/test_dataprovider.py: -------------------------------------------------------------------------------- 1 | from unittest.mock import MagicMock 2 | 3 | from pandas import DataFrame 4 | 5 | from freqtrade.data.dataprovider import DataProvider 6 | from freqtrade.state import RunMode 7 | from freqtrade.tests.conftest import get_patched_exchange 8 | 9 | 10 | def test_ohlcv(mocker, default_conf, ticker_history): 11 | default_conf["runmode"] = RunMode.DRY_RUN 12 | ticker_interval = default_conf["ticker_interval"] 13 | exchange = get_patched_exchange(mocker, default_conf) 14 | exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history 15 | exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history 16 | 17 | dp = DataProvider(default_conf, exchange) 18 | assert dp.runmode == RunMode.DRY_RUN 19 | assert ticker_history.equals(dp.ohlcv("UNITTEST/BTC", ticker_interval)) 20 | assert isinstance(dp.ohlcv("UNITTEST/BTC", ticker_interval), DataFrame) 21 | assert dp.ohlcv("UNITTEST/BTC", ticker_interval) is not ticker_history 22 | assert dp.ohlcv("UNITTEST/BTC", ticker_interval, copy=False) is ticker_history 23 | assert not dp.ohlcv("UNITTEST/BTC", ticker_interval).empty 24 | assert dp.ohlcv("NONESENSE/AAA", ticker_interval).empty 25 | 26 | # Test with and without parameter 27 | assert dp.ohlcv("UNITTEST/BTC", ticker_interval).equals(dp.ohlcv("UNITTEST/BTC")) 28 | 29 | default_conf["runmode"] = RunMode.LIVE 30 | dp = DataProvider(default_conf, exchange) 31 | assert dp.runmode == RunMode.LIVE 32 | assert isinstance(dp.ohlcv("UNITTEST/BTC", ticker_interval), DataFrame) 33 | 34 | default_conf["runmode"] = RunMode.BACKTEST 35 | dp = DataProvider(default_conf, exchange) 36 | assert dp.runmode == RunMode.BACKTEST 37 | assert dp.ohlcv("UNITTEST/BTC", ticker_interval).empty 38 | 39 | 40 | def test_historic_ohlcv(mocker, default_conf, ticker_history): 41 | historymock = MagicMock(return_value=ticker_history) 42 | mocker.patch("freqtrade.data.dataprovider.load_pair_history", historymock) 43 | 44 | dp = DataProvider(default_conf, None) 45 | data = dp.historic_ohlcv("UNITTEST/BTC", "5m") 46 | assert isinstance(data, DataFrame) 47 | assert historymock.call_count == 1 48 | assert historymock.call_args_list[0][1]["datadir"] is None 49 | assert historymock.call_args_list[0][1]["refresh_pairs"] is False 50 | assert historymock.call_args_list[0][1]["ticker_interval"] == "5m" 51 | 52 | 53 | def test_get_pair_dataframe(mocker, default_conf, ticker_history): 54 | default_conf["runmode"] = RunMode.DRY_RUN 55 | ticker_interval = default_conf["ticker_interval"] 56 | exchange = get_patched_exchange(mocker, default_conf) 57 | exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history 58 | exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history 59 | 60 | dp = DataProvider(default_conf, exchange) 61 | assert dp.runmode == RunMode.DRY_RUN 62 | assert ticker_history.equals(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval)) 63 | assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame) 64 | assert dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval) is not ticker_history 65 | assert not dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval).empty 66 | assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty 67 | 68 | # Test with and without parameter 69 | assert dp.get_pair_dataframe("UNITTEST/BTC", 70 | ticker_interval).equals(dp.get_pair_dataframe("UNITTEST/BTC")) 71 | 72 | default_conf["runmode"] = RunMode.LIVE 73 | dp = DataProvider(default_conf, exchange) 74 | assert dp.runmode == RunMode.LIVE 75 | assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame) 76 | assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty 77 | 78 | historymock = MagicMock(return_value=ticker_history) 79 | mocker.patch("freqtrade.data.dataprovider.load_pair_history", historymock) 80 | default_conf["runmode"] = RunMode.BACKTEST 81 | dp = DataProvider(default_conf, exchange) 82 | assert dp.runmode == RunMode.BACKTEST 83 | assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame) 84 | # assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty 85 | 86 | 87 | def test_available_pairs(mocker, default_conf, ticker_history): 88 | exchange = get_patched_exchange(mocker, default_conf) 89 | ticker_interval = default_conf["ticker_interval"] 90 | exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history 91 | exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history 92 | 93 | dp = DataProvider(default_conf, exchange) 94 | assert len(dp.available_pairs) == 2 95 | assert dp.available_pairs == [ 96 | ("XRP/BTC", ticker_interval), 97 | ("UNITTEST/BTC", ticker_interval), 98 | ] 99 | 100 | 101 | def test_refresh(mocker, default_conf, ticker_history): 102 | refresh_mock = MagicMock() 103 | mocker.patch("freqtrade.exchange.Exchange.refresh_latest_ohlcv", refresh_mock) 104 | 105 | exchange = get_patched_exchange(mocker, default_conf, id="binance") 106 | ticker_interval = default_conf["ticker_interval"] 107 | pairs = [("XRP/BTC", ticker_interval), ("UNITTEST/BTC", ticker_interval)] 108 | 109 | pairs_non_trad = [("ETH/USDT", ticker_interval), ("BTC/TUSD", "1h")] 110 | 111 | dp = DataProvider(default_conf, exchange) 112 | dp.refresh(pairs) 113 | 114 | assert refresh_mock.call_count == 1 115 | assert len(refresh_mock.call_args[0]) == 1 116 | assert len(refresh_mock.call_args[0][0]) == len(pairs) 117 | assert refresh_mock.call_args[0][0] == pairs 118 | 119 | refresh_mock.reset_mock() 120 | dp.refresh(pairs, pairs_non_trad) 121 | assert refresh_mock.call_count == 1 122 | assert len(refresh_mock.call_args[0]) == 1 123 | assert len(refresh_mock.call_args[0][0]) == len(pairs) + len(pairs_non_trad) 124 | assert refresh_mock.call_args[0][0] == pairs + pairs_non_trad 125 | -------------------------------------------------------------------------------- /freqtrade/tests/edge/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/freqtrade/tests/edge/__init__.py -------------------------------------------------------------------------------- /freqtrade/tests/exchange/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/freqtrade/tests/exchange/__init__.py -------------------------------------------------------------------------------- /freqtrade/tests/exchange/test_binance.py: -------------------------------------------------------------------------------- 1 | from random import randint 2 | from unittest.mock import MagicMock 3 | 4 | import ccxt 5 | import pytest 6 | 7 | from freqtrade import (DependencyException, InvalidOrderException, 8 | OperationalException, TemporaryError) 9 | from freqtrade.tests.conftest import get_patched_exchange 10 | 11 | 12 | def test_stoploss_limit_order(default_conf, mocker): 13 | api_mock = MagicMock() 14 | order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) 15 | order_type = 'stop_loss_limit' 16 | 17 | api_mock.create_order = MagicMock(return_value={ 18 | 'id': order_id, 19 | 'info': { 20 | 'foo': 'bar' 21 | } 22 | }) 23 | 24 | default_conf['dry_run'] = False 25 | mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) 26 | mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) 27 | 28 | exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') 29 | 30 | with pytest.raises(OperationalException): 31 | order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=190, rate=200) 32 | 33 | api_mock.create_order.reset_mock() 34 | 35 | order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) 36 | 37 | assert 'id' in order 38 | assert 'info' in order 39 | assert order['id'] == order_id 40 | assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' 41 | assert api_mock.create_order.call_args[0][1] == order_type 42 | assert api_mock.create_order.call_args[0][2] == 'sell' 43 | assert api_mock.create_order.call_args[0][3] == 1 44 | assert api_mock.create_order.call_args[0][4] == 200 45 | assert api_mock.create_order.call_args[0][5] == {'stopPrice': 220} 46 | 47 | # test exception handling 48 | with pytest.raises(DependencyException): 49 | api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance")) 50 | exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') 51 | exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) 52 | 53 | with pytest.raises(InvalidOrderException): 54 | api_mock.create_order = MagicMock( 55 | side_effect=ccxt.InvalidOrder("binance Order would trigger immediately.")) 56 | exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') 57 | exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) 58 | 59 | with pytest.raises(TemporaryError): 60 | api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No connection")) 61 | exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') 62 | exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) 63 | 64 | with pytest.raises(OperationalException, match=r".*DeadBeef.*"): 65 | api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef")) 66 | exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') 67 | exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) 68 | 69 | 70 | def test_stoploss_limit_order_dry_run(default_conf, mocker): 71 | api_mock = MagicMock() 72 | order_type = 'stop_loss_limit' 73 | default_conf['dry_run'] = True 74 | mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) 75 | mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) 76 | 77 | exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance') 78 | 79 | with pytest.raises(OperationalException): 80 | order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=190, rate=200) 81 | 82 | api_mock.create_order.reset_mock() 83 | 84 | order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) 85 | 86 | assert 'id' in order 87 | assert 'info' in order 88 | assert 'type' in order 89 | 90 | assert order['type'] == order_type 91 | assert order['price'] == 220 92 | assert order['amount'] == 1 93 | -------------------------------------------------------------------------------- /freqtrade/tests/exchange/test_kraken.py: -------------------------------------------------------------------------------- 1 | # pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement 2 | # pragma pylint: disable=protected-access 3 | from random import randint 4 | from unittest.mock import MagicMock 5 | 6 | from freqtrade.tests.conftest import get_patched_exchange 7 | 8 | 9 | def test_buy_kraken_trading_agreement(default_conf, mocker): 10 | api_mock = MagicMock() 11 | order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6)) 12 | order_type = 'limit' 13 | time_in_force = 'ioc' 14 | api_mock.options = {} 15 | api_mock.create_order = MagicMock(return_value={ 16 | 'id': order_id, 17 | 'info': { 18 | 'foo': 'bar' 19 | } 20 | }) 21 | default_conf['dry_run'] = False 22 | 23 | mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) 24 | mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) 25 | exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") 26 | 27 | order = exchange.buy(pair='ETH/BTC', ordertype=order_type, 28 | amount=1, rate=200, time_in_force=time_in_force) 29 | 30 | assert 'id' in order 31 | assert 'info' in order 32 | assert order['id'] == order_id 33 | assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' 34 | assert api_mock.create_order.call_args[0][1] == order_type 35 | assert api_mock.create_order.call_args[0][2] == 'buy' 36 | assert api_mock.create_order.call_args[0][3] == 1 37 | assert api_mock.create_order.call_args[0][4] == 200 38 | assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'ioc', 39 | 'trading_agreement': 'agree'} 40 | 41 | 42 | def test_sell_kraken_trading_agreement(default_conf, mocker): 43 | api_mock = MagicMock() 44 | order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6)) 45 | order_type = 'market' 46 | api_mock.options = {} 47 | api_mock.create_order = MagicMock(return_value={ 48 | 'id': order_id, 49 | 'info': { 50 | 'foo': 'bar' 51 | } 52 | }) 53 | default_conf['dry_run'] = False 54 | 55 | mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y) 56 | mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y) 57 | exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken") 58 | 59 | order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) 60 | 61 | assert 'id' in order 62 | assert 'info' in order 63 | assert order['id'] == order_id 64 | assert api_mock.create_order.call_args[0][0] == 'ETH/BTC' 65 | assert api_mock.create_order.call_args[0][1] == order_type 66 | assert api_mock.create_order.call_args[0][2] == 'sell' 67 | assert api_mock.create_order.call_args[0][3] == 1 68 | assert api_mock.create_order.call_args[0][4] is None 69 | assert api_mock.create_order.call_args[0][5] == {'trading_agreement': 'agree'} 70 | -------------------------------------------------------------------------------- /freqtrade/tests/optimize/__init__.py: -------------------------------------------------------------------------------- 1 | from typing import NamedTuple, List 2 | 3 | import arrow 4 | from pandas import DataFrame 5 | 6 | from freqtrade.exchange import timeframe_to_minutes 7 | from freqtrade.strategy.interface import SellType 8 | 9 | ticker_start_time = arrow.get(2018, 10, 3) 10 | tests_ticker_interval = '1h' 11 | 12 | 13 | class BTrade(NamedTuple): 14 | """ 15 | Minimalistic Trade result used for functional backtesting 16 | """ 17 | sell_reason: SellType 18 | open_tick: int 19 | close_tick: int 20 | 21 | 22 | class BTContainer(NamedTuple): 23 | """ 24 | Minimal BacktestContainer defining Backtest inputs and results. 25 | """ 26 | data: List[float] 27 | stop_loss: float 28 | roi: float 29 | trades: List[BTrade] 30 | profit_perc: float 31 | trailing_stop: bool = False 32 | trailing_only_offset_is_reached: bool = False 33 | trailing_stop_positive: float = None 34 | trailing_stop_positive_offset: float = 0.0 35 | use_sell_signal: bool = False 36 | 37 | 38 | def _get_frame_time_from_offset(offset): 39 | return ticker_start_time.shift(minutes=(offset * timeframe_to_minutes(tests_ticker_interval)) 40 | ).datetime 41 | 42 | 43 | def _build_backtest_dataframe(ticker_with_signals): 44 | columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell'] 45 | 46 | frame = DataFrame.from_records(ticker_with_signals, columns=columns) 47 | frame['date'] = frame['date'].apply(_get_frame_time_from_offset) 48 | # Ensure floats are in place 49 | for column in ['open', 'high', 'low', 'close', 'volume']: 50 | frame[column] = frame[column].astype('float64') 51 | return frame 52 | -------------------------------------------------------------------------------- /freqtrade/tests/optimize/test_edge_cli.py: -------------------------------------------------------------------------------- 1 | # pragma pylint: disable=missing-docstring, C0103, C0330 2 | # pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments 3 | 4 | from unittest.mock import MagicMock 5 | 6 | import pytest 7 | 8 | from freqtrade.edge import PairInfo 9 | from freqtrade.optimize import setup_configuration, start_edge 10 | from freqtrade.optimize.edge_cli import EdgeCli 11 | from freqtrade.state import RunMode 12 | from freqtrade.tests.conftest import (get_args, log_has, log_has_re, 13 | patch_exchange, 14 | patched_configuration_load_config_file) 15 | 16 | 17 | def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None: 18 | patched_configuration_load_config_file(mocker, default_conf) 19 | 20 | args = [ 21 | '--config', 'config.json', 22 | '--strategy', 'DefaultStrategy', 23 | 'edge' 24 | ] 25 | 26 | config = setup_configuration(get_args(args), RunMode.EDGE) 27 | assert config['runmode'] == RunMode.EDGE 28 | 29 | assert 'max_open_trades' in config 30 | assert 'stake_currency' in config 31 | assert 'stake_amount' in config 32 | assert 'exchange' in config 33 | assert 'pair_whitelist' in config['exchange'] 34 | assert 'datadir' in config 35 | assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) 36 | assert 'ticker_interval' in config 37 | assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog) 38 | 39 | assert 'refresh_pairs' not in config 40 | assert not log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog) 41 | 42 | assert 'timerange' not in config 43 | assert 'stoploss_range' not in config 44 | 45 | 46 | @pytest.mark.filterwarnings("ignore:DEPRECATED") 47 | def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> None: 48 | patched_configuration_load_config_file(mocker, edge_conf) 49 | mocker.patch( 50 | 'freqtrade.configuration.configuration.create_datadir', 51 | lambda c, x: x 52 | ) 53 | 54 | args = [ 55 | '--config', 'config.json', 56 | '--strategy', 'DefaultStrategy', 57 | '--datadir', '/foo/bar', 58 | 'edge', 59 | '--ticker-interval', '1m', 60 | '--refresh-pairs-cached', 61 | '--timerange', ':100', 62 | '--stoplosses=-0.01,-0.10,-0.001' 63 | ] 64 | 65 | config = setup_configuration(get_args(args), RunMode.EDGE) 66 | assert 'max_open_trades' in config 67 | assert 'stake_currency' in config 68 | assert 'stake_amount' in config 69 | assert 'exchange' in config 70 | assert 'pair_whitelist' in config['exchange'] 71 | assert 'datadir' in config 72 | assert config['runmode'] == RunMode.EDGE 73 | assert log_has('Using data directory: {} ...'.format(config['datadir']), caplog) 74 | assert 'ticker_interval' in config 75 | assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...', 76 | caplog) 77 | 78 | assert 'refresh_pairs' in config 79 | assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog) 80 | assert 'timerange' in config 81 | assert log_has('Parameter --timerange detected: {} ...'.format(config['timerange']), caplog) 82 | 83 | 84 | def test_start(mocker, fee, edge_conf, caplog) -> None: 85 | start_mock = MagicMock() 86 | mocker.patch('freqtrade.exchange.Exchange.get_fee', fee) 87 | patch_exchange(mocker) 88 | mocker.patch('freqtrade.optimize.edge_cli.EdgeCli.start', start_mock) 89 | patched_configuration_load_config_file(mocker, edge_conf) 90 | 91 | args = [ 92 | '--config', 'config.json', 93 | '--strategy', 'DefaultStrategy', 94 | 'edge' 95 | ] 96 | args = get_args(args) 97 | start_edge(args) 98 | assert log_has('Starting freqtrade in Edge mode', caplog) 99 | assert start_mock.call_count == 1 100 | 101 | 102 | def test_edge_init(mocker, edge_conf) -> None: 103 | patch_exchange(mocker) 104 | edge_conf['stake_amount'] = 20 105 | edge_cli = EdgeCli(edge_conf) 106 | assert edge_cli.config == edge_conf 107 | assert edge_cli.config['stake_amount'] == 'unlimited' 108 | assert callable(edge_cli.edge.calculate) 109 | 110 | 111 | def test_generate_edge_table(edge_conf, mocker): 112 | patch_exchange(mocker) 113 | edge_cli = EdgeCli(edge_conf) 114 | 115 | results = {} 116 | results['ETH/BTC'] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60) 117 | 118 | assert edge_cli._generate_edge_table(results).count(':|') == 7 119 | assert edge_cli._generate_edge_table(results).count('| ETH/BTC |') == 1 120 | assert edge_cli._generate_edge_table(results).count( 121 | '| risk reward ratio | required risk reward | expectancy |') == 1 122 | -------------------------------------------------------------------------------- /freqtrade/tests/pairlist/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/freqtrade/tests/pairlist/__init__.py -------------------------------------------------------------------------------- /freqtrade/tests/rpc/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/freqtrade/tests/rpc/__init__.py -------------------------------------------------------------------------------- /freqtrade/tests/strategy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/freqtrade/tests/strategy/__init__.py -------------------------------------------------------------------------------- /freqtrade/tests/strategy/test_default_strategy.py: -------------------------------------------------------------------------------- 1 | import json 2 | 3 | import pytest 4 | from pandas import DataFrame 5 | 6 | from freqtrade.data.converter import parse_ticker_dataframe 7 | from freqtrade.strategy.default_strategy import DefaultStrategy 8 | 9 | 10 | @pytest.fixture 11 | def result(): 12 | with open('freqtrade/tests/testdata/ETH_BTC-1m.json') as data_file: 13 | return parse_ticker_dataframe(json.load(data_file), '1m', pair="UNITTEST/BTC", 14 | fill_missing=True) 15 | 16 | 17 | def test_default_strategy_structure(): 18 | assert hasattr(DefaultStrategy, 'minimal_roi') 19 | assert hasattr(DefaultStrategy, 'stoploss') 20 | assert hasattr(DefaultStrategy, 'ticker_interval') 21 | assert hasattr(DefaultStrategy, 'populate_indicators') 22 | assert hasattr(DefaultStrategy, 'populate_buy_trend') 23 | assert hasattr(DefaultStrategy, 'populate_sell_trend') 24 | 25 | 26 | def test_default_strategy(result): 27 | strategy = DefaultStrategy({}) 28 | 29 | metadata = {'pair': 'ETH/BTC'} 30 | assert type(strategy.minimal_roi) is dict 31 | assert type(strategy.stoploss) is float 32 | assert type(strategy.ticker_interval) is str 33 | indicators = strategy.populate_indicators(result, metadata) 34 | assert type(indicators) is DataFrame 35 | assert type(strategy.populate_buy_trend(indicators, metadata)) is DataFrame 36 | assert type(strategy.populate_sell_trend(indicators, metadata)) is DataFrame 37 | -------------------------------------------------------------------------------- /freqtrade/tests/test_indicator_helpers.py: -------------------------------------------------------------------------------- 1 | # pragma pylint: disable=missing-docstring 2 | 3 | import pandas as pd 4 | 5 | from freqtrade.indicator_helpers import went_down, went_up 6 | 7 | 8 | def test_went_up(): 9 | series = pd.Series([1, 2, 3, 1]) 10 | assert went_up(series).equals(pd.Series([False, True, True, False])) 11 | 12 | 13 | def test_went_down(): 14 | series = pd.Series([1, 2, 3, 1]) 15 | assert went_down(series).equals(pd.Series([False, False, False, True])) 16 | -------------------------------------------------------------------------------- /freqtrade/tests/test_misc.py: -------------------------------------------------------------------------------- 1 | # pragma pylint: disable=missing-docstring,C0103 2 | 3 | import datetime 4 | from pathlib import Path 5 | from unittest.mock import MagicMock 6 | 7 | from freqtrade.data.converter import parse_ticker_dataframe 8 | from freqtrade.data.history import pair_data_filename 9 | from freqtrade.misc import (datesarray_to_datetimearray, file_dump_json, 10 | file_load_json, format_ms_time, shorten_date) 11 | 12 | 13 | def test_shorten_date() -> None: 14 | str_data = '1 day, 2 hours, 3 minutes, 4 seconds ago' 15 | str_shorten_data = '1 d, 2 h, 3 min, 4 sec ago' 16 | assert shorten_date(str_data) == str_shorten_data 17 | 18 | 19 | def test_datesarray_to_datetimearray(ticker_history_list): 20 | dataframes = parse_ticker_dataframe(ticker_history_list, "5m", pair="UNITTEST/BTC", 21 | fill_missing=True) 22 | dates = datesarray_to_datetimearray(dataframes['date']) 23 | 24 | assert isinstance(dates[0], datetime.datetime) 25 | assert dates[0].year == 2017 26 | assert dates[0].month == 11 27 | assert dates[0].day == 26 28 | assert dates[0].hour == 8 29 | assert dates[0].minute == 50 30 | 31 | date_len = len(dates) 32 | assert date_len == 2 33 | 34 | 35 | def test_file_dump_json(mocker) -> None: 36 | file_open = mocker.patch('freqtrade.misc.open', MagicMock()) 37 | json_dump = mocker.patch('rapidjson.dump', MagicMock()) 38 | file_dump_json(Path('somefile'), [1, 2, 3]) 39 | assert file_open.call_count == 1 40 | assert json_dump.call_count == 1 41 | file_open = mocker.patch('freqtrade.misc.gzip.open', MagicMock()) 42 | json_dump = mocker.patch('rapidjson.dump', MagicMock()) 43 | file_dump_json(Path('somefile'), [1, 2, 3], True) 44 | assert file_open.call_count == 1 45 | assert json_dump.call_count == 1 46 | 47 | 48 | def test_file_load_json(mocker) -> None: 49 | 50 | # 7m .json does not exist 51 | ret = file_load_json(pair_data_filename(None, 'UNITTEST/BTC', '7m')) 52 | assert not ret 53 | # 1m json exists (but no .gz exists) 54 | ret = file_load_json(pair_data_filename(None, 'UNITTEST/BTC', '1m')) 55 | assert ret 56 | # 8 .json is empty and will fail if it's loaded. .json.gz is a copy of 1.json 57 | ret = file_load_json(pair_data_filename(None, 'UNITTEST/BTC', '8m')) 58 | assert ret 59 | 60 | 61 | def test_format_ms_time() -> None: 62 | # Date 2018-04-10 18:02:01 63 | date_in_epoch_ms = 1523383321000 64 | date = format_ms_time(date_in_epoch_ms) 65 | assert type(date) is str 66 | res = datetime.datetime(2018, 4, 10, 18, 2, 1, tzinfo=datetime.timezone.utc) 67 | assert date == res.astimezone(None).strftime('%Y-%m-%dT%H:%M:%S') 68 | res = datetime.datetime(2017, 12, 13, 8, 2, 1, tzinfo=datetime.timezone.utc) 69 | # Date 2017-12-13 08:02:01 70 | date_in_epoch_ms = 1513152121000 71 | assert format_ms_time(date_in_epoch_ms) == res.astimezone(None).strftime('%Y-%m-%dT%H:%M:%S') 72 | -------------------------------------------------------------------------------- /freqtrade/tests/test_talib.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import talib.abstract as ta 4 | import pandas as pd 5 | 6 | 7 | def test_talib_bollingerbands_near_zero_values(): 8 | inputs = pd.DataFrame([ 9 | {'close': 0.00000010}, 10 | {'close': 0.00000011}, 11 | {'close': 0.00000012}, 12 | {'close': 0.00000013}, 13 | {'close': 0.00000014} 14 | ]) 15 | bollinger = ta.BBANDS(inputs, matype=0, timeperiod=2) 16 | assert bollinger['upperband'][3] != bollinger['middleband'][3] 17 | -------------------------------------------------------------------------------- /freqtrade/tests/test_timerange.py: -------------------------------------------------------------------------------- 1 | # pragma pylint: disable=missing-docstring, C0103 2 | import pytest 3 | 4 | from freqtrade.configuration import TimeRange 5 | 6 | 7 | def test_parse_timerange_incorrect() -> None: 8 | assert TimeRange(None, 'line', 0, -200) == TimeRange.parse_timerange('-200') 9 | assert TimeRange('line', None, 200, 0) == TimeRange.parse_timerange('200-') 10 | assert TimeRange('index', 'index', 200, 500) == TimeRange.parse_timerange('200-500') 11 | 12 | assert TimeRange('date', None, 1274486400, 0) == TimeRange.parse_timerange('20100522-') 13 | assert TimeRange(None, 'date', 0, 1274486400) == TimeRange.parse_timerange('-20100522') 14 | timerange = TimeRange.parse_timerange('20100522-20150730') 15 | assert timerange == TimeRange('date', 'date', 1274486400, 1438214400) 16 | 17 | # Added test for unix timestamp - BTC genesis date 18 | assert TimeRange('date', None, 1231006505, 0) == TimeRange.parse_timerange('1231006505-') 19 | assert TimeRange(None, 'date', 0, 1233360000) == TimeRange.parse_timerange('-1233360000') 20 | timerange = TimeRange.parse_timerange('1231006505-1233360000') 21 | assert TimeRange('date', 'date', 1231006505, 1233360000) == timerange 22 | 23 | # TODO: Find solution for the following case (passing timestamp in ms) 24 | timerange = TimeRange.parse_timerange('1231006505000-1233360000000') 25 | assert TimeRange('date', 'date', 1231006505, 1233360000) != timerange 26 | 27 | with pytest.raises(Exception, match=r'Incorrect syntax.*'): 28 | TimeRange.parse_timerange('-') 29 | -------------------------------------------------------------------------------- /freqtrade/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import re 2 | from unittest.mock import MagicMock, PropertyMock 3 | 4 | import pytest 5 | 6 | from freqtrade.state import RunMode 7 | from freqtrade.tests.conftest import get_args, log_has, patch_exchange 8 | from freqtrade.utils import (setup_utils_configuration, start_create_userdir, 9 | start_download_data, start_list_exchanges) 10 | 11 | 12 | def test_setup_utils_configuration(): 13 | args = [ 14 | '--config', 'config.json.example', 15 | ] 16 | 17 | config = setup_utils_configuration(get_args(args), RunMode.OTHER) 18 | assert "exchange" in config 19 | assert config['exchange']['dry_run'] is True 20 | assert config['exchange']['key'] == '' 21 | assert config['exchange']['secret'] == '' 22 | 23 | 24 | def test_list_exchanges(capsys): 25 | 26 | args = [ 27 | "list-exchanges", 28 | ] 29 | 30 | start_list_exchanges(get_args(args)) 31 | captured = capsys.readouterr() 32 | assert re.match(r"Exchanges supported by ccxt and available.*", captured.out) 33 | assert re.match(r".*binance,.*", captured.out) 34 | assert re.match(r".*bittrex,.*", captured.out) 35 | 36 | # Test with --one-column 37 | args = [ 38 | "list-exchanges", 39 | "--one-column", 40 | ] 41 | 42 | start_list_exchanges(get_args(args)) 43 | captured = capsys.readouterr() 44 | assert not re.match(r"Exchanges supported by ccxt and available.*", captured.out) 45 | assert re.search(r"^binance$", captured.out, re.MULTILINE) 46 | assert re.search(r"^bittrex$", captured.out, re.MULTILINE) 47 | 48 | 49 | def test_create_datadir_failed(caplog): 50 | 51 | args = [ 52 | "create-userdir", 53 | ] 54 | with pytest.raises(SystemExit): 55 | start_create_userdir(get_args(args)) 56 | assert log_has("`create-userdir` requires --userdir to be set.", caplog) 57 | 58 | 59 | def test_create_datadir(caplog, mocker): 60 | cud = mocker.patch("freqtrade.utils.create_userdata_dir", MagicMock()) 61 | args = [ 62 | "create-userdir", 63 | "--userdir", 64 | "/temp/freqtrade/test" 65 | ] 66 | start_create_userdir(get_args(args)) 67 | 68 | assert cud.call_count == 1 69 | assert len(caplog.record_tuples) == 0 70 | 71 | 72 | def test_download_data_keyboardInterrupt(mocker, caplog, markets): 73 | dl_mock = mocker.patch('freqtrade.utils.refresh_backtest_ohlcv_data', 74 | MagicMock(side_effect=KeyboardInterrupt)) 75 | patch_exchange(mocker) 76 | mocker.patch( 77 | 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets) 78 | ) 79 | args = [ 80 | "download-data", 81 | "--exchange", "binance", 82 | "--pairs", "ETH/BTC", "XRP/BTC", 83 | ] 84 | with pytest.raises(SystemExit): 85 | start_download_data(get_args(args)) 86 | 87 | assert dl_mock.call_count == 1 88 | 89 | 90 | def test_download_data_no_markets(mocker, caplog): 91 | dl_mock = mocker.patch('freqtrade.utils.refresh_backtest_ohlcv_data', 92 | MagicMock(return_value=["ETH/BTC", "XRP/BTC"])) 93 | patch_exchange(mocker) 94 | mocker.patch( 95 | 'freqtrade.exchange.Exchange.markets', PropertyMock(return_value={}) 96 | ) 97 | args = [ 98 | "download-data", 99 | "--exchange", "binance", 100 | "--pairs", "ETH/BTC", "XRP/BTC", 101 | "--days", "20" 102 | ] 103 | start_download_data(get_args(args)) 104 | assert dl_mock.call_args[1]['timerange'].starttype == "date" 105 | assert log_has("Pairs [ETH/BTC,XRP/BTC] not available on exchange binance.", caplog) 106 | -------------------------------------------------------------------------------- /freqtrade/tests/test_wallets.py: -------------------------------------------------------------------------------- 1 | # pragma pylint: disable=missing-docstring 2 | from freqtrade.tests.conftest import get_patched_freqtradebot 3 | from unittest.mock import MagicMock 4 | 5 | 6 | def test_sync_wallet_at_boot(mocker, default_conf): 7 | default_conf['dry_run'] = False 8 | mocker.patch.multiple( 9 | 'freqtrade.exchange.Exchange', 10 | get_balances=MagicMock(return_value={ 11 | "BNT": { 12 | "free": 1.0, 13 | "used": 2.0, 14 | "total": 3.0 15 | }, 16 | "GAS": { 17 | "free": 0.260739, 18 | "used": 0.0, 19 | "total": 0.260739 20 | }, 21 | }) 22 | ) 23 | 24 | freqtrade = get_patched_freqtradebot(mocker, default_conf) 25 | 26 | assert len(freqtrade.wallets._wallets) == 2 27 | assert freqtrade.wallets._wallets['BNT'].free == 1.0 28 | assert freqtrade.wallets._wallets['BNT'].used == 2.0 29 | assert freqtrade.wallets._wallets['BNT'].total == 3.0 30 | assert freqtrade.wallets._wallets['GAS'].free == 0.260739 31 | assert freqtrade.wallets._wallets['GAS'].used == 0.0 32 | assert freqtrade.wallets._wallets['GAS'].total == 0.260739 33 | assert freqtrade.wallets.get_free('BNT') == 1.0 34 | 35 | mocker.patch.multiple( 36 | 'freqtrade.exchange.Exchange', 37 | get_balances=MagicMock(return_value={ 38 | "BNT": { 39 | "free": 1.2, 40 | "used": 1.9, 41 | "total": 3.5 42 | }, 43 | "GAS": { 44 | "free": 0.270739, 45 | "used": 0.1, 46 | "total": 0.260439 47 | }, 48 | }) 49 | ) 50 | 51 | freqtrade.wallets.update() 52 | 53 | assert len(freqtrade.wallets._wallets) == 2 54 | assert freqtrade.wallets._wallets['BNT'].free == 1.2 55 | assert freqtrade.wallets._wallets['BNT'].used == 1.9 56 | assert freqtrade.wallets._wallets['BNT'].total == 3.5 57 | assert freqtrade.wallets._wallets['GAS'].free == 0.270739 58 | assert freqtrade.wallets._wallets['GAS'].used == 0.1 59 | assert freqtrade.wallets._wallets['GAS'].total == 0.260439 60 | assert freqtrade.wallets.get_free('GAS') == 0.270739 61 | assert freqtrade.wallets.get_used('GAS') == 0.1 62 | assert freqtrade.wallets.get_total('GAS') == 0.260439 63 | 64 | 65 | def test_sync_wallet_missing_data(mocker, default_conf): 66 | default_conf['dry_run'] = False 67 | mocker.patch.multiple( 68 | 'freqtrade.exchange.Exchange', 69 | get_balances=MagicMock(return_value={ 70 | "BNT": { 71 | "free": 1.0, 72 | "used": 2.0, 73 | "total": 3.0 74 | }, 75 | "GAS": { 76 | "free": 0.260739, 77 | "total": 0.260739 78 | }, 79 | }) 80 | ) 81 | 82 | freqtrade = get_patched_freqtradebot(mocker, default_conf) 83 | 84 | assert len(freqtrade.wallets._wallets) == 2 85 | assert freqtrade.wallets._wallets['BNT'].free == 1.0 86 | assert freqtrade.wallets._wallets['BNT'].used == 2.0 87 | assert freqtrade.wallets._wallets['BNT'].total == 3.0 88 | assert freqtrade.wallets._wallets['GAS'].free == 0.260739 89 | assert freqtrade.wallets._wallets['GAS'].used is None 90 | assert freqtrade.wallets._wallets['GAS'].total == 0.260739 91 | assert freqtrade.wallets.get_free('GAS') == 0.260739 92 | -------------------------------------------------------------------------------- /freqtrade/tests/testdata/UNITTEST_BTC-8m.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/freqtrade/tests/testdata/UNITTEST_BTC-8m.json -------------------------------------------------------------------------------- /freqtrade/tests/testdata/UNITTEST_BTC-8m.json.gz: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/freqtrade/tests/testdata/UNITTEST_BTC-8m.json.gz -------------------------------------------------------------------------------- /freqtrade/tests/testdata/pairs.json: -------------------------------------------------------------------------------- 1 | [ 2 | "ADA/BTC", 3 | "BAT/BTC", 4 | "DASH/BTC", 5 | "ETC/BTC", 6 | "ETH/BTC", 7 | "GBYTE/BTC", 8 | "LSK/BTC", 9 | "LTC/BTC", 10 | "NEO/BTC", 11 | "NXT/BTC", 12 | "POWR/BTC", 13 | "STORJ/BTC", 14 | "QTUM/BTC", 15 | "WAVES/BTC", 16 | "VTC/BTC", 17 | "XLM/BTC", 18 | "XMR/BTC", 19 | "XVG/BTC", 20 | "XRP/BTC", 21 | "ZEC/BTC", 22 | "BTC/USDT", 23 | "LTC/USDT", 24 | "ETH/USDT" 25 | ] 26 | 27 | -------------------------------------------------------------------------------- /freqtrade/utils.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import sys 3 | from argparse import Namespace 4 | from pathlib import Path 5 | from typing import Any, Dict, List 6 | 7 | import arrow 8 | 9 | from freqtrade.configuration import Configuration, TimeRange 10 | from freqtrade.configuration.directory_operations import create_userdata_dir 11 | from freqtrade.data.history import refresh_backtest_ohlcv_data 12 | from freqtrade.exchange import available_exchanges 13 | from freqtrade.resolvers import ExchangeResolver 14 | from freqtrade.state import RunMode 15 | 16 | logger = logging.getLogger(__name__) 17 | 18 | 19 | def setup_utils_configuration(args: Namespace, method: RunMode) -> Dict[str, Any]: 20 | """ 21 | Prepare the configuration for utils subcommands 22 | :param args: Cli args from Arguments() 23 | :return: Configuration 24 | """ 25 | configuration = Configuration(args, method) 26 | config = configuration.get_config() 27 | 28 | config['exchange']['dry_run'] = True 29 | # Ensure we do not use Exchange credentials 30 | config['exchange']['key'] = '' 31 | config['exchange']['secret'] = '' 32 | 33 | return config 34 | 35 | 36 | def start_list_exchanges(args: Namespace) -> None: 37 | """ 38 | Print available exchanges 39 | :param args: Cli args from Arguments() 40 | :return: None 41 | """ 42 | 43 | if args.print_one_column: 44 | print('\n'.join(available_exchanges())) 45 | else: 46 | print(f"Exchanges supported by ccxt and available for Freqtrade: " 47 | f"{', '.join(available_exchanges())}") 48 | 49 | 50 | def start_create_userdir(args: Namespace) -> None: 51 | """ 52 | Create "user_data" directory to contain user data strategies, hyperopts, ...) 53 | :param args: Cli args from Arguments() 54 | :return: None 55 | """ 56 | if "user_data_dir" in args and args.user_data_dir: 57 | create_userdata_dir(args.user_data_dir, create_dir=True) 58 | else: 59 | logger.warning("`create-userdir` requires --userdir to be set.") 60 | sys.exit(1) 61 | 62 | 63 | def start_download_data(args: Namespace) -> None: 64 | """ 65 | Download data (former download_backtest_data.py script) 66 | """ 67 | config = setup_utils_configuration(args, RunMode.OTHER) 68 | 69 | timerange = TimeRange() 70 | if 'days' in config: 71 | time_since = arrow.utcnow().shift(days=-config['days']).strftime("%Y%m%d") 72 | timerange = TimeRange.parse_timerange(f'{time_since}-') 73 | 74 | dl_path = Path(config['datadir']) 75 | logger.info(f'About to download pairs: {config["pairs"]}, ' 76 | f'intervals: {config["timeframes"]} to {dl_path}') 77 | 78 | pairs_not_available: List[str] = [] 79 | 80 | try: 81 | # Init exchange 82 | exchange = ExchangeResolver(config['exchange']['name'], config).exchange 83 | 84 | pairs_not_available = refresh_backtest_ohlcv_data( 85 | exchange, pairs=config["pairs"], timeframes=config["timeframes"], 86 | dl_path=Path(config['datadir']), timerange=timerange, erase=config.get("erase")) 87 | 88 | except KeyboardInterrupt: 89 | sys.exit("SIGINT received, aborting ...") 90 | 91 | finally: 92 | if pairs_not_available: 93 | logger.info(f"Pairs [{','.join(pairs_not_available)}] not available " 94 | f"on exchange {config['exchange']['name']}.") 95 | -------------------------------------------------------------------------------- /freqtrade/vendor/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/freqtrade/vendor/__init__.py -------------------------------------------------------------------------------- /freqtrade/vendor/qtpylib/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/freqtrade/vendor/qtpylib/__init__.py -------------------------------------------------------------------------------- /freqtrade/wallets.py: -------------------------------------------------------------------------------- 1 | # pragma pylint: disable=W0603 2 | """ Wallet """ 3 | 4 | import logging 5 | from typing import Dict, NamedTuple 6 | from freqtrade.exchange import Exchange 7 | from freqtrade import constants 8 | 9 | logger = logging.getLogger(__name__) 10 | 11 | 12 | # wallet data structure 13 | class Wallet(NamedTuple): 14 | currency: str 15 | free: float = 0 16 | used: float = 0 17 | total: float = 0 18 | 19 | 20 | class Wallets(object): 21 | 22 | def __init__(self, config: dict, exchange: Exchange) -> None: 23 | self._config = config 24 | self._exchange = exchange 25 | self._wallets: Dict[str, Wallet] = {} 26 | 27 | self.update() 28 | 29 | def get_free(self, currency) -> float: 30 | 31 | if self._config['dry_run']: 32 | return self._config.get('dry_run_wallet', constants.DRY_RUN_WALLET) 33 | 34 | balance = self._wallets.get(currency) 35 | if balance and balance.free: 36 | return balance.free 37 | else: 38 | return 0 39 | 40 | def get_used(self, currency) -> float: 41 | 42 | if self._config['dry_run']: 43 | return self._config.get('dry_run_wallet', constants.DRY_RUN_WALLET) 44 | 45 | balance = self._wallets.get(currency) 46 | if balance and balance.used: 47 | return balance.used 48 | else: 49 | return 0 50 | 51 | def get_total(self, currency) -> float: 52 | 53 | if self._config['dry_run']: 54 | return self._config.get('dry_run_wallet', constants.DRY_RUN_WALLET) 55 | 56 | balance = self._wallets.get(currency) 57 | if balance and balance.total: 58 | return balance.total 59 | else: 60 | return 0 61 | 62 | def update(self) -> None: 63 | 64 | balances = self._exchange.get_balances() 65 | 66 | for currency in balances: 67 | self._wallets[currency] = Wallet( 68 | currency, 69 | balances[currency].get('free', None), 70 | balances[currency].get('used', None), 71 | balances[currency].get('total', None) 72 | ) 73 | 74 | logger.info('Wallets synced.') 75 | -------------------------------------------------------------------------------- /mkdocs.yml: -------------------------------------------------------------------------------- 1 | site_name: Freqtrade 2 | nav: 3 | - About: index.md 4 | - Installation: installation.md 5 | - Installation Docker: docker.md 6 | - Configuration: configuration.md 7 | - Strategy Customization: strategy-customization.md 8 | - Stoploss: stoploss.md 9 | - Start the bot: bot-usage.md 10 | - Control the bot: 11 | - Telegram: telegram-usage.md 12 | - Web Hook: webhook-config.md 13 | - REST API: rest-api.md 14 | - Backtesting: backtesting.md 15 | - Hyperopt: hyperopt.md 16 | - Edge positioning: edge.md 17 | - FAQ: faq.md 18 | - Data Analysis: data-analysis.md 19 | - Plotting: plotting.md 20 | - SQL Cheatsheet: sql_cheatsheet.md 21 | - Sandbox testing: sandbox-testing.md 22 | - Deprecated features: deprecated.md 23 | - Contributors guide: developer.md 24 | theme: 25 | name: material 26 | logo: 'images/logo.png' 27 | custom_dir: 'docs' 28 | palette: 29 | primary: 'blue grey' 30 | accent: 'tear' 31 | markdown_extensions: 32 | - admonition 33 | - codehilite: 34 | guess_lang: false 35 | - toc: 36 | permalink: true 37 | - pymdownx.arithmatex 38 | - pymdownx.caret 39 | - pymdownx.critic 40 | - pymdownx.details 41 | - pymdownx.inlinehilite 42 | - pymdownx.magiclink 43 | - pymdownx.mark 44 | - pymdownx.smartsymbols 45 | - pymdownx.superfences 46 | - pymdownx.tasklist: 47 | custom_checkbox: true 48 | - pymdownx.tilde 49 | -------------------------------------------------------------------------------- /requirements-common.txt: -------------------------------------------------------------------------------- 1 | # requirements without requirements installable via conda 2 | # mainly used for Raspberry pi installs 3 | ccxt==1.18.1115 4 | SQLAlchemy==1.3.8 5 | python-telegram-bot==12.0.0 6 | arrow==0.14.6 7 | cachetools==3.1.1 8 | requests==2.31.0 9 | urllib3==1.26.5 10 | wrapt==1.11.2 11 | scikit-learn==0.21.3 12 | joblib==0.13.2 13 | jsonschema==3.0.2 14 | TA-Lib==0.4.17 15 | tabulate==0.8.3 16 | coinmarketcap==5.0.3 17 | 18 | # Required for hyperopt 19 | scikit-optimize==0.5.2 20 | filelock==3.0.12 21 | 22 | # find first, C search in arrays 23 | py_find_1st==1.1.4 24 | 25 | #Load ticker files 30% faster 26 | python-rapidjson==0.8.0 27 | 28 | # Notify systemd 29 | sdnotify==0.3.2 30 | 31 | # Api server 32 | flask==1.1.1 33 | 34 | # Support for colorized terminal output 35 | colorama==0.4.1 36 | -------------------------------------------------------------------------------- /requirements-dev.txt: -------------------------------------------------------------------------------- 1 | # Include all requirements to run the bot. 2 | -r requirements.txt 3 | -r requirements-plot.txt 4 | 5 | coveralls==1.8.2 6 | flake8==3.7.8 7 | flake8-type-annotations==0.1.0 8 | flake8-tidy-imports==2.0.0 9 | mypy==0.720 10 | pytest==5.1.2 11 | pytest-asyncio==0.10.0 12 | pytest-cov==2.7.1 13 | pytest-mock==1.10.4 14 | pytest-random-order==1.0.4 15 | -------------------------------------------------------------------------------- /requirements-plot.txt: -------------------------------------------------------------------------------- 1 | # Include all requirements to run the bot. 2 | -r requirements.txt 3 | 4 | plotly==4.1.0 5 | 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # Load common requirements 2 | -r requirements-common.txt 3 | 4 | numpy==1.17.1 5 | pandas==0.25.1 6 | scipy==1.3.1 7 | -------------------------------------------------------------------------------- /scripts/download_backtest_data.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | 5 | 6 | print("This script has been integrated into freqtrade " 7 | "and its functionality is available by calling `freqtrade download-data`.") 8 | print("Please check the documentation on https://www.freqtrade.io/en/latest/backtesting/ " 9 | "for details.") 10 | 11 | sys.exit(1) 12 | -------------------------------------------------------------------------------- /scripts/get_market_pairs.py: -------------------------------------------------------------------------------- 1 | """ 2 | This script was adapted from ccxt here: 3 | https://github.com/ccxt/ccxt/blob/master/examples/py/arbitrage-pairs.py 4 | """ 5 | import os 6 | import sys 7 | import traceback 8 | 9 | root = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) 10 | sys.path.append(root + '/python') 11 | 12 | import ccxt # noqa: E402 13 | 14 | 15 | def style(s, style): 16 | return style + s + '\033[0m' 17 | 18 | 19 | def green(s): 20 | return style(s, '\033[92m') 21 | 22 | 23 | def blue(s): 24 | return style(s, '\033[94m') 25 | 26 | 27 | def yellow(s): 28 | return style(s, '\033[93m') 29 | 30 | 31 | def red(s): 32 | return style(s, '\033[91m') 33 | 34 | 35 | def pink(s): 36 | return style(s, '\033[95m') 37 | 38 | 39 | def bold(s): 40 | return style(s, '\033[1m') 41 | 42 | 43 | def underline(s): 44 | return style(s, '\033[4m') 45 | 46 | 47 | def dump(*args): 48 | print(' '.join([str(arg) for arg in args])) 49 | 50 | 51 | def print_supported_exchanges(): 52 | dump('Supported exchanges:', green(', '.join(ccxt.exchanges))) 53 | 54 | 55 | try: 56 | 57 | if len(sys.argv) < 2: 58 | dump("Usage: python " + sys.argv[0], green('id')) 59 | print_supported_exchanges() 60 | sys.exit(1) 61 | 62 | id = sys.argv[1] # get exchange id from command line arguments 63 | 64 | # check if the exchange is supported by ccxt 65 | exchange_found = id in ccxt.exchanges 66 | 67 | if exchange_found: 68 | dump('Instantiating', green(id), 'exchange') 69 | 70 | # instantiate the exchange by id 71 | exchange = getattr(ccxt, id)({ 72 | # 'proxy':'https://cors-anywhere.herokuapp.com/', 73 | }) 74 | 75 | # load all markets from the exchange 76 | markets = exchange.load_markets() 77 | 78 | # output a list of all market symbols 79 | dump(green(id), 'has', len(exchange.symbols), 'symbols:', exchange.symbols) 80 | 81 | tuples = list(ccxt.Exchange.keysort(markets).items()) 82 | 83 | # debug 84 | for (k, v) in tuples: 85 | print(v) 86 | 87 | # output a table of all markets 88 | dump(pink('{:<15} {:<15} {:<15} {:<15}'.format('id', 'symbol', 'base', 'quote'))) 89 | 90 | for (k, v) in tuples: 91 | dump('{:<15} {:<15} {:<15} {:<15}'.format(v['id'], v['symbol'], v['base'], v['quote'])) 92 | 93 | else: 94 | 95 | dump('Exchange ' + red(id) + ' not found') 96 | print_supported_exchanges() 97 | 98 | except Exception as e: 99 | dump('[' + type(e).__name__ + ']', str(e)) 100 | dump(traceback.format_exc()) 101 | dump("Usage: python " + sys.argv[0], green('id')) 102 | print_supported_exchanges() 103 | sys.exit(1) 104 | -------------------------------------------------------------------------------- /scripts/plot_dataframe.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | 5 | 6 | print("This script has been integrated into freqtrade " 7 | "and its functionality is available by calling `freqtrade plot-dataframe`.") 8 | print("Please check the documentation on https://www.freqtrade.io/en/latest/plotting/ " 9 | "for details.") 10 | 11 | sys.exit(1) 12 | -------------------------------------------------------------------------------- /scripts/plot_profit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | 5 | 6 | print("This script has been integrated into freqtrade " 7 | "and its functionality is available by calling `freqtrade plot-profit`.") 8 | print("Please check the documentation on https://www.freqtrade.io/en/latest/plotting/ " 9 | "for details.") 10 | 11 | sys.exit(1) 12 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | #ignore = 3 | max-line-length = 100 4 | max-complexity = 12 5 | 6 | [mypy] 7 | ignore_missing_imports = True 8 | 9 | [mypy-freqtrade.tests.*] 10 | ignore_errors = True 11 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from sys import version_info 2 | from setuptools import setup 3 | 4 | if version_info.major == 3 and version_info.minor < 6 or \ 5 | version_info.major < 3: 6 | print('Your Python interpreter must be 3.6 or greater!') 7 | exit(1) 8 | 9 | from freqtrade import __version__ 10 | 11 | # Requirements used for submodules 12 | api = ['flask'] 13 | plot = ['plotly>=4.0'] 14 | 15 | develop = [ 16 | 'coveralls', 17 | 'flake8', 18 | 'flake8-type-annotations', 19 | 'flake8-tidy-imports', 20 | 'mypy', 21 | 'pytest', 22 | 'pytest-asyncio', 23 | 'pytest-cov', 24 | 'pytest-mock', 25 | 'pytest-random-order', 26 | ] 27 | 28 | jupyter = [ 29 | 'jupyter', 30 | 'nbstripout', 31 | 'ipykernel', 32 | ] 33 | 34 | all_extra = api + plot + develop + jupyter 35 | 36 | setup(name='freqtrade', 37 | version=__version__, 38 | description='Crypto Trading Bot', 39 | url='https://github.com/freqtrade/freqtrade', 40 | author='gcarq and contributors', 41 | author_email='michael.egger@tsn.at', 42 | license='GPLv3', 43 | packages=['freqtrade'], 44 | setup_requires=['pytest-runner', 'numpy'], 45 | tests_require=['pytest', 'pytest-mock', 'pytest-cov'], 46 | install_requires=[ 47 | # from requirements-common.txt 48 | 'ccxt>=1.18.1080', 49 | 'SQLAlchemy', 50 | 'python-telegram-bot', 51 | 'arrow', 52 | 'cachetools', 53 | 'requests', 54 | 'urllib3', 55 | 'wrapt', 56 | 'scikit-learn', 57 | 'joblib', 58 | 'jsonschema', 59 | 'TA-Lib', 60 | 'tabulate', 61 | 'coinmarketcap', 62 | 'scikit-optimize', 63 | 'filelock', 64 | 'py_find_1st', 65 | 'python-rapidjson', 66 | 'sdnotify', 67 | 'colorama', 68 | # from requirements.txt 69 | 'numpy', 70 | 'pandas', 71 | 'scipy', 72 | ], 73 | extras_require={ 74 | 'api': api, 75 | 'dev': all_extra, 76 | 'plot': plot, 77 | 'all': all_extra, 78 | 'jupyter': jupyter, 79 | 80 | }, 81 | include_package_data=True, 82 | zip_safe=False, 83 | entry_points={ 84 | 'console_scripts': [ 85 | 'freqtrade = freqtrade.main:main', 86 | ], 87 | }, 88 | classifiers=[ 89 | 'Programming Language :: Python :: 3.6', 90 | 'License :: OSI Approved :: GNU General Public License v3 (GPLv3)', 91 | 'Topic :: Office/Business :: Financial :: Investment', 92 | 'Intended Audience :: Science/Research', 93 | ]) 94 | -------------------------------------------------------------------------------- /user_data/backtest_data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/user_data/backtest_data/.gitkeep -------------------------------------------------------------------------------- /user_data/backtest_results/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/user_data/backtest_results/.gitkeep -------------------------------------------------------------------------------- /user_data/data/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/user_data/data/.gitkeep -------------------------------------------------------------------------------- /user_data/hyperopts/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/user_data/hyperopts/__init__.py -------------------------------------------------------------------------------- /user_data/hyperopts/sample_hyperopt_loss.py: -------------------------------------------------------------------------------- 1 | from math import exp 2 | from datetime import datetime 3 | 4 | from pandas import DataFrame 5 | 6 | from freqtrade.optimize.hyperopt import IHyperOptLoss 7 | 8 | # Define some constants: 9 | 10 | # set TARGET_TRADES to suit your number concurrent trades so its realistic 11 | # to the number of days 12 | TARGET_TRADES = 600 13 | # This is assumed to be expected avg profit * expected trade count. 14 | # For example, for 0.35% avg per trade (or 0.0035 as ratio) and 1100 trades, 15 | # self.expected_max_profit = 3.85 16 | # Check that the reported Σ% values do not exceed this! 17 | # Note, this is ratio. 3.85 stated above means 385Σ%. 18 | EXPECTED_MAX_PROFIT = 3.0 19 | 20 | # max average trade duration in minutes 21 | # if eval ends with higher value, we consider it a failed eval 22 | MAX_ACCEPTED_TRADE_DURATION = 300 23 | 24 | 25 | class SampleHyperOptLoss(IHyperOptLoss): 26 | """ 27 | Defines the default loss function for hyperopt 28 | This is intended to give you some inspiration for your own loss function. 29 | 30 | The Function needs to return a number (float) - which becomes for better backtest results. 31 | """ 32 | 33 | @staticmethod 34 | def hyperopt_loss_function(results: DataFrame, trade_count: int, 35 | min_date: datetime, max_date: datetime, 36 | *args, **kwargs) -> float: 37 | """ 38 | Objective function, returns smaller number for better results 39 | """ 40 | total_profit = results.profit_percent.sum() 41 | trade_duration = results.trade_duration.mean() 42 | 43 | trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8) 44 | profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT) 45 | duration_loss = 0.4 * min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1) 46 | result = trade_loss + profit_loss + duration_loss 47 | return result 48 | -------------------------------------------------------------------------------- /user_data/notebooks/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/user_data/notebooks/.gitkeep -------------------------------------------------------------------------------- /user_data/strategies/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hackobi/AI-Scalpel-Trading-Bot/30eed4130d4808a25a5be6a0ee6cf42e3f19bc22/user_data/strategies/__init__.py --------------------------------------------------------------------------------