Compare commits

..

182 Commits

Author SHA1 Message Date
Matthias
b530600718 Merge pull request #6134 from freqtrade/new_release
New release 2021.12
2021-12-29 17:00:19 +01:00
Matthias
043218cc7e Version bump to 2021.12 2021-12-29 16:18:14 +01:00
Matthias
c3e9ef27f6 Merge branch 'stable' into new_release 2021-12-29 16:17:56 +01:00
Matthias
24807515c1 Fix random test failure 2021-12-28 09:04:14 +01:00
Matthias
5a546855e6 Import TTLCache from cachetools
Importing from cachetools.ttl is deprecated, and will be removed in 5.0
2021-12-27 19:30:17 +01:00
Matthias
df53873dab Merge pull request #6119 from freqtrade/dependabot/pip/develop/scikit-learn-1.0.2
Bump scikit-learn from 1.0.1 to 1.0.2
2021-12-27 16:48:48 +01:00
Matthias
1b739acc08 Merge pull request #6118 from freqtrade/dependabot/pip/develop/sqlalchemy-1.4.29
Bump sqlalchemy from 1.4.28 to 1.4.29
2021-12-27 10:03:06 +01:00
dependabot[bot]
3804a17775 Bump scikit-learn from 1.0.1 to 1.0.2
Bumps [scikit-learn](https://github.com/scikit-learn/scikit-learn) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/scikit-learn/scikit-learn/releases)
- [Commits](https://github.com/scikit-learn/scikit-learn/compare/1.0.1...1.0.2)

---
updated-dependencies:
- dependency-name: scikit-learn
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-27 08:45:22 +00:00
Matthias
c8253790b6 Merge pull request #6125 from freqtrade/dependabot/pip/develop/filelock-3.4.2
Bump filelock from 3.4.0 to 3.4.2
2021-12-27 09:44:48 +01:00
Matthias
a215e29d2a Merge pull request #6117 from freqtrade/dependabot/pip/develop/ccxt-1.65.25
Bump ccxt from 1.64.44 to 1.65.25
2021-12-27 09:42:40 +01:00
Matthias
d58ed0e242 Merge pull request #6122 from freqtrade/dependabot/pip/develop/types-python-dateutil-2.8.4
Bump types-python-dateutil from 2.8.3 to 2.8.4
2021-12-27 08:45:01 +01:00
Matthias
2ab8f467dd Merge pull request #6121 from freqtrade/dependabot/pip/develop/jsonschema-4.3.2
Bump jsonschema from 4.3.1 to 4.3.2
2021-12-27 08:44:11 +01:00
Matthias
c1ec368c0c Merge pull request #6123 from freqtrade/dependabot/pip/develop/mypy-0.930
Bump mypy from 0.920 to 0.930
2021-12-27 08:43:54 +01:00
Matthias
29fff65598 Merge pull request #6120 from freqtrade/dependabot/pip/develop/plotly-5.5.0
Bump plotly from 5.4.0 to 5.5.0
2021-12-27 08:43:36 +01:00
dependabot[bot]
3cba405b2e Bump filelock from 3.4.0 to 3.4.2
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.4.0 to 3.4.2.
- [Release notes](https://github.com/tox-dev/py-filelock/releases)
- [Changelog](https://github.com/tox-dev/py-filelock/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/py-filelock/compare/3.4.0...3.4.2)

---
updated-dependencies:
- dependency-name: filelock
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-27 03:54:13 +00:00
dependabot[bot]
24d16d7dab Bump mypy from 0.920 to 0.930
Bumps [mypy](https://github.com/python/mypy) from 0.920 to 0.930.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.920...v0.930)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-27 03:54:06 +00:00
dependabot[bot]
2e84b8f0d5 Bump types-python-dateutil from 2.8.3 to 2.8.4
Bumps [types-python-dateutil](https://github.com/python/typeshed) from 2.8.3 to 2.8.4.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-python-dateutil
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-27 03:54:03 +00:00
dependabot[bot]
470ef7c160 Bump jsonschema from 4.3.1 to 4.3.2
Bumps [jsonschema](https://github.com/Julian/jsonschema) from 4.3.1 to 4.3.2.
- [Release notes](https://github.com/Julian/jsonschema/releases)
- [Changelog](https://github.com/Julian/jsonschema/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/Julian/jsonschema/compare/v4.3.1...v4.3.2)

---
updated-dependencies:
- dependency-name: jsonschema
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-27 03:53:58 +00:00
dependabot[bot]
1093f22b80 Bump plotly from 5.4.0 to 5.5.0
Bumps [plotly](https://github.com/plotly/plotly.py) from 5.4.0 to 5.5.0.
- [Release notes](https://github.com/plotly/plotly.py/releases)
- [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md)
- [Commits](https://github.com/plotly/plotly.py/compare/v5.4.0...v5.5.0)

---
updated-dependencies:
- dependency-name: plotly
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-27 03:53:54 +00:00
dependabot[bot]
e085058621 Bump sqlalchemy from 1.4.28 to 1.4.29
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.4.28 to 1.4.29.
- [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases)
- [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES)
- [Commits](https://github.com/sqlalchemy/sqlalchemy/commits)

---
updated-dependencies:
- dependency-name: sqlalchemy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-27 03:53:47 +00:00
dependabot[bot]
81b383fe5c Bump ccxt from 1.64.44 to 1.65.25
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.64.44 to 1.65.25.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg)
- [Commits](https://github.com/ccxt/ccxt/compare/1.64.44...1.65.25)

---
updated-dependencies:
- dependency-name: ccxt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-27 03:53:41 +00:00
Matthias
2917cc1f2e Bitpanda's "fetch_my_trades" requires "to" argument
closes #4938
2021-12-25 14:28:22 +01:00
Matthias
6fdad8c6bd Prevent exception, ensure deletion occurs 2021-12-25 14:03:44 +01:00
Matthias
356b2d3d91 Reestablish backward compatibility 2021-12-25 13:47:28 +01:00
Matthias
b1feb69ca9 Use Pathlib to delete testfile 2021-12-25 10:30:59 +01:00
Matthias
49aa34c6f3 Merge pull request #6112 from xataxxx/develop
Fix test not running when user_data contains historical data.
2021-12-25 10:11:15 +01:00
Reigo Reinmets
d11a8928d4 Fix test not running when user_data contains historical data. 2021-12-25 10:39:27 +02:00
Matthias
58663180e0 Merge pull request #6107 from freqtrade/remove_slack
Update CI to notify on discord only
2021-12-23 21:50:49 +01:00
Matthias
98f6d2d722 Update CI to notify on discord only 2021-12-23 21:27:30 +01:00
Matthias
110e48c541 Remove travis config file
Travisci seems to no longer offer a free plan for open source
repositories, and other repositories report the need to get in touch
with support again and again.

This complication is not necessary with github actions, which covers our
CI needs well.
2021-12-23 20:38:07 +01:00
Matthias
61dbb6206f Slightly reduce verbosity when reload_conf is issued
part of #6095
2021-12-23 20:33:13 +01:00
Matthias
9a9cc31d83 Update docs regarding multiarch builds 2021-12-23 17:01:44 +01:00
Matthias
f88b6af26f Merge pull request #6070 from cdimauro/suppress_logs
Suppress additional logs for pairs in blacklist
2021-12-21 21:07:15 +01:00
Matthias
e5aaef6440 Fix CI failure 2021-12-21 19:20:09 +01:00
cdimauro
6ba8b17fdd Use LoggingMixin.log_once to remove/reduce logs on pairlists 2021-12-21 09:11:57 +01:00
Matthias
40036bc710 Force dry-run for webserver backtest mode
closes #6094
2021-12-20 19:41:33 +01:00
Matthias
afad9be53f Merge pull request #6093 from freqtrade/dependabot/pip/develop/cryptography-36.0.1
Bump cryptography from 36.0.0 to 36.0.1
2021-12-20 09:01:25 +01:00
Matthias
6fe09b6dee Merge pull request #6090 from freqtrade/dependabot/pip/develop/mypy-0.920
Bump mypy from 0.910 to 0.920
2021-12-20 07:16:21 +01:00
dependabot[bot]
21da01f777 Bump cryptography from 36.0.0 to 36.0.1
Bumps [cryptography](https://github.com/pyca/cryptography) from 36.0.0 to 36.0.1.
- [Release notes](https://github.com/pyca/cryptography/releases)
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/36.0.0...36.0.1)

---
updated-dependencies:
- dependency-name: cryptography
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-20 05:39:04 +00:00
Matthias
260c627e99 Merge pull request #6091 from freqtrade/dependabot/pip/develop/time-machine-2.5.0
Bump time-machine from 2.4.1 to 2.5.0
2021-12-20 06:38:46 +01:00
Matthias
d47167c9c4 Merge pull request #6087 from freqtrade/dependabot/pip/develop/numpy-1.21.5
Bump numpy from 1.21.4 to 1.21.5
2021-12-20 06:38:25 +01:00
dependabot[bot]
b6f8765d3b Bump mypy from 0.910 to 0.920
Bumps [mypy](https://github.com/python/mypy) from 0.910 to 0.920.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.910...v0.920)

---
updated-dependencies:
- dependency-name: mypy
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-20 05:38:03 +00:00
Matthias
5b608c9005 Merge pull request #6088 from freqtrade/dependabot/pip/develop/ccxt-1.64.44
Bump ccxt from 1.63.65 to 1.64.44
2021-12-20 06:37:59 +01:00
Matthias
cfad873ea7 Merge pull request #6092 from freqtrade/dependabot/pip/develop/jsonschema-4.3.1
Bump jsonschema from 4.2.1 to 4.3.1
2021-12-20 06:37:41 +01:00
Matthias
480eb55721 Merge pull request #6086 from freqtrade/dependabot/pip/develop/mkdocs-material-8.1.3
Bump mkdocs-material from 8.1.0 to 8.1.3
2021-12-20 06:37:23 +01:00
Matthias
e754cc09fc Merge pull request #6089 from freqtrade/dependabot/pip/develop/types-requests-2.26.2
Bump types-requests from 2.26.1 to 2.26.2
2021-12-20 06:36:46 +01:00
dependabot[bot]
cde35509db Bump jsonschema from 4.2.1 to 4.3.1
Bumps [jsonschema](https://github.com/Julian/jsonschema) from 4.2.1 to 4.3.1.
- [Release notes](https://github.com/Julian/jsonschema/releases)
- [Changelog](https://github.com/Julian/jsonschema/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/Julian/jsonschema/compare/v4.2.1...v4.3.1)

---
updated-dependencies:
- dependency-name: jsonschema
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-20 03:01:52 +00:00
dependabot[bot]
5a3a5e98d6 Bump time-machine from 2.4.1 to 2.5.0
Bumps [time-machine](https://github.com/adamchainz/time-machine) from 2.4.1 to 2.5.0.
- [Release notes](https://github.com/adamchainz/time-machine/releases)
- [Changelog](https://github.com/adamchainz/time-machine/blob/main/HISTORY.rst)
- [Commits](https://github.com/adamchainz/time-machine/compare/2.4.1...2.5.0)

---
updated-dependencies:
- dependency-name: time-machine
  dependency-type: direct:development
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-20 03:01:47 +00:00
dependabot[bot]
44ac002cf0 Bump types-requests from 2.26.1 to 2.26.2
Bumps [types-requests](https://github.com/python/typeshed) from 2.26.1 to 2.26.2.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-20 03:01:41 +00:00
dependabot[bot]
56d96d6cff Bump ccxt from 1.63.65 to 1.64.44
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.63.65 to 1.64.44.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg)
- [Commits](https://github.com/ccxt/ccxt/compare/1.63.65...1.64.44)

---
updated-dependencies:
- dependency-name: ccxt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-20 03:01:40 +00:00
dependabot[bot]
36632b48c7 Bump numpy from 1.21.4 to 1.21.5
Bumps [numpy](https://github.com/numpy/numpy) from 1.21.4 to 1.21.5.
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/main/doc/HOWTO_RELEASE.rst.txt)
- [Commits](https://github.com/numpy/numpy/compare/v1.21.4...v1.21.5)

---
updated-dependencies:
- dependency-name: numpy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-20 03:01:34 +00:00
dependabot[bot]
1b3aaffef4 Bump mkdocs-material from 8.1.0 to 8.1.3
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 8.1.0 to 8.1.3.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/8.1.0...8.1.3)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-20 03:01:26 +00:00
Matthias
1cbc4da72b Merge pull request #6082 from Rikj000/docs/hyperopt-import-any
📝 Docs - Added `Any` import
2021-12-19 07:54:49 +01:00
Rik Helsen
58c3d69d14 📝 Docs - Added Any import 2021-12-18 23:29:55 +01:00
Matthias
ea38b58081 Add base_currency to allowed webhook fields
closes #6075
2021-12-16 20:18:01 +01:00
Matthias
b2fc3e814e Merge pull request #6055 from freqtrade/blacklist_delete
Add Blacklist delete
2021-12-16 13:41:18 +01:00
Matthias
39f0a17e62 Fix formatting 2021-12-16 07:11:35 +01:00
Matthias
f9aa36f291 Don't hard-fail when executing emergency sell fails
closes #6068
2021-12-15 19:37:35 +01:00
Matthias
b80b5ed1ad Improve uri_logging test
part of #6069
2021-12-15 19:25:30 +01:00
cdimauro
9d8646072c Add test case for checking removal of logs for pains in blacklist 2021-12-14 06:23:40 +01:00
Matthias
dda302eea2 Merge pull request #6026 from freqtrade/dependabot/pip/develop/ta-lib-0.4.22
Bump ta-lib from 0.4.21 to 0.4.22
2021-12-13 19:44:46 +01:00
Matthias
793d090561 Improve log message wording for rejected stake amounts
closes #6064
2021-12-13 19:29:07 +01:00
Matthias
95949bd466 Update windows wheels to ta-lib 0.4.22 2021-12-13 19:05:35 +01:00
Matthias
1d0af074ac Merge pull request #6061 from freqtrade/dependabot/pip/develop/ccxt-1.63.65
Bump ccxt from 1.63.55 to 1.63.65
2021-12-13 06:51:17 +01:00
Matthias
f2d55a91cd Merge pull request #6063 from freqtrade/dependabot/pip/develop/fastapi-0.70.1
Bump fastapi from 0.70.0 to 0.70.1
2021-12-13 06:51:05 +01:00
Matthias
5371458c99 Merge pull request #6062 from freqtrade/dependabot/pip/develop/pandas-1.3.5
Bump pandas from 1.3.4 to 1.3.5
2021-12-13 06:50:32 +01:00
dependabot[bot]
884a04c7fe Bump fastapi from 0.70.0 to 0.70.1
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.70.0 to 0.70.1.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.70.0...0.70.1)

---
updated-dependencies:
- dependency-name: fastapi
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-13 03:01:49 +00:00
dependabot[bot]
172b9383c0 Bump pandas from 1.3.4 to 1.3.5
Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.3.4 to 1.3.5.
- [Release notes](https://github.com/pandas-dev/pandas/releases)
- [Changelog](https://github.com/pandas-dev/pandas/blob/master/RELEASE.md)
- [Commits](https://github.com/pandas-dev/pandas/compare/v1.3.4...v1.3.5)

---
updated-dependencies:
- dependency-name: pandas
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-13 03:01:44 +00:00
dependabot[bot]
ec4a24649c Bump ccxt from 1.63.55 to 1.63.65
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.63.55 to 1.63.65.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg)
- [Commits](https://github.com/ccxt/ccxt/compare/1.63.55...1.63.65)

---
updated-dependencies:
- dependency-name: ccxt
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-13 03:01:38 +00:00
Matthias
3398469e55 Update PerformanceFilter to have min_profit as ratio again.
closes #6056
2021-12-12 13:21:36 +01:00
cdimauro
8dd3128ed4 Add type annotation to new logs suppression code 2021-12-12 12:32:09 +01:00
cdimauro
5b998aeca7 Remove unused import
Remove the import from copy, since deepcopy() isn't used anymore
(list.copy() is used instead).
2021-12-12 10:21:54 +01:00
cdimauro
878e16545d Suppress additional logs for pairs in blacklist
Every time that there's freqtrade "ticks", pairs in the blacklist are
checked and a warning message is displayed.
So, the logs are continuously flooded with the same warnings.

For example:
2021-07-26 06:24:45 freqtrade.plugins.pairlistmanager: WARNING -
Pair XTZUP/USDT in your blacklist. Removing it from whitelist...
2021-07-26 06:24:45 freqtrade.plugins.pairlistmanager: WARNING -
Pair SUSHIUP/USDT in your blacklist. Removing it from whitelist...
2021-07-26 06:24:45 freqtrade.plugins.pairlistmanager: WARNING -
Pair XTZDOWN/USDT in your blacklist. Removing it from whitelist...
2021-07-26 06:24:50 freqtrade.plugins.pairlistmanager: WARNING -
Pair XTZUP/USDT in your blacklist. Removing it from whitelist...
2021-07-26 06:24:50 freqtrade.plugins.pairlistmanager: WARNING -
Pair SUSHIUP/USDT in your blacklist. Removing it from whitelist...
2021-07-26 06:24:50 freqtrade.plugins.pairlistmanager: WARNING -
Pair XTZDOWN/USDT in your blacklist. Removing it from whitelist...

This patch shows the warning only the first time, by keeping track
of which pairs in the blacklist were already logged.
2021-12-12 10:20:08 +01:00
Matthias
c12f2378db Merge pull request #6045 from freqtrade/trade_fee_fallback_value
Add unknown_fee_rate parameter
2021-12-11 20:00:01 +01:00
Matthias
1a4b403792 Merge pull request #6047 from freqtrade/dependabot/pip/develop/uvicorn-0.16.0
Bump uvicorn from 0.15.0 to 0.16.0
2021-12-11 19:50:18 +01:00
Matthias
b90c5e56fb Fix webserver schema bug when running in webserver mode 2021-12-11 19:46:35 +01:00
Matthias
8fdef2900e Increment API version to let clients know this is now available 2021-12-11 19:41:30 +01:00
Matthias
2918032dac Merge pull request #6046 from freqtrade/dependabot/pip/develop/python-telegram-bot-13.9
Bump python-telegram-bot from 13.8.1 to 13.9
2021-12-11 19:41:14 +01:00
Matthias
06bd8a1540 Merge pull request #6052 from freqtrade/dependabot/github_actions/develop/peter-evans/dockerhub-description-2.4.3
Bump peter-evans/dockerhub-description from 2.1.0 to 2.4.3
2021-12-11 17:26:43 +01:00
dependabot[bot]
58cd91bd80 Bump python-telegram-bot from 13.8.1 to 13.9
Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 13.8.1 to 13.9.
- [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases)
- [Changelog](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/CHANGES.rst)
- [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v13.8.1...v13.9)

---
updated-dependencies:
- dependency-name: python-telegram-bot
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 16:00:35 +00:00
Matthias
dbe97bcdb1 Merge pull request #6053 from freqtrade/dependabot/github_actions/develop/crazy-max/ghaction-docker-buildx-3.3.1
Bump crazy-max/ghaction-docker-buildx from 1 to 3.3.1
2021-12-11 16:59:56 +01:00
Matthias
843eec63f0 Merge pull request #6051 from freqtrade/dependabot/pip/develop/sqlalchemy-1.4.28
Bump sqlalchemy from 1.4.27 to 1.4.28
2021-12-11 16:59:42 +01:00
Matthias
0df8786af6 Merge pull request #6048 from freqtrade/dependabot/pip/develop/prompt-toolkit-3.0.24
Bump prompt-toolkit from 3.0.23 to 3.0.24
2021-12-11 16:35:07 +01:00
Matthias
b4ed90788b Merge pull request #6050 from freqtrade/dependabot/pip/develop/ccxt-1.63.55
Bump ccxt from 1.63.1 to 1.63.55
2021-12-11 16:34:36 +01:00
Matthias
c871e51dcc Merge pull request #6049 from freqtrade/dependabot/pip/develop/mkdocs-material-8.1.0
Bump mkdocs-material from 8.0.4 to 8.1.0
2021-12-11 16:34:28 +01:00
Matthias
857f4ec125 Remove exception-handlers which catch exceptions that are never raised 2021-12-11 16:20:09 +01:00
dependabot[bot]
783ee633aa Bump crazy-max/ghaction-docker-buildx from 1 to 3.3.1
Bumps [crazy-max/ghaction-docker-buildx](https://github.com/crazy-max/ghaction-docker-buildx) from 1 to 3.3.1.
- [Release notes](https://github.com/crazy-max/ghaction-docker-buildx/releases)
- [Changelog](https://github.com/crazy-max/ghaction-docker-buildx/blob/master/CHANGELOG.md)
- [Commits](https://github.com/crazy-max/ghaction-docker-buildx/compare/v1...v3.3.1)

---
updated-dependencies:
- dependency-name: crazy-max/ghaction-docker-buildx
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 15:13:10 +00:00
dependabot[bot]
fb134c67a9 Bump peter-evans/dockerhub-description from 2.1.0 to 2.4.3
Bumps [peter-evans/dockerhub-description](https://github.com/peter-evans/dockerhub-description) from 2.1.0 to 2.4.3.
- [Release notes](https://github.com/peter-evans/dockerhub-description/releases)
- [Commits](https://github.com/peter-evans/dockerhub-description/compare/v2.1.0...v2.4.3)

---
updated-dependencies:
- dependency-name: peter-evans/dockerhub-description
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 15:13:08 +00:00
Matthias
849ca1ec06 Dependabot - use correct branch 2021-12-11 16:12:36 +01:00
Matthias
8da79d0ab2 Add blacklist-control to telegram 2021-12-11 16:12:24 +01:00
dependabot[bot]
aaf5f4ce39 Bump sqlalchemy from 1.4.27 to 1.4.28
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.4.27 to 1.4.28.
- [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases)
- [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES)
- [Commits](https://github.com/sqlalchemy/sqlalchemy/commits)

---
updated-dependencies:
- dependency-name: sqlalchemy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 14:55:27 +00:00
dependabot[bot]
ae92bf56bf Bump ccxt from 1.63.1 to 1.63.55
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.63.1 to 1.63.55.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg)
- [Commits](https://github.com/ccxt/ccxt/compare/1.63.1...1.63.55)

---
updated-dependencies:
- dependency-name: ccxt
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 14:55:21 +00:00
dependabot[bot]
f47cfbd2a9 Bump mkdocs-material from 8.0.4 to 8.1.0
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 8.0.4 to 8.1.0.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/8.0.4...8.1.0)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 14:55:13 +00:00
dependabot[bot]
c9c683f2b0 Bump prompt-toolkit from 3.0.23 to 3.0.24
Bumps [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) from 3.0.23 to 3.0.24.
- [Release notes](https://github.com/prompt-toolkit/python-prompt-toolkit/releases)
- [Changelog](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/CHANGELOG)
- [Commits](https://github.com/prompt-toolkit/python-prompt-toolkit/compare/3.0.23...3.0.24)

---
updated-dependencies:
- dependency-name: prompt-toolkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 14:55:09 +00:00
dependabot[bot]
81cafd090d Bump uvicorn from 0.15.0 to 0.16.0
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.15.0 to 0.16.0.
- [Release notes](https://github.com/encode/uvicorn/releases)
- [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/uvicorn/compare/0.15.0...0.16.0)

---
updated-dependencies:
- dependency-name: uvicorn
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-11 14:55:05 +00:00
Matthias
671b9903d7 Add github-actions dependabot 2021-12-11 15:54:09 +01:00
Matthias
cc96db76f0 Add possibility to delete pairs from the pairlist via api 2021-12-11 15:53:44 +01:00
Matthias
e729fad99c Add unknown_fee_rate parameter 2021-12-11 15:26:08 +01:00
Matthias
e9c3f0cbbd Add note about python3.10 not being supported
closes #6041
2021-12-11 10:01:55 +01:00
Matthias
be6b1f6f83 Import from enums, not submodules 2021-12-09 06:18:21 +01:00
Matthias
b79f2f2981 Merge pull request #6035 from freqtrade/revert-6034-dependabot/docker/python-3.10.1-slim-bullseye
Revert "Bump python from 3.9.9-slim-bullseye to 3.10.1-slim-bullseye"
2021-12-09 06:18:11 +01:00
Matthias
facb5b3991 Revert "Bump python from 3.9.9-slim-bullseye to 3.10.1-slim-bullseye" 2021-12-09 06:17:56 +01:00
Matthias
79a87649b9 Merge pull request #6034 from freqtrade/dependabot/docker/python-3.10.1-slim-bullseye
Bump python from 3.9.9-slim-bullseye to 3.10.1-slim-bullseye
2021-12-09 06:15:16 +01:00
dependabot[bot]
7848e17a49 Bump python from 3.9.9-slim-bullseye to 3.10.1-slim-bullseye
Bumps python from 3.9.9-slim-bullseye to 3.10.1-slim-bullseye.

---
updated-dependencies:
- dependency-name: python
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-09 03:01:59 +00:00
Matthias
decaa24f81 Merge pull request #6028 from freqtrade/dependabot/pip/develop/mkdocs-material-8.0.4
Bump mkdocs-material from 8.0.1 to 8.0.4
2021-12-06 07:03:50 +01:00
Matthias
f9529c1fb6 Merge pull request #6027 from freqtrade/dependabot/pip/develop/ccxt-1.63.1
Bump ccxt from 1.62.42 to 1.63.1
2021-12-06 06:22:58 +01:00
dependabot[bot]
3dda0ef2ef Bump mkdocs-material from 8.0.1 to 8.0.4
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 8.0.1 to 8.0.4.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/8.0.1...8.0.4)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-06 03:01:35 +00:00
dependabot[bot]
50a6eaea22 Bump ccxt from 1.62.42 to 1.63.1
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.62.42 to 1.63.1.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg)
- [Commits](https://github.com/ccxt/ccxt/compare/1.62.42...1.63.1)

---
updated-dependencies:
- dependency-name: ccxt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-06 03:01:30 +00:00
dependabot[bot]
61211a1194 Bump ta-lib from 0.4.21 to 0.4.22
Bumps [ta-lib](https://github.com/mrjbq7/ta-lib) from 0.4.21 to 0.4.22.
- [Release notes](https://github.com/mrjbq7/ta-lib/releases)
- [Changelog](https://github.com/mrjbq7/ta-lib/blob/master/CHANGELOG)
- [Commits](https://github.com/mrjbq7/ta-lib/compare/TA_Lib-0.4.21...TA_Lib-0.4.22)

---
updated-dependencies:
- dependency-name: ta-lib
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-12-06 03:01:18 +00:00
Matthias
fbd64d757d Improve doc wording 2021-12-05 09:26:44 +01:00
Matthias
4278c5a24a add note about arm64 installation
closes #6025
2021-12-05 09:24:40 +01:00
Matthias
243e59cabb Merge pull request #5929 from dvdmchl/develop
Telegram and log prints strategy version.
2021-12-04 15:16:42 +01:00
Matthias
210202a797 Merge pull request #5756 from GluTbl/patch-1
add custom entry/exit price support to backtesting
2021-12-04 15:16:15 +01:00
Matthias
c981cc335d Remove wrong comment 2021-12-04 14:51:55 +01:00
Matthias
d0467b30ba Add strategy_version to API response 2021-12-04 14:49:45 +01:00
Matthias
e3190cf8a8 Update documentation 2021-12-04 14:44:03 +01:00
Matthias
848a2d5383 Merge branch 'develop' into pr/dvdmchl/5929 2021-12-04 14:40:15 +01:00
Matthias
2080bf0952 Fix some formatting errors, add test for strategy version 2021-12-04 14:40:05 +01:00
Matthias
68ac8008ec Call custom_exit_price only for sell_signal and custom_sell 2021-12-04 14:14:22 +01:00
Matthias
84ad176287 Improve documentation wording 2021-12-04 13:33:38 +01:00
Matthias
86910b58dc Bracket entry/exit prices to low/high of the candle 2021-12-03 17:44:53 +01:00
Matthias
d1209fe415 Merge branch 'develop' into pr/GluTbl/5756 2021-12-03 17:37:44 +01:00
Matthias
d09a30cc67 OrderTypeValues should be in enums 2021-12-03 15:34:28 +01:00
Matthias
ad5c8f601c Simplify datahandler classes by exploiting commonalities 2021-12-02 20:19:22 +01:00
Matthias
d3ad4fb52e Don't crash dry-run if orderbook side is empty
closes #6018
2021-12-02 19:17:47 +01:00
Matthias
294c98ed5e Document exchange.uid
part of #6016
2021-12-02 06:55:08 +01:00
Matthias
c1fed8a077 Merge pull request #6014 from freqtrade/double_notifications
Double notifications
2021-12-02 06:39:18 +01:00
Matthias
0375a08302 use to_hdf instead of HDFStore 2021-12-01 20:32:23 +01:00
Matthias
5ce1eeecf5 Reorder messages to be sent in correct order
buy first, then buy fill,
sell first, then sell fill.
2021-12-01 19:57:24 +01:00
Matthias
c22f381dfe Fix Schema issue
closes #6010
2021-11-30 20:46:47 +01:00
Matthias
542963c7a6 Reduce code complexity by combining buy and buy_fill methods 2021-11-30 19:45:20 +01:00
Matthias
f0abe218a2 Batch ohlcv requests to not overwelm ccxt's async throttler
closes #6003
2021-11-30 07:10:12 +01:00
Matthias
231b1e2f57 Improve Async error message content 2021-11-30 07:10:12 +01:00
Matthias
de7e1e6bf7 Merge pull request #5980 from incrementby1/ShuffleFilterDetectLiveMode
Shuffle filter use seed only in backtesting mode
2021-11-30 06:37:35 +01:00
incrementby1
85b1f6f6b3 Update pairlists.md 2021-11-29 20:44:51 +01:00
incrementby1
60eca8b1f1 revert to random object 2021-11-29 20:35:43 +01:00
Matthias
06d8217e62 Merge pull request #5983 from PostmanSpat/webhook-raw-retry
Added raw config and retry config to webhook
2021-11-29 20:30:06 +01:00
Matthias
dfb148f8d7 Fix formatting 2021-11-29 19:54:54 +01:00
Matthias
f8cb3d2901 Restore openAPI functioning 2021-11-29 19:52:40 +01:00
Matthias
bd8348451e Merge pull request #5999 from freqtrade/dependabot/pip/develop/mkdocs-material-8.0.1
Bump mkdocs-material from 7.3.6 to 8.0.1
2021-11-29 19:50:59 +01:00
Matthias
0f15340269 Merge pull request #5995 from freqtrade/dependabot/pip/develop/aiofiles-0.8.0
Bump aiofiles from 0.7.0 to 0.8.0
2021-11-29 19:32:45 +01:00
Matthias
2e51477455 Update mkdocs file to 8.0 2021-11-29 19:32:16 +01:00
Spat
018407852a Added missing webhook config params to constants 2021-11-29 18:17:59 +11:00
Matthias
56b4457a9c Merge pull request #5996 from freqtrade/dependabot/pip/develop/time-machine-2.4.1
Bump time-machine from 2.4.0 to 2.4.1
2021-11-29 08:07:30 +01:00
Matthias
2db064d8f7 Merge pull request #6000 from stash86/fix-docs
Add few sentences to make clear about backtest + pairlist handlers
2021-11-29 07:09:36 +01:00
Matthias
f0bf9b51dc Merge pull request #5992 from freqtrade/dependabot/pip/develop/ccxt-1.62.42
Bump ccxt from 1.61.92 to 1.62.42
2021-11-29 07:08:46 +01:00
dependabot[bot]
57e55eb938 Bump time-machine from 2.4.0 to 2.4.1
Bumps [time-machine](https://github.com/adamchainz/time-machine) from 2.4.0 to 2.4.1.
- [Release notes](https://github.com/adamchainz/time-machine/releases)
- [Changelog](https://github.com/adamchainz/time-machine/blob/main/HISTORY.rst)
- [Commits](https://github.com/adamchainz/time-machine/compare/2.4.0...2.4.1)

---
updated-dependencies:
- dependency-name: time-machine
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-29 06:00:32 +00:00
Matthias
5ee5600cb9 Merge pull request #5993 from freqtrade/dependabot/pip/develop/scipy-1.7.3
Bump scipy from 1.7.2 to 1.7.3
2021-11-29 06:59:51 +01:00
Matthias
828ab874c1 Merge pull request #5991 from freqtrade/dependabot/pip/develop/prompt-toolkit-3.0.23
Bump prompt-toolkit from 3.0.22 to 3.0.23
2021-11-29 06:59:30 +01:00
Matthias
90892e5a89 Merge pull request #5997 from freqtrade/dependabot/pip/develop/types-python-dateutil-2.8.3
Bump types-python-dateutil from 2.8.2 to 2.8.3
2021-11-29 06:59:14 +01:00
Matthias
180df0514f Merge pull request #5998 from freqtrade/dependabot/pip/develop/types-requests-2.26.1
Bump types-requests from 2.26.0 to 2.26.1
2021-11-29 06:58:55 +01:00
Matthias
731208936f Merge pull request #5994 from freqtrade/dependabot/pip/develop/types-cachetools-4.2.6
Bump types-cachetools from 4.2.5 to 4.2.6
2021-11-29 06:58:41 +01:00
Stefano Ariestasia
3b4051488f Merge branch 'fix-docs' of https://github.com/stash86/freqtrade into fix-docs 2021-11-29 14:32:37 +09:00
Stefano Ariestasia
c126d2530a Add few sentences on docs
- Add warning that PrecisionFilter can't be used on backtest that use multiple strategies
- Add note that not all pairlist handlers can be used on backtest
2021-11-29 14:32:33 +09:00
dependabot[bot]
24997fb36f Bump mkdocs-material from 7.3.6 to 8.0.1
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 7.3.6 to 8.0.1.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Upgrade guide](https://github.com/squidfunk/mkdocs-material/blob/master/docs/upgrade.md)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/7.3.6...8.0.1)

---
updated-dependencies:
- dependency-name: mkdocs-material
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-29 03:01:50 +00:00
dependabot[bot]
b81d768eb3 Bump types-requests from 2.26.0 to 2.26.1
Bumps [types-requests](https://github.com/python/typeshed) from 2.26.0 to 2.26.1.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-requests
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-29 03:01:45 +00:00
dependabot[bot]
39c3175b69 Bump types-python-dateutil from 2.8.2 to 2.8.3
Bumps [types-python-dateutil](https://github.com/python/typeshed) from 2.8.2 to 2.8.3.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-python-dateutil
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-29 03:01:43 +00:00
dependabot[bot]
b0b2fdba70 Bump aiofiles from 0.7.0 to 0.8.0
Bumps [aiofiles](https://github.com/Tinche/aiofiles) from 0.7.0 to 0.8.0.
- [Release notes](https://github.com/Tinche/aiofiles/releases)
- [Commits](https://github.com/Tinche/aiofiles/compare/v0.7.0...v0.8.0)

---
updated-dependencies:
- dependency-name: aiofiles
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-29 03:01:38 +00:00
dependabot[bot]
c2a7b1930b Bump types-cachetools from 4.2.5 to 4.2.6
Bumps [types-cachetools](https://github.com/python/typeshed) from 4.2.5 to 4.2.6.
- [Release notes](https://github.com/python/typeshed/releases)
- [Commits](https://github.com/python/typeshed/commits)

---
updated-dependencies:
- dependency-name: types-cachetools
  dependency-type: direct:development
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-29 03:01:36 +00:00
dependabot[bot]
589c9f55e0 Bump scipy from 1.7.2 to 1.7.3
Bumps [scipy](https://github.com/scipy/scipy) from 1.7.2 to 1.7.3.
- [Release notes](https://github.com/scipy/scipy/releases)
- [Commits](https://github.com/scipy/scipy/compare/v1.7.2...v1.7.3)

---
updated-dependencies:
- dependency-name: scipy
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-29 03:01:33 +00:00
dependabot[bot]
e9e8023d73 Bump ccxt from 1.61.92 to 1.62.42
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.61.92 to 1.62.42.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg)
- [Commits](https://github.com/ccxt/ccxt/compare/1.61.92...1.62.42)

---
updated-dependencies:
- dependency-name: ccxt
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-29 03:01:27 +00:00
dependabot[bot]
df09fe5df6 Bump prompt-toolkit from 3.0.22 to 3.0.23
Bumps [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) from 3.0.22 to 3.0.23.
- [Release notes](https://github.com/prompt-toolkit/python-prompt-toolkit/releases)
- [Changelog](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/CHANGELOG)
- [Commits](https://github.com/prompt-toolkit/python-prompt-toolkit/compare/3.0.22...3.0.23)

---
updated-dependencies:
- dependency-name: prompt-toolkit
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2021-11-29 03:01:20 +00:00
Spat
29180a1d2b Moved retry config to constants 2021-11-29 10:48:35 +11:00
Spat
0fa5bf54cd Changed comment 2021-11-29 10:30:41 +11:00
Matthias
cf5ff9257d Add plotconfig as property documentation and sample 2021-11-28 19:39:43 +01:00
incrementby1
c7d10e2c7e delete unneeded comment 2021-11-28 19:05:02 +01:00
Matthias
2414c0bd9f Merge pull request #5982 from stash86/fix-docs
add weekly and monthly to valid keys
2021-11-28 08:28:13 +01:00
Spat
fb6ae174b9 Added raw config and retry config to webhook 2021-11-28 11:42:57 +11:00
Stefano Ariestasia
fd9bf2adb0 add weekly and monthly to valid keys 2021-11-28 08:23:02 +09:00
Matthias
6429205d39 Improve Notebook documentation to include Dataprovider
fix #5975
2021-11-27 19:53:37 +01:00
Matthias
2b3e7eeb21 Use Enum values within bot code 2021-11-27 19:41:36 +01:00
Matthias
409a801763 Fix caching problem in refresh_ohlcv
closes #5978
2021-11-27 19:31:39 +01:00
incrementby1
b90303c9a3 Update ShuffleFilter.py
random.Random() is deprecated since 3.9
2021-11-27 18:26:30 +01:00
Matthias
cb95b362ec Merge pull request #5976 from freqtrade/forcebuy
allow force options with ordertype
2021-11-27 17:01:18 +01:00
incrementby1
62d248d182 Merge pull request #2 from incrementby1/ShuffleFilterDetectLiveModes
Update pairlists.md
2021-11-27 16:30:46 +01:00
incrementby1
2f0f576fce Update pairlists.md
ShuffleFilter will automatically detect runmodes and apply the `seed` only for backtesting modes - if ad `seed` value is set.
2021-11-27 16:28:41 +01:00
incrementby1
8c52ba3360 ShuffleFilterDetectLiveMode
# Apply seed in backtesting mode to get comparable results,
        # but not in live modes to get a non-repeating order of pairs during live modes.
2021-11-27 16:21:23 +01:00
Matthias
bc52b3db56 Properly handle None values via API 2021-11-27 09:26:14 +01:00
Matthias
80ed5283b2 Add forcesell market/limit distinction 2021-11-27 09:10:18 +01:00
Matthias
338fe333a9 Allow forcebuy to specify order_type 2021-11-24 20:20:58 +01:00
Dardon
d4fd13bf50 Telegram and log prints strategy version. 2021-11-20 16:26:07 +00:00
GluTbl
00406ea7d5 Update backtesting.py
Support for custom entry-prices and exit-prices during backtesting.
2021-10-19 17:15:45 +05:30
76 changed files with 912 additions and 477 deletions

View File

@@ -5,9 +5,17 @@ updates:
schedule:
interval: daily
open-pull-requests-limit: 10
- package-ecosystem: pip
directory: "/"
schedule:
interval: weekly
open-pull-requests-limit: 10
target-branch: develop
- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
open-pull-requests-limit: 10
target-branch: develop

View File

@@ -101,16 +101,13 @@ jobs:
run: |
mypy freqtrade scripts
- name: Slack Notification
uses: lazy-actions/slatify@v3.0.0
- name: Discord notification
uses: rjstone/discord-webhook-notify@v1
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
type: ${{ job.status }}
job_name: '*Freqtrade CI ${{ matrix.os }}*'
mention: 'here'
mention_if: 'failure'
channel: '#notifications'
url: ${{ secrets.SLACK_WEBHOOK }}
severity: error
details: Freqtrade CI failed on ${{ matrix.os }}
webhookUrl: ${{ secrets.DISCORD_WEBHOOK }}
build_macos:
runs-on: ${{ matrix.os }}
@@ -194,17 +191,13 @@ jobs:
run: |
mypy freqtrade scripts
- name: Slack Notification
uses: lazy-actions/slatify@v3.0.0
- name: Discord notification
uses: rjstone/discord-webhook-notify@v1
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
type: ${{ job.status }}
job_name: '*Freqtrade CI ${{ matrix.os }}*'
mention: 'here'
mention_if: 'failure'
channel: '#notifications'
url: ${{ secrets.SLACK_WEBHOOK }}
severity: error
details: Test Succeeded!
webhookUrl: ${{ secrets.DISCORD_WEBHOOK }}
build_windows:
@@ -257,16 +250,13 @@ jobs:
run: |
mypy freqtrade scripts
- name: Slack Notification
uses: lazy-actions/slatify@v3.0.0
- name: Discord notification
uses: rjstone/discord-webhook-notify@v1
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
type: ${{ job.status }}
job_name: '*Freqtrade CI windows*'
mention: 'here'
mention_if: 'failure'
channel: '#notifications'
url: ${{ secrets.SLACK_WEBHOOK }}
severity: error
details: Test Failed
webhookUrl: ${{ secrets.DISCORD_WEBHOOK }}
docs_check:
runs-on: ubuntu-20.04
@@ -288,14 +278,13 @@ jobs:
pip install mkdocs
mkdocs build
- name: Slack Notification
uses: lazy-actions/slatify@v3.0.0
- name: Discord notification
uses: rjstone/discord-webhook-notify@v1
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
type: ${{ job.status }}
job_name: '*Freqtrade Docs*'
channel: '#notifications'
url: ${{ secrets.SLACK_WEBHOOK }}
severity: error
details: Freqtrade doc test failed!
webhookUrl: ${{ secrets.DISCORD_WEBHOOK }}
cleanup-prior-runs:
runs-on: ubuntu-20.04
@@ -306,7 +295,7 @@ jobs:
env:
GITHUB_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
# Notify on slack only once - when CI completes (and after deploy) in case it's successfull
# Notify only once - when CI completes (and after deploy) in case it's successfull
notify-complete:
needs: [ build_linux, build_macos, build_windows, docs_check ]
runs-on: ubuntu-20.04
@@ -320,14 +309,13 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Slack Notification
uses: lazy-actions/slatify@v3.0.0
- name: Discord notification
uses: rjstone/discord-webhook-notify@v1
if: always() && steps.check.outputs.has-permission && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
type: ${{ job.status }}
job_name: '*Freqtrade CI*'
channel: '#notifications'
url: ${{ secrets.SLACK_WEBHOOK }}
severity: info
details: Test Completed!
webhookUrl: ${{ secrets.DISCORD_WEBHOOK }}
deploy:
needs: [ build_linux, build_macos, build_windows, docs_check ]
@@ -385,7 +373,7 @@ jobs:
- name: Set up Docker Buildx
id: buildx
uses: crazy-max/ghaction-docker-buildx@v1
uses: crazy-max/ghaction-docker-buildx@v3.3.1
with:
buildx-version: latest
qemu-version: latest
@@ -400,17 +388,13 @@ jobs:
run: |
build_helpers/publish_docker_multi.sh
- name: Slack Notification
uses: lazy-actions/slatify@v3.0.0
- name: Discord notification
uses: rjstone/discord-webhook-notify@v1
if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
type: ${{ job.status }}
job_name: '*Freqtrade CI Deploy*'
mention: 'here'
mention_if: 'failure'
channel: '#notifications'
url: ${{ secrets.SLACK_WEBHOOK }}
severity: info
details: Deploy Succeeded!
webhookUrl: ${{ secrets.DISCORD_WEBHOOK }}
deploy_arm:

View File

@@ -10,7 +10,7 @@ jobs:
steps:
- uses: actions/checkout@v1
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v2.1.0
uses: peter-evans/dockerhub-description@v2.4.3
env:
DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}

View File

@@ -1,55 +0,0 @@
os:
- linux
dist: bionic
language: python
python:
- 3.8
services:
- docker
env:
global:
- IMAGE_NAME=freqtradeorg/freqtrade
install:
- cd build_helpers && ./install_ta-lib.sh ${HOME}/dependencies; cd ..
- export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
- export TA_LIBRARY_PATH=${HOME}/dependencies/lib
- export TA_INCLUDE_PATH=${HOME}/dependencies/include
- pip install -r requirements-dev.txt
- pip install -e .
jobs:
include:
- stage: tests
script:
- pytest --random-order --cov=freqtrade --cov-config=.coveragerc
# Allow failure for coveralls
# - coveralls || true
name: pytest
- script:
- cp config_examples/config_bittrex.example.json config.json
- freqtrade create-userdir --userdir user_data
- freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
name: backtest
- script:
- cp config_examples/config_bittrex.example.json config.json
- freqtrade create-userdir --userdir user_data
- freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt-loss SharpeHyperOptLossDaily
name: hyperopt
- script: flake8
name: flake8
- script:
# Test Documentation boxes -
# !!! <TYPE>: is not allowed!
# !!! <TYPE> "title" - Title needs to be quoted!
- grep -Er '^!{3}\s\S+:|^!{3}\s\S+\s[^"]' docs/*; test $? -ne 0
name: doc syntax
- script: mypy freqtrade scripts
name: mypy
notifications:
slack:
secure: bKLXmOrx8e2aPZl7W8DA5BdPAXWGpI5UzST33oc1G/thegXcDVmHBTJrBs4sZak6bgAclQQrdZIsRd2eFYzHLalJEaw6pk7hoAw8SvLnZO0ZurWboz7qg2+aZZXfK4eKl/VUe4sM9M4e/qxjkK+yWG7Marg69c4v1ypF7ezUi1fPYILYw8u0paaiX0N5UX8XNlXy+PBlga2MxDjUY70MuajSZhPsY2pDUvYnMY1D/7XN3cFW0g+3O8zXjF0IF4q1Z/1ASQe+eYjKwPQacE+O8KDD+ZJYoTOFBAPllrtpO1jnOPFjNGf3JIbVMZw4bFjIL0mSQaiSUaUErbU3sFZ5Or79rF93XZ81V7uEZ55vD8KMfR2CB1cQJcZcj0v50BxLo0InkFqa0Y8Nra3sbpV4fV5Oe8pDmomPJrNFJnX6ULQhQ1gTCe0M5beKgVms5SITEpt4/Y0CmLUr6iHDT0CUiyMIRWAXdIgbGh1jfaWOMksybeRevlgDsIsNBjXmYI1Sw2ZZR2Eo2u4R6zyfyjOMLwYJ3vgq9IrACv2w5nmf0+oguMWHf6iWi2hiOqhlAN1W74+3HsYQcqnuM3LGOmuCnPprV1oGBqkPXjIFGpy21gNx4vHfO1noLUyJnMnlu2L7SSuN1CdLsnjJ1hVjpJjPfqB4nn8g12x87TqM1bOm+3Q=
cache:
pip: True
directories:
- $HOME/dependencies

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -6,13 +6,13 @@ python -m pip install --upgrade pip
$pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
if ($pyv -eq '3.7') {
pip install build_helpers\TA_Lib-0.4.21-cp37-cp37m-win_amd64.whl
pip install build_helpers\TA_Lib-0.4.22-cp37-cp37m-win_amd64.whl
}
if ($pyv -eq '3.8') {
pip install build_helpers\TA_Lib-0.4.21-cp38-cp38-win_amd64.whl
pip install build_helpers\TA_Lib-0.4.22-cp38-cp38-win_amd64.whl
}
if ($pyv -eq '3.9') {
pip install build_helpers\TA_Lib-0.4.21-cp39-cp39-win_amd64.whl
pip install build_helpers\TA_Lib-0.4.22-cp39-cp39-win_amd64.whl
}
pip install -r requirements-dev.txt

View File

@@ -13,7 +13,7 @@ A sample of this can be found below, which is identical to the Default Hyperopt
``` python
from datetime import datetime
from typing import Dict
from typing import Any, Dict
from pandas import DataFrame

View File

@@ -115,7 +115,7 @@ The result of backtesting will confirm if your bot has better odds of making a p
All profit calculations include fees, and freqtrade will use the exchange's default fees for the calculation.
!!! Warning "Using dynamic pairlists for backtesting"
Using dynamic pairlists is possible, however it relies on the current market conditions - which will not reflect the historic status of the pairlist.
Using dynamic pairlists is possible (not all of the handlers are allowed to be used in backtest mode), however it relies on the current market conditions - which will not reflect the historic status of the pairlist.
Also, when using pairlists other than StaticPairlist, reproducibility of backtesting-results cannot be guaranteed.
Please read the [pairlists documentation](plugins.md#pairlists) for more information.

View File

@@ -56,7 +56,11 @@ This loop will be repeated again and again until the bot is stopped.
* Calculate buy / sell signals (calls `populate_buy_trend()` and `populate_sell_trend()` once per pair).
* Loops per candle simulating entry and exit points.
* Confirm trade buy / sell (calls `confirm_trade_entry()` and `confirm_trade_exit()` if implemented in the strategy).
* Call `custom_entry_price()` (if implemented in the strategy) to determine entry price (Prices are moved to be within the opening candle).
* Determine stake size by calling the `custom_stake_amount()` callback.
* Call `custom_stoploss()` and `custom_sell()` to find custom exit points.
* For sells based on sell-signal and custom-sell: Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle).
* Generate backtest report output
!!! Note

View File

@@ -126,14 +126,16 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `exchange.uid` | API uid to use for the exchange. Only required when you are in production mode and for exchanges that use uid for API requests.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `exchange.pair_whitelist` | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Supports regex pairs as `.*/BTC`. Not used by VolumePairList. [More information](plugins.md#pairlists-and-pairlist-handlers). <br> **Datatype:** List
| `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting. [More information](plugins.md#pairlists-and-pairlist-handlers). <br> **Datatype:** List
| `exchange.ccxt_config` | Additional CCXT parameters passed to both ccxt instances (sync and async). This is usually the correct place for ccxt configurations. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> **Datatype:** Dict
| `exchange.ccxt_config` | Additional CCXT parameters passed to both ccxt instances (sync and async). This is usually the correct place for additional ccxt configurations. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation). Please avoid adding exchange secrets here (use the dedicated fields instead), as they may be contained in logs. <br> **Datatype:** Dict
| `exchange.ccxt_sync_config` | Additional CCXT parameters passed to the regular (sync) ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> **Datatype:** Dict
| `exchange.ccxt_async_config` | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> **Datatype:** Dict
| `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded. <br>*Defaults to `60` minutes.* <br> **Datatype:** Positive Integer
| `exchange.skip_pair_validation` | Skip pairlist validation on startup.<br>*Defaults to `false`<br> **Datatype:** Boolean
| `exchange.skip_open_order_update` | Skips open order updates on startup should the exchange cause problems. Only relevant in live conditions.<br>*Defaults to `false`<br> **Datatype:** Boolean
| `exchange.unknown_fee_rate` | Fallback value to use when calculating trading fees. This can be useful for exchanges which have fees in non-tradable currencies. The value provided here will be multiplied with the "fee cost".<br>*Defaults to `None`<br> **Datatype:** float
| `exchange.log_responses` | Log relevant exchange responses. For debug mode only - use with care.<br>*Defaults to `false`<br> **Datatype:** Boolean
| `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation.
| `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now. <br>*Defaults to `true`.* <br> **Datatype:** Boolean

View File

@@ -324,9 +324,8 @@ jupyter nbconvert --ClearOutputPreprocessor.enabled=True --to markdown freqtrade
This documents some decisions taken for the CI Pipeline.
* CI runs on all OS variants, Linux (ubuntu), macOS and Windows.
* Docker images are build for the branches `stable` and `develop`.
* Docker images are build for the branches `stable` and `develop`, and are built as multiarch builds, supporting multiple platforms via the same tag.
* Docker images containing Plot dependencies are also available as `stable_plot` and `develop_plot`.
* Raspberry PI Docker images are postfixed with `_pi` - so tags will be `:stable_pi` and `develop_pi`.
* Docker images contain a file, `/freqtrade/freqtrade_commit` containing the commit this image is based of.
* Full docker image rebuilds are run once a week via schedule.
* Deployments run on ubuntu.

View File

@@ -199,6 +199,11 @@ OKEX requires a passphrase for each api key, you will therefore need to add this
!!! Warning
OKEX only provides 100 candles per api call. Therefore, the strategy will only have a pretty low amount of data available in backtesting mode.
## Gate.io
Gate.io allows the use of `POINT` to pay for fees. As this is not a tradable currency (no regular market available), automatic fee calculations will fail (and default to a fee of 0).
The configuration parameter `exchange.unknown_fee_rate` can be used to specify the exchange rate between Point and the stake currency. Obviously, changing the stake-currency will also require changes to this value.
## All exchanges
Should you experience constant errors with Nonce (like `InvalidNonce`), it is best to regenerate the API keys. Resetting Nonce is difficult and it's usually easier to regenerate the API keys.

View File

@@ -196,7 +196,7 @@ Trade count is used as a tie breaker.
You can use the `minutes` parameter to only consider performance of the past X minutes (rolling window).
Not defining this parameter (or setting it to 0) will use all-time performance.
The optional `min_profit` parameter defines the minimum profit a pair must have to be considered.
The optional `min_profit` (as ratio -> a setting of `0.01` corresponds to 1%) parameter defines the minimum profit a pair must have to be considered.
Pairs below this level will be filtered out.
Using this parameter without `minutes` is highly discouraged, as it can lead to an empty pairlist without a way to recover.
@@ -206,7 +206,7 @@ Using this parameter without `minutes` is highly discouraged, as it can lead to
{
"method": "PerformanceFilter",
"minutes": 1440, // rolling 24h
"min_profit": 0.01
"min_profit": 0.01 // minimal profit 1%
}
],
```
@@ -220,6 +220,9 @@ As this Filter uses past performance of the bot, it'll have some startup-period
Filters low-value coins which would not allow setting stoplosses.
!!! Warning "Backtesting"
`PrecisionFilter` does not support backtesting mode using multiple strategies.
#### PriceFilter
The `PriceFilter` allows filtering of pairs by price. Currently the following price filters are supported:
@@ -257,7 +260,7 @@ Min price precision for SHITCOIN/BTC is 8 decimals. If its price is 0.00000011 -
Shuffles (randomizes) pairs in the pairlist. It can be used for preventing the bot from trading some of the pairs more frequently then others when you want all pairs be treated with the same priority.
!!! Tip
You may set the `seed` value for this Pairlist to obtain reproducible results, which can be useful for repeated backtesting sessions. If `seed` is not set, the pairs are shuffled in the non-repeatable random order.
You may set the `seed` value for this Pairlist to obtain reproducible results, which can be useful for repeated backtesting sessions. If `seed` is not set, the pairs are shuffled in the non-repeatable random order. ShuffleFilter will automatically detect runmodes and apply the `seed` only for backtesting modes - if a `seed` value is set.
#### SpreadFilter

View File

@@ -36,6 +36,10 @@ The easiest way to install and run Freqtrade is to clone the bot Github reposito
These requirements apply to both [Script Installation](#script-installation) and [Manual Installation](#manual-installation).
!!! Note "ARM64 systems"
If you are running an ARM64 system (like a MacOS M1 or an Oracle VM), please use [docker](docker_quickstart.md) to run freqtrade.
While native installation is possible with some manual effort, this is not supported at the moment.
### Install guide
* [Python >= 3.7.x](http://docs.python-guide.org/en/latest/starting/installation/)
@@ -52,6 +56,10 @@ OS Specific steps are listed first, the [Common](#common) section below is neces
!!! Note
Python3.7 or higher and the corresponding pip are assumed to be available.
!!! Warning "Python 3.10 support"
Due to issues with dependencies, freqtrade is currently unable to support python 3.10.
We're working on supporting python 3.10, are however dependant on support from dependencies.
=== "Debian/Ubuntu"
#### Install necessary dependencies

View File

@@ -164,16 +164,17 @@ The resulting plot will have the following elements:
An advanced plot configuration can be specified in the strategy in the `plot_config` parameter.
Additional features when using plot_config include:
Additional features when using `plot_config` include:
* Specify colors per indicator
* Specify additional subplots
* Specify indicator pairs to fill area in between
* Specify indicator pairs to fill area in between
The sample plot configuration below specifies fixed colors for the indicators. Otherwise, consecutive plots may produce different color schemes each time, making comparisons difficult.
It also allows multiple subplots to display both MACD and RSI at the same time.
Plot type can be configured using `type` key. Possible types are:
* `scatter` corresponding to `plotly.graph_objects.Scatter` class (default).
* `bar` corresponding to `plotly.graph_objects.Bar` class.
@@ -182,40 +183,89 @@ Extra parameters to `plotly.graph_objects.*` constructor can be specified in `pl
Sample configuration with inline comments explaining the process:
``` python
plot_config = {
'main_plot': {
# Configuration for main plot indicators.
# Specifies `ema10` to be red, and `ema50` to be a shade of gray
'ema10': {'color': 'red'},
'ema50': {'color': '#CCCCCC'},
# By omitting color, a random color is selected.
'sar': {},
# fill area between senkou_a and senkou_b
'senkou_a': {
'color': 'green', #optional
'fill_to': 'senkou_b',
'fill_label': 'Ichimoku Cloud', #optional
'fill_color': 'rgba(255,76,46,0.2)', #optional
},
# plot senkou_b, too. Not only the area to it.
'senkou_b': {}
@property
def plot_config(self):
"""
There are a lot of solutions how to build the return dictionary.
The only important point is the return value.
Example:
plot_config = {'main_plot': {}, 'subplots': {}}
"""
plot_config = {}
plot_config['main_plot'] = {
# Configuration for main plot indicators.
# Assumes 2 parameters, emashort and emalong to be specified.
f'ema_{self.emashort.value}': {'color': 'red'},
f'ema_{self.emalong.value}': {'color': '#CCCCCC'},
# By omitting color, a random color is selected.
'sar': {},
# fill area between senkou_a and senkou_b
'senkou_a': {
'color': 'green', #optional
'fill_to': 'senkou_b',
'fill_label': 'Ichimoku Cloud', #optional
'fill_color': 'rgba(255,76,46,0.2)', #optional
},
'subplots': {
# Create subplot MACD
"MACD": {
'macd': {'color': 'blue', 'fill_to': 'macdhist'},
'macdsignal': {'color': 'orange'},
'macdhist': {'type': 'bar', 'plotly': {'opacity': 0.9}}
},
# Additional subplot RSI
"RSI": {
'rsi': {'color': 'red'}
}
# plot senkou_b, too. Not only the area to it.
'senkou_b': {}
}
plot_config['subplots'] = {
# Create subplot MACD
"MACD": {
'macd': {'color': 'blue', 'fill_to': 'macdhist'},
'macdsignal': {'color': 'orange'},
'macdhist': {'type': 'bar', 'plotly': {'opacity': 0.9}}
},
# Additional subplot RSI
"RSI": {
'rsi': {'color': 'red'}
}
}
return plot_config
```
??? Note "As attribute (former method)"
Assigning plot_config is also possible as Attribute (this used to be the default way).
This has the disadvantage that strategy parameters are not available, preventing certain configurations from working.
``` python
plot_config = {
'main_plot': {
# Configuration for main plot indicators.
# Specifies `ema10` to be red, and `ema50` to be a shade of gray
'ema10': {'color': 'red'},
'ema50': {'color': '#CCCCCC'},
# By omitting color, a random color is selected.
'sar': {},
# fill area between senkou_a and senkou_b
'senkou_a': {
'color': 'green', #optional
'fill_to': 'senkou_b',
'fill_label': 'Ichimoku Cloud', #optional
'fill_color': 'rgba(255,76,46,0.2)', #optional
},
# plot senkou_b, too. Not only the area to it.
'senkou_b': {}
},
'subplots': {
# Create subplot MACD
"MACD": {
'macd': {'color': 'blue', 'fill_to': 'macdhist'},
'macdsignal': {'color': 'orange'},
'macdhist': {'type': 'bar', 'plotly': {'opacity': 0.9}}
},
# Additional subplot RSI
"RSI": {
'rsi': {'color': 'red'}
}
}
}
```
!!! Note
The above configuration assumes that `ema10`, `ema50`, `senkou_a`, `senkou_b`,
`macd`, `macdsignal`, `macdhist` and `rsi` are columns in the DataFrame created by the strategy.

View File

@@ -1,4 +1,4 @@
mkdocs==1.2.3
mkdocs-material==7.3.6
mkdocs-material==8.1.3
mdx_truly_sane_lists==1.2
pymdown-extensions==9.1

View File

@@ -127,6 +127,21 @@ The provided exit-tag is then used as sell-reason - and shown as such in backtes
!!! Note
`sell_reason` is limited to 100 characters, remaining data will be truncated.
## Strategy version
You can implement custom strategy versioning by using the "version" method, and returning the version you would like this strategy to have.
``` python
def version(self) -> str:
"""
Returns version of the strategy.
"""
return "1.1"
```
!!! Note
You should make sure to implement proper version control (like a git repository) alongside this, as freqtrade will not keep historic versions of your strategy, so it's up to the user to be able to eventually roll back to a prior version of the strategy.
## Derived strategies
The strategies can be derived from other strategies. This avoids duplication of your custom strategy code. You can use this technique to override small parts of your main strategy, leaving the rest untouched:

View File

@@ -387,8 +387,10 @@ class AwesomeStrategy(IStrategy):
**Example**:
If the new_entryprice is 97, the proposed_rate is 100 and the `custom_price_max_distance_ratio` is set to 2%, The retained valid custom entry price will be 98, which is 2% below the current (proposed) rate.
!!! Warning "No backtesting support"
Custom entry-prices are currently not supported during backtesting.
!!! Warning "Backtesting"
While Custom prices are supported in backtesting (starting with 2021.12), prices will be moved to within the candle's high/low prices.
This behavior is currently being tested, and might be changed at a later point.
`custom_exit_price()` is only called for sells of type Sell_signal and Custom sell. All other sell-types will use regular backtesting prices.
## Custom order timeout rules

View File

@@ -50,7 +50,9 @@ candles.head()
```python
# Load strategy using values set above
from freqtrade.resolvers import StrategyResolver
from freqtrade.data.dataprovider import DataProvider
strategy = StrategyResolver.load_strategy(config)
strategy.dp = DataProvider(config, None, None)
# Generate buy/sell signals using strategy
df = strategy.analyze_ticker(candles, {'pair': pair})
@@ -228,7 +230,7 @@ graph = generate_candlestick_graph(pair=pair,
# Show graph inline
# graph.show()
# Render graph in a separate window
# Render graph in a seperate window
graph.show(renderer="browser")
```

View File

@@ -50,7 +50,7 @@ Sample configuration (tested using IFTTT).
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 your event and key to the url.
You can set the POST body format to Form-Encoded (default) or JSON-Encoded. Use `"format": "form"` or `"format": "json"` respectively. Example configuration for Mattermost Cloud integration:
You can set the POST body format to Form-Encoded (default), JSON-Encoded, or raw data. Use `"format": "form"`, `"format": "json"`, or `"format": "raw"` respectively. Example configuration for Mattermost Cloud integration:
```json
"webhook": {
@@ -63,7 +63,36 @@ You can set the POST body format to Form-Encoded (default) or JSON-Encoded. Use
},
```
The result would be POST request with e.g. `{"text":"Status: running"}` body and `Content-Type: application/json` header which results `Status: running` message in the Mattermost channel.
The result would be a POST request with e.g. `{"text":"Status: running"}` body and `Content-Type: application/json` header which results `Status: running` message in the Mattermost channel.
When using the Form-Encoded or JSON-Encoded configuration you can configure any number of payload values, and both the key and value will be ouput in the POST request. However, when using the raw data format you can only configure one value and it **must** be named `"data"`. In this instance the data key will not be output in the POST request, only the value. For example:
```json
"webhook": {
"enabled": true,
"url": "https://<YOURHOOKURL>",
"format": "raw",
"webhookstatus": {
"data": "Status: {status}"
}
},
```
The result would be a POST request with e.g. `Status: running` body and `Content-Type: text/plain` header.
Optional parameters are available to enable automatic retries for webhook messages. The `webhook.retries` parameter can be set for the maximum number of retries the webhook request should attempt if it is unsuccessful (i.e. HTTP response status is not 200). By default this is set to `0` which is disabled. An additional `webhook.retry_delay` parameter can be set to specify the time in seconds between retry attempts. By default this is set to `0.1` (i.e. 100ms). Note that increasing the number of retries or retry delay may slow down the trader if there are connectivity issues with the webhook. Example configuration for retries:
```json
"webhook": {
"enabled": true,
"url": "https://<YOURHOOKURL>",
"retries": 3,
"retry_delay": 0.2,
"webhookstatus": {
"status": "Status: {status}"
}
},
```
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.
@@ -75,11 +104,13 @@ Possible parameters are:
* `trade_id`
* `exchange`
* `pair`
* `limit`
* ~~`limit` # Deprecated - should no longer be used.~~
* `open_rate`
* `amount`
* `open_date`
* `stake_amount`
* `stake_currency`
* `base_currency`
* `fiat_currency`
* `order_type`
* `current_rate`
@@ -98,6 +129,7 @@ Possible parameters are:
* `open_date`
* `stake_amount`
* `stake_currency`
* `base_currency`
* `fiat_currency`
* `order_type`
* `current_rate`
@@ -116,7 +148,10 @@ Possible parameters are:
* `open_date`
* `stake_amount`
* `stake_currency`
* `base_currency`
* `fiat_currency`
* `order_type`
* `current_rate`
* `buy_tag`
### Webhooksell
@@ -134,6 +169,7 @@ Possible parameters are:
* `profit_amount`
* `profit_ratio`
* `stake_currency`
* `base_currency`
* `fiat_currency`
* `sell_reason`
* `order_type`
@@ -156,6 +192,7 @@ Possible parameters are:
* `profit_amount`
* `profit_ratio`
* `stake_currency`
* `base_currency`
* `fiat_currency`
* `sell_reason`
* `order_type`
@@ -178,6 +215,7 @@ Possible parameters are:
* `profit_amount`
* `profit_ratio`
* `stake_currency`
* `base_currency`
* `fiat_currency`
* `sell_reason`
* `order_type`

View File

@@ -23,9 +23,9 @@ git clone https://github.com/freqtrade/freqtrade.git
Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows).
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial pre-compiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which need to be downloaded and installed using `pip install TA_Lib-0.4.21-cp38-cp38-win_amd64.whl` (make sure to use the version matching your python version).
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial pre-compiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which need to be downloaded and installed using `pip install TA_Lib0.4.22cp38cp38win_amd64.whl` (make sure to use the version matching your python version).
Freqtrade provides these dependencies for the latest 2 Python versions (3.7 and 3.8) and for 64bit Windows.
Freqtrade provides these dependencies for the latest 3 Python versions (3.7, 3.8 and 3.9) and for 64bit Windows.
Other versions must be downloaded from the above link.
``` powershell

View File

@@ -1,5 +1,5 @@
""" Freqtrade bot """
__version__ = '2021.11'
__version__ = '2021.12'
if __version__ == 'develop':

View File

@@ -1,6 +1,6 @@
from datetime import datetime, timezone
from cachetools.ttl import TTLCache
from cachetools import TTLCache
class PeriodicCache(TTLCache):

View File

@@ -50,6 +50,8 @@ USERPATH_STRATEGIES = 'strategies'
USERPATH_NOTEBOOKS = 'notebooks'
TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent']
WEBHOOK_FORMAT_OPTIONS = ['form', 'json', 'raw']
ENV_VAR_PREFIX = 'FREQTRADE__'
NON_OPEN_EXCHANGE_STATES = ('cancelled', 'canceled', 'closed', 'expired')
@@ -312,10 +314,16 @@ CONF_SCHEMA = {
'type': 'object',
'properties': {
'enabled': {'type': 'boolean'},
'url': {'type': 'string'},
'format': {'type': 'string', 'enum': WEBHOOK_FORMAT_OPTIONS, 'default': 'form'},
'retries': {'type': 'integer', 'minimum': 0},
'retry_delay': {'type': 'number', 'minimum': 0},
'webhookbuy': {'type': 'object'},
'webhookbuycancel': {'type': 'object'},
'webhookbuyfill': {'type': 'object'},
'webhooksell': {'type': 'object'},
'webhooksellcancel': {'type': 'object'},
'webhooksellfill': {'type': 'object'},
'webhookstatus': {'type': 'object'},
},
},
@@ -387,6 +395,7 @@ CONF_SCHEMA = {
},
'uniqueItems': True
},
'unknown_fee_rate': {'type': 'number'},
'outdated_offset': {'type': 'integer', 'minimum': 1},
'markets_refresh_interval': {'type': 'integer'},
'ccxt_config': {'type': 'object'},

View File

@@ -6,7 +6,6 @@ from typing import List, Optional
import numpy as np
import pandas as pd
from freqtrade import misc
from freqtrade.configuration import TimeRange
from freqtrade.constants import (DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS,
ListPairsWithTimeframes, TradeList)
@@ -61,10 +60,10 @@ class HDF5DataHandler(IDataHandler):
filename = self._pair_data_filename(self._datadir, pair, timeframe)
ds = pd.HDFStore(filename, mode='a', complevel=9, complib='blosc')
ds.put(key, _data.loc[:, self._columns], format='table', data_columns=['date'])
ds.close()
_data.loc[:, self._columns].to_hdf(
filename, key, mode='a', complevel=9, complib='blosc',
format='table', data_columns=['date']
)
def _ohlcv_load(self, pair: str, timeframe: str,
timerange: Optional[TimeRange] = None) -> pd.DataFrame:
@@ -99,19 +98,6 @@ class HDF5DataHandler(IDataHandler):
'low': 'float', 'close': 'float', 'volume': 'float'})
return pairdata
def ohlcv_purge(self, pair: str, timeframe: str) -> bool:
"""
Remove data for this pair
:param pair: Delete data for this pair.
:param timeframe: Timeframe (e.g. "5m")
:return: True when deleted, false if file did not exist.
"""
filename = self._pair_data_filename(self._datadir, pair, timeframe)
if filename.exists():
filename.unlink()
return True
return False
def ohlcv_append(self, pair: str, timeframe: str, data: pd.DataFrame) -> None:
"""
Append data to existing data structures
@@ -142,11 +128,11 @@ class HDF5DataHandler(IDataHandler):
"""
key = self._pair_trades_key(pair)
ds = pd.HDFStore(self._pair_trades_filename(self._datadir, pair),
mode='a', complevel=9, complib='blosc')
ds.put(key, pd.DataFrame(data, columns=DEFAULT_TRADES_COLUMNS),
format='table', data_columns=['timestamp'])
ds.close()
pd.DataFrame(data, columns=DEFAULT_TRADES_COLUMNS).to_hdf(
self._pair_trades_filename(self._datadir, pair), key,
mode='a', complevel=9, complib='blosc',
format='table', data_columns=['timestamp']
)
def trades_append(self, pair: str, data: TradeList):
"""
@@ -180,17 +166,9 @@ class HDF5DataHandler(IDataHandler):
trades[['id', 'type']] = trades[['id', 'type']].replace({np.nan: None})
return trades.values.tolist()
def trades_purge(self, pair: str) -> bool:
"""
Remove data for this pair
:param pair: Delete data for this pair.
:return: True when deleted, false if file did not exist.
"""
filename = self._pair_trades_filename(self._datadir, pair)
if filename.exists():
filename.unlink()
return True
return False
@classmethod
def _get_file_extension(cls):
return "h5"
@classmethod
def _pair_ohlcv_key(cls, pair: str, timeframe: str) -> str:
@@ -199,15 +177,3 @@ class HDF5DataHandler(IDataHandler):
@classmethod
def _pair_trades_key(cls, pair: str) -> str:
return f"{pair}/trades"
@classmethod
def _pair_data_filename(cls, datadir: Path, pair: str, timeframe: str) -> Path:
pair_s = misc.pair_to_filename(pair)
filename = datadir.joinpath(f'{pair_s}-{timeframe}.h5')
return filename
@classmethod
def _pair_trades_filename(cls, datadir: Path, pair: str) -> Path:
pair_s = misc.pair_to_filename(pair)
filename = datadir.joinpath(f'{pair_s}-trades.h5')
return filename

View File

@@ -12,6 +12,7 @@ from typing import List, Optional, Type
from pandas import DataFrame
from freqtrade import misc
from freqtrade.configuration import TimeRange
from freqtrade.constants import ListPairsWithTimeframes, TradeList
from freqtrade.data.converter import clean_ohlcv_dataframe, trades_remove_duplicates, trim_dataframe
@@ -26,6 +27,13 @@ class IDataHandler(ABC):
def __init__(self, datadir: Path) -> None:
self._datadir = datadir
@classmethod
def _get_file_extension(cls) -> str:
"""
Get file extension for this particular datahandler
"""
raise NotImplementedError()
@abstractclassmethod
def ohlcv_get_available_data(cls, datadir: Path) -> ListPairsWithTimeframes:
"""
@@ -70,7 +78,6 @@ class IDataHandler(ABC):
:return: DataFrame with ohlcv data, or empty DataFrame
"""
@abstractmethod
def ohlcv_purge(self, pair: str, timeframe: str) -> bool:
"""
Remove data for this pair
@@ -78,6 +85,11 @@ class IDataHandler(ABC):
:param timeframe: Timeframe (e.g. "5m")
:return: True when deleted, false if file did not exist.
"""
filename = self._pair_data_filename(self._datadir, pair, timeframe)
if filename.exists():
filename.unlink()
return True
return False
@abstractmethod
def ohlcv_append(self, pair: str, timeframe: str, data: DataFrame) -> None:
@@ -123,13 +135,17 @@ class IDataHandler(ABC):
:return: List of trades
"""
@abstractmethod
def trades_purge(self, pair: str) -> bool:
"""
Remove data for this pair
:param pair: Delete data for this pair.
:return: True when deleted, false if file did not exist.
"""
filename = self._pair_trades_filename(self._datadir, pair)
if filename.exists():
filename.unlink()
return True
return False
def trades_load(self, pair: str, timerange: Optional[TimeRange] = None) -> TradeList:
"""
@@ -141,6 +157,18 @@ class IDataHandler(ABC):
"""
return trades_remove_duplicates(self._trades_load(pair, timerange=timerange))
@classmethod
def _pair_data_filename(cls, datadir: Path, pair: str, timeframe: str) -> Path:
pair_s = misc.pair_to_filename(pair)
filename = datadir.joinpath(f'{pair_s}-{timeframe}.{cls._get_file_extension()}')
return filename
@classmethod
def _pair_trades_filename(cls, datadir: Path, pair: str) -> Path:
pair_s = misc.pair_to_filename(pair)
filename = datadir.joinpath(f'{pair_s}-trades.{cls._get_file_extension()}')
return filename
def ohlcv_load(self, pair, timeframe: str,
timerange: Optional[TimeRange] = None,
fill_missing: bool = True,

View File

@@ -174,34 +174,10 @@ class JsonDataHandler(IDataHandler):
pass
return tradesdata
def trades_purge(self, pair: str) -> bool:
"""
Remove data for this pair
:param pair: Delete data for this pair.
:return: True when deleted, false if file did not exist.
"""
filename = self._pair_trades_filename(self._datadir, pair)
if filename.exists():
filename.unlink()
return True
return False
@classmethod
def _pair_data_filename(cls, datadir: Path, pair: str, timeframe: str) -> Path:
pair_s = misc.pair_to_filename(pair)
filename = datadir.joinpath(f'{pair_s}-{timeframe}.{cls._get_file_extension()}')
return filename
@classmethod
def _get_file_extension(cls):
return "json.gz" if cls._use_zip else "json"
@classmethod
def _pair_trades_filename(cls, datadir: Path, pair: str) -> Path:
pair_s = misc.pair_to_filename(pair)
filename = datadir.joinpath(f'{pair_s}-trades.{cls._get_file_extension()}')
return filename
class JsonGzDataHandler(JsonDataHandler):

View File

@@ -1,5 +1,6 @@
# flake8: noqa: F401
from freqtrade.enums.backteststate import BacktestState
from freqtrade.enums.ordertypevalue import OrderTypeValues
from freqtrade.enums.rpcmessagetype import RPCMessageType
from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode
from freqtrade.enums.selltype import SellType

View File

@@ -0,0 +1,6 @@
from enum import Enum
class OrderTypeValues(str, Enum):
limit = 'limit'
market = 'market'

View File

@@ -5,6 +5,7 @@ from freqtrade.exchange.exchange import Exchange
# isort: on
from freqtrade.exchange.bibox import Bibox
from freqtrade.exchange.binance import Binance
from freqtrade.exchange.bitpanda import Bitpanda
from freqtrade.exchange.bittrex import Bittrex
from freqtrade.exchange.bybit import Bybit
from freqtrade.exchange.coinbasepro import Coinbasepro

View File

@@ -0,0 +1,37 @@
""" Bitpanda exchange subclass """
import logging
from datetime import datetime, timezone
from typing import Dict, List, Optional
from freqtrade.exchange import Exchange
logger = logging.getLogger(__name__)
class Bitpanda(Exchange):
"""
Bitpanda exchange class. Contains adjustments needed for Freqtrade to work
with this exchange.
"""
def get_trades_for_order(self, order_id: str, pair: str, since: datetime,
params: Optional[Dict] = None) -> List:
"""
Fetch Orders using the "fetch_my_trades" endpoint and filter them by order-id.
The "since" argument passed in is coming from the database and is in UTC,
as timezone-native datetime object.
From the python documentation:
> Naive datetime instances are assumed to represent local time
Therefore, calling "since.timestamp()" will get the UTC timestamp, after applying the
transformation from local timezone to UTC.
This works for timezones UTC+ since then the result will contain trades from a few hours
instead of from the last 5 seconds, however fails for UTC- timezones,
since we're then asking for trades with a "since" argument in the future.
:param order_id order_id: Order-id as given when creating the order
:param pair: Pair the order is for
:param since: datetime object of the order creation time. Assumes object is in UTC.
"""
params = {'to': int(datetime.now(timezone.utc).timestamp() * 1000)}
return super().get_trades_for_order(order_id, pair, since, params)

View File

@@ -685,16 +685,20 @@ class Exchange:
if not self.exchange_has('fetchL2OrderBook'):
return True
ob = self.fetch_l2_order_book(pair, 1)
if side == 'buy':
price = ob['asks'][0][0]
logger.debug(f"{pair} checking dry buy-order: price={price}, limit={limit}")
if limit >= price:
return True
else:
price = ob['bids'][0][0]
logger.debug(f"{pair} checking dry sell-order: price={price}, limit={limit}")
if limit <= price:
return True
try:
if side == 'buy':
price = ob['asks'][0][0]
logger.debug(f"{pair} checking dry buy-order: price={price}, limit={limit}")
if limit >= price:
return True
else:
price = ob['bids'][0][0]
logger.debug(f"{pair} checking dry sell-order: price={price}, limit={limit}")
if limit <= price:
return True
except IndexError:
# Ignore empty orderbooks when filling - can be filled with the next iteration.
pass
return False
def check_dry_limit_order_filled(self, order: Dict[str, Any]) -> Dict[str, Any]:
@@ -1087,7 +1091,8 @@ class Exchange:
# Fee handling
@retrier
def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> List:
def get_trades_for_order(self, order_id: str, pair: str, since: datetime,
params: Optional[Dict] = None) -> List:
"""
Fetch Orders using the "fetch_my_trades" endpoint and filter them by order-id.
The "since" argument passed in is coming from the database and is in UTC,
@@ -1111,8 +1116,10 @@ class Exchange:
try:
# Allow 5s offset to catch slight time offsets (discovered in #1185)
# since needs to be int in milliseconds
_params = params if params else {}
my_trades = self._api.fetch_my_trades(
pair, int((since.replace(tzinfo=timezone.utc).timestamp() - 5) * 1000))
pair, int((since.replace(tzinfo=timezone.utc).timestamp() - 5) * 1000),
params=_params)
matched_trades = [trade for trade in my_trades if trade['order'] == order_id]
self._log_exchange_response('get_trades_for_order', matched_trades)
@@ -1190,9 +1197,11 @@ class Exchange:
tick = self.fetch_ticker(comb)
fee_to_quote_rate = safe_value_fallback2(tick, tick, 'last', 'ask')
return round((order['fee']['cost'] * fee_to_quote_rate) / order['cost'], 8)
except ExchangeError:
return None
fee_to_quote_rate = self._config['exchange'].get('unknown_fee_rate', None)
if not fee_to_quote_rate:
return None
return round((order['fee']['cost'] * fee_to_quote_rate) / order['cost'], 8)
def extract_cost_curr_rate(self, order: Dict) -> Tuple[float, str, Optional[float]]:
"""
@@ -1263,7 +1272,7 @@ class Exchange:
results = await asyncio.gather(*input_coro, return_exceptions=True)
for res in results:
if isinstance(res, Exception):
logger.warning("Async code raised an exception: %s", res.__class__.__name__)
logger.warning(f"Async code raised an exception: {repr(res)}")
if raise_:
raise
continue
@@ -1294,7 +1303,7 @@ class Exchange:
cached_pairs = []
# Gather coroutines to run
for pair, timeframe in set(pair_list):
if ((pair, timeframe) not in self._klines
if ((pair, timeframe) not in self._klines or not cache
or self._now_is_time_to_refresh(pair, timeframe)):
if not since_ms and self.required_candle_call_count > 1:
# Multiple calls for one pair - to get more history
@@ -1317,27 +1326,30 @@ class Exchange:
)
cached_pairs.append((pair, timeframe))
results = asyncio.get_event_loop().run_until_complete(
asyncio.gather(*input_coroutines, return_exceptions=True))
results_df = {}
# handle caching
for res in results:
if isinstance(res, Exception):
logger.warning("Async code raised an exception: %s", res.__class__.__name__)
continue
# Deconstruct tuple (has 3 elements)
pair, timeframe, ticks = res
# keeping last candle time as last refreshed time of the pair
if ticks:
self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000
# keeping parsed dataframe in cache
ohlcv_df = ohlcv_to_dataframe(
ticks, timeframe, pair=pair, fill_missing=True,
drop_incomplete=self._ohlcv_partial_candle)
results_df[(pair, timeframe)] = ohlcv_df
if cache:
self._klines[(pair, timeframe)] = ohlcv_df
# Chunk requests into batches of 100 to avoid overwelming ccxt Throttling
for input_coro in chunks(input_coroutines, 100):
results = asyncio.get_event_loop().run_until_complete(
asyncio.gather(*input_coro, return_exceptions=True))
# handle caching
for res in results:
if isinstance(res, Exception):
logger.warning(f"Async code raised an exception: {repr(res)}")
continue
# Deconstruct tuple (has 3 elements)
pair, timeframe, ticks = res
# keeping last candle time as last refreshed time of the pair
if ticks:
self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000
# keeping parsed dataframe in cache
ohlcv_df = ohlcv_to_dataframe(
ticks, timeframe, pair=pair, fill_missing=True,
drop_incomplete=self._ohlcv_partial_candle)
results_df[(pair, timeframe)] = ohlcv_df
if cache:
self._klines[(pair, timeframe)] = ohlcv_df
# Return cached klines
for pair, timeframe in cached_pairs:
results_df[(pair, timeframe)] = self.klines((pair, timeframe), copy=False)

View File

@@ -278,7 +278,8 @@ class FreqtradeBot(LoggingMixin):
if order:
logger.info(f"Updating sell-fee on trade {trade} for order {order.order_id}.")
self.update_trade_state(trade, order.order_id,
stoploss_order=order.ft_order_side == 'stoploss')
stoploss_order=order.ft_order_side == 'stoploss',
send_msg=False)
trades: List[Trade] = Trade.get_open_trades_without_assigned_fees()
for trade in trades:
@@ -286,7 +287,7 @@ class FreqtradeBot(LoggingMixin):
order = trade.select_order('buy', False)
if order:
logger.info(f"Updating buy-fee on trade {trade} for order {order.order_id}.")
self.update_trade_state(trade, order.order_id)
self.update_trade_state(trade, order.order_id, send_msg=False)
def handle_insufficient_funds(self, trade: Trade):
"""
@@ -308,7 +309,7 @@ class FreqtradeBot(LoggingMixin):
order = trade.select_order('buy', False)
if order:
logger.info(f"Updating buy-fee on trade {trade} for order {order.order_id}.")
self.update_trade_state(trade, order.order_id)
self.update_trade_state(trade, order.order_id, send_msg=False)
def refind_lost_order(self, trade):
"""
@@ -466,8 +467,8 @@ class FreqtradeBot(LoggingMixin):
logger.info(f"Bids to asks delta for {pair} does not satisfy condition.")
return False
def execute_entry(self, pair: str, stake_amount: float, price: Optional[float] = None,
forcebuy: bool = False, buy_tag: Optional[str] = None) -> bool:
def execute_entry(self, pair: str, stake_amount: float, price: Optional[float] = None, *,
ordertype: Optional[str] = None, buy_tag: Optional[str] = None) -> bool:
"""
Executes a limit buy for the given pair
:param pair: pair for which we want to create a LIMIT_BUY
@@ -510,10 +511,7 @@ class FreqtradeBot(LoggingMixin):
f"{stake_amount} ...")
amount = stake_amount / enter_limit_requested
order_type = self.strategy.order_types['buy']
if forcebuy:
# Forcebuy can define a different ordertype
order_type = self.strategy.order_types.get('forcebuy', order_type)
order_type = ordertype or self.strategy.order_types['buy']
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
pair=pair, order_type=order_type, amount=amount, rate=enter_limit_requested,
@@ -581,10 +579,6 @@ class FreqtradeBot(LoggingMixin):
)
trade.orders.append(order_obj)
# Update fees if order is closed
if order_status == 'closed':
self.update_trade_state(trade, order_id, order)
Trade.query.session.add(trade)
Trade.commit()
@@ -593,19 +587,25 @@ class FreqtradeBot(LoggingMixin):
self._notify_enter(trade, order_type)
# Update fees if order is closed
if order_status == 'closed':
self.update_trade_state(trade, order_id, order)
return True
def _notify_enter(self, trade: Trade, order_type: str) -> None:
def _notify_enter(self, trade: Trade, order_type: Optional[str] = None,
fill: bool = False) -> None:
"""
Sends rpc notification when a buy occurred.
"""
msg = {
'trade_id': trade.id,
'type': RPCMessageType.BUY,
'type': RPCMessageType.BUY_FILL if fill else RPCMessageType.BUY,
'buy_tag': trade.buy_tag,
'exchange': self.exchange.name.capitalize(),
'pair': trade.pair,
'limit': trade.open_rate,
'limit': trade.open_rate, # Deprecated (?)
'open_rate': trade.open_rate,
'order_type': order_type,
'stake_amount': trade.stake_amount,
'stake_currency': self.config['stake_currency'],
@@ -644,22 +644,6 @@ class FreqtradeBot(LoggingMixin):
# Send the message
self.rpc.send_msg(msg)
def _notify_enter_fill(self, trade: Trade) -> None:
msg = {
'trade_id': trade.id,
'type': RPCMessageType.BUY_FILL,
'buy_tag': trade.buy_tag,
'exchange': self.exchange.name.capitalize(),
'pair': trade.pair,
'open_rate': trade.open_rate,
'stake_amount': trade.stake_amount,
'stake_currency': self.config['stake_currency'],
'fiat_currency': self.config.get('fiat_display_currency', None),
'amount': trade.amount,
'open_date': trade.open_date,
}
self.rpc.send_msg(msg)
#
# SELL / exit positions / close trades logic and methods
#
@@ -682,7 +666,7 @@ class FreqtradeBot(LoggingMixin):
trades_closed += 1
except DependencyException as exception:
logger.warning('Unable to sell trade %s: %s', trade.pair, exception)
logger.warning(f'Unable to sell trade {trade.pair}: {exception}')
# Updating wallets if any trade occurred
if trades_closed:
@@ -868,7 +852,7 @@ class FreqtradeBot(LoggingMixin):
logger.info(
f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}. '
f'Tag: {exit_tag if exit_tag is not None else "None"}')
self.execute_trade_exit(trade, exit_rate, should_sell, exit_tag)
self.execute_trade_exit(trade, exit_rate, should_sell, exit_tag=exit_tag)
return True
return False
@@ -926,8 +910,12 @@ class FreqtradeBot(LoggingMixin):
if max_timeouts > 0 and canceled_count >= max_timeouts:
logger.warning(f'Emergencyselling trade {trade}, as the sell order '
f'timed out {max_timeouts} times.')
self.execute_trade_exit(trade, order.get('price'), sell_reason=SellCheckTuple(
sell_type=SellType.EMERGENCY_SELL))
try:
self.execute_trade_exit(
trade, order.get('price'),
sell_reason=SellCheckTuple(sell_type=SellType.EMERGENCY_SELL))
except DependencyException as exception:
logger.warning(f'Unable to emergency sell trade {trade.pair}: {exception}')
def cancel_all_open_orders(self) -> None:
"""
@@ -1081,7 +1069,10 @@ class FreqtradeBot(LoggingMixin):
trade: Trade,
limit: float,
sell_reason: SellCheckTuple,
exit_tag: Optional[str] = None) -> bool:
*,
exit_tag: Optional[str] = None,
ordertype: Optional[str] = None,
) -> bool:
"""
Executes a trade exit for the given trade and limit
:param trade: Trade instance
@@ -1119,14 +1110,10 @@ class FreqtradeBot(LoggingMixin):
except InvalidOrderException:
logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}")
order_type = self.strategy.order_types[sell_type]
order_type = ordertype or self.strategy.order_types[sell_type]
if sell_reason.sell_type == SellType.EMERGENCY_SELL:
# Emergency sells (default to market!)
order_type = self.strategy.order_types.get("emergencysell", "market")
if sell_reason.sell_type == SellType.FORCE_SELL:
# Force sells (default to the sell_type defined in the strategy,
# but we allow this value to be changed)
order_type = self.strategy.order_types.get("forcesell", order_type)
amount = self._safe_exit_amount(trade.pair, trade.amount)
time_in_force = self.strategy.order_time_in_force['sell']
@@ -1158,16 +1145,16 @@ class FreqtradeBot(LoggingMixin):
trade.sell_order_status = ''
trade.close_rate_requested = limit
trade.sell_reason = exit_tag or sell_reason.sell_reason
# In case of market sell orders the order can be closed immediately
if order.get('status', 'unknown') in ('closed', 'expired'):
self.update_trade_state(trade, trade.open_order_id, order)
Trade.commit()
# Lock pair for one candle to prevent immediate re-buys
self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc),
reason='Auto lock')
self._notify_exit(trade, order_type)
# In case of market sell orders the order can be closed immediately
if order.get('status', 'unknown') in ('closed', 'expired'):
self.update_trade_state(trade, trade.open_order_id, order)
Trade.commit()
return True
@@ -1264,13 +1251,14 @@ class FreqtradeBot(LoggingMixin):
#
def update_trade_state(self, trade: Trade, order_id: str, action_order: Dict[str, Any] = None,
stoploss_order: bool = False) -> bool:
stoploss_order: bool = False, send_msg: bool = True) -> bool:
"""
Checks trades with open orders and updates the amount if necessary
Handles closing both buy and sell orders.
:param trade: Trade object of the trade we're analyzing
:param order_id: Order-id of the order we're analyzing
:param action_order: Already acquired order object
:param send_msg: Send notification - should always be True except in "recovery" methods
:return: True if order has been cancelled without being filled partially, False otherwise
"""
if not order_id:
@@ -1310,13 +1298,13 @@ class FreqtradeBot(LoggingMixin):
# Updating wallets when order is closed
if not trade.is_open:
if not stoploss_order and not trade.open_order_id:
if send_msg and not stoploss_order and not trade.open_order_id:
self._notify_exit(trade, '', True)
self.handle_protections(trade.pair)
self.wallets.update()
elif not trade.open_order_id:
elif send_msg and not trade.open_order_id:
# Buy fill
self._notify_enter_fill(trade)
self._notify_enter(trade, fill=True)
return False

View File

@@ -342,10 +342,7 @@ class Backtesting:
# use Open rate if open_rate > calculated sell rate
return sell_row[OPEN_IDX]
# Use the maximum between close_rate and low as we
# cannot sell outside of a candle.
# Applies when a new ROI setting comes in place and the whole candle is above that.
return min(max(close_rate, sell_row[LOW_IDX]), sell_row[HIGH_IDX])
return close_rate
else:
# This should not be reached...
@@ -366,6 +363,17 @@ class Backtesting:
trade_dur = int((trade.close_date_utc - trade.open_date_utc).total_seconds() // 60)
closerate = self._get_close_rate(sell_row, trade, sell, trade_dur)
# call the custom exit price,with default value as previous closerate
current_profit = trade.calc_profit_ratio(closerate)
if sell.sell_type in (SellType.SELL_SIGNAL, SellType.CUSTOM_SELL):
# Custom exit pricing only for sell-signals
closerate = strategy_safe_wrapper(self.strategy.custom_exit_price,
default_retval=closerate)(
pair=trade.pair, trade=trade,
current_time=sell_row[DATE_IDX],
proposed_rate=closerate, current_profit=current_profit)
# Use the maximum between close_rate and low as we cannot sell outside of a candle.
closerate = min(max(closerate, sell_row[LOW_IDX]), sell_row[HIGH_IDX])
# Confirm trade exit:
time_in_force = self.strategy.order_time_in_force['sell']
@@ -424,13 +432,21 @@ class Backtesting:
stake_amount = self.wallets.get_trade_stake_amount(pair, None)
except DependencyException:
return None
# let's call the custom entry price, using the open price as default price
propose_rate = strategy_safe_wrapper(self.strategy.custom_entry_price,
default_retval=row[OPEN_IDX])(
pair=pair, current_time=row[DATE_IDX].to_pydatetime(),
proposed_rate=row[OPEN_IDX]) # default value is the open rate
min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, row[OPEN_IDX], -0.05) or 0
# Move rate to within the candle's low/high rate
propose_rate = min(max(propose_rate, row[LOW_IDX]), row[HIGH_IDX])
min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, propose_rate, -0.05) or 0
max_stake_amount = self.wallets.get_available_stake_amount()
stake_amount = strategy_safe_wrapper(self.strategy.custom_stake_amount,
default_retval=stake_amount)(
pair=pair, current_time=row[DATE_IDX].to_pydatetime(), current_rate=row[OPEN_IDX],
pair=pair, current_time=row[DATE_IDX].to_pydatetime(), current_rate=propose_rate,
proposed_stake=stake_amount, min_stake=min_stake_amount, max_stake=max_stake_amount)
stake_amount = self.wallets.validate_stake_amount(pair, stake_amount, min_stake_amount)
@@ -441,7 +457,7 @@ class Backtesting:
time_in_force = self.strategy.order_time_in_force['sell']
# Confirm trade entry:
if not strategy_safe_wrapper(self.strategy.confirm_trade_entry, default_retval=True)(
pair=pair, order_type=order_type, amount=stake_amount, rate=row[OPEN_IDX],
pair=pair, order_type=order_type, amount=stake_amount, rate=propose_rate,
time_in_force=time_in_force, current_time=row[DATE_IDX].to_pydatetime()):
return None
@@ -450,10 +466,10 @@ class Backtesting:
has_buy_tag = len(row) >= BUY_TAG_IDX + 1
trade = LocalTrade(
pair=pair,
open_rate=row[OPEN_IDX],
open_rate=propose_rate,
open_date=row[DATE_IDX].to_pydatetime(),
stake_amount=stake_amount,
amount=round(stake_amount / row[OPEN_IDX], 8),
amount=round(stake_amount / propose_rate, 8),
fee_open=self.fee,
fee_close=self.fee,
is_open=True,

View File

@@ -68,14 +68,14 @@ class PerformanceFilter(IPairList):
# - then pair name alphametically
sorted_df = list_df.merge(performance, on='pair', how='left')\
.fillna(0).sort_values(by=['count', 'pair'], ascending=True)\
.sort_values(by=['profit'], ascending=False)
.sort_values(by=['profit_ratio'], ascending=False)
if self._min_profit is not None:
removed = sorted_df[sorted_df['profit'] < self._min_profit]
removed = sorted_df[sorted_df['profit_ratio'] < self._min_profit]
for _, row in removed.iterrows():
self.log_once(
f"Removing pair {row['pair']} since {row['profit']} is "
f"Removing pair {row['pair']} since {row['profit_ratio']} is "
f"below {self._min_profit}", logger.info)
sorted_df = sorted_df[sorted_df['profit'] >= self._min_profit]
sorted_df = sorted_df[sorted_df['profit_ratio'] >= self._min_profit]
pairlist = sorted_df['pair'].tolist()

View File

@@ -5,6 +5,7 @@ import logging
import random
from typing import Any, Dict, List
from freqtrade.enums import RunMode
from freqtrade.plugins.pairlist.IPairList import IPairList
@@ -18,7 +19,15 @@ class ShuffleFilter(IPairList):
pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
self._seed = pairlistconfig.get('seed')
# Apply seed in backtesting mode to get comparable results,
# but not in live modes to get a non-repeating order of pairs during live modes.
if config.get('runmode') in (RunMode.LIVE, RunMode.DRY_RUN):
self._seed = None
logger.info("Live mode detected, not applying seed.")
else:
self._seed = pairlistconfig.get('seed')
logger.info(f"Backtesting mode detected, applying seed value: {self._seed}")
self._random = random.Random(self._seed)
@property

View File

@@ -8,7 +8,7 @@ from typing import Any, Dict, List, Optional
import arrow
import numpy as np
from cachetools.ttl import TTLCache
from cachetools import TTLCache
from pandas import DataFrame
from freqtrade.exceptions import OperationalException

View File

@@ -8,7 +8,7 @@ from functools import partial
from typing import Any, Dict, List
import arrow
from cachetools.ttl import TTLCache
from cachetools import TTLCache
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes

View File

@@ -6,7 +6,7 @@ from copy import deepcopy
from typing import Any, Dict, List, Optional
import arrow
from cachetools.ttl import TTLCache
from cachetools import TTLCache
from pandas import DataFrame
from freqtrade.exceptions import OperationalException

View File

@@ -2,13 +2,14 @@
PairList manager class
"""
import logging
from copy import deepcopy
from functools import partial
from typing import Dict, List
from cachetools import TTLCache, cached
from freqtrade.constants import ListPairsWithTimeframes
from freqtrade.exceptions import OperationalException
from freqtrade.mixins import LoggingMixin
from freqtrade.plugins.pairlist.IPairList import IPairList
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.resolvers import PairListResolver
@@ -17,7 +18,7 @@ from freqtrade.resolvers import PairListResolver
logger = logging.getLogger(__name__)
class PairListManager():
class PairListManager(LoggingMixin):
def __init__(self, exchange, config: dict) -> None:
self._exchange = exchange
@@ -41,6 +42,9 @@ class PairListManager():
if not self._pairlist_handlers:
raise OperationalException("No Pairlist Handlers defined")
refresh_period = config.get('pairlist_refresh_period', 3600)
LoggingMixin.__init__(self, logger, refresh_period)
@property
def whitelist(self) -> List[str]:
"""The current whitelist"""
@@ -108,9 +112,10 @@ class PairListManager():
except ValueError as err:
logger.error(f"Pair blacklist contains an invalid Wildcard: {err}")
return []
for pair in deepcopy(pairlist):
log_once = partial(self.log_once, logmethod=logmethod)
for pair in pairlist.copy():
if pair in blacklist:
logmethod(f"Pair {pair} in your blacklist. Removing it from whitelist...")
log_once(f"Pair {pair} in your blacklist. Removing it from whitelist...")
pairlist.remove(pair)
return pairlist

View File

@@ -33,6 +33,9 @@ async def api_start_backtest(bt_settings: BacktestRequest, background_tasks: Bac
if settings[setting] is not None:
btconfig[setting] = settings[setting]
# Force dry-run for backtesting
btconfig['dry_run'] = True
# Start backtesting
# Initialize backtesting object
def run_backtest():

View File

@@ -4,6 +4,7 @@ from typing import Any, Dict, List, Optional, Union
from pydantic import BaseModel
from freqtrade.constants import DATETIME_PRINT_FORMAT
from freqtrade.enums import OrderTypeValues
class Ping(BaseModel):
@@ -125,25 +126,26 @@ class Daily(BaseModel):
class UnfilledTimeout(BaseModel):
buy: int
sell: int
unit: str
buy: Optional[int]
sell: Optional[int]
unit: Optional[str]
exit_timeout_count: Optional[int]
class OrderTypes(BaseModel):
buy: str
sell: str
emergencysell: Optional[str]
forcesell: Optional[str]
forcebuy: Optional[str]
stoploss: str
buy: OrderTypeValues
sell: OrderTypeValues
emergencysell: Optional[OrderTypeValues]
forcesell: Optional[OrderTypeValues]
forcebuy: Optional[OrderTypeValues]
stoploss: OrderTypeValues
stoploss_on_exchange: bool
stoploss_on_exchange_interval: Optional[int]
class ShowConfig(BaseModel):
version: str
strategy_version: Optional[str]
api_version: float
dry_run: bool
stake_currency: str
@@ -158,7 +160,7 @@ class ShowConfig(BaseModel):
trailing_stop_positive_offset: Optional[float]
trailing_only_offset_is_reached: Optional[bool]
unfilledtimeout: UnfilledTimeout
order_types: OrderTypes
order_types: Optional[OrderTypes]
use_custom_stoploss: Optional[bool]
timeframe: Optional[str]
timeframe_ms: int
@@ -274,10 +276,12 @@ class Logs(BaseModel):
class ForceBuyPayload(BaseModel):
pair: str
price: Optional[float]
ordertype: Optional[OrderTypeValues]
class ForceSellPayload(BaseModel):
tradeid: str
ordertype: Optional[OrderTypeValues]
class BlacklistPayload(BaseModel):

View File

@@ -3,7 +3,7 @@ from copy import deepcopy
from pathlib import Path
from typing import List, Optional
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, Query
from fastapi.exceptions import HTTPException
from freqtrade import __version__
@@ -29,7 +29,9 @@ logger = logging.getLogger(__name__)
# API version
# Pre-1.1, no version was provided
# Version increments should happen in "small" steps (1.1, 1.12, ...) unless big changes happen.
API_VERSION = 1.1
# 1.11: forcebuy and forcesell accept ordertype
# 1.12: add blacklist delete endpoint
API_VERSION = 1.12
# Public API, requires no auth.
router_public = APIRouter()
@@ -120,16 +122,19 @@ def edge(rpc: RPC = Depends(get_rpc)):
@router.get('/show_config', response_model=ShowConfig, tags=['info'])
def show_config(rpc: Optional[RPC] = Depends(get_rpc_optional), config=Depends(get_config)):
state = ''
strategy_version = None
if rpc:
state = rpc._freqtrade.state
resp = RPC._rpc_show_config(config, state)
strategy_version = rpc._freqtrade.strategy.version()
resp = RPC._rpc_show_config(config, state, strategy_version)
resp['api_version'] = API_VERSION
return resp
@router.post('/forcebuy', response_model=ForceBuyResponse, tags=['trading'])
def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)):
trade = rpc._rpc_forcebuy(payload.pair, payload.price)
ordertype = payload.ordertype.value if payload.ordertype else None
trade = rpc._rpc_forcebuy(payload.pair, payload.price, ordertype)
if trade:
return ForceBuyResponse.parse_obj(trade.to_json())
@@ -139,7 +144,8 @@ def forcebuy(payload: ForceBuyPayload, rpc: RPC = Depends(get_rpc)):
@router.post('/forcesell', response_model=ResultMsg, tags=['trading'])
def forcesell(payload: ForceSellPayload, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_forcesell(payload.tradeid)
ordertype = payload.ordertype.value if payload.ordertype else None
return rpc._rpc_forcesell(payload.tradeid, ordertype)
@router.get('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist'])
@@ -152,6 +158,13 @@ def blacklist_post(payload: BlacklistPayload, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_blacklist(payload.blacklist)
@router.delete('/blacklist', response_model=BlacklistResponse, tags=['info', 'pairlist'])
def blacklist_delete(pairs_to_delete: List[str] = Query([]), rpc: RPC = Depends(get_rpc)):
"""Provide a list of pairs to delete from the blacklist"""
return rpc._rpc_blacklist_delete(pairs_to_delete)
@router.get('/whitelist', response_model=WhitelistResponse, tags=['info', 'pairlist'])
def whitelist(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_whitelist()

View File

@@ -7,7 +7,7 @@ import datetime
import logging
from typing import Dict, List
from cachetools.ttl import TTLCache
from cachetools import TTLCache
from pycoingecko import CoinGeckoAPI
from requests.exceptions import RequestException

View File

@@ -98,7 +98,8 @@ class RPC:
self._fiat_converter = CryptoToFiatConverter()
@staticmethod
def _rpc_show_config(config, botstate: Union[State, str]) -> Dict[str, Any]:
def _rpc_show_config(config, botstate: Union[State, str],
strategy_version: Optional[str] = None) -> Dict[str, Any]:
"""
Return a dict of config options.
Explicitly does NOT return the full config to avoid leakage of sensitive
@@ -106,6 +107,7 @@ class RPC:
"""
val = {
'version': __version__,
'strategy_version': strategy_version,
'dry_run': config['dry_run'],
'stake_currency': config['stake_currency'],
'stake_currency_decimals': decimals_per_coin(config['stake_currency']),
@@ -640,7 +642,7 @@ class RPC:
return {'status': 'No more buy will occur from now. Run /reload_config to reset.'}
def _rpc_forcesell(self, trade_id: str) -> Dict[str, str]:
def _rpc_forcesell(self, trade_id: str, ordertype: Optional[str] = None) -> Dict[str, str]:
"""
Handler for forcesell <id>.
Sells the given trade at current price
@@ -664,7 +666,11 @@ class RPC:
current_rate = self._freqtrade.exchange.get_rate(
trade.pair, refresh=False, side="sell")
sell_reason = SellCheckTuple(sell_type=SellType.FORCE_SELL)
self._freqtrade.execute_trade_exit(trade, current_rate, sell_reason)
order_type = ordertype or self._freqtrade.strategy.order_types.get(
"forcesell", self._freqtrade.strategy.order_types["sell"])
self._freqtrade.execute_trade_exit(
trade, current_rate, sell_reason, ordertype=order_type)
# ---- EOF def _exec_forcesell ----
if self._freqtrade.state != State.RUNNING:
@@ -692,7 +698,8 @@ class RPC:
self._freqtrade.wallets.update()
return {'result': f'Created sell order for trade {trade_id}.'}
def _rpc_forcebuy(self, pair: str, price: Optional[float]) -> Optional[Trade]:
def _rpc_forcebuy(self, pair: str, price: Optional[float],
order_type: Optional[str] = None) -> Optional[Trade]:
"""
Handler for forcebuy <asset> <price>
Buys a pair trade at the given or current price
@@ -720,7 +727,10 @@ class RPC:
stakeamount = self._freqtrade.wallets.get_trade_stake_amount(pair)
# execute buy
if self._freqtrade.execute_entry(pair, stakeamount, price, forcebuy=True):
if not order_type:
order_type = self._freqtrade.strategy.order_types.get(
'forcebuy', self._freqtrade.strategy.order_types['buy'])
if self._freqtrade.execute_entry(pair, stakeamount, price, ordertype=order_type):
Trade.commit()
trade = Trade.get_trades([Trade.is_open.is_(True), Trade.pair == pair]).first()
return trade
@@ -850,6 +860,20 @@ class RPC:
}
return res
def _rpc_blacklist_delete(self, delete: List[str]) -> Dict:
""" Removes pairs from currently active blacklist """
errors = {}
for pair in delete:
if pair in self._freqtrade.pairlists.blacklist:
self._freqtrade.pairlists.blacklist.remove(pair)
else:
errors[pair] = {
'error_msg': f"Pair {pair} is not in the current blacklist."
}
resp = self._rpc_blacklist()
resp['errors'] = errors
return resp
def _rpc_blacklist(self, add: List[str] = None) -> Dict:
""" Returns the currently active blacklist"""
errors = {}

View File

@@ -60,6 +60,10 @@ class RPCManager:
}
"""
logger.info('Sending rpc message: %s', msg)
if 'pair' in msg:
msg.update({
'base_currency': self._rpc._freqtrade.exchange.get_pair_base_currency(msg['pair'])
})
for mod in self.registered_modules:
logger.debug('Forwarding message to rpc.%s', mod.name)
try:

View File

@@ -111,8 +111,9 @@ class Telegram(RPCHandler):
r'/daily$', r'/daily \d+$', r'/profit$', r'/profit \d+',
r'/stats$', r'/count$', r'/locks$', r'/balance$',
r'/stopbuy$', r'/reload_config$', r'/show_config$',
r'/logs$', r'/whitelist$', r'/blacklist$', r'/edge$',
r'/forcebuy$', r'/help$', r'/version$']
r'/logs$', r'/whitelist$', r'/blacklist$', r'/bl_delete$',
r'/weekly$', r'/weekly \d+$', r'/monthly$', r'/monthly \d+$',
r'/forcebuy$', r'/edge$', r'/help$', r'/version$']
# Create keys for generation
valid_keys_print = [k.replace('$', '') for k in valid_keys]
@@ -169,6 +170,7 @@ class Telegram(RPCHandler):
CommandHandler('stopbuy', self._stopbuy),
CommandHandler('whitelist', self._whitelist),
CommandHandler('blacklist', self._blacklist),
CommandHandler(['blacklist_delete', 'bl_delete'], self._blacklist_delete),
CommandHandler('logs', self._logs),
CommandHandler('edge', self._edge),
CommandHandler('help', self._help),
@@ -1161,22 +1163,28 @@ class Telegram(RPCHandler):
Handler for /blacklist
Shows the currently active blacklist
"""
try:
self.send_blacklist_msg(self._rpc._rpc_blacklist(context.args))
blacklist = self._rpc._rpc_blacklist(context.args)
errmsgs = []
for pair, error in blacklist['errors'].items():
errmsgs.append(f"Error adding `{pair}` to blacklist: `{error['error_msg']}`")
if errmsgs:
self._send_msg('\n'.join(errmsgs))
def send_blacklist_msg(self, blacklist: Dict):
errmsgs = []
for pair, error in blacklist['errors'].items():
errmsgs.append(f"Error adding `{pair}` to blacklist: `{error['error_msg']}`")
if errmsgs:
self._send_msg('\n'.join(errmsgs))
message = f"Blacklist contains {blacklist['length']} pairs\n"
message += f"`{', '.join(blacklist['blacklist'])}`"
message = f"Blacklist contains {blacklist['length']} pairs\n"
message += f"`{', '.join(blacklist['blacklist'])}`"
logger.debug(message)
self._send_msg(message)
except RPCException as e:
self._send_msg(str(e))
logger.debug(message)
self._send_msg(message)
@authorized_only
def _blacklist_delete(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /bl_delete
Deletes pair(s) from current blacklist
"""
self.send_blacklist_msg(self._rpc._rpc_blacklist_delete(context.args or []))
@authorized_only
def _logs(self, update: Update, context: CallbackContext) -> None:
@@ -1257,6 +1265,8 @@ class Telegram(RPCHandler):
"*/whitelist:* `Show current whitelist` \n"
"*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs "
"to the blacklist.` \n"
"*/blacklist_delete [pairs]| /bl_delete [pairs]:* "
"`Delete pair / pattern from blacklist. Will reset on reload_conf.` \n"
"*/reload_config:* `Reload configuration file` \n"
"*/unlock <pair|id>:* `Unlock this Pair (or this lock id if it's numeric)`\n"
@@ -1304,7 +1314,12 @@ class Telegram(RPCHandler):
:param update: message update
:return: None
"""
self._send_msg('*Version:* `{}`'.format(__version__))
strategy_version = self._rpc._freqtrade.strategy.version()
version_string = f'*Version:* `{__version__}`'
if strategy_version is not None:
version_string += f', *Strategy version: * `{strategy_version}`'
self._send_msg(version_string)
@authorized_only
def _show_config(self, update: Update, context: CallbackContext) -> None:

View File

@@ -2,6 +2,7 @@
This module manages webhook communication
"""
import logging
import time
from typing import Any, Dict
from requests import RequestException, post
@@ -28,12 +29,9 @@ class Webhook(RPCHandler):
super().__init__(rpc, config)
self._url = self._config['webhook']['url']
self._format = self._config['webhook'].get('format', 'form')
if self._format != 'form' and self._format != 'json':
raise NotImplementedError('Unknown webhook format `{}`, possible values are '
'`form` (default) and `json`'.format(self._format))
self._retries = self._config['webhook'].get('retries', 0)
self._retry_delay = self._config['webhook'].get('retry_delay', 0.1)
def cleanup(self) -> None:
"""
@@ -77,13 +75,30 @@ class Webhook(RPCHandler):
def _send_msg(self, payload: dict) -> None:
"""do the actual call to the webhook"""
try:
if self._format == 'form':
post(self._url, data=payload)
elif self._format == 'json':
post(self._url, json=payload)
else:
raise NotImplementedError('Unknown format: {}'.format(self._format))
success = False
attempts = 0
while not success and attempts <= self._retries:
if attempts:
if self._retry_delay:
time.sleep(self._retry_delay)
logger.info("Retrying webhook...")
except RequestException as exc:
logger.warning("Could not call webhook url. Exception: %s", exc)
attempts += 1
try:
if self._format == 'form':
response = post(self._url, data=payload)
elif self._format == 'json':
response = post(self._url, json=payload)
elif self._format == 'raw':
response = post(self._url, data=payload['data'],
headers={'Content-Type': 'text/plain'})
else:
raise NotImplementedError('Unknown format: {}'.format(self._format))
# Throw a RequestException if the post was not successful
response.raise_for_status()
success = True
except RequestException as exc:
logger.warning("Could not call webhook url. Exception: %s", exc)

View File

@@ -394,6 +394,12 @@ class IStrategy(ABC, HyperStrategyMixin):
"""
return []
def version(self) -> Optional[str]:
"""
Returns version of the strategy.
"""
return None
###
# END - Intended to be overridden by strategy
###

View File

@@ -87,6 +87,7 @@ class {{ strategy }}(IStrategy):
'sell': 'gtc'
}
{{ plot_config | indent(4) }}
def informative_pairs(self):
"""
Define additional, informative pair/interval combinations to be cached from the exchange.

View File

@@ -79,7 +79,9 @@
"source": [
"# Load strategy using values set above\n",
"from freqtrade.resolvers import StrategyResolver\n",
"from freqtrade.data.dataprovider import DataProvider\n",
"strategy = StrategyResolver.load_strategy(config)\n",
"strategy.dp = DataProvider(config, None, None)\n",
"\n",
"# Generate buy/sell signals using strategy\n",
"df = strategy.analyze_ticker(candles, {'pair': pair})\n",

View File

@@ -1,18 +1,20 @@
plot_config = {
# Main plot indicators (Moving averages, ...)
'main_plot': {
'tema': {},
'sar': {'color': 'white'},
},
'subplots': {
# Subplots - each dict defines one additional plot
"MACD": {
'macd': {'color': 'blue'},
'macdsignal': {'color': 'orange'},
@property
def plot_config(self):
return {
# Main plot indicators (Moving averages, ...)
'main_plot': {
'tema': {},
'sar': {'color': 'white'},
},
"RSI": {
'rsi': {'color': 'red'},
'subplots': {
# Subplots - each dict defines one additional plot
"MACD": {
'macd': {'color': 'blue'},
'macdsignal': {'color': 'orange'},
},
"RSI": {
'rsi': {'color': 'red'},
}
}
}
}

View File

@@ -260,8 +260,8 @@ class Wallets:
if self._log:
logger.info(
f"Adjusted stake amount for pair {pair} is more than 30% bigger than "
f"the desired stake ({stake_amount} * 1.3 > {max_stake_amount}), "
f"ignoring trade."
f"the desired stake amount of ({stake_amount:.8f} * 1.3 = "
f"{stake_amount * 1.3:.8f}) < {min_stake_amount}), ignoring trade."
)
return 0
stake_amount = min_stake_amount

View File

@@ -85,9 +85,12 @@ class Worker:
# Log state transition
if state != old_state:
self.freqtrade.notify_status(f'{state.name.lower()}')
logger.info(f"Changing state to: {state.name}")
if old_state != State.RELOAD_CONFIG:
self.freqtrade.notify_status(f'{state.name.lower()}')
logger.info(
f"Changing state{f' from {old_state.name}' if old_state else ''} to: {state.name}")
if state == State.RUNNING:
self.freqtrade.startup()
@@ -113,8 +116,12 @@ class Worker:
if self._heartbeat_interval:
now = time.time()
if (now - self._heartbeat_msg) > self._heartbeat_interval:
version = __version__
strategy_version = self.freqtrade.strategy.version()
if (strategy_version is not None):
version += ', strategy_version: ' + strategy_version
logger.info(f"Bot heartbeat. PID={getpid()}, "
f"version='{__version__}', state='{state.name}'")
f"version='{version}', state='{state.name}'")
self._heartbeat_msg = now
return state

View File

@@ -81,8 +81,10 @@ markdown_extensions:
- pymdownx.snippets:
base_path: docs
check_paths: true
- pymdownx.tabbed
- pymdownx.superfences
- pymdownx.tabbed:
alternate_style: true
- pymdownx.tasklist:
custom_checkbox: true
- pymdownx.tilde
- mdx_truly_sane_lists

View File

@@ -6,7 +6,7 @@
coveralls==3.3.1
flake8==4.0.1
flake8-tidy-imports==4.5.0
mypy==0.910
mypy==0.930
pytest==6.2.5
pytest-asyncio==0.16.0
pytest-cov==3.0.0
@@ -14,16 +14,16 @@ pytest-mock==3.6.1
pytest-random-order==1.0.4
isort==5.10.1
# For datetime mocking
time-machine==2.4.0
time-machine==2.5.0
# Convert jupyter notebooks to markdown documents
nbconvert==6.3.0
# mypy types
types-cachetools==4.2.5
types-cachetools==4.2.6
types-filelock==3.2.1
types-requests==2.26.0
types-requests==2.26.2
types-tabulate==0.8.3
# Extensions to datetime library
types-python-dateutil==2.8.2
types-python-dateutil==2.8.4

View File

@@ -2,10 +2,10 @@
-r requirements.txt
# Required for hyperopt
scipy==1.7.2
scikit-learn==1.0.1
scipy==1.7.3
scikit-learn==1.0.2
scikit-optimize==0.9.0
filelock==3.4.0
filelock==3.4.2
joblib==1.1.0
psutil==5.8.0
progressbar2==3.55.0

View File

@@ -1,5 +1,5 @@
# Include all requirements to run the bot.
-r requirements.txt
plotly==5.4.0
plotly==5.5.0

View File

@@ -1,19 +1,19 @@
numpy==1.21.4
pandas==1.3.4
numpy==1.21.5
pandas==1.3.5
pandas-ta==0.3.14b
ccxt==1.61.92
ccxt==1.65.25
# Pin cryptography for now due to rust build errors with piwheels
cryptography==36.0.0
cryptography==36.0.1
aiohttp==3.8.1
SQLAlchemy==1.4.27
python-telegram-bot==13.8.1
SQLAlchemy==1.4.29
python-telegram-bot==13.9
arrow==1.2.1
cachetools==4.2.2
requests==2.26.0
urllib3==1.26.7
jsonschema==4.2.1
TA-Lib==0.4.21
jsonschema==4.3.2
TA-Lib==0.4.22
technical==1.3.0
tabulate==0.8.9
pycoingecko==2.2.0
@@ -31,16 +31,16 @@ python-rapidjson==1.5
sdnotify==0.3.2
# API Server
fastapi==0.70.0
uvicorn==0.15.0
fastapi==0.70.1
uvicorn==0.16.0
pyjwt==2.3.0
aiofiles==0.7.0
aiofiles==0.8.0
psutil==5.8.0
# Support for colorized terminal output
colorama==0.4.4
# Building config files interactively
questionary==1.10.0
prompt-toolkit==3.0.22
prompt-toolkit==3.0.24
# Extensions to datetime library
python-dateutil==2.8.2

View File

@@ -36,7 +36,8 @@ function check_installed_python() {
fi
done
echo "No usable python found. Please make sure to have python3.7 or newer installed"
echo "No usable python found. Please make sure to have python3.7 or newer installed."
echo "python3.10 is currently not supported."
exit 1
}
@@ -205,7 +206,7 @@ function config() {
}
function install() {
echo_block "Installing mandatory dependencies"
if [ "$(uname -s)" == "Darwin" ]; then

View File

@@ -0,0 +1,47 @@
from datetime import datetime
from unittest.mock import MagicMock
from tests.conftest import get_patched_exchange
def test_get_trades_for_order(default_conf, mocker):
exchange_name = 'bitpanda'
order_id = 'ABCD-ABCD'
since = datetime(2018, 5, 5, 0, 0, 0)
default_conf["dry_run"] = False
mocker.patch('freqtrade.exchange.Exchange.exchange_has', return_value=True)
api_mock = MagicMock()
api_mock.fetch_my_trades = MagicMock(return_value=[{'id': 'TTR67E-3PFBD-76IISV',
'order': 'ABCD-ABCD',
'info': {'pair': 'XLTCZBTC',
'time': 1519860024.4388,
'type': 'buy',
'ordertype': 'limit',
'price': '20.00000',
'cost': '38.62000',
'fee': '0.06179',
'vol': '5',
'id': 'ABCD-ABCD'},
'timestamp': 1519860024438,
'datetime': '2018-02-28T23:20:24.438Z',
'symbol': 'LTC/BTC',
'type': 'limit',
'side': 'buy',
'price': 165.0,
'amount': 0.2340606,
'fee': {'cost': 0.06179, 'currency': 'BTC'}
}])
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
orders = exchange.get_trades_for_order(order_id, 'LTC/BTC', since)
assert len(orders) == 1
assert orders[0]['price'] == 165
assert api_mock.fetch_my_trades.call_count == 1
# since argument should be
assert isinstance(api_mock.fetch_my_trades.call_args[0][1], int)
assert api_mock.fetch_my_trades.call_args[0][0] == 'LTC/BTC'
# Same test twice, hardcoded number and doing the same calculation
assert api_mock.fetch_my_trades.call_args[0][1] == 1525478395000
# bitpanda requires "to" argument.
assert 'to' in api_mock.fetch_my_trades.call_args[1]['params']

View File

@@ -1026,6 +1026,12 @@ def test_create_dry_run_order_limit_fill(default_conf, mocker, side, startprice,
assert order_closed['status'] == 'closed'
assert order['fee']
# Empty orderbook test
mocker.patch('freqtrade.exchange.Exchange.fetch_l2_order_book',
return_value={'asks': [], 'bids': []})
exchange._dry_run_open_orders[order['id']]['status'] = 'open'
order_closed = exchange.fetch_dry_run_order(order['id'])
@pytest.mark.parametrize("side,rate,amount,endprice", [
# spread is 25.263-25.266
@@ -1667,12 +1673,21 @@ def test_refresh_latest_ohlcv(mocker, default_conf, caplog) -> None:
assert len(res) == len(pairs)
assert exchange._api_async.fetch_ohlcv.call_count == 0
exchange.required_candle_call_count = 1
assert log_has(f"Using cached candle (OHLCV) data for pair {pairs[0][0]}, "
f"timeframe {pairs[0][1]} ...",
caplog)
res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m'), ('XRP/ETH', '1d')],
cache=False)
assert len(res) == 3
assert exchange._api_async.fetch_ohlcv.call_count == 3
# Test the same again, should NOT return from cache!
exchange._api_async.fetch_ohlcv.reset_mock()
res = exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m'), ('XRP/ETH', '1d')],
cache=False)
assert len(res) == 3
assert exchange._api_async.fetch_ohlcv.call_count == 3
@pytest.mark.asyncio
@@ -1768,7 +1783,7 @@ def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog):
assert len(res) == 1
# Test that each is in list at least once as order is not guaranteed
assert log_has("Error loading ETH/BTC. Result was [[]].", caplog)
assert log_has("Async code raised an exception: TypeError", caplog)
assert log_has("Async code raised an exception: TypeError()", caplog)
def test_get_next_limit_in_list():
@@ -2933,39 +2948,49 @@ def test_extract_cost_curr_rate(mocker, default_conf, order, expected) -> None:
assert ex.extract_cost_curr_rate(order) == expected
@pytest.mark.parametrize("order,expected", [
@pytest.mark.parametrize("order,unknown_fee_rate,expected", [
# Using base-currency
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, 0.1),
'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, None, 0.1),
({'symbol': 'ETH/BTC', 'amount': 0.05, 'cost': 0.05,
'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, 0.08),
'fee': {'currency': 'ETH', 'cost': 0.004, 'rate': None}}, None, 0.08),
# Using quote currency
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': 'BTC', 'cost': 0.005}}, 0.1),
'fee': {'currency': 'BTC', 'cost': 0.005}}, None, 0.1),
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': 'BTC', 'cost': 0.002, 'rate': None}}, 0.04),
'fee': {'currency': 'BTC', 'cost': 0.002, 'rate': None}}, None, 0.04),
# Using foreign currency
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': 'NEO', 'cost': 0.0012}}, 0.001944),
'fee': {'currency': 'NEO', 'cost': 0.0012}}, None, 0.001944),
({'symbol': 'ETH/BTC', 'amount': 2.21, 'cost': 0.02992561,
'fee': {'currency': 'NEO', 'cost': 0.00027452}}, 0.00074305),
'fee': {'currency': 'NEO', 'cost': 0.00027452}}, None, 0.00074305),
# Rate included in return - return as is
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.01}}, 0.01),
'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.01}}, None, 0.01),
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.05,
'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.005}}, 0.005),
'fee': {'currency': 'USDT', 'cost': 0.34, 'rate': 0.005}}, None, 0.005),
# 0.1% filled - no costs (kraken - #3431)
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0,
'fee': {'currency': 'BTC', 'cost': 0.0, 'rate': None}}, None),
'fee': {'currency': 'BTC', 'cost': 0.0, 'rate': None}}, None, None),
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0,
'fee': {'currency': 'ETH', 'cost': 0.0, 'rate': None}}, 0.0),
'fee': {'currency': 'ETH', 'cost': 0.0, 'rate': None}}, None, 0.0),
({'symbol': 'ETH/BTC', 'amount': 0.04, 'cost': 0.0,
'fee': {'currency': 'NEO', 'cost': 0.0, 'rate': None}}, None),
'fee': {'currency': 'NEO', 'cost': 0.0, 'rate': None}}, None, None),
# Invalid pair combination - POINT/BTC is not a pair
({'symbol': 'POINT/BTC', 'amount': 0.04, 'cost': 0.5,
'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, None, None),
({'symbol': 'POINT/BTC', 'amount': 0.04, 'cost': 0.5,
'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 1, 4.0),
({'symbol': 'POINT/BTC', 'amount': 0.04, 'cost': 0.5,
'fee': {'currency': 'POINT', 'cost': 2.0, 'rate': None}}, 2, 8.0),
])
def test_calculate_fee_rate(mocker, default_conf, order, expected) -> None:
def test_calculate_fee_rate(mocker, default_conf, order, expected, unknown_fee_rate) -> None:
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', return_value={'last': 0.081})
if unknown_fee_rate:
default_conf['exchange']['unknown_fee_rate'] = unknown_fee_rate
ex = get_patched_exchange(mocker, default_conf)
assert ex.calculate_fee_rate(order) == expected

View File

@@ -169,6 +169,7 @@ def test_start_no_hyperopt_allowed(mocker, hyperopt_conf, caplog) -> None:
def test_start_no_data(mocker, hyperopt_conf) -> None:
hyperopt_conf['user_data_dir'] = Path("tests")
patched_configuration_load_config_file(mocker, hyperopt_conf)
mocker.patch('freqtrade.data.history.load_pair_history', MagicMock(return_value=pd.DataFrame))
mocker.patch(
@@ -189,6 +190,12 @@ def test_start_no_data(mocker, hyperopt_conf) -> None:
with pytest.raises(OperationalException, match='No data found. Terminating.'):
start_hyperopt(pargs)
# Cleanup since that failed hyperopt start leaves a lockfile.
try:
Path(Hyperopt.get_lock_filename(hyperopt_conf)).unlink()
except Exception:
pass
def test_start_filelock(mocker, hyperopt_conf, caplog) -> None:
hyperopt_mock = MagicMock(side_effect=Timeout(Hyperopt.get_lock_filename(hyperopt_conf)))

View File

@@ -1,5 +1,6 @@
# pragma pylint: disable=missing-docstring,C0103,protected-access
import logging
import time
from unittest.mock import MagicMock, PropertyMock
@@ -7,6 +8,7 @@ import pytest
import time_machine
from freqtrade.constants import AVAILABLE_PAIRLISTS
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.persistence import Trade
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
@@ -216,6 +218,40 @@ def test_invalid_blacklist(mocker, markets, static_pl_conf, caplog):
log_has_re(r"Pair blacklist contains an invalid Wildcard.*", caplog)
def test_remove_logs_for_pairs_already_in_blacklist(mocker, markets, static_pl_conf, caplog):
logger = logging.getLogger(__name__)
freqtrade = get_patched_freqtradebot(mocker, static_pl_conf)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
exchange_has=MagicMock(return_value=True),
markets=PropertyMock(return_value=markets),
)
freqtrade.pairlists.refresh_pairlist()
whitelist = ['ETH/BTC', 'TKN/BTC']
caplog.clear()
caplog.set_level(logging.INFO)
# Ensure all except those in whitelist are removed.
assert set(whitelist) == set(freqtrade.pairlists.whitelist)
assert static_pl_conf['exchange']['pair_blacklist'] == freqtrade.pairlists.blacklist
# Ensure that log message wasn't generated.
assert not log_has('Pair BLK/BTC in your blacklist. Removing it from whitelist...', caplog)
new_whitelist = freqtrade.pairlists.verify_blacklist(whitelist + ['BLK/BTC'], logger.warning)
# Ensure that the pair is removed from the white list, and properly logged.
assert set(whitelist) == set(new_whitelist)
matches = sum(1 for message in caplog.messages
if message == 'Pair BLK/BTC in your blacklist. Removing it from whitelist...')
assert matches == 1
new_whitelist = freqtrade.pairlists.verify_blacklist(whitelist + ['BLK/BTC'], logger.warning)
# Ensure that the pair is not logged anymore when being removed from the pair list.
assert set(whitelist) == set(new_whitelist)
matches = sum(1 for message in caplog.messages
if message == 'Pair BLK/BTC in your blacklist. Removing it from whitelist...')
assert matches == 1
def test_refresh_pairlist_dynamic(mocker, shitcoinmarkets, tickers, whitelist_conf):
mocker.patch.multiple(
@@ -657,6 +693,22 @@ def test_PerformanceFilter_error(mocker, whitelist_conf, caplog) -> None:
assert log_has("PerformanceFilter is not available in this mode.", caplog)
def test_ShuffleFilter_init(mocker, whitelist_conf, caplog) -> None:
whitelist_conf['pairlists'] = [
{"method": "StaticPairList"},
{"method": "ShuffleFilter", "seed": 42}
]
exchange = get_patched_exchange(mocker, whitelist_conf)
PairListManager(exchange, whitelist_conf)
assert log_has("Backtesting mode detected, applying seed value: 42", caplog)
caplog.clear()
whitelist_conf['runmode'] = RunMode.DRY_RUN
PairListManager(exchange, whitelist_conf)
assert not log_has("Backtesting mode detected, applying seed value: 42", caplog)
assert log_has("Live mode detected, not applying seed.", caplog)
@pytest.mark.usefixtures("init_persistence")
def test_PerformanceFilter_lookback(mocker, whitelist_conf, fee, caplog) -> None:
whitelist_conf['exchange']['pair_whitelist'].append('XRP/BTC')
@@ -1089,33 +1141,34 @@ def test_pairlistmanager_no_pairlist(mocker, whitelist_conf):
# Happy path: Descending order, all values filled
([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}],
['ETH/BTC', 'TKN/BTC'],
[{'pair': 'TKN/BTC', 'profit': 5, 'count': 3}, {'pair': 'ETH/BTC', 'profit': 4, 'count': 2}],
[{'pair': 'TKN/BTC', 'profit_ratio': 0.05, 'count': 3},
{'pair': 'ETH/BTC', 'profit_ratio': 0.04, 'count': 2}],
['TKN/BTC', 'ETH/BTC']),
# Performance data outside allow list ignored
([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}],
['ETH/BTC', 'TKN/BTC'],
[{'pair': 'OTHER/BTC', 'profit': 5, 'count': 3},
{'pair': 'ETH/BTC', 'profit': 4, 'count': 2}],
[{'pair': 'OTHER/BTC', 'profit_ratio': 0.05, 'count': 3},
{'pair': 'ETH/BTC', 'profit_ratio': 0.04, 'count': 2}],
['ETH/BTC', 'TKN/BTC']),
# Partial performance data missing and sorted between positive and negative profit
([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}],
['ETH/BTC', 'TKN/BTC', 'LTC/BTC'],
[{'pair': 'ETH/BTC', 'profit': -5, 'count': 100},
{'pair': 'TKN/BTC', 'profit': 4, 'count': 2}],
[{'pair': 'ETH/BTC', 'profit_ratio': -0.05, 'count': 100},
{'pair': 'TKN/BTC', 'profit_ratio': 0.04, 'count': 2}],
['TKN/BTC', 'LTC/BTC', 'ETH/BTC']),
# Tie in performance data broken by count (ascending)
([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}],
['ETH/BTC', 'TKN/BTC', 'LTC/BTC'],
[{'pair': 'LTC/BTC', 'profit': -5.01, 'count': 101},
{'pair': 'TKN/BTC', 'profit': -5.01, 'count': 2},
{'pair': 'ETH/BTC', 'profit': -5.01, 'count': 100}],
[{'pair': 'LTC/BTC', 'profit_ratio': -0.0501, 'count': 101},
{'pair': 'TKN/BTC', 'profit_ratio': -0.0501, 'count': 2},
{'pair': 'ETH/BTC', 'profit_ratio': -0.0501, 'count': 100}],
['TKN/BTC', 'ETH/BTC', 'LTC/BTC']),
# Tie in performance and count, broken by alphabetical sort
([{"method": "StaticPairList"}, {"method": "PerformanceFilter"}],
['ETH/BTC', 'TKN/BTC', 'LTC/BTC'],
[{'pair': 'LTC/BTC', 'profit': -5.01, 'count': 1},
{'pair': 'TKN/BTC', 'profit': -5.01, 'count': 1},
{'pair': 'ETH/BTC', 'profit': -5.01, 'count': 1}],
[{'pair': 'LTC/BTC', 'profit_ratio': -0.0501, 'count': 1},
{'pair': 'TKN/BTC', 'profit_ratio': -0.0501, 'count': 1},
{'pair': 'ETH/BTC', 'profit_ratio': -0.0501, 'count': 1}],
['ETH/BTC', 'LTC/BTC', 'TKN/BTC']),
])
def test_performance_filter(mocker, whitelist_conf, pairlists, pair_allowlist, overall_performance,

View File

@@ -424,7 +424,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
assert stats['trade_count'] == 2
assert stats['first_trade_date'] == 'just now'
assert stats['latest_trade_date'] == 'just now'
assert stats['avg_duration'] in ('0:00:00', '0:00:01')
assert stats['avg_duration'] in ('0:00:00', '0:00:01', '0:00:02')
assert stats['best_pair'] == 'ETH/BTC'
assert prec_satoshi(stats['best_rate'], 6.2)
@@ -435,7 +435,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
assert stats['trade_count'] == 2
assert stats['first_trade_date'] == 'just now'
assert stats['latest_trade_date'] == 'just now'
assert stats['avg_duration'] in ('0:00:00', '0:00:01')
assert stats['avg_duration'] in ('0:00:00', '0:00:01', '0:00:02')
assert stats['best_pair'] == 'ETH/BTC'
assert prec_satoshi(stats['best_rate'], 6.2)
assert isnan(stats['profit_all_coin'])
@@ -1093,7 +1093,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
with pytest.raises(RPCException, match=r'position for ETH/BTC already open - id: 1'):
rpc._rpc_forcebuy(pair, 0.0001)
pair = 'XRP/BTC'
trade = rpc._rpc_forcebuy(pair, 0.0001)
trade = rpc._rpc_forcebuy(pair, 0.0001, order_type='limit')
assert isinstance(trade, Trade)
assert trade.pair == pair
assert trade.open_rate == 0.0001
@@ -1225,6 +1225,16 @@ def test_rpc_blacklist(mocker, default_conf) -> None:
assert 'errors' in ret
assert isinstance(ret['errors'], dict)
ret = rpc._rpc_blacklist_delete(["DOGE/BTC", 'HOT/BTC'])
assert 'StaticPairList' in ret['method']
assert len(ret['blacklist']) == 2
assert ret['blacklist'] == default_conf['exchange']['pair_blacklist']
assert ret['blacklist'] == ['ETH/BTC', 'XRP/.*']
assert ret['blacklist_expanded'] == ['ETH/BTC', 'XRP/BTC', 'XRP/USDT']
assert 'errors' in ret
assert isinstance(ret['errors'], dict)
def test_rpc_edge_disabled(mocker, default_conf) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())

View File

@@ -533,6 +533,7 @@ def test_api_show_config(botclient):
assert rc.json()['timeframe_min'] == 5
assert rc.json()['state'] == 'running'
assert rc.json()['bot_name'] == 'freqtrade'
assert rc.json()['strategy_version'] is None
assert not rc.json()['trailing_stop']
assert 'bid_strategy' in rc.json()
assert 'ask_strategy' in rc.json()
@@ -954,6 +955,38 @@ def test_api_blacklist(botclient, mocker):
"errors": {},
}
rc = client_delete(client, f"{BASE_URI}/blacklist?pairs_to_delete=DOGE/BTC")
assert_response(rc)
assert rc.json() == {"blacklist": ["HOT/BTC", "ETH/BTC", "XRP/.*"],
"blacklist_expanded": ["ETH/BTC", "XRP/BTC", "XRP/USDT"],
"length": 3,
"method": ["StaticPairList"],
"errors": {},
}
rc = client_delete(client, f"{BASE_URI}/blacklist?pairs_to_delete=NOTHING/BTC")
assert_response(rc)
assert rc.json() == {"blacklist": ["HOT/BTC", "ETH/BTC", "XRP/.*"],
"blacklist_expanded": ["ETH/BTC", "XRP/BTC", "XRP/USDT"],
"length": 3,
"method": ["StaticPairList"],
"errors": {
"NOTHING/BTC": {
"error_msg": "Pair NOTHING/BTC is not in the current blacklist."
}
},
}
rc = client_delete(
client,
f"{BASE_URI}/blacklist?pairs_to_delete=HOT/BTC&pairs_to_delete=ETH/BTC")
assert_response(rc)
assert rc.json() == {"blacklist": ["XRP/.*"],
"blacklist_expanded": ["XRP/BTC", "XRP/USDT"],
"length": 1,
"method": ["StaticPairList"],
"errors": {},
}
def test_api_whitelist(botclient):
ftbot, client = botclient

View File

@@ -98,7 +98,7 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
"['stats'], ['daily'], ['weekly'], ['monthly'], "
"['count'], ['locks'], ['unlock', 'delete_locks'], "
"['reload_config', 'reload_conf'], ['show_config', 'show_conf'], "
"['stopbuy'], ['whitelist'], ['blacklist'], "
"['stopbuy'], ['whitelist'], ['blacklist'], ['blacklist_delete', 'bl_delete'], "
"['logs'], ['edge'], ['help'], ['version']"
"]")
@@ -937,7 +937,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
telegram._forcesell(update=update, context=context)
assert msg_mock.call_count == 4
last_msg = msg_mock.call_args_list[-1][0][0]
last_msg = msg_mock.call_args_list[-2][0][0]
assert {
'type': RPCMessageType.SELL,
'trade_id': 1,
@@ -952,6 +952,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
'profit_amount': 6.314e-05,
'profit_ratio': 0.0629778,
'stake_currency': 'BTC',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'buy_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value,
@@ -1001,7 +1002,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
assert msg_mock.call_count == 4
last_msg = msg_mock.call_args_list[-1][0][0]
last_msg = msg_mock.call_args_list[-2][0][0]
assert {
'type': RPCMessageType.SELL,
'trade_id': 1,
@@ -1016,6 +1017,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
'profit_amount': -5.497e-05,
'profit_ratio': -0.05482878,
'stake_currency': 'BTC',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'buy_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value,
@@ -1055,7 +1057,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
# Called for each trade 2 times
assert msg_mock.call_count == 8
msg = msg_mock.call_args_list[1][0][0]
msg = msg_mock.call_args_list[0][0][0]
assert {
'type': RPCMessageType.SELL,
'trade_id': 1,
@@ -1070,6 +1072,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
'profit_amount': -4.09e-06,
'profit_ratio': -0.00408133,
'stake_currency': 'BTC',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'buy_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value,
@@ -1470,6 +1473,13 @@ def test_blacklist_static(default_conf, update, mocker) -> None:
in msg_mock.call_args_list[0][0][0])
assert freqtradebot.pairlists.blacklist == ["DOGE/BTC", "HOT/BTC", "ETH/BTC", "XRP/.*"]
msg_mock.reset_mock()
context.args = ["DOGE/BTC"]
telegram._blacklist_delete(update=update, context=context)
assert msg_mock.call_count == 1
assert ("Blacklist contains 3 pairs\n`HOT/BTC, ETH/BTC, XRP/.*`"
in msg_mock.call_args_list[0][0][0])
def test_telegram_logs(default_conf, update, mocker) -> None:
mocker.patch.multiple(
@@ -1597,12 +1607,20 @@ def test_help_handle(default_conf, update, mocker) -> None:
def test_version_handle(default_conf, update, mocker) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram._version(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert '*Version:* `{}`'.format(__version__) in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
freqtradebot.strategy.version = lambda: '1.1.1'
telegram._version(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert '*Version:* `{}`'.format(__version__) in msg_mock.call_args_list[0][0][0]
assert '*Strategy version: * `1.1.1`' in msg_mock.call_args_list[0][0][0]
def test_show_config_handle(default_conf, update, mocker) -> None:

View File

@@ -292,3 +292,15 @@ def test__send_msg_with_json_format(default_conf, mocker, caplog):
webhook._send_msg(msg)
assert post.call_args[1] == {'json': msg}
def test__send_msg_with_raw_format(default_conf, mocker, caplog):
default_conf["webhook"] = get_webhook_dict()
default_conf["webhook"]["format"] = "raw"
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
msg = {'data': 'Hello'}
post = MagicMock()
mocker.patch("freqtrade.rpc.webhook.post", post)
webhook._send_msg(msg)
assert post.call_args[1] == {'data': msg['data'], 'headers': {'Content-Type': 'text/plain'}}

View File

@@ -2171,10 +2171,20 @@ def test_check_handle_timedout_sell_usercustom(default_conf_usdt, ticker_usdt, l
assert open_trade.is_open is True
assert freqtrade.strategy.check_sell_timeout.call_count == 1
# 2nd canceled trade ...
# 2nd canceled trade - Fail execute sell
caplog.clear()
open_trade.open_order_id = 'order_id_2'
mocker.patch('freqtrade.persistence.Trade.get_exit_order_count', return_value=1)
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit',
side_effect=DependencyException)
freqtrade.check_handle_timedout()
assert log_has_re('Unable to emergency sell .*', caplog)
et_mock = mocker.patch('freqtrade.freqtradebot.FreqtradeBot.execute_trade_exit')
caplog.clear()
# 2nd canceled trade ...
open_trade.open_order_id = 'order_id_2'
freqtrade.check_handle_timedout()
assert log_has_re('Emergencyselling trade.*', caplog)
assert et_mock.call_count == 1
@@ -2979,7 +2989,7 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee,
assert trade.close_profit == 0.09451372
assert rpc_mock.call_count == 3
last_msg = rpc_mock.call_args_list[-1][0][0]
last_msg = rpc_mock.call_args_list[-2][0][0]
assert {
'type': RPCMessageType.SELL,
'trade_id': 1,

View File

@@ -184,16 +184,18 @@ def test_render_template_fallback(mocker):
assert 'if self.dp' in val
def test_parse_db_uri_for_logging() -> None:
postgresql_conn_uri = "postgresql+psycopg2://scott123:scott123@host/dbname"
mariadb_conn_uri = "mariadb+mariadbconnector://app_user:Password123!@127.0.0.1:3306/company"
mysql_conn_uri = "mysql+pymysql://user:pass@some_mariadb/dbname?charset=utf8mb4"
sqlite_conn_uri = "sqlite:////freqtrade/user_data/tradesv3.sqlite"
censored_pwd = "*****"
@pytest.mark.parametrize('conn_url,expected', [
("postgresql+psycopg2://scott123:scott123@host:1245/dbname",
"postgresql+psycopg2://scott123:*****@host:1245/dbname"),
("postgresql+psycopg2://scott123:scott123@host.name.com/dbname",
"postgresql+psycopg2://scott123:*****@host.name.com/dbname"),
("mariadb+mariadbconnector://app_user:Password123!@127.0.0.1:3306/company",
"mariadb+mariadbconnector://app_user:*****@127.0.0.1:3306/company"),
("mysql+pymysql://user:pass@some_mariadb/dbname?charset=utf8mb4",
"mysql+pymysql://user:*****@some_mariadb/dbname?charset=utf8mb4"),
("sqlite:////freqtrade/user_data/tradesv3.sqlite",
"sqlite:////freqtrade/user_data/tradesv3.sqlite"),
])
def test_parse_db_uri_for_logging(conn_url, expected) -> None:
def get_pwd(x): return x.split(':')[2].split('@')[0]
assert get_pwd(parse_db_uri_for_logging(postgresql_conn_uri)) == censored_pwd
assert get_pwd(parse_db_uri_for_logging(mariadb_conn_uri)) == censored_pwd
assert get_pwd(parse_db_uri_for_logging(mysql_conn_uri)) == censored_pwd
assert sqlite_conn_uri == parse_db_uri_for_logging(sqlite_conn_uri)
assert parse_db_uri_for_logging(conn_url) == expected

View File

@@ -43,7 +43,7 @@ def test_worker_stopped(mocker, default_conf, caplog) -> None:
worker.freqtrade.state = State.STOPPED
state = worker._worker(old_state=State.RUNNING)
assert state is State.STOPPED
assert log_has('Changing state to: STOPPED', caplog)
assert log_has('Changing state from RUNNING to: STOPPED', caplog)
assert mock_throttle.call_count == 1