Compare commits

...

326 Commits

Author SHA1 Message Date
Matthias
01d10aebca Merge pull request #10599 from freqtrade/new_release
New release 2024.8
2024-08-31 16:03:49 +02:00
Matthias
5e9d2323e3 chore: bump version to 2024.8 2024-08-31 08:25:52 +02:00
Matthias
a98b5dd86e Merge branch 'stable' into new_release 2024-08-31 08:25:20 +02:00
Matthias
a250cf7ebe test: Remove unnecessary asyncio decorators 2024-08-29 20:38:57 +02:00
Matthias
1c5ca0f022 chore: improved fix for terminal error 2024-08-29 20:38:25 +02:00
Matthias
ca3dee7b37 chore: add setting to avoid deprecation warning from pytest-asyncio 2024-08-29 20:24:52 +02:00
Matthias
59d47955a0 chore: fix test failure due to terminal error 2024-08-29 20:05:48 +02:00
Matthias
d05ca3db0b fix: handle small terminal width
closes #10572
2024-08-29 07:14:32 +02:00
Matthias
87678eff98 fix: avoid hyperopt-results not showing past terminal height 2024-08-29 07:14:32 +02:00
Matthias
c1f54b14d0 Merge pull request #10594 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-08-29 06:32:13 +02:00
xmatthias
4c487d666f chore: update pre-commit hooks 2024-08-29 03:12:45 +00:00
Matthias
655a300acb docs: re-establish search box on develop documentation 2024-08-28 20:27:36 +02:00
Matthias
8d61d66d79 Merge pull request #10573 from freqtrade/dependabot/pip/develop/types-451e0821cf
chore(deps-dev): bump the types group with 2 updates
2024-08-27 20:27:36 +02:00
Matthias
13f391fe4a Merge pull request #10577 from freqtrade/dependabot/pip/develop/websockets-13.0
chore(deps): bump websockets from 12.0 to 13.0
2024-08-27 19:48:21 +02:00
Matthias
660a5d910a chore: bump pre-commit type deps 2024-08-27 19:44:09 +02:00
Matthias
a58c5b372c Merge pull request #10588 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-08-27 07:26:19 +02:00
xmatthias
ec55fdb8d8 chore: update pre-commit hooks 2024-08-27 03:02:41 +00:00
Matthias
19d670826d Merge pull request #10582 from freqtrade/dependabot/pip/develop/mypy-1.11.2
chore(deps-dev): bump mypy from 1.11.1 to 1.11.2
2024-08-26 11:58:55 +02:00
Matthias
7033bd19fe Merge pull request #10580 from freqtrade/dependabot/pip/develop/aiohttp-3.10.5
chore(deps): bump aiohttp from 3.10.4 to 3.10.5
2024-08-26 10:45:35 +02:00
Matthias
aea75b9e52 Merge pull request #10581 from freqtrade/dependabot/pip/develop/fastapi-0.112.2
chore(deps): bump fastapi from 0.112.1 to 0.112.2
2024-08-26 09:55:02 +02:00
dependabot[bot]
eaf68fe105 chore(deps): bump aiohttp from 3.10.4 to 3.10.5
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.4 to 3.10.5.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.4...v3.10.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 07:29:04 +00:00
Matthias
a27237286c Merge pull request #10578 from freqtrade/dependabot/pip/develop/ccxt-4.3.88
chore(deps): bump ccxt from 4.3.85 to 4.3.88
2024-08-26 09:27:25 +02:00
Matthias
502ca6b612 Merge pull request #10579 from freqtrade/dependabot/pip/develop/scipy-1.14.1
chore(deps): bump scipy from 1.14.0 to 1.14.1
2024-08-26 09:09:24 +02:00
dependabot[bot]
a9451a5413 chore(deps-dev): bump mypy from 1.11.1 to 1.11.2
Bumps [mypy](https://github.com/python/mypy) from 1.11.1 to 1.11.2.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.11.1...v1.11.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 06:40:25 +00:00
Matthias
ee54047b94 Merge pull request #10576 from freqtrade/dependabot/pip/develop/ruff-0.6.2
chore(deps-dev): bump ruff from 0.6.1 to 0.6.2
2024-08-26 08:39:58 +02:00
Matthias
a2e2c0a41a Merge pull request #10575 from freqtrade/dependabot/pip/develop/mkdocs-d35dd8fcd7
chore(deps): bump mkdocs-material from 9.5.32 to 9.5.33 in the mkdocs group
2024-08-26 08:39:39 +02:00
Matthias
8e7bfba0ab Merge pull request #10574 from freqtrade/dependabot/pip/develop/pytest-c16e4178ec
chore(deps-dev): bump pytest-asyncio from 0.23.8 to 0.24.0 in the pytest group
2024-08-26 08:39:00 +02:00
dependabot[bot]
6d280be081 chore(deps): bump fastapi from 0.112.1 to 0.112.2
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.112.1 to 0.112.2.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.112.1...0.112.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 04:01:39 +00:00
dependabot[bot]
877c6635e4 chore(deps): bump scipy from 1.14.0 to 1.14.1
Bumps [scipy](https://github.com/scipy/scipy) from 1.14.0 to 1.14.1.
- [Release notes](https://github.com/scipy/scipy/releases)
- [Commits](https://github.com/scipy/scipy/compare/v1.14.0...v1.14.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 04:01:13 +00:00
dependabot[bot]
ca0be181bc chore(deps): bump ccxt from 4.3.85 to 4.3.88
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.3.85 to 4.3.88.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.3.85...4.3.88)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 04:00:57 +00:00
dependabot[bot]
ba2cf8015b chore(deps): bump websockets from 12.0 to 13.0
Bumps [websockets](https://github.com/python-websockets/websockets) from 12.0 to 13.0.
- [Release notes](https://github.com/python-websockets/websockets/releases)
- [Commits](https://github.com/python-websockets/websockets/compare/12.0...13.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 04:00:51 +00:00
dependabot[bot]
f1f4ed97ca chore(deps-dev): bump ruff from 0.6.1 to 0.6.2
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.1 to 0.6.2.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.1...0.6.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 04:00:46 +00:00
dependabot[bot]
24785d28e6 chore(deps): bump mkdocs-material in the mkdocs group
Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `mkdocs-material` from 9.5.32 to 9.5.33
- [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/9.5.32...9.5.33)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 04:00:35 +00:00
dependabot[bot]
0076205da6 chore(deps-dev): bump pytest-asyncio in the pytest group
Bumps the pytest group with 1 update: [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio).


Updates `pytest-asyncio` from 0.23.8 to 0.24.0
- [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases)
- [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.23.8...v0.24.0)

---
updated-dependencies:
- dependency-name: pytest-asyncio
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: pytest
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 04:00:21 +00:00
dependabot[bot]
6235b50c9d chore(deps-dev): bump the types group with 2 updates
Bumps the types group with 2 updates: [types-cachetools](https://github.com/python/typeshed) and [types-python-dateutil](https://github.com/python/typeshed).


Updates `types-cachetools` from 5.4.0.20240717 to 5.5.0.20240820
- [Commits](https://github.com/python/typeshed/commits)

Updates `types-python-dateutil` from 2.9.0.20240316 to 2.9.0.20240821
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-26 03:59:43 +00:00
Matthias
5cca19bb83 refactor: simplify binance liquidation test setup 2024-08-24 18:07:19 +02:00
Matthias
1b7056853b refactor: move test_liquidation_price_binance to binance test file 2024-08-24 18:06:19 +02:00
Matthias
d1bc519599 docs: Show version alias on versions 2024-08-24 09:33:33 +02:00
Matthias
bcae1dce7b docs: reduce font-weight of version_list 2024-08-24 09:19:07 +02:00
Matthias
e87927564b chore: Improve typing 2024-08-23 18:18:05 +02:00
Matthias
01b7ad4a3f feat: prevent freqAI startup on exchanges without history
closes #10570
2024-08-23 18:16:06 +02:00
Matthias
235d38752c Merge pull request #10566 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-08-22 06:33:17 +02:00
xmatthias
fd30edf2bb chore: update pre-commit hooks 2024-08-22 03:13:36 +00:00
Matthias
0a2be142ff Merge pull request #10565 from froggleston/develop
Add clarification for untradeable pairs vs markets
2024-08-21 20:25:12 +02:00
Matthias
33614d8ff0 docs: Improve wording for untradeable pairs 2024-08-21 19:51:24 +02:00
Matthias
3dce1d32f9 Merge pull request #10564 from iridescentGray/develop
chore: fix test param
2024-08-21 18:15:55 +02:00
Robert Davey
4a62199682 Add clarification for untradeable pairs vs markets 2024-08-21 15:26:19 +01:00
colorfulgray0
68be56240d chore: fix test param 2024-08-21 17:46:58 +08:00
Matthias
19ccb27dbd chore: deploy through github internal pipeline 2024-08-20 21:19:26 +02:00
Matthias
a7e2bf071b chore: Move deployment to gh native actions 2024-08-20 20:32:53 +02:00
Matthias
e05a6e976e chore: add Ci for gha deployment 2024-08-20 20:20:25 +02:00
Matthias
c7485e3fd4 chore: add mike to mkdocs config 2024-08-20 20:17:36 +02:00
Matthias
80ad1a68e7 Merge pull request #10554 from freqtrade/dependabot/pip/develop/tables-3.10.1
chore(deps): bump tables from 3.9.1 to 3.10.1
2024-08-20 07:45:55 +02:00
Matthias
f4440d43de chore: increase wait time on ws to avoid flukes 2024-08-20 06:45:09 +02:00
Matthias
201a5c06fe Merge pull request #10553 from freqtrade/dependabot/pip/develop/ruff-0.6.1
chore(deps-dev): bump ruff from 0.5.7 to 0.6.1
2024-08-19 20:37:12 +02:00
Matthias
6bd21b8995 chore: pin tables for python 3.9 2024-08-19 20:01:19 +02:00
Matthias
ce66fbb595 chore: ruff format notebook 2024-08-19 19:59:15 +02:00
Matthias
226ebdd935 Merge pull request #10560 from freqtrade/dependabot/pip/develop/ccxt-4.3.85
chore(deps): bump ccxt from 4.3.84 to 4.3.85
2024-08-19 19:10:41 +02:00
Matthias
fcc400b20d Merge pull request #10559 from freqtrade/dependabot/pip/develop/mkdocs-ffd8a664e0
chore(deps): bump mkdocs-material from 9.5.31 to 9.5.32 in the mkdocs group
2024-08-19 18:54:50 +02:00
Matthias
d2c908b1ab chore: bump ruff pre-commit version 2024-08-19 18:23:53 +02:00
Matthias
976f9b2590 chore: re-format ipynb notebook 2024-08-19 18:23:36 +02:00
dependabot[bot]
4d175a466e chore(deps): bump ccxt from 4.3.84 to 4.3.85
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.3.84 to 4.3.85.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.3.84...4.3.85)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 16:20:09 +00:00
Matthias
986ff7d1b1 chore: rename parameter to avoid naming collision 2024-08-19 18:19:42 +02:00
dependabot[bot]
bc719feb5d chore(deps): bump mkdocs-material in the mkdocs group
Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `mkdocs-material` from 9.5.31 to 9.5.32
- [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/9.5.31...9.5.32)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 16:19:33 +00:00
Matthias
fe41612738 Merge pull request #10557 from freqtrade/dependabot/pip/develop/fastapi-0.112.1
chore(deps): bump fastapi from 0.112.0 to 0.112.1
2024-08-19 10:31:32 +02:00
Matthias
91f36ae42a Merge pull request #10552 from freqtrade/dependabot/pip/develop/matplotlib-3.9.2
chore(deps): bump matplotlib from 3.9.1.post1 to 3.9.2
2024-08-19 10:02:02 +02:00
Matthias
2750981b64 Merge pull request #10551 from freqtrade/dependabot/pip/develop/ccxt-4.3.84
chore(deps): bump ccxt from 4.3.79 to 4.3.84
2024-08-19 09:45:43 +02:00
Matthias
5737165f37 Merge pull request #10547 from freqtrade/dependabot/pip/develop/cachetools-5.5.0
chore(deps): bump cachetools from 5.4.0 to 5.5.0
2024-08-19 09:16:40 +02:00
dependabot[bot]
a70116ed4d chore(deps): bump fastapi from 0.112.0 to 0.112.1
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.112.0 to 0.112.1.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.112.0...0.112.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 06:50:46 +00:00
Matthias
31d2296777 Merge pull request #10555 from freqtrade/dependabot/pip/develop/uvicorn-0.30.6
chore(deps): bump uvicorn from 0.30.5 to 0.30.6
2024-08-19 08:49:35 +02:00
dependabot[bot]
b859d7f3a5 chore(deps): bump ccxt from 4.3.79 to 4.3.84
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.3.79 to 4.3.84.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.3.79...4.3.84)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 06:00:52 +00:00
dependabot[bot]
75714ae84a chore(deps): bump matplotlib from 3.9.1.post1 to 3.9.2
Bumps [matplotlib](https://github.com/matplotlib/matplotlib) from 3.9.1.post1 to 3.9.2.
- [Release notes](https://github.com/matplotlib/matplotlib/releases)
- [Commits](https://github.com/matplotlib/matplotlib/compare/v3.9.1.post1...v3.9.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 06:00:50 +00:00
Matthias
be221c5a3e Merge pull request #10550 from freqtrade/dependabot/pip/develop/tensorboard-2.17.1
chore(deps): bump tensorboard from 2.17.0 to 2.17.1
2024-08-19 08:00:15 +02:00
Matthias
064ff34866 Merge pull request #10549 from freqtrade/dependabot/pip/develop/aiohttp-3.10.4
chore(deps): bump aiohttp from 3.10.3 to 3.10.4
2024-08-19 08:00:03 +02:00
Matthias
9807d6bb2c Merge pull request #10548 from freqtrade/dependabot/pip/develop/markdown-3.7
chore(deps): bump markdown from 3.6 to 3.7
2024-08-19 07:59:54 +02:00
dependabot[bot]
8321425e62 chore(deps): bump uvicorn from 0.30.5 to 0.30.6
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.30.5 to 0.30.6.
- [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.30.5...0.30.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 03:49:10 +00:00
dependabot[bot]
ba3223a9a3 chore(deps): bump tables from 3.9.1 to 3.10.1
Bumps [tables](https://github.com/PyTables/PyTables) from 3.9.1 to 3.10.1.
- [Release notes](https://github.com/PyTables/PyTables/releases)
- [Changelog](https://github.com/PyTables/PyTables/blob/master/RELEASE_NOTES.rst)
- [Commits](https://github.com/PyTables/PyTables/compare/v3.9.1...v3.10.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 03:49:05 +00:00
dependabot[bot]
a266997b69 chore(deps-dev): bump ruff from 0.5.7 to 0.6.1
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.7 to 0.6.1.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.5.7...0.6.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 03:49:02 +00:00
dependabot[bot]
314983b139 chore(deps): bump tensorboard from 2.17.0 to 2.17.1
Bumps [tensorboard](https://github.com/tensorflow/tensorboard) from 2.17.0 to 2.17.1.
- [Release notes](https://github.com/tensorflow/tensorboard/releases)
- [Changelog](https://github.com/tensorflow/tensorboard/blob/master/RELEASE.md)
- [Commits](https://github.com/tensorflow/tensorboard/compare/2.17.0...2.17.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 03:48:37 +00:00
dependabot[bot]
8896b0ae7c chore(deps): bump aiohttp from 3.10.3 to 3.10.4
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.3 to 3.10.4.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.3...v3.10.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 03:48:31 +00:00
dependabot[bot]
b6aa922c09 chore(deps): bump markdown from 3.6 to 3.7
Bumps [markdown](https://github.com/Python-Markdown/markdown) from 3.6 to 3.7.
- [Release notes](https://github.com/Python-Markdown/markdown/releases)
- [Changelog](https://github.com/Python-Markdown/markdown/blob/master/docs/changelog.md)
- [Commits](https://github.com/Python-Markdown/markdown/compare/3.6...3.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 03:48:17 +00:00
dependabot[bot]
95732f4170 chore(deps): bump cachetools from 5.4.0 to 5.5.0
Bumps [cachetools](https://github.com/tkem/cachetools) from 5.4.0 to 5.5.0.
- [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/tkem/cachetools/compare/v5.4.0...v5.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-19 03:48:14 +00:00
Matthias
624dfdf6ac Merge pull request #10546 from stash86/fix-recursive
add startup count from strategy to the analysis
2024-08-18 17:16:56 +02:00
Stefano Ariestasia
83e0cf75c5 add startup count from strategy to the analysis 2024-08-18 23:48:11 +09:00
Matthias
7fe23ad8c9 chore: add alias_for to tests 2024-08-18 13:15:10 +02:00
Matthias
fd9ec438dc feat: show name, class name and eventually the replacement alias 2024-08-18 11:36:34 +02:00
Matthias
7cab973cbf feat: get new name for aliased ccxt exchanges 2024-08-18 11:36:06 +02:00
Matthias
9e3e5038f7 Merge pull request #10544 from freqtrade/feat/strategy_star_import
Improved imports for strategy
2024-08-18 09:53:45 +02:00
Matthias
7952712c5e chore: update samples to use doublequotes 2024-08-18 08:44:37 +02:00
Matthias
d754a2e295 feat: improve default imports 2024-08-18 08:38:59 +02:00
Matthias
768b4e5e2b chore: Update formatting of default export sequence 2024-08-18 08:38:11 +02:00
Matthias
b1ae09c003 docs: remove callback examples imports 2024-08-18 08:37:34 +02:00
Matthias
9408e858cd chore: use aligned quoting strategy for templtae 2024-08-17 16:43:46 +02:00
Matthias
0995164110 feat: improve formatting of generated strategy 2024-08-17 16:36:21 +02:00
Matthias
b3a042a63b feat: don't use commented typehints
Imports are correct now
2024-08-17 16:32:38 +02:00
Matthias
c2ac70ff10 feat: update base_strategy to include all imports 2024-08-17 16:30:06 +02:00
Matthias
e7b57d8dee chore: Update import for qtpylib to technical 2024-08-17 16:28:56 +02:00
Matthias
5bc8b02b0f feat: Update imports for sample strategy 2024-08-17 16:28:19 +02:00
Matthias
d6f96b2c53 chore: remove typing imports
These shouldn't be star imported, but should be explicitly imported.
2024-08-17 16:26:21 +02:00
Matthias
6c131b5648 chore: add comment to better explain imports 2024-08-17 16:25:47 +02:00
Matthias
27a4a502d7 docs: Add section explaining strategy imports 2024-08-17 16:25:02 +02:00
Matthias
f0a25ea485 feat: Add __all__ export to strategy's init file 2024-08-17 16:24:59 +02:00
Matthias
4ca6e61726 fix: use dynamic trading_mode for trades loading
closes #10540
2024-08-16 18:27:30 +02:00
Matthias
e26ac6ed00 test: speed up detail test 2024-08-15 20:02:45 +02:00
Matthias
f341edb975 feat: Enable websocket support for okx 2024-08-15 19:54:08 +02:00
Matthias
fdad24aaac feat: add leverage to telegram's /status table 2024-08-15 17:57:00 +02:00
Matthias
3a676f98db test: improve telegram balance test 2024-08-15 17:36:14 +02:00
Matthias
8498cb17e7 test: add explicit test for telegram's short behavior 2024-08-15 17:36:14 +02:00
Matthias
36098f6b78 test: update tests for removal of leverage 2024-08-15 17:36:14 +02:00
Matthias
34667c69d3 chore: remove leverage from /balance endpoint 2024-08-15 17:00:11 +02:00
Matthias
756fef53f9 refactor: improve live positions update 2024-08-15 17:00:08 +02:00
Matthias
2ffe938206 test: update test behavior - wallets has 0, never none 2024-08-15 08:21:25 +02:00
Matthias
d521699305 refactor: type fetch_positions response 2024-08-15 08:08:50 +02:00
Matthias
5ad23405b7 chore: align safevalue_fallback types 2024-08-15 08:06:50 +02:00
Matthias
04cdd807ba chore: improved type ordering 2024-08-15 07:30:21 +02:00
Matthias
646ed50f37 chore: improve typing for balance endpoint 2024-08-15 07:29:19 +02:00
Matthias
1b0ba0fa68 fix: typo in armhf dockerfile causing build to fail 2024-08-15 06:58:43 +02:00
Matthias
21c5c919ea chore: Improve typehinting for hyperopt 2024-08-15 06:50:22 +02:00
Matthias
d9f6f0847d docs: improve readability of hyperopt-loss sample 2024-08-15 06:50:04 +02:00
Matthias
59c897b53e Merge pull request #10534 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-08-15 06:21:15 +02:00
xmatthias
77b32e94f1 chore: update pre-commit hooks 2024-08-15 03:12:42 +00:00
Matthias
331159a3d8 fix: ensure handle_onexchange_order works without false warnings
futures were not properly handled in this command.

closes #10533
2024-08-14 21:18:51 +02:00
Matthias
23510c80be fix: don't auto-populate non-existing secret entries 2024-08-14 08:19:58 +02:00
Matthias
ef497beaea Merge pull request #10484 from freqtrade/feat/trades_async
Move trades-refresh to async
2024-08-13 15:18:26 +02:00
Matthias
6ea450a4e1 chore: bitvavo uses DECIMAL_PLACES for amount rounding
closes #9560
2024-08-13 14:30:01 +02:00
Matthias
f64786543d feat: hyperliquid requires different precision modes 2024-08-13 12:21:13 +02:00
Matthias
c60e00c77f Merge pull request #10530 from freqtrade/feat/price_precision_mode
add price_precision_mode
2024-08-13 12:20:53 +02:00
Matthias
7e502beafc Merge pull request #10527 from freqtrade/feat/bt_generator
Backtesting - dynamic pairlist sorting
2024-08-13 09:56:19 +02:00
Matthias
aa6c30ade6 chore: fix line too long issue 2024-08-13 09:50:34 +02:00
Matthias
0b8dfa6878 chore: improved docstring for precision_mode_price 2024-08-13 09:29:36 +02:00
Matthias
d7bee0c9e0 test: update further tests for precision_mode_price 2024-08-13 09:23:43 +02:00
Matthias
350c2241c4 test: adjust test mocks for precision_mode_price 2024-08-13 09:20:40 +02:00
Matthias
cfa591838f feat: use "precision_mode_price" where applicable 2024-08-13 09:13:10 +02:00
Matthias
ac1ac0debe feat: set precision_mode_price when creating trade objects 2024-08-13 09:11:44 +02:00
Matthias
54bc60b08f test: Update test for new to-json field 2024-08-13 09:10:50 +02:00
Matthias
f8de46cea9 feat: Add precision_mode_price column 2024-08-13 09:09:12 +02:00
Matthias
6fc2a604b4 Merge pull request #10529 from freqtrade/feat/list-data-trades
add --trades to list-data command
2024-08-13 08:13:23 +02:00
Matthias
1e410feed1 test: fix missing test arg 2024-08-13 07:33:13 +02:00
Matthias
948e67a2b7 docs: improved wording 2024-08-13 07:14:08 +02:00
Matthias
0f820e4498 chore: Fix 3.9 syntax error 2024-08-13 07:12:57 +02:00
Matthias
c7bc1b10e2 docs: fix messed up formatting 2024-08-13 07:04:51 +02:00
Matthias
ef04324f9d docs: update --help output docs 2024-08-13 07:03:00 +02:00
Matthias
2b86865b9b chore: improve wording in subcommand helptext 2024-08-13 07:02:53 +02:00
Matthias
f009625c1a docs: update list-data documentation 2024-08-13 07:01:23 +02:00
Matthias
a991c76842 feat: add test for test_list_data command 2024-08-13 06:54:35 +02:00
Matthias
d02ea3244a feat: add "trades" switch to list-data command 2024-08-13 06:46:02 +02:00
Matthias
5a9f87ac63 feat: add start_list_trades_data command to output trades data 2024-08-13 06:44:28 +02:00
Matthias
9bfd0cb63c feat: add test for trades_data_minmax 2024-08-13 06:43:26 +02:00
Matthias
3f4c19abbc chore: add test for trades_get_available_data 2024-08-13 06:40:11 +02:00
Matthias
cf26635e3c feat: add trades helper functions
trades_get_available data and trades_data_min_max
2024-08-13 06:36:39 +02:00
Matthias
e540862401 Merge pull request #10528 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-08-13 06:34:44 +02:00
xmatthias
263be72c11 chore: update pre-commit hooks 2024-08-13 03:02:59 +00:00
Matthias
b63c04df4f chore: update help wording 2024-08-12 20:11:43 +02:00
Matthias
784208dd87 chore: improve variable naming for ohlcv 2024-08-12 19:44:55 +02:00
Matthias
5cb6c234c4 chore: improve naming for refresh_latest_trades 2024-08-12 19:44:45 +02:00
Matthias
7972a023ed fix: oddly wrong fee_cost calculation 2024-08-12 16:45:51 +02:00
Matthias
6cf92c2a90 chore: enhanced aggregation syntax 2024-08-12 16:41:07 +02:00
Matthias
50835c878e chore: add more test coverage 2024-08-12 15:35:31 +02:00
Matthias
b727e5ca1c chore: simplify update code 2024-08-12 14:51:42 +02:00
Matthias
5773d1fd8d docs: Update documentation for new flow 2024-08-12 14:51:42 +02:00
Matthias
530226dbe8 chore: Add "use_detail" to detail test 2024-08-12 14:51:42 +02:00
Matthias
4882a18bf9 chore: add pair_detail test 2024-08-12 14:51:42 +02:00
Matthias
70f3018e67 feat: remove "open_trade_count_start" workaround
Due to the updated pair ordering logic, we can open trades on
different pairs during the same candle without
superating the max_open_trades limit
2024-08-12 14:51:42 +02:00
Matthias
08c10c1f9b chore: exclude right boundary from parallelism test 2024-08-12 14:51:42 +02:00
Matthias
7945eba386 feat: Evaluate pairs with open trades first
This will enable further improved logic for pairs with no open trade.
2024-08-12 14:51:42 +02:00
Matthias
b6f4e124ce chore: improve backtesting test details
ensure all candles used the same pairlist ordering
2024-08-12 14:51:42 +02:00
Matthias
f01e101447 feat: extract backtest iteration into generator 2024-08-12 14:51:42 +02:00
Matthias
980b81f009 chore: Simplify futures backtest 2024-08-12 14:51:37 +02:00
Matthias
10f0522a6b chore: update attribute wording to bt_profit 2024-08-12 10:57:53 +02:00
Matthias
2bc9cdafb2 chore: update attribute wording to bt_trades 2024-08-12 10:57:53 +02:00
Matthias
e643a2ea32 chore: update attribute wording to bt_trades_open 2024-08-12 10:57:53 +02:00
Matthias
b456afa2ac chore: improve backtesting test 2024-08-12 10:20:36 +02:00
Matthias
50b55c3f31 Merge pull request #10522 from freqtrade/dependabot/pip/develop/ccxt-4.3.79
chore(deps): bump ccxt from 4.3.76 to 4.3.79
2024-08-12 09:45:55 +02:00
Matthias
88b754e38c chore: update test to reflect a fix in ccxt 2024-08-12 09:14:36 +02:00
Matthias
b3868a77f1 Merge pull request #10525 from freqtrade/dependabot/pip/develop/orjson-3.10.7
chore(deps): bump orjson from 3.10.6 to 3.10.7
2024-08-12 07:11:24 +02:00
dependabot[bot]
16d5d7b318 chore(deps): bump ccxt from 4.3.76 to 4.3.79
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.3.76 to 4.3.79.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.3.76...4.3.79)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 04:43:08 +00:00
Matthias
dbef33fe00 Merge pull request #10524 from freqtrade/dependabot/pip/develop/ruff-0.5.7
chore(deps-dev): bump ruff from 0.5.6 to 0.5.7
2024-08-12 06:42:31 +02:00
Matthias
8c11ea69a0 Merge pull request #10523 from freqtrade/dependabot/pip/develop/aiohttp-3.10.3
chore(deps): bump aiohttp from 3.10.2 to 3.10.3
2024-08-12 06:39:37 +02:00
dependabot[bot]
fa0ee035e9 chore(deps): bump orjson from 3.10.6 to 3.10.7
Bumps [orjson](https://github.com/ijl/orjson) from 3.10.6 to 3.10.7.
- [Release notes](https://github.com/ijl/orjson/releases)
- [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ijl/orjson/compare/3.10.6...3.10.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 03:26:27 +00:00
dependabot[bot]
010dbf82f3 chore(deps-dev): bump ruff from 0.5.6 to 0.5.7
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.6 to 0.5.7.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.5.6...0.5.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 03:26:18 +00:00
dependabot[bot]
9a9d27b862 chore(deps): bump aiohttp from 3.10.2 to 3.10.3
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.2 to 3.10.3.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.2...v3.10.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-12 03:25:59 +00:00
Matthias
0afd3fc5e1 fix: improved handling for corrupt trades files
part of #10515
2024-08-10 20:13:36 +02:00
Matthias
f5ebfcca5a chore: accept that trades dataframes may be empty for some reason
part of #10515
2024-08-10 20:13:30 +02:00
Matthias
6f33115187 Merge pull request #10400 from simwai/feature/stoploss-start-at
Added unlock_at field for protection config
2024-08-10 16:40:09 +02:00
Matthias
58c65ab48c Merge pull request #10519 from freqtrade/dependabot/pip/aiohttp-3.10.2
chore(deps): bump aiohttp from 3.10.1 to 3.10.2
2024-08-10 08:06:29 +02:00
dependabot[bot]
42294ff695 chore(deps): bump aiohttp from 3.10.1 to 3.10.2
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.1 to 3.10.2.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.1...v3.10.2)

---
updated-dependencies:
- dependency-name: aiohttp
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-09 17:02:14 +00:00
Matthias
ed59f74cb8 chore: move asyncio import to only import when necessary 2024-08-09 06:45:08 +02:00
Matthias
d91dee141d Merge pull request #10500 from freqtrade/dependabot/pip/develop/aiohttp-3.10.1
chore(deps): bump aiohttp from 3.9.5 to 3.10.1
2024-08-09 06:44:19 +02:00
Matthias
2b4438720c chore: call selectorPolicy 2024-08-09 06:16:44 +02:00
Matthias
758e532a6a chore: add todo to uvicorn workaround 2024-08-08 20:25:17 +02:00
Matthias
d4ca6617de chore: set asyncio-policy for windows 2024-08-08 20:25:09 +02:00
Matthias
67fe7f8d3b Merge pull request #10513 from freqtrade/dependabot/pip/develop/matplotlib-3.9.1.post1
chore(deps): bump matplotlib from 3.9.0 to 3.9.1.post1
2024-08-08 18:18:11 +02:00
dependabot[bot]
f6040c5f06 chore(deps): bump aiohttp from 3.9.5 to 3.10.1
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.9.5 to 3.10.1.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.9.5...v3.10.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-08 07:53:56 +00:00
Matthias
324c384fdf Merge pull request #10514 from freqtrade/dependabot/pip/develop/ccxt-4.3.76
chore(deps): bump ccxt from 4.3.73 to 4.3.76
2024-08-08 09:53:00 +02:00
dependabot[bot]
de70ee117c chore(deps): bump matplotlib from 3.9.0 to 3.9.1.post1
Bumps [matplotlib](https://github.com/matplotlib/matplotlib) from 3.9.0 to 3.9.1.post1.
- [Release notes](https://github.com/matplotlib/matplotlib/releases)
- [Commits](https://github.com/matplotlib/matplotlib/compare/v3.9.0...v3.9.1.post1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-08 06:37:43 +00:00
dependabot[bot]
85844c8ed4 chore(deps): bump ccxt from 4.3.73 to 4.3.76
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.3.73 to 4.3.76.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.3.73...4.3.76)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-08 06:37:18 +00:00
Matthias
101dc850a2 Update pre-commit sqlalchemy types 2024-08-08 08:36:14 +02:00
Matthias
d88c7c76c6 Merge pull request #10512 from freqtrade/dependabot/pip/develop/time-machine-2.15.0
chore(deps-dev): bump time-machine from 2.14.2 to 2.15.0
2024-08-08 08:26:51 +02:00
Matthias
1fea5f53bd Merge pull request #10511 from freqtrade/dependabot/pip/develop/python-rapidjson-1.20
chore(deps): bump python-rapidjson from 1.19 to 1.20
2024-08-08 08:08:22 +02:00
Matthias
ca13109a20 Merge pull request #10510 from freqtrade/dependabot/pip/develop/sqlalchemy-2.0.32
chore(deps): bump sqlalchemy from 2.0.31 to 2.0.32
2024-08-08 07:19:16 +02:00
Matthias
5650de0627 chore: dependabot shouldn't update major versions 2024-08-08 06:25:52 +02:00
dependabot[bot]
999ee981f7 chore(deps-dev): bump time-machine from 2.14.2 to 2.15.0
Bumps [time-machine](https://github.com/adamchainz/time-machine) from 2.14.2 to 2.15.0.
- [Changelog](https://github.com/adamchainz/time-machine/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/adamchainz/time-machine/compare/2.14.2...2.15.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>
2024-08-08 04:22:36 +00:00
dependabot[bot]
ee6e78927f chore(deps): bump python-rapidjson from 1.19 to 1.20
Bumps [python-rapidjson](https://github.com/python-rapidjson/python-rapidjson) from 1.19 to 1.20.
- [Changelog](https://github.com/python-rapidjson/python-rapidjson/blob/master/CHANGES.rst)
- [Commits](https://github.com/python-rapidjson/python-rapidjson/compare/v1.19...v1.20)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-08 04:22:22 +00:00
dependabot[bot]
9dd9ae7a2f chore(deps): bump sqlalchemy from 2.0.31 to 2.0.32
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.31 to 2.0.32.
- [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases)
- [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst)
- [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>
2024-08-08 04:22:06 +00:00
Matthias
cbd178dab2 chore: bump armhf dockerfile to 3.11 2024-08-08 06:20:58 +02:00
Matthias
e34a28ee53 chore: dependabot should monitor /docker, too 2024-08-08 06:18:31 +02:00
Matthias
ffcc55b42b Merge pull request #10508 from freqtrade/dependabot/docker/python-3.12.5-slim-bookworm
chore(deps): bump python from 3.12.4-slim-bookworm to 3.12.5-slim-bookworm
2024-08-08 06:09:43 +02:00
Matthias
72028a9a2e Merge pull request #10507 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-08-08 06:09:28 +02:00
dependabot[bot]
d453aa849a chore(deps): bump python
Bumps python from 3.12.4-slim-bookworm to 3.12.5-slim-bookworm.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-08 03:26:08 +00:00
xmatthias
9d0cd961b4 chore: update pre-commit hooks 2024-08-08 03:12:08 +00:00
Matthias
900922760a feat: add json schema validation docs 2024-08-07 20:20:08 +02:00
Matthias
a1d02ca689 Merge pull request #10505 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-08-06 06:23:30 +02:00
xmatthias
95546e0a7b chore: update pre-commit hooks 2024-08-06 03:02:53 +00:00
Matthias
fa2fc63b7c Merge pull request #10498 from freqtrade/dependabot/pip/develop/pyjwt-2.9.0
chore(deps): bump pyjwt from 2.8.0 to 2.9.0
2024-08-05 13:35:16 +02:00
Matthias
4ad915761b Merge pull request #10495 from freqtrade/dependabot/pip/develop/ccxt-4.3.73
chore(deps): bump ccxt from 4.3.68 to 4.3.73
2024-08-05 12:02:22 +02:00
Matthias
d6a29c1ad5 Merge pull request #10501 from freqtrade/dependabot/pip/develop/mypy-1.11.1
chore(deps-dev): bump mypy from 1.11.0 to 1.11.1
2024-08-05 11:44:23 +02:00
dependabot[bot]
3d439c8c01 chore(deps): bump pyjwt from 2.8.0 to 2.9.0
Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.8.0 to 2.9.0.
- [Release notes](https://github.com/jpadilla/pyjwt/releases)
- [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/jpadilla/pyjwt/compare/2.8.0...2.9.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 08:58:46 +00:00
Matthias
af00374cb0 Merge pull request #10494 from freqtrade/dependabot/pip/develop/uvicorn-0.30.5
chore(deps): bump uvicorn from 0.30.3 to 0.30.5
2024-08-05 10:57:56 +02:00
Matthias
0bb46aaef4 Merge pull request #10489 from freqtrade/dependabot/pip/develop/mkdocs-fecff3054d
chore(deps): bump mkdocs-material from 9.5.30 to 9.5.31 in the mkdocs group
2024-08-05 10:29:46 +02:00
Matthias
00377e91b4 Merge pull request #10496 from freqtrade/dependabot/pip/develop/tqdm-4.66.5
chore(deps): bump tqdm from 4.66.4 to 4.66.5
2024-08-05 09:33:01 +02:00
dependabot[bot]
477448114a chore(deps-dev): bump mypy from 1.11.0 to 1.11.1
Bumps [mypy](https://github.com/python/mypy) from 1.11.0 to 1.11.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.11...v1.11.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 06:34:01 +00:00
dependabot[bot]
91da1c3f8b chore(deps): bump uvicorn from 0.30.3 to 0.30.5
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.30.3 to 0.30.5.
- [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.30.3...0.30.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 06:33:26 +00:00
Matthias
eddf99ddd0 Merge pull request #10492 from freqtrade/dependabot/pip/develop/ruff-0.5.6
chore(deps-dev): bump ruff from 0.5.5 to 0.5.6
2024-08-05 08:33:23 +02:00
Matthias
f27580b5ae Merge pull request #10493 from freqtrade/dependabot/pip/develop/technical-1.4.4
chore(deps): bump technical from 1.4.3 to 1.4.4
2024-08-05 08:33:15 +02:00
Matthias
b2d35751b4 Merge pull request #10490 from freqtrade/dependabot/pip/develop/fastapi-0.112.0
chore(deps): bump fastapi from 0.111.1 to 0.112.0
2024-08-05 08:32:31 +02:00
Matthias
18df06a7ce Merge pull request #10487 from froggleston/develop
Add rich table width if jupyter in modules
2024-08-05 06:53:43 +02:00
dependabot[bot]
0bee3c9db0 chore(deps): bump tqdm from 4.66.4 to 4.66.5
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.4 to 4.66.5.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.66.4...v4.66.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 03:27:30 +00:00
dependabot[bot]
e3ba28d767 chore(deps): bump ccxt from 4.3.68 to 4.3.73
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.3.68 to 4.3.73.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.3.68...4.3.73)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 03:27:23 +00:00
dependabot[bot]
1f9c2cd181 chore(deps): bump technical from 1.4.3 to 1.4.4
Bumps [technical](https://github.com/freqtrade/technical) from 1.4.3 to 1.4.4.
- [Release notes](https://github.com/freqtrade/technical/releases)
- [Commits](https://github.com/freqtrade/technical/compare/1.4.3...1.4.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 03:27:08 +00:00
dependabot[bot]
ea2b12a548 chore(deps-dev): bump ruff from 0.5.5 to 0.5.6
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.5 to 0.5.6.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.5.5...0.5.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 03:27:05 +00:00
dependabot[bot]
c9f4db2a4f chore(deps): bump fastapi from 0.111.1 to 0.112.0
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.111.1 to 0.112.0.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.111.1...0.112.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 03:26:45 +00:00
dependabot[bot]
1e5154c901 chore(deps): bump mkdocs-material in the mkdocs group
Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `mkdocs-material` from 9.5.30 to 9.5.31
- [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/9.5.30...9.5.31)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-08-05 03:26:37 +00:00
froggleston
17dc41279c Ruff formatting 2024-08-04 21:59:07 +01:00
froggleston
cb4747aed2 Add rich table width if jupyter in modules 2024-08-04 21:43:00 +01:00
Matthias
ce8d03ddce chore: improve comment as to why matplotlib is pinned in the first place 2024-08-04 19:55:40 +02:00
Matthias
366c7e2b91 fix: pin matplotlib to 3.9.0 to fix windows wheels
caused by the partial yank of 3.9.1 (only the windows wheels have been deleted).

Ref: https://github.com/matplotlib/matplotlib/issues/28551
2024-08-04 19:54:51 +02:00
Matthias
6c5fb5e22b chore: add_config_files to config_schema 2024-08-04 09:03:32 +02:00
Matthias
4854bdd02f chore: Add log_responses to config schema 2024-08-04 08:29:15 +02:00
Matthias
d7ecdc9b07 chore: Downgrade cryptography for RPI
https://github.com/piwheels/packages/issues/464
2024-08-03 18:31:39 +02:00
Matthias
c8d30ae801 chore: fix oneline exchange-list output 2024-08-03 18:30:21 +02:00
Matthias
9eebe82b34 chore: fix api-server tests 2024-08-03 18:29:16 +02:00
Matthias
b3915ff8fd chore: use classname to show exchanges 2024-08-03 18:25:31 +02:00
Matthias
805c946b33 feat: improve structure of list_exchange endpoints 2024-08-03 18:24:52 +02:00
Matthias
8bc1949466 docs: update link to technical documentation 2024-08-03 16:41:22 +02:00
Matthias
a6689b1035 chore: Remove unnecessary, duplicate mkdocs install 2024-08-03 13:29:54 +02:00
Matthias
f63910d355 chore: improve wording for cooldown_period 2024-08-02 20:15:46 +02:00
Matthias
98c8521057 chore: fix minor gotcha 2024-08-02 20:13:59 +02:00
Matthias
57139295b5 tests: Add unlock_at test 2024-08-02 20:12:44 +02:00
Matthias
79d4dc1646 Merge branch 'develop' into feature/stoploss-start-at 2024-08-02 19:51:28 +02:00
Matthias
1760624954 test: Test "invalid date format" 2024-08-02 19:48:43 +02:00
Matthias
9429657a2b chore: make Hyperliquid class actually usable 2024-08-02 07:28:09 +02:00
Matthias
2b0b1e23eb chore: enhance error message on ohlcv error 2024-08-02 07:26:54 +02:00
Matthias
dd55baf148 chore: support snake_case for api keys 2024-08-02 07:26:54 +02:00
Matthias
4542157192 Merge pull request #10470 from freqtrade/dependabot/pip/develop/torch-2.4.0
chore(deps): bump torch from 2.2.2 to 2.4.0
2024-08-02 06:56:13 +02:00
Matthias
9e47172d69 chore: Reduce test flakyness of ws test 2024-08-01 20:37:40 +02:00
Matthias
a840969512 feat: move trades-refresh to async 2024-08-01 19:58:17 +02:00
Matthias
67fdfdf584 chore: Update schema file 2024-08-01 19:39:06 +02:00
Matthias
abef8e376c feat: add $schema to config examples 2024-08-01 07:03:34 +02:00
Matthias
8a85077e70 chore: add download_trades config key, reorder some keys 2024-08-01 07:02:47 +02:00
Matthias
b3ac296cac chore: Improve schema wording 2024-08-01 06:58:17 +02:00
Matthias
b2db733c83 Merge pull request #10482 from freqtrade/maint/remove_pip_24_lock
chore: Remove pip pin from ci
2024-08-01 06:43:31 +02:00
Matthias
92dfcf3b6d Merge pull request #10483 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-08-01 06:43:19 +02:00
xmatthias
af554fc3f7 chore: update pre-commit hooks 2024-08-01 03:15:58 +00:00
Matthias
02621eee74 chore: remove pip version lock from instal scripts 2024-07-31 20:39:21 +02:00
Matthias
8105f51603 chore: remove pip lock from Dockerfiles 2024-07-31 20:39:12 +02:00
Matthias
c40ac27d71 chore: Remove pip pin from ci 2024-07-31 20:36:48 +02:00
Matthias
d33c930f26 Merge pull request #10454 from jainanuj94/feature/10348
Add  percent change pairlist
2024-07-30 20:58:27 +02:00
Matthias
eb0fc0fc80 docs: Fix minor typo 2024-07-30 20:29:21 +02:00
Matthias
a6563543a3 Merge pull request #10477 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-07-30 06:10:05 +02:00
xmatthias
40b20c5595 chore: update pre-commit hooks 2024-07-30 03:02:51 +00:00
Matthias
1ebbfffd2a chore: hyperliquid doesn't have historic ohlcv 2024-07-29 19:42:28 +02:00
Matthias
24d3e09618 Merge pull request #10467 from freqtrade/dependabot/pip/develop/pre-commit-3.8.0
chore(deps-dev): bump pre-commit from 3.7.1 to 3.8.0
2024-07-29 14:24:09 +02:00
Matthias
d92ddc4c7a Merge pull request #10471 from freqtrade/dependabot/pip/develop/pymdown-extensions-10.9
chore(deps): bump pymdown-extensions from 10.8.1 to 10.9
2024-07-29 11:38:11 +02:00
dependabot[bot]
c8b7580830 chore(deps-dev): bump pre-commit from 3.7.1 to 3.8.0
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.7.1 to 3.8.0.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v3.7.1...v3.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 08:27:01 +00:00
Matthias
a9d0c052bc Merge pull request #10468 from freqtrade/dependabot/pip/develop/ruff-0.5.5
chore(deps-dev): bump ruff from 0.5.4 to 0.5.5
2024-07-29 10:26:15 +02:00
Matthias
ad0b349a3f Merge pull request #10474 from freqtrade/dependabot/pip/develop/lightgbm-4.5.0
chore(deps): bump lightgbm from 4.4.0 to 4.5.0
2024-07-29 08:50:36 +02:00
Matthias
9da51a8d85 Merge pull request #10473 from freqtrade/dependabot/pip/develop/plotly-5.23.0
chore(deps): bump plotly from 5.22.0 to 5.23.0
2024-07-29 08:50:23 +02:00
Matthias
814f21a50e Merge pull request #10472 from freqtrade/dependabot/pip/develop/python-rapidjson-1.19
chore(deps): bump python-rapidjson from 1.18 to 1.19
2024-07-29 08:49:54 +02:00
Matthias
092669fb9d Merge pull request #10469 from freqtrade/dependabot/pip/develop/ccxt-4.3.68
chore(deps): bump ccxt from 4.3.65 to 4.3.68
2024-07-29 07:45:22 +02:00
dependabot[bot]
3789e1339b chore(deps): bump pymdown-extensions from 10.8.1 to 10.9
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.8.1 to 10.9.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.8.1...10.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 05:32:28 +00:00
dependabot[bot]
a1490d07b4 chore(deps-dev): bump ruff from 0.5.4 to 0.5.5
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.5.4 to 0.5.5.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.5.4...0.5.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 05:32:28 +00:00
Matthias
08144382b8 Merge pull request #10466 from freqtrade/dependabot/pip/develop/mkdocs-1c45c0a9b7
chore(deps): bump mkdocs-material from 9.5.29 to 9.5.30 in the mkdocs group
2024-07-29 07:31:51 +02:00
Matthias
cb90e1388f Merge pull request #10465 from freqtrade/dependabot/pip/develop/pytest-d2133643f0
chore(deps-dev): bump pytest from 8.3.1 to 8.3.2 in the pytest group
2024-07-29 07:31:37 +02:00
Matthias
5e1038dc67 chore: Fix torch version bump 2024-07-29 07:00:17 +02:00
dependabot[bot]
5e852ebb5d chore(deps): bump lightgbm from 4.4.0 to 4.5.0
Bumps [lightgbm](https://github.com/microsoft/LightGBM) from 4.4.0 to 4.5.0.
- [Release notes](https://github.com/microsoft/LightGBM/releases)
- [Commits](https://github.com/microsoft/LightGBM/compare/v4.4.0...v4.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 03:08:24 +00:00
dependabot[bot]
097786c62d chore(deps): bump plotly from 5.22.0 to 5.23.0
Bumps [plotly](https://github.com/plotly/plotly.py) from 5.22.0 to 5.23.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.22.0...v5.23.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 03:08:18 +00:00
dependabot[bot]
baeced32c3 chore(deps): bump python-rapidjson from 1.18 to 1.19
Bumps [python-rapidjson](https://github.com/python-rapidjson/python-rapidjson) from 1.18 to 1.19.
- [Changelog](https://github.com/python-rapidjson/python-rapidjson/blob/master/CHANGES.rst)
- [Commits](https://github.com/python-rapidjson/python-rapidjson/compare/v1.18...v1.19)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 03:08:10 +00:00
dependabot[bot]
2f4e4343c2 chore(deps): bump torch from 2.2.2 to 2.4.0
Bumps [torch](https://github.com/pytorch/pytorch) from 2.2.2 to 2.4.0.
- [Release notes](https://github.com/pytorch/pytorch/releases)
- [Changelog](https://github.com/pytorch/pytorch/blob/main/RELEASE.md)
- [Commits](https://github.com/pytorch/pytorch/compare/v2.2.2...v2.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 03:07:44 +00:00
dependabot[bot]
9fd6d7318e chore(deps): bump ccxt from 4.3.65 to 4.3.68
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.3.65 to 4.3.68.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.3.65...4.3.68)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 03:07:22 +00:00
dependabot[bot]
fd2be958ba chore(deps): bump mkdocs-material in the mkdocs group
Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `mkdocs-material` from 9.5.29 to 9.5.30
- [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/9.5.29...9.5.30)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 03:06:12 +00:00
dependabot[bot]
719889b27a chore(deps-dev): bump pytest from 8.3.1 to 8.3.2 in the pytest group
Bumps the pytest group with 1 update: [pytest](https://github.com/pytest-dev/pytest).


Updates `pytest` from 8.3.1 to 8.3.2
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/8.3.1...8.3.2)

---
updated-dependencies:
- dependency-name: pytest
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: pytest
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-07-29 03:05:45 +00:00
Matthias
faaa1050da chore: Bump dev version to 2024.8 2024-07-28 20:10:30 +02:00
jainanuj94
27aed5cd7e Update schema.json 2024-07-28 22:34:34 +05:30
jainanuj94
aa327643f5 Merge remote-tracking branch 'upstream/develop' into feature/10348 2024-07-28 22:26:58 +05:30
jainanuj94
ac1e405c34 Update documentation and fix doc test 2024-07-28 21:10:20 +05:30
jainanuj94
4932473b3f Add documentation 2024-07-27 23:41:32 +05:30
Matthias
206baf7d80 chore: add a bit of typehinting 2024-07-27 16:13:17 +02:00
Matthias
4ac7a4fdab Allow empty min_Value setting... 2024-07-27 16:07:51 +02:00
Matthias
283e8045d8 PercentChangePairlist should partecipate in regular tests 2024-07-27 16:05:59 +02:00
Matthias
8637f4a70d Remove SortKey dynamics and setting 2024-07-27 16:04:51 +02:00
jainanuj94
4a768682ea Remove unnecessary logs and up description 2024-07-26 13:13:26 +05:30
jainanuj94
dad4f30597 Correct calculation for percent calculation and use tickers 2024-07-25 23:33:28 +05:30
jainanuj94
1b81de01b4 10348 | run ruff formatter 2024-07-25 00:04:06 +05:30
jainanuj94
b09f9e8c12 10348 | Update tests and add pairlist constants 2024-07-24 19:12:11 +05:30
jainanuj94
4b1177e07e 10348 | Create new pair list to dynamically fetch pairs based on percent volume change 2024-07-24 19:09:45 +05:30
simwai
f714d1ab28 Added unlock_at field to protections document 2024-07-18 15:08:12 +02:00
Matthias
dcc9d20cca Remove unnecessary statement 2024-07-16 07:31:11 +02:00
Matthias
d590ab003f Add unlock_at config test, simplify validation 2024-07-16 07:26:41 +02:00
Matthias
a3c52445ee Simplify validation 2024-07-16 07:14:46 +02:00
Matthias
be3fcd90e2 Remove unneeded property 2024-07-16 07:14:33 +02:00
Matthias
26aa336450 Combine "until" logic into calculate_lock_end 2024-07-16 07:05:42 +02:00
Matthias
65972d9c0c Add cooldown with timeperiod test 2024-07-16 06:51:31 +02:00
Matthias
d13f47ec0b align wording to simplify "locking for" element 2024-07-16 06:48:30 +02:00
Matthias
1e36bc98b9 chore: Remove unused method 2024-07-16 06:35:32 +02:00
Matthias
16dd86e732 _unlock_at should be private 2024-07-16 06:31:12 +02:00
simwai
be894664ef Fixed building of wrong reason texts
Removed unnecessary method set_unlock_at_as_stop_duration()
2024-07-14 21:47:43 +02:00
Simon Waiblinger
f126120421 Merge branch 'freqtrade:develop' into feature/stoploss-start-at 2024-07-05 22:23:56 +02:00
simwai
af505b346c Fixed an access on the config by a wrong config key 2024-07-05 22:17:40 +02:00
simwai
77b4689ac8 Fixed implementation of unlock_at and updated unit tests 2024-07-05 22:14:35 +02:00
simwai
57118691d8 Removed entry in gitignore 2024-07-04 10:53:14 +02:00
simwai
2b456cbdeb Added unlock_at field for protection config 2024-07-04 10:29:13 +02:00
Simon Waiblinger
1d3ca5743b Merge branch 'freqtrade:develop' into develop 2024-06-27 16:38:13 +02:00
Simon Waiblinger
5f0a27d355 Merge branch 'freqtrade:develop' into develop 2024-06-02 15:38:56 +02:00
Simon Waiblinger
42d0f342b2 Merge branch 'freqtrade:develop' into develop 2024-05-30 10:10:03 +02:00
Simon Waiblinger
c12adea655 Merge branch 'freqtrade:develop' into develop 2024-05-26 15:59:29 +02:00
Simon Waiblinger
2a82e00857 Merge branch 'freqtrade:develop' into develop 2024-05-24 20:47:41 +02:00
125 changed files with 6040 additions and 1964 deletions

View File

@@ -1,9 +1,14 @@
version: 2
updates:
- package-ecosystem: docker
directory: "/"
directories:
- "/"
- "/docker"
schedule:
interval: daily
ignore:
- dependency-name: "*"
update-types: ["version-update:semver-major"]
open-pull-requests-limit: 10
- package-ecosystem: pip

View File

@@ -55,7 +55,7 @@ jobs:
- name: Installation - *nix
run: |
python -m pip install --upgrade "pip<=24.0" wheel
python -m pip install --upgrade pip wheel
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
export TA_INCLUDE_PATH=${HOME}/dependencies/include
@@ -197,7 +197,7 @@ jobs:
- name: Installation (python)
run: |
python -m pip install --upgrade "pip<=24.0" wheel
python -m pip install --upgrade pip wheel
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
export TA_INCLUDE_PATH=${HOME}/dependencies/include
@@ -384,7 +384,6 @@ jobs:
- name: Documentation build
run: |
pip install -r docs/requirements-docs.txt
pip install mkdocs
mkdocs build
- name: Discord notification
@@ -427,7 +426,7 @@ jobs:
- name: Installation - *nix
run: |
python -m pip install --upgrade "pip<=24.0" wheel
python -m pip install --upgrade pip wheel
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
export TA_INCLUDE_PATH=${HOME}/dependencies/include

55
.github/workflows/deploy-docs.yml vendored Normal file
View File

@@ -0,0 +1,55 @@
name: Build Documentation
on:
push:
branches:
- develop
release:
types: [published]
# disable permissions for all of the available permissions
permissions: {}
jobs:
build-docs:
permissions:
contents: write # for mike to push
name: Deploy Docs through mike
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r docs/requirements-docs.txt
- name: Fetch gh-pages branch
run: |
git fetch origin gh-pages --depth=1
- name: Configure Git user
run: |
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
- name: Build and push Mike
if: ${{ github.event_name == 'push' }}
run: |
mike deploy ${{ github.ref_name }} latest --push --update-aliases
- name: Build and push Mike - Release
if: ${{ github.event_name == 'release' }}
run: |
mike deploy ${{ github.ref_name }} stable --push --update-aliases
- name: Show mike versions
run: |
mike list

2
.gitignore vendored
View File

@@ -114,3 +114,5 @@ target/
!config_examples/config_full.example.json
!config_examples/config_kraken.example.json
!config_examples/config_freqai.example.json
docker-compose-*.yml

View File

@@ -2,24 +2,24 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pycqa/flake8
rev: "7.1.0"
rev: "7.1.1"
hooks:
- id: flake8
additional_dependencies: [Flake8-pyproject]
# stages: [push]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.11.0"
rev: "v1.11.2"
hooks:
- id: mypy
exclude: build_helpers
additional_dependencies:
- types-cachetools==5.4.0.20240717
- types-cachetools==5.5.0.20240820
- types-filelock==3.2.7
- types-requests==2.32.0.20240712
- types-tabulate==0.9.0.20240106
- types-python-dateutil==2.9.0.20240316
- SQLAlchemy==2.0.31
- types-python-dateutil==2.9.0.20240821
- SQLAlchemy==2.0.32
# stages: [push]
- repo: https://github.com/pycqa/isort
@@ -31,7 +31,7 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.5.4'
rev: 'v0.6.2'
hooks:
- id: ruff

View File

@@ -1,4 +1,4 @@
FROM python:3.12.4-slim-bookworm as base
FROM python:3.12.5-slim-bookworm as base
# Setup env
ENV LANG C.UTF-8
@@ -25,7 +25,7 @@ FROM base as python-deps
RUN apt-get update \
&& apt-get -y install build-essential libssl-dev git libffi-dev libgfortran5 pkg-config cmake gcc \
&& apt-get clean \
&& pip install --upgrade "pip<=24.0" wheel
&& pip install --upgrade pip wheel
# Install TA-lib
COPY build_helpers/* /tmp/

View File

@@ -86,41 +86,50 @@ For further (native) installation methods, please refer to the [Installation doc
```
usage: freqtrade [-h] [-V]
{trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver}
{trade,create-userdir,new-config,show-config,new-strategy,download-data,convert-data,convert-trade-data,trades-to-ohlcv,list-data,backtesting,backtesting-show,backtesting-analysis,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-markets,list-pairs,list-strategies,list-freqaimodels,list-timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-dataframe,plot-profit,webserver,strategy-updater,lookahead-analysis,recursive-analysis}
...
Free, open source crypto trading bot
positional arguments:
{trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver}
{trade,create-userdir,new-config,show-config,new-strategy,download-data,convert-data,convert-trade-data,trades-to-ohlcv,list-data,backtesting,backtesting-show,backtesting-analysis,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-markets,list-pairs,list-strategies,list-freqaimodels,list-timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-dataframe,plot-profit,webserver,strategy-updater,lookahead-analysis,recursive-analysis}
trade Trade module.
create-userdir Create user-data directory.
new-config Create new config
show-config Show resolved config
new-strategy Create new strategy
download-data Download backtesting data.
convert-data Convert candle (OHLCV) data from one format to
another.
convert-trade-data Convert trade data from one format to another.
trades-to-ohlcv Convert trade data to OHLCV data.
list-data List downloaded data.
backtesting Backtesting module.
backtesting-show Show past Backtest results
backtesting-analysis
Backtest Analysis module.
edge Edge module.
hyperopt Hyperopt module.
hyperopt-list List Hyperopt results
hyperopt-show Show details of Hyperopt results
list-exchanges Print available exchanges.
list-hyperopts Print available hyperopt classes.
list-markets Print markets on exchange.
list-pairs Print pairs on exchange.
list-strategies Print available strategies.
list-freqaimodels Print available freqAI models.
list-timeframes Print available timeframes for the exchange.
show-trades Show trades.
test-pairlist Test your pairlist configuration.
convert-db Migrate database to different system
install-ui Install FreqUI
plot-dataframe Plot candles with indicators.
plot-profit Generate plot showing profits.
webserver Webserver module.
strategy-updater updates outdated strategy files to the current version
lookahead-analysis Check for potential look ahead bias.
recursive-analysis Check for potential recursive formula issue.
optional arguments:
options:
-h, --help show this help message and exit
-V, --version show program's version number and exit

View File

@@ -1,6 +1,6 @@
# vendored Wheels compiled via https://github.com/xmatthias/ta-lib-python/tree/ta_bundled_040
python -m pip install --upgrade "pip<=24.0" wheel
python -m pip install --upgrade pip wheel
$pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"

View File

@@ -9,11 +9,6 @@
],
"minimum": -1
},
"new_pairs_days": {
"description": "Download data of new pairs for given number of days",
"type": "integer",
"default": 30
},
"timeframe": {
"description": "The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). \nUsually specified in the strategy and missing in the configuration.",
"type": "string"
@@ -562,6 +557,7 @@
"enum": [
"StaticPairList",
"VolumePairList",
"PercentChangePairList",
"ProducerPairList",
"RemotePairList",
"MarketCapPairList",
@@ -609,6 +605,10 @@
"type": "number",
"minimum": 0
},
"unlock_at": {
"description": "Time when trading will be unlocked regularly. Format: HH:MM",
"type": "string"
},
"trade_limit": {
"description": "Minimum number of trades required during lookback period.",
"type": "number",
@@ -1064,7 +1064,7 @@
"default": {},
"properties": {
"process_throttle_secs": {
"description": "Throttle time in seconds for processing.",
"description": "Minimum loop duration for one bot iteration in seconds.",
"type": "integer"
},
"interval": {
@@ -1105,6 +1105,15 @@
"description": "Enable position adjustment. \nUsually specified in the strategy and missing in the configuration.",
"type": "boolean"
},
"new_pairs_days": {
"description": "Download data of new pairs for given number of days",
"type": "integer",
"default": 30
},
"download_trades": {
"description": "Download trades data by default (instead of ohlcv data).",
"type": "boolean"
},
"max_entry_position_adjustment": {
"description": "Maximum entry position adjustment allowed. \nUsually specified in the strategy and missing in the configuration.",
"type": [
@@ -1113,6 +1122,13 @@
],
"minimum": -1
},
"add_config_files": {
"description": "Additional configuration files to load.",
"type": "array",
"items": {
"type": "string"
}
},
"orderflow": {
"description": "Settings related to order flow.",
"type": "object",
@@ -1208,6 +1224,11 @@
},
"uniqueItems": true
},
"log_responses": {
"description": "Log responses from the exchange.Useful/required to debug issues with order processing.",
"type": "boolean",
"default": false
},
"unknown_fee_rate": {
"description": "Fee rate for unknown markets.",
"type": "number"

View File

@@ -1,4 +1,5 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 3,
"stake_currency": "USDT",
"stake_amount": 0.05,

View File

@@ -1,4 +1,5 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"trading_mode": "futures",
"margin_mode": "isolated",
"max_open_trades": 5,

View File

@@ -1,4 +1,5 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 3,
"stake_currency": "BTC",
"stake_amount": 0.05,

View File

@@ -1,4 +1,5 @@
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 5,
"stake_currency": "EUR",
"stake_amount": 10,

View File

@@ -1,4 +1,4 @@
FROM python:3.11.8-slim-bookworm as base
FROM python:3.11.9-slim-bookworm as base
# Setup env
ENV LANG C.UTF-8
@@ -17,7 +17,7 @@ RUN mkdir /freqtrade \
&& chown ftuser:ftuser /freqtrade \
# Allow sudoers
&& echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers \
&& pip install --upgrade "pip<=24.0"
&& pip install --upgrade pip
WORKDIR /freqtrade

View File

@@ -30,11 +30,17 @@ class SuperDuperHyperOptLoss(IHyperOptLoss):
"""
@staticmethod
def hyperopt_loss_function(results: DataFrame, trade_count: int,
min_date: datetime, max_date: datetime,
config: Config, processed: Dict[str, DataFrame],
backtest_stats: Dict[str, Any],
*args, **kwargs) -> float:
def hyperopt_loss_function(
*,
results: DataFrame,
trade_count: int,
min_date: datetime,
max_date: datetime,
config: Config,
processed: Dict[str, DataFrame],
backtest_stats: Dict[str, Any],
**kwargs,
) -> float:
"""
Objective function, returns smaller number for better results
This is the legacy algorithm (used until now in freqtrade).

View File

@@ -530,10 +530,10 @@ You can then load the trades to perform further analysis as shown in the [data a
Since backtesting lacks some detailed information about what happens within a candle, it needs to take a few assumptions:
- Exchange [trading limits](#trading-limits-in-backtesting) are respected
- Entries happen at open-price
- Entries happen at open-price unless a custom price logic has been specified
- All orders are filled at the requested price (no slippage) as long as the price is within the candle's high/low range
- Exit-signal exits happen at open-price of the consecutive candle
- Exits don't free their trade slot for a new trade until the next candle
- Exits free their trade slot for a new trade with a different pair
- Exit-signal is favored over Stoploss, because exit-signals are assumed to trigger on candle's open
- ROI
- Exits are compared to high - but the ROI value is used (e.g. ROI = 2%, high=5% - so the exit will be at 2%)

View File

@@ -12,41 +12,50 @@ This page explains the different parameters of the bot and how to run it.
```
usage: freqtrade [-h] [-V]
{trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver}
{trade,create-userdir,new-config,show-config,new-strategy,download-data,convert-data,convert-trade-data,trades-to-ohlcv,list-data,backtesting,backtesting-show,backtesting-analysis,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-markets,list-pairs,list-strategies,list-freqaimodels,list-timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-dataframe,plot-profit,webserver,strategy-updater,lookahead-analysis,recursive-analysis}
...
Free, open source crypto trading bot
positional arguments:
{trade,create-userdir,new-config,new-strategy,download-data,convert-data,convert-trade-data,list-data,backtesting,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-hyperopts,list-markets,list-pairs,list-strategies,list-timeframes,show-trades,test-pairlist,install-ui,plot-dataframe,plot-profit,webserver}
{trade,create-userdir,new-config,show-config,new-strategy,download-data,convert-data,convert-trade-data,trades-to-ohlcv,list-data,backtesting,backtesting-show,backtesting-analysis,edge,hyperopt,hyperopt-list,hyperopt-show,list-exchanges,list-markets,list-pairs,list-strategies,list-freqaimodels,list-timeframes,show-trades,test-pairlist,convert-db,install-ui,plot-dataframe,plot-profit,webserver,strategy-updater,lookahead-analysis,recursive-analysis}
trade Trade module.
create-userdir Create user-data directory.
new-config Create new config
show-config Show resolved config
new-strategy Create new strategy
download-data Download backtesting data.
convert-data Convert candle (OHLCV) data from one format to
another.
convert-trade-data Convert trade data from one format to another.
trades-to-ohlcv Convert trade data to OHLCV data.
list-data List downloaded data.
backtesting Backtesting module.
backtesting-show Show past Backtest results
backtesting-analysis
Backtest Analysis module.
edge Edge module.
hyperopt Hyperopt module.
hyperopt-list List Hyperopt results
hyperopt-show Show details of Hyperopt results
list-exchanges Print available exchanges.
list-hyperopts Print available hyperopt classes.
list-markets Print markets on exchange.
list-pairs Print pairs on exchange.
list-strategies Print available strategies.
list-freqaimodels Print available freqAI models.
list-timeframes Print available timeframes for the exchange.
show-trades Show trades.
test-pairlist Test your pairlist configuration.
convert-db Migrate database to different system
install-ui Install FreqUI
plot-dataframe Plot candles with indicators.
plot-profit Generate plot showing profits.
webserver Webserver module.
strategy-updater updates outdated strategy files to the current version
lookahead-analysis Check for potential look ahead bias.
recursive-analysis Check for potential recursive formula issue.
optional arguments:
options:
-h, --help show this help message and exit
-V, --version show program's version number and exit

View File

@@ -123,6 +123,19 @@ This is similar to using multiple `--config` parameters, but simpler in usage as
If multiple files are in the `add_config_files` section, then they will be assumed to be at identical levels, having the last occurrence override the earlier config (unless a parent already defined such a key).
## Editor autocomplete and validation
If you are using an editor that supports JSON schema, you can use the schema provided by Freqtrade to get autocompletion and validation of your configuration file by adding the following line to the top of your configuration file:
``` json
{
"$schema": "https://schema.freqtrade.io/schema.json",
}
```
??? Note "Develop version"
The develop schema is available as `https://schema.freqtrade.io/schema_dev.json` - though we recommend to stick to the stable version for the best experience.
## Configuration parameters
The table below will list all configuration parameters available.

View File

@@ -423,7 +423,8 @@ You can get a list of downloaded data using the `list-data` sub-command.
usage: freqtrade list-data [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [--exchange EXCHANGE]
[--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}]
[-p PAIRS [PAIRS ...]]
[--data-format-trades {json,jsongz,hdf5,feather,parquet}]
[--trades] [-p PAIRS [PAIRS ...]]
[--trading-mode {spot,margin,futures}]
[--show-timerange]
@@ -433,6 +434,10 @@ options:
--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}
Storage format for downloaded candle (OHLCV) data.
(default: `feather`).
--data-format-trades {json,jsongz,hdf5,feather,parquet}
Storage format for downloaded trades data. (default:
`feather`).
--trades Work on trades data instead of OHLCV data.
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Limit command to these pairs. Pairs are space-
separated.
@@ -465,13 +470,29 @@ Common arguments:
```bash
> freqtrade list-data --userdir ~/.freqtrade/user_data/
Found 33 pair / timeframe combinations.
pairs timeframe
---------- -----------------------------------------
ADA/BTC 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d
ADA/ETH 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d
ETH/BTC 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d
ETH/USDT 5m, 15m, 30m, 1h, 2h, 4h
Found 33 pair / timeframe combinations.
┏━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┳━━━━━━┓
┃ Pair ┃ Timeframe ┃ Type ┃
┡━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━╇━━━━━━┩
│ ADA/BTC │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │
│ ADA/ETH │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │
│ ETH/BTC │ 5m, 15m, 30m, 1h, 2h, 4h, 6h, 12h, 1d │ spot │
│ ETH/USDT │ 5m, 15m, 30m, 1h, 2h, 4h │ spot │
└───────────────┴───────────────────────────────────────────┴──────┘
```
Show all trades data including from/to timerange
``` bash
> freqtrade list-data --show --trades
Found trades data for 1 pair.
┏━━━━━━━━━┳━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━┳━━━━━━━━┓
┃ Pair ┃ Type ┃ From ┃ To ┃ Trades ┃
┡━━━━━━━━━╇━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━╇━━━━━━━━┩
│ XRP/ETH │ spot │ 2019-10-11 00:00:11 │ 2019-10-13 11:19:28 │ 12477 │
└─────────┴──────┴─────────────────────┴─────────────────────┴────────┘
```
## Trades (tick) data

View File

@@ -2,11 +2,11 @@
Pairlist Handlers define the list of pairs (pairlist) that the bot should trade. They are configured in the `pairlists` section of the configuration settings.
In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list) Pairlist Handler).
In your configuration, you can use Static Pairlist (defined by the [`StaticPairList`](#static-pair-list) Pairlist Handler) and Dynamic Pairlist (defined by the [`VolumePairList`](#volume-pair-list) and [`PercentChangePairList`](#percent-change-pair-list) Pairlist Handlers).
Additionally, [`AgeFilter`](#agefilter), [`PrecisionFilter`](#precisionfilter), [`PriceFilter`](#pricefilter), [`ShuffleFilter`](#shufflefilter), [`SpreadFilter`](#spreadfilter) and [`VolatilityFilter`](#volatilityfilter) act as Pairlist Filters, removing certain pairs and/or moving their positions in the pairlist.
If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You can define either `StaticPairList`, `VolumePairList`, `ProducerPairList`, `RemotePairList` or `MarketCapPairList` as the starting Pairlist Handler.
If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You can define either `StaticPairList`, `VolumePairList`, `ProducerPairList`, `RemotePairList`, `MarketCapPairList` or `PercentChangePairList` as the starting Pairlist Handler.
Inactive markets are always removed from the resulting pairlist. Explicitly blacklisted pairs (those in the `pair_blacklist` configuration setting) are also always removed from the resulting pairlist.
@@ -22,6 +22,7 @@ You may also use something like `.*DOWN/BTC` or `.*UP/BTC` to exclude leveraged
* [`StaticPairList`](#static-pair-list) (default, if not configured differently)
* [`VolumePairList`](#volume-pair-list)
* [`PercentChangePairList`](#percent-change-pair-list)
* [`ProducerPairList`](#producerpairlist)
* [`RemotePairList`](#remotepairlist)
* [`MarketCapPairList`](#marketcappairlist)
@@ -152,6 +153,89 @@ More sophisticated approach can be used, by using `lookback_timeframe` for candl
!!! Note
`VolumePairList` does not support backtesting mode.
#### Percent Change Pair List
`PercentChangePairList` filters and sorts pairs based on the percentage change in their price over the last 24 hours or any defined timeframe as part of advanced options. This allows traders to focus on assets that have experienced significant price movements, either positive or negative.
**Configuration Options**
* `number_assets`: Specifies the number of top pairs to select based on the 24-hour percentage change.
* `min_value`: Sets a minimum percentage change threshold. Pairs with a percentage change below this value will be filtered out.
* `max_value`: Sets a maximum percentage change threshold. Pairs with a percentage change above this value will be filtered out.
* `sort_direction`: Specifies the order in which pairs are sorted based on their percentage change. Accepts two values: `asc` for ascending order and `desc` for descending order.
* `refresh_period`: Defines the interval (in seconds) at which the pairlist will be refreshed. The default is 1800 seconds (30 minutes).
* `lookback_days`: Number of days to look back. When `lookback_days` is selected, the `lookback_timeframe` is defaulted to 1 day.
* `lookback_timeframe`: Timeframe to use for the lookback period.
* `lookback_period`: Number of periods to look back at.
When PercentChangePairList is used after other Pairlist Handlers, it will operate on the outputs of those handlers. If it is the leading Pairlist Handler, it will select pairs from all available markets with the specified stake currency.
`PercentChangePairList` uses ticker data from the exchange, provided via the ccxt library:
The percentage change is calculated as the change in price over the last 24 hours.
??? Note "Unsupported exchanges"
On some exchanges (like HTX), regular PercentChangePairList does not work as the api does not natively provide 24h percent change in price. This can be worked around by using candle data to calculate the percentage change. To roughly simulate 24h percent change, you can use the following configuration. Please note that these pairlists will only refresh once per day.
```json
"pairlists": [
{
"method": "PercentChangePairList",
"number_assets": 20,
"min_value": 0,
"refresh_period": 86400,
"lookback_days": 1
}
],
```
**Example Configuration to Read from Ticker**
```json
"pairlists": [
{
"method": "PercentChangePairList",
"number_assets": 15,
"min_value": -10,
"max_value": 50
}
],
```
In this configuration:
1. The top 15 pairs are selected based on the highest percentage change in price over the last 24 hours.
2. Only pairs with a percentage change between -10% and 50% are considered.
**Example Configuration to Read from Candles**
```json
"pairlists": [
{
"method": "PercentChangePairList",
"number_assets": 15,
"sort_key": "percentage",
"min_value": 0,
"refresh_period": 3600,
"lookback_timeframe": "1h",
"lookback_period": 72
}
],
```
This example builds the percent change pairs based on a rolling period of 3 days of 1-hour candles by using `lookback_timeframe` for candle size and `lookback_period` which specifies the number of candles.
The percent change in price is calculated using the following formula, which expresses the percentage difference between the current candle's close price and the previous candle's close price, as defined by the specified timeframe and lookback period:
$$ Percent Change = (\frac{Current Close - Previous Close}{Previous Close}) * 100 $$
!!! Warning "Range look back and refresh period"
When used in conjunction with `lookback_days` and `lookback_timeframe` the `refresh_period` can not be smaller than the candle size in seconds. As this will result in unnecessary requests to the exchanges API.
!!! Warning "Performance implications when using lookback range"
If used in first position in combination with lookback, the computation of the range-based percent change can be time and resource consuming, as it downloads candles for all tradable pairs. Hence it's highly advised to use the standard approach with `PercentChangePairList` to narrow the pairlist down for further percent-change calculation.
!!! Note "Backtesting"
`PercentChangePairList` does not support backtesting mode.
#### ProducerPairList
With `ProducerPairList`, you can reuse the pairlist from a [Producer](producer-consumer.md) without explicitly defining the pairlist on each consumer.

View File

@@ -36,6 +36,7 @@ All protection end times are rounded up to the next candle to avoid sudden, unex
| `lookback_period_candles` | Only trades that completed within the last `lookback_period_candles` candles will be considered. This setting may be ignored by some Protections. <br> **Datatype:** Positive integer (in candles).
| `lookback_period` | Only trades that completed after `current_time - lookback_period` will be considered. <br>Cannot be used together with `lookback_period_candles`. <br>This setting may be ignored by some Protections. <br> **Datatype:** Float (in minutes)
| `trade_limit` | Number of trades required at minimum (not used by all Protections). <br> **Datatype:** Positive integer
| `unlock_at` | Time when trading will be unlocked regularly (not used by all Protections). <br> **Datatype:** string <br>**Input Format:** "HH:MM" (24-hours)
!!! Note "Durations"
Durations (`stop_duration*` and `lookback_period*` can be defined in either minutes or candles).
@@ -44,7 +45,7 @@ All protection end times are rounded up to the next candle to avoid sudden, unex
#### Stoploss Guard
`StoplossGuard` selects all trades within `lookback_period` in minutes (or in candles when using `lookback_period_candles`).
If `trade_limit` or more trades resulted in stoploss, trading will stop for `stop_duration` in minutes (or in candles when using `stop_duration_candles`).
If `trade_limit` or more trades resulted in stoploss, trading will stop for `stop_duration` in minutes (or in candles when using `stop_duration_candles`, or until the set time when using `unlock_at`).
This applies across all pairs, unless `only_per_pair` is set to true, which will then only look at one pair at a time.
@@ -97,7 +98,7 @@ def protections(self):
#### Low Profit Pairs
`LowProfitPairs` uses all trades for a pair within `lookback_period` in minutes (or in candles when using `lookback_period_candles`) to determine the overall profit ratio.
If that ratio is below `required_profit`, that pair will be locked for `stop_duration` in minutes (or in candles when using `stop_duration_candles`).
If that ratio is below `required_profit`, that pair will be locked for `stop_duration` in minutes (or in candles when using `stop_duration_candles`, or until the set time when using `unlock_at`).
For futures bots, setting `only_per_side` will make the bot only consider one side, and will then only lock this one side, allowing for example shorts to continue after a series of long losses.
@@ -120,7 +121,7 @@ def protections(self):
#### Cooldown Period
`CooldownPeriod` locks a pair for `stop_duration` in minutes (or in candles when using `stop_duration_candles`) after selling, avoiding a re-entry for this pair for `stop_duration` minutes.
`CooldownPeriod` locks a pair for `stop_duration` in minutes (or in candles when using `stop_duration_candles`, or until the set time when using `unlock_at`) after exiting, avoiding a re-entry for this pair for `stop_duration` minutes.
The below example will stop trading a pair for 2 candles after closing a trade, allowing this pair to "cool down".

View File

@@ -0,0 +1,45 @@
## Imports necessary for a strategy
When creating a strategy, you will need to import the necessary modules and classes. The following imports are required for a strategy:
By default, we recommend the following imports as a base line for your strategy:
This will cover all imports necessary for freqtrade functions to work.
Obviously you can add more imports as needed for your strategy.
``` python
# flake8: noqa: F401
# isort: skip_file
# --- Do not remove these imports ---
import numpy as np
import pandas as pd
from datetime import datetime, timedelta, timezone
from pandas import DataFrame
from typing import Dict, Optional, Union, Tuple
from freqtrade.strategy import (
IStrategy,
Trade,
Order,
PairLocks,
informative, # @informative decorator
# Hyperopt Parameters
BooleanParameter,
CategoricalParameter,
DecimalParameter,
IntParameter,
RealParameter,
# timeframe helpers
timeframe_to_minutes,
timeframe_to_next_date,
timeframe_to_prev_date,
# Strategy helper functions
merge_informative_pair,
stoploss_from_absolute,
stoploss_from_open,
)
# --------------------------------
# Add your lib to import here
import talib.abstract as ta
from technical import qtpylib
```

View File

@@ -1,6 +1,7 @@
markdown==3.6
markdown==3.7
mkdocs==1.6.0
mkdocs-material==9.5.29
mkdocs-material==9.5.33
mdx_truly_sane_lists==1.3
pymdown-extensions==10.8.1
pymdown-extensions==10.9
jinja2==3.1.4
mike==2.1.3

View File

@@ -24,6 +24,8 @@ Currently available callbacks:
!!! Tip "Callback calling sequence"
You can find the callback calling sequence in [bot-basics](bot-basics.md#bot-execution-logic)
--8<-- "includes/strategy-imports.md"
## Bot start
A simple callback which is called once when the strategy is loaded.
@@ -41,10 +43,10 @@ class AwesomeStrategy(IStrategy):
Called only once after bot instantiation.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
"""
if self.config['runmode'].value in ('live', 'dry_run'):
if self.config["runmode"].value in ("live", "dry_run"):
# Assign this to the class by using self.*
# can then be used by populate_* methods
self.custom_remote_data = requests.get('https://some_remote_source.example.com')
self.custom_remote_data = requests.get("https://some_remote_source.example.com")
```
@@ -57,6 +59,7 @@ seconds, unless configured differently) or once per candle in backtest/hyperopt
This can be used to perform calculations which are pair independent (apply to all pairs), loading of external data, etc.
``` python
# Default imports
import requests
class AwesomeStrategy(IStrategy):
@@ -71,10 +74,10 @@ class AwesomeStrategy(IStrategy):
:param current_time: datetime object, containing the current datetime
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
"""
if self.config['runmode'].value in ('live', 'dry_run'):
if self.config["runmode"].value in ("live", "dry_run"):
# Assign this to the class by using self.*
# can then be used by populate_* methods
self.remote_data = requests.get('https://some_remote_source.example.com')
self.remote_data = requests.get("https://some_remote_source.example.com")
```
@@ -83,6 +86,8 @@ class AwesomeStrategy(IStrategy):
Called before entering a trade, makes it possible to manage your position size when placing a new trade.
```python
# Default imports
class AwesomeStrategy(IStrategy):
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: Optional[float], max_stake: float,
@@ -92,13 +97,13 @@ class AwesomeStrategy(IStrategy):
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
current_candle = dataframe.iloc[-1].squeeze()
if current_candle['fastk_rsi_1h'] > current_candle['fastd_rsi_1h']:
if self.config['stake_amount'] == 'unlimited':
if current_candle["fastk_rsi_1h"] > current_candle["fastd_rsi_1h"]:
if self.config["stake_amount"] == "unlimited":
# Use entire available wallet during favorable conditions when in compounding mode.
return max_stake
else:
# Compound profits during favorable conditions instead of using a static stake.
return self.wallets.get_total_stake_amount() / self.config['max_open_trades']
return self.wallets.get_total_stake_amount() / self.config["max_open_trades"]
# Use default stake amount.
return proposed_stake
@@ -129,25 +134,27 @@ Using `custom_exit()` signals in place of stoploss though *is not recommended*.
An example of how we can use different indicators depending on the current profit and also exit trades that were open longer than one day:
``` python
# Default imports
class AwesomeStrategy(IStrategy):
def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, **kwargs):
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
# Above 20% profit, sell when rsi < 80
if current_profit > 0.2:
if last_candle['rsi'] < 80:
return 'rsi_below_80'
if last_candle["rsi"] < 80:
return "rsi_below_80"
# Between 2% and 10%, sell if EMA-long above EMA-short
if 0.02 < current_profit < 0.1:
if last_candle['emalong'] > last_candle['emashort']:
return 'ema_long_below_80'
if last_candle["emalong"] > last_candle["emashort"]:
return "ema_long_below_80"
# Sell any positions at a loss if they are held for more than one day.
if current_profit < 0.0 and (current_time - trade.open_date_utc).days >= 1:
return 'unclog'
return "unclog"
```
See [Dataframe access](strategy-advanced.md#dataframe-access) for more information about dataframe use in strategy callbacks.
@@ -168,7 +175,6 @@ The absolute value of the return value is used (the sign is ignored), so returni
Returning `None` will be interpreted as "no desire to change", and is the only safe way to return when you'd like to not modify the stoploss.
`NaN` and `inf` values are considered invalid and will be ignored (identical to `None`).
Stoploss on exchange works similar to `trailing_stop`, and the stoploss on exchange is updated as configured in `stoploss_on_exchange_interval` ([More details about stoploss on exchange](stoploss.md#stop-loss-on-exchangefreqtrade)).
!!! Note "Use of dates"
@@ -196,9 +202,7 @@ Of course, many more things are possible, and all examples can be combined at wi
To simulate a regular trailing stoploss of 4% (trailing 4% behind the maximum reached price) you would use the following very simple method:
``` python
# additional imports required
from datetime import datetime
from freqtrade.persistence import Trade
# Default imports
class AwesomeStrategy(IStrategy):
@@ -206,7 +210,7 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
"""
@@ -236,8 +240,7 @@ class AwesomeStrategy(IStrategy):
Use the initial stoploss for the first 60 minutes, after this change to 10% trailing stoploss, and after 2 hours (120 minutes) we use a 5% trailing stoploss.
``` python
from datetime import datetime, timedelta
from freqtrade.persistence import Trade
# Default imports
class AwesomeStrategy(IStrategy):
@@ -245,7 +248,7 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
@@ -263,8 +266,7 @@ Use the initial stoploss for the first 60 minutes, after this change to 10% trai
If an additional order fills, set stoploss to -10% below the new `open_rate` ([Averaged across all entries](#position-adjust-calculations)).
``` python
from datetime import datetime, timedelta
from freqtrade.persistence import Trade
# Default imports
class AwesomeStrategy(IStrategy):
@@ -272,7 +274,7 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
@@ -293,8 +295,7 @@ Use a different stoploss depending on the pair.
In this example, we'll trail the highest price with 10% trailing stoploss for `ETH/BTC` and `XRP/BTC`, with 5% trailing stoploss for `LTC/BTC` and with 15% for all other pairs.
``` python
from datetime import datetime
from freqtrade.persistence import Trade
# Default imports
class AwesomeStrategy(IStrategy):
@@ -302,13 +303,13 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
if pair in ('ETH/BTC', 'XRP/BTC'):
if pair in ("ETH/BTC", "XRP/BTC"):
return -0.10
elif pair in ('LTC/BTC'):
elif pair in ("LTC/BTC"):
return -0.05
return -0.15
```
@@ -320,8 +321,7 @@ Use the initial stoploss until the profit is above 4%, then use a trailing stopl
Please note that the stoploss can only increase, values lower than the current stoploss are ignored.
``` python
from datetime import datetime, timedelta
from freqtrade.persistence import Trade
# Default imports
class AwesomeStrategy(IStrategy):
@@ -329,7 +329,7 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
@@ -353,9 +353,7 @@ Instead of continuously trailing behind the current price, this example sets fix
* Once profit is > 40% - set stoploss to 25% above open price.
``` python
from datetime import datetime
from freqtrade.persistence import Trade
from freqtrade.strategy import stoploss_from_open
# Default imports
class AwesomeStrategy(IStrategy):
@@ -363,7 +361,7 @@ class AwesomeStrategy(IStrategy):
use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
@@ -384,15 +382,17 @@ class AwesomeStrategy(IStrategy):
Absolute stoploss value may be derived from indicators stored in dataframe. Example uses parabolic SAR below the price as stoploss.
``` python
# Default imports
class AwesomeStrategy(IStrategy):
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# <...>
dataframe['sar'] = ta.SAR(dataframe)
dataframe["sar"] = ta.SAR(dataframe)
use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
@@ -400,7 +400,7 @@ class AwesomeStrategy(IStrategy):
last_candle = dataframe.iloc[-1].squeeze()
# Use parabolic sar as absolute stoploss price
stoploss_price = last_candle['sar']
stoploss_price = last_candle["sar"]
# Convert absolute price to percentage relative to current_rate
if stoploss_price < current_rate:
@@ -429,10 +429,7 @@ Stoploss values returned from `custom_stoploss()` must specify a percentage rela
``` python
from datetime import datetime
from freqtrade.persistence import Trade
from freqtrade.strategy import IStrategy, stoploss_from_open
# Default imports
class AwesomeStrategy(IStrategy):
@@ -440,7 +437,7 @@ Stoploss values returned from `custom_stoploss()` must specify a percentage rela
use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
@@ -469,38 +466,34 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab
??? Example "Returning a stoploss using absolute price from the custom stoploss function"
If we want to trail a stop price at 2xATR below current price we can call `stoploss_from_absolute(current_rate + (side * candle['atr'] * 2), current_rate=current_rate, is_short=trade.is_short, leverage=trade.leverage)`.
If we want to trail a stop price at 2xATR below current price we can call `stoploss_from_absolute(current_rate + (side * candle["atr"] * 2), current_rate=current_rate, is_short=trade.is_short, leverage=trade.leverage)`.
For futures, we need to adjust the direction (up or down), as well as adjust for leverage, since the [`custom_stoploss`](strategy-callbacks.md#custom-stoploss) callback returns the ["risk for this trade"](stoploss.md#stoploss-and-leverage) - not the relative price movement.
``` python
from datetime import datetime
from freqtrade.persistence import Trade
from freqtrade.strategy import IStrategy, stoploss_from_absolute, timeframe_to_prev_date
# Default imports
class AwesomeStrategy(IStrategy):
use_custom_stoploss = True
def populate_indicators_1h(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['atr'] = ta.ATR(dataframe, timeperiod=14)
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe["atr"] = ta.ATR(dataframe, timeperiod=14)
return dataframe
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
trade_date = timeframe_to_prev_date(self.timeframe, trade.open_date_utc)
candle = dataframe.iloc[-1].squeeze()
side = 1 if trade.is_short else -1
return stoploss_from_absolute(current_rate + (side * candle['atr'] * 2),
return stoploss_from_absolute(current_rate + (side * candle["atr"] * 2),
current_rate=current_rate,
is_short=trade.is_short,
leverage=trade.leverage)
```
---
## Custom order price rules
@@ -520,19 +513,18 @@ Each of these methods are called right before placing an order on the exchange.
### Custom order entry and exit price example
``` python
from datetime import datetime, timedelta, timezone
from freqtrade.persistence import Trade
# Default imports
class AwesomeStrategy(IStrategy):
# ... populate_* methods
def custom_entry_price(self, pair: str, trade: Optional['Trade'], current_time: datetime, proposed_rate: float,
def custom_entry_price(self, pair: str, trade: Optional[Trade], current_time: datetime, proposed_rate: float,
entry_tag: Optional[str], side: str, **kwargs) -> float:
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
timeframe=self.timeframe)
new_entryprice = dataframe['bollinger_10_lowerband'].iat[-1]
new_entryprice = dataframe["bollinger_10_lowerband"].iat[-1]
return new_entryprice
@@ -542,7 +534,7 @@ class AwesomeStrategy(IStrategy):
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
timeframe=self.timeframe)
new_exitprice = dataframe['bollinger_10_upperband'].iat[-1]
new_exitprice = dataframe["bollinger_10_upperband"].iat[-1]
return new_exitprice
@@ -579,8 +571,7 @@ It applies a tight timeout for higher priced assets, while allowing more time to
The function must return either `True` (cancel order) or `False` (keep order alive).
``` python
from datetime import datetime, timedelta
from freqtrade.persistence import Trade, Order
# Default imports
class AwesomeStrategy(IStrategy):
@@ -588,11 +579,11 @@ class AwesomeStrategy(IStrategy):
# Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours.
unfilledtimeout = {
'entry': 60 * 25,
'exit': 60 * 25
"entry": 60 * 25,
"exit": 60 * 25
}
def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order',
def check_entry_timeout(self, pair: str, trade: Trade, order: Order,
current_time: datetime, **kwargs) -> bool:
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
return True
@@ -603,7 +594,7 @@ class AwesomeStrategy(IStrategy):
return False
def check_exit_timeout(self, pair: str, trade: Trade, order: 'Order',
def check_exit_timeout(self, pair: str, trade: Trade, order: Order,
current_time: datetime, **kwargs) -> bool:
if trade.open_rate > 100 and trade.open_date_utc < current_time - timedelta(minutes=5):
return True
@@ -620,8 +611,7 @@ class AwesomeStrategy(IStrategy):
### Custom order timeout example (using additional data)
``` python
from datetime import datetime
from freqtrade.persistence import Trade, Order
# Default imports
class AwesomeStrategy(IStrategy):
@@ -629,24 +619,24 @@ class AwesomeStrategy(IStrategy):
# Set unfilledtimeout to 25 hours, since the maximum timeout from below is 24 hours.
unfilledtimeout = {
'entry': 60 * 25,
'exit': 60 * 25
"entry": 60 * 25,
"exit": 60 * 25
}
def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order',
def check_entry_timeout(self, pair: str, trade: Trade, order: Order,
current_time: datetime, **kwargs) -> bool:
ob = self.dp.orderbook(pair, 1)
current_price = ob['bids'][0][0]
current_price = ob["bids"][0][0]
# Cancel buy order if price is more than 2% above the order.
if current_price > order.price * 1.02:
return True
return False
def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order',
def check_exit_timeout(self, pair: str, trade: Trade, order: Order,
current_time: datetime, **kwargs) -> bool:
ob = self.dp.orderbook(pair, 1)
current_price = ob['asks'][0][0]
current_price = ob["asks"][0][0]
# Cancel sell order if price is more than 2% below the order.
if current_price < order.price * 0.98:
return True
@@ -665,6 +655,8 @@ This are the last methods that will be called before an order is placed.
`confirm_trade_entry()` can be used to abort a trade entry at the latest second (maybe because the price is not what we expect).
``` python
# Default imports
class AwesomeStrategy(IStrategy):
# ... populate_* methods
@@ -689,7 +681,7 @@ class AwesomeStrategy(IStrategy):
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param current_time: datetime object, containing the current datetime
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
:param side: 'long' or 'short' - indicating the direction of the proposed trade
:param side: "long" or "short" - indicating the direction of the proposed trade
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return bool: When True is returned, then the buy-order is placed on the exchange.
False aborts the process
@@ -711,8 +703,7 @@ The exit-reasons (if applicable) will be in the following sequence:
* `trailing_stop_loss`
``` python
from freqtrade.persistence import Trade
# Default imports
class AwesomeStrategy(IStrategy):
@@ -738,14 +729,14 @@ class AwesomeStrategy(IStrategy):
or current rate for market orders.
:param time_in_force: Time in force. Defaults to GTC (Good-til-cancelled).
:param exit_reason: Exit reason.
Can be any of ['roi', 'stop_loss', 'stoploss_on_exchange', 'trailing_stop_loss',
'exit_signal', 'force_exit', 'emergency_exit']
Can be any of ["roi", "stop_loss", "stoploss_on_exchange", "trailing_stop_loss",
"exit_signal", "force_exit", "emergency_exit"]
:param current_time: datetime object, containing the current datetime
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return bool: When True, then the exit-order is placed on the exchange.
False aborts the process
"""
if exit_reason == 'force_exit' and trade.calc_profit_ratio(rate) < 0:
if exit_reason == "force_exit" and trade.calc_profit_ratio(rate) < 0:
# Reject force-sells with negative profit
# This is just a sample, please adjust to your needs
# (this does not necessarily make sense, assuming you know when you're force-selling)
@@ -771,7 +762,7 @@ This callback is **not** called when there is an open order (either buy or sell)
`adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible.
Position adjustments will always be applied in the direction of the trade, so a positive value will always increase your position (negative values will decrease your position), no matter if it's a long or short trade.
Adjustment orders can be assigned with a tag by returning a 2 element Tuple, with the first element being the adjustment amount, and the 2nd element the tag (e.g. `return 250, 'increase_favorable_conditions'`).
Adjustment orders can be assigned with a tag by returning a 2 element Tuple, with the first element being the adjustment amount, and the 2nd element the tag (e.g. `return 250, "increase_favorable_conditions"`).
Modifications to leverage are not possible, and the stake-amount returned is assumed to be before applying leverage.
@@ -793,7 +784,7 @@ Returning a value more than the above (so remaining stake_amount would become ne
!!! Note "About stake size"
Using fixed stake size means it will be the amount used for the first order, just like without position adjustment.
If you wish to buy additional orders with DCA, then make sure to leave enough funds in the wallet for that.
Using 'unlimited' stake amount with DCA orders requires you to also implement the `custom_stake_amount()` callback to avoid allocating all funds to the initial order.
Using `"unlimited"` stake amount with DCA orders requires you to also implement the `custom_stake_amount()` callback to avoid allocating all funds to the initial order.
!!! Warning "Stoploss calculation"
Stoploss is still calculated from the initial opening price, not averaged price.
@@ -811,9 +802,7 @@ Returning a value more than the above (so remaining stake_amount would become ne
Trades with long duration and 10s or even 100ds of position adjustments are therefore not recommended, and should be closed at regular intervals to not affect performance.
``` python
from freqtrade.persistence import Trade
from typing import Optional, Tuple, Union
# Default imports
class DigDeeperStrategy(IStrategy):
@@ -876,7 +865,7 @@ class DigDeeperStrategy(IStrategy):
if current_profit > 0.05 and trade.nr_of_successful_exits == 0:
# Take half of the profit at +5%
return -(trade.stake_amount / 2), 'half_profit_5%'
return -(trade.stake_amount / 2), "half_profit_5%"
if current_profit > -0.05:
return None
@@ -886,7 +875,7 @@ class DigDeeperStrategy(IStrategy):
# Only buy when not actively falling price.
last_candle = dataframe.iloc[-1].squeeze()
previous_candle = dataframe.iloc[-2].squeeze()
if last_candle['close'] < previous_candle['close']:
if last_candle["close"] < previous_candle["close"]:
return None
filled_entries = trade.select_filled_orders(trade.entry_side)
@@ -904,7 +893,7 @@ class DigDeeperStrategy(IStrategy):
stake_amount = filled_entries[0].stake_amount
# This then calculates current safety order size
stake_amount = stake_amount * (1 + (count_of_entries * 0.25))
return stake_amount, '1/3rd_increase'
return stake_amount, "1/3rd_increase"
except Exception as exception:
return None
@@ -951,8 +940,7 @@ If the cancellation of the original order fails, then the order will not be repl
Entry Orders that are cancelled via the above methods will not have this callback called. Be sure to update timeout values to match your expectations.
```python
from freqtrade.persistence import Trade
from datetime import timedelta, datetime
# Default imports
class AwesomeStrategy(IStrategy):
@@ -977,13 +965,18 @@ class AwesomeStrategy(IStrategy):
:param proposed_rate: Rate, calculated based on pricing settings in entry_pricing.
:param current_order_rate: Rate of the existing order in place.
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
:param side: 'long' or 'short' - indicating the direction of the proposed trade
:param side: "long" or "short" - indicating the direction of the proposed trade
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: New entry price value if provided
"""
# Limit orders to use and follow SMA200 as price target for the first 10 minutes since entry trigger for BTC/USDT pair.
if pair == 'BTC/USDT' and entry_tag == 'long_sma200' and side == 'long' and (current_time - timedelta(minutes=10)) > trade.open_date_utc:
if (
pair == "BTC/USDT"
and entry_tag == "long_sma200"
and side == "long"
and (current_time - timedelta(minutes=10)) > trade.open_date_utc
):
# just cancel the order if it has been filled more than half of the amount
if order.filled > order.remaining:
return None
@@ -991,7 +984,7 @@ class AwesomeStrategy(IStrategy):
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
current_candle = dataframe.iloc[-1].squeeze()
# desired price
return current_candle['sma_200']
return current_candle["sma_200"]
# default: maintain existing order
return current_order_rate
```
@@ -1006,6 +999,8 @@ Values that are above `max_leverage` will be adjusted to `max_leverage`.
For markets / exchanges that don't support leverage, this method is ignored.
``` python
# Default imports
class AwesomeStrategy(IStrategy):
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
@@ -1019,7 +1014,7 @@ class AwesomeStrategy(IStrategy):
:param proposed_leverage: A leverage proposed by the bot.
:param max_leverage: Max leverage allowed on this pair
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
:param side: 'long' or 'short' - indicating the direction of the proposed trade
:param side: "long" or "short" - indicating the direction of the proposed trade
:return: A leverage amount, which is between 1.0 and max_leverage.
"""
return 1.0
@@ -1036,6 +1031,8 @@ It will be called independent of the order type (entry, exit, stoploss or positi
Assuming that your strategy needs to store the high value of the candle at trade entry, this is possible with this callback as the following example show.
``` python
# Default imports
class AwesomeStrategy(IStrategy):
def order_filled(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> None:
"""
@@ -1052,7 +1049,7 @@ class AwesomeStrategy(IStrategy):
last_candle = dataframe.iloc[-1].squeeze()
if (trade.nr_of_successful_entries == 1) and (order.ft_order_side == trade.entry_side):
trade.set_custom_data(key='entry_candle_high', value=last_candle['high'])
trade.set_custom_data(key="entry_candle_high", value=last_candle["high"])
return None

View File

@@ -158,7 +158,7 @@ Out of the box, freqtrade installs the following technical libraries:
- [ta-lib](https://ta-lib.github.io/ta-lib-python/)
- [pandas-ta](https://twopirllc.github.io/pandas-ta/)
- [technical](https://github.com/freqtrade/technical/)
- [technical](https://technical.freqtrade.io)
Additional technical libraries can be installed as necessary, or custom indicators may be written / invented by the strategy author.
@@ -407,6 +407,8 @@ Currently this is `pair`, which can be accessed using `metadata['pair']` - and w
The Metadata-dict should not be modified and does not persist information across multiple calls.
Instead, have a look at the [Storing information](strategy-advanced.md#storing-information-persistent) section.
--8<-- "includes/strategy-imports.md"
## Strategy file loading
By default, freqtrade will attempt to load strategies from all `.py` files within `user_data/strategies`.

View File

@@ -13,19 +13,22 @@ Please follow the [documentation](https://www.freqtrade.io/en/stable/data-downlo
import os
from pathlib import Path
# Change directory
# Modify this cell to insure that the output shows the correct path.
# Define all paths relative to the project root shown in the cell output
project_root = "somedir/freqtrade"
i=0
i = 0
try:
os.chdir(project_root)
assert Path('LICENSE').is_file()
except:
while i<4 and (not Path('LICENSE').is_file()):
os.chdir(Path(Path.cwd(), '../'))
i+=1
project_root = Path.cwd()
if not Path("LICENSE").is_file():
i = 0
while i < 4 and (not Path("LICENSE").is_file()):
os.chdir(Path(Path.cwd(), "../"))
i += 1
project_root = Path.cwd()
except FileNotFoundError:
print("Please define the project root relative to the current directory")
print(Path.cwd())
```
@@ -35,6 +38,7 @@ print(Path.cwd())
```python
from freqtrade.configuration import Configuration
# Customize these according to your needs.
# Initialize empty configuration object
@@ -58,12 +62,14 @@ pair = "BTC/USDT"
from freqtrade.data.history import load_pair_history
from freqtrade.enums import CandleType
candles = load_pair_history(datadir=data_location,
timeframe=config["timeframe"],
pair=pair,
data_format = "json", # Make sure to update this to your data
candle_type=CandleType.SPOT,
)
candles = load_pair_history(
datadir=data_location,
timeframe=config["timeframe"],
pair=pair,
data_format="json", # Make sure to update this to your data
candle_type=CandleType.SPOT,
)
# Confirm success
print(f"Loaded {len(candles)} rows of data for {pair} from {data_location}")
@@ -76,14 +82,16 @@ candles.head()
```python
# Load strategy using values set above
from freqtrade.resolvers import StrategyResolver
from freqtrade.data.dataprovider import DataProvider
from freqtrade.resolvers import StrategyResolver
strategy = StrategyResolver.load_strategy(config)
strategy.dp = DataProvider(config, None, None)
strategy.ft_bot_start()
# Generate buy/sell signals using strategy
df = strategy.analyze_ticker(candles, {'pair': pair})
df = strategy.analyze_ticker(candles, {"pair": pair})
df.tail()
```
@@ -102,7 +110,7 @@ df.tail()
```python
# Report results
print(f"Generated {df['enter_long'].sum()} entry signals")
data = df.set_index('date', drop=False)
data = df.set_index("date", drop=False)
data.tail()
```
@@ -119,10 +127,13 @@ Analyze a trades dataframe (also used below for plotting)
```python
from freqtrade.data.btanalysis import load_backtest_data, load_backtest_stats
# if backtest_dir points to a directory, it'll automatically load the last backtest file.
backtest_dir = config["user_data_dir"] / "backtest_results"
# backtest_dir can also point to a specific file
# backtest_dir = config["user_data_dir"] / "backtest_results/backtest-result-2020-07-01_20-04-22.json"
# backtest_dir can also point to a specific file
# backtest_dir = (
# config["user_data_dir"] / "backtest_results/backtest-result-2020-07-01_20-04-22.json"
# )
```
@@ -131,24 +142,24 @@ backtest_dir = config["user_data_dir"] / "backtest_results"
# This contains all information used to generate the backtest result.
stats = load_backtest_stats(backtest_dir)
strategy = 'SampleStrategy'
# All statistics are available per strategy, so if `--strategy-list` was used during backtest, this will be reflected here as well.
strategy = "SampleStrategy"
# All statistics are available per strategy, so if `--strategy-list` was used during backtest,
# this will be reflected here as well.
# Example usages:
print(stats['strategy'][strategy]['results_per_pair'])
print(stats["strategy"][strategy]["results_per_pair"])
# Get pairlist used for this backtest
print(stats['strategy'][strategy]['pairlist'])
print(stats["strategy"][strategy]["pairlist"])
# Get market change (average change of all pairs from start to end of the backtest period)
print(stats['strategy'][strategy]['market_change'])
print(stats["strategy"][strategy]["market_change"])
# Maximum drawdown ()
print(stats['strategy'][strategy]['max_drawdown'])
print(stats["strategy"][strategy]["max_drawdown"])
# Maximum drawdown start and end
print(stats['strategy'][strategy]['drawdown_start'])
print(stats['strategy'][strategy]['drawdown_end'])
print(stats["strategy"][strategy]["drawdown_start"])
print(stats["strategy"][strategy]["drawdown_end"])
# Get strategy comparison (only relevant if multiple strategies were compared)
print(stats['strategy_comparison'])
print(stats["strategy_comparison"])
```
@@ -166,24 +177,25 @@ trades.groupby("pair")["exit_reason"].value_counts()
```python
# Plotting equity line (starting with 0 on day 1 and adding daily profit for each backtested day)
import pandas as pd
import plotly.express as px
from freqtrade.configuration import Configuration
from freqtrade.data.btanalysis import load_backtest_stats
import plotly.express as px
import pandas as pd
# strategy = 'SampleStrategy'
# config = Configuration.from_files(["user_data/config.json"])
# backtest_dir = config["user_data_dir"] / "backtest_results"
stats = load_backtest_stats(backtest_dir)
strategy_stats = stats['strategy'][strategy]
strategy_stats = stats["strategy"][strategy]
df = pd.DataFrame(columns=['dates','equity'], data=strategy_stats['daily_profit'])
df['equity_daily'] = df['equity'].cumsum()
df = pd.DataFrame(columns=["dates", "equity"], data=strategy_stats["daily_profit"])
df["equity_daily"] = df["equity"].cumsum()
fig = px.line(df, x="dates", y="equity_daily")
fig.show()
```
### Load live trading results into a pandas dataframe
@@ -194,6 +206,7 @@ In case you did already some trading and want to analyze your performance
```python
from freqtrade.data.btanalysis import load_trades_from_db
# Fetch trades from database
trades = load_trades_from_db("sqlite:///tradesv3.sqlite")
@@ -210,8 +223,9 @@ This can be useful to find the best `max_open_trades` parameter, when used with
```python
from freqtrade.data.btanalysis import analyze_trade_parallelism
# Analyze the above
parallel_trades = analyze_trade_parallelism(trades, '5m')
parallel_trades = analyze_trade_parallelism(trades, "5m")
parallel_trades.plot()
```
@@ -222,23 +236,23 @@ Freqtrade offers interactive plotting capabilities based on plotly.
```python
from freqtrade.plot.plotting import generate_candlestick_graph
from freqtrade.plot.plotting import generate_candlestick_graph
# Limit graph period to keep plotly quick and reactive
# Filter trades to one pair
trades_red = trades.loc[trades['pair'] == pair]
trades_red = trades.loc[trades["pair"] == pair]
data_red = data['2019-06-01':'2019-06-10']
data_red = data["2019-06-01":"2019-06-10"]
# Generate candlestick graph
graph = generate_candlestick_graph(pair=pair,
data=data_red,
trades=trades_red,
indicators1=['sma20', 'ema50', 'ema55'],
indicators2=['rsi', 'macd', 'macdsignal', 'macdhist']
)
graph = generate_candlestick_graph(
pair=pair,
data=data_red,
trades=trades_red,
indicators1=["sma20", "ema50", "ema55"],
indicators2=["rsi", "macd", "macdsignal", "macdhist"],
)
```
@@ -248,7 +262,6 @@ graph = generate_candlestick_graph(pair=pair,
# Render graph in a separate window
graph.show(renderer="browser")
```
## Plot average profit per trade as distribution graph
@@ -257,12 +270,12 @@ graph.show(renderer="browser")
```python
import plotly.figure_factory as ff
hist_data = [trades.profit_ratio]
group_labels = ['profit_ratio'] # name of the dataset
group_labels = ["profit_ratio"] # name of the dataset
fig = ff.create_distplot(hist_data, group_labels, bin_size=0.01)
fig.show()
```
Feel free to submit an issue or Pull Request enhancing this document if you would like to share ideas on how to best analyze the data.

View File

@@ -11,3 +11,7 @@
.rst-versions .rst-other-versions {
color: white;
}
.md-version__list {
font-weight: 500 !important;
}

View File

@@ -418,8 +418,9 @@ Common arguments:
```
By default, only active pairs/markets are shown. Active pairs/markets are those that can currently be traded
on the exchange. The see the list of all pairs/markets (not only the active ones), use the `-a`/`-all` option.
By default, only active pairs/markets are shown. Active pairs/markets are those that can currently be traded on the exchange.
You can use the `-a`/`-all` option to see the list of all pairs/markets, including the inactive ones.
Pairs may be listed as untradeable if the smallest tradeable price for the market is very small, i.e. less than `1e-11` (`0.00000000001`)
Pairs/markets are sorted by its symbol string in the printed output.

View File

@@ -1,6 +1,6 @@
"""Freqtrade bot"""
__version__ = "2024.7.1"
__version__ = "2024.8"
if "dev" in __version__:
from pathlib import Path

View File

@@ -15,6 +15,7 @@ from freqtrade.commands.data_commands import (
start_convert_trades,
start_download_data,
start_list_data,
start_list_trades_data,
)
from freqtrade.commands.db_commands import start_convert_db
from freqtrade.commands.deploy_commands import (

View File

@@ -132,7 +132,15 @@ ARGS_CONVERT_TRADES = [
"trading_mode",
]
ARGS_LIST_DATA = ["exchange", "dataformat_ohlcv", "pairs", "trading_mode", "show_timerange"]
ARGS_LIST_DATA = [
"exchange",
"dataformat_ohlcv",
"dataformat_trades",
"trades",
"pairs",
"trading_mode",
"show_timerange",
]
ARGS_DOWNLOAD_DATA = [
"pairs",

View File

@@ -446,8 +446,12 @@ AVAILABLE_CLI_OPTIONS = {
),
"download_trades": Arg(
"--dl-trades",
help="Download trades instead of OHLCV data. The bot will resample trades to the "
"desired timeframe as specified as --timeframes/-t.",
help="Download trades instead of OHLCV data.",
action="store_true",
),
"trades": Arg(
"--trades",
help="Work on trades data instead of OHLCV data.",
action="store_true",
),
"convert_trades": Arg(

View File

@@ -14,6 +14,7 @@ from freqtrade.data.history import download_data_main
from freqtrade.enums import CandleType, RunMode, TradingMode
from freqtrade.exceptions import ConfigurationError
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.misc import plural
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
from freqtrade.resolvers import ExchangeResolver
from freqtrade.util import print_rich_table
@@ -115,9 +116,13 @@ def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None:
def start_list_data(args: Dict[str, Any]) -> None:
"""
List available backtest data
List available OHLCV data
"""
if args["trades"]:
start_list_trades_data(args)
return
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
from freqtrade.data.history import get_datahandler
@@ -127,7 +132,6 @@ def start_list_data(args: Dict[str, Any]) -> None:
paircombs = dhc.ohlcv_get_available_data(
config["datadir"], config.get("trading_mode", TradingMode.SPOT)
)
if args["pairs"]:
paircombs = [comb for comb in paircombs if comb[0] in args["pairs"]]
title = f"Found {len(paircombs)} pair / timeframe combinations."
@@ -171,3 +175,51 @@ def start_list_data(args: Dict[str, Any]) -> None:
summary=title,
table_kwargs={"min_width": 50},
)
def start_list_trades_data(args: Dict[str, Any]) -> None:
"""
List available Trades data
"""
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
from freqtrade.data.history import get_datahandler
dhc = get_datahandler(config["datadir"], config["dataformat_trades"])
paircombs = dhc.trades_get_available_data(
config["datadir"], config.get("trading_mode", TradingMode.SPOT)
)
if args["pairs"]:
paircombs = [comb for comb in paircombs if comb in args["pairs"]]
title = f"Found trades data for {len(paircombs)} {plural(len(paircombs), 'pair')}."
if not config.get("show_timerange"):
print_rich_table(
[(pair, config.get("candle_type_def", CandleType.SPOT)) for pair in sorted(paircombs)],
("Pair", "Type"),
title,
table_kwargs={"min_width": 50},
)
else:
paircombs1 = [
(pair, *dhc.trades_data_min_max(pair, config.get("trading_mode", TradingMode.SPOT)))
for pair in paircombs
]
print_rich_table(
[
(
pair,
config.get("candle_type_def", CandleType.SPOT),
start.strftime(DATETIME_PRINT_FORMAT),
end.strftime(DATETIME_PRINT_FORMAT),
str(length),
)
for pair, start, end, length in sorted(paircombs1, key=lambda x: (x[0]))
],
("Pair", "Type", "From", "To", "Trades"),
summary=title,
table_kwargs={"min_width": 50},
)

View File

@@ -32,7 +32,7 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
)
if args["print_one_column"]:
print("\n".join([e["name"] for e in available_exchanges]))
print("\n".join([e["classname"] for e in available_exchanges]))
else:
if args["list_exchanges_all"]:
title = (
@@ -46,14 +46,20 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
table = Table(title=title)
table.add_column("Exchange Name")
table.add_column("Class Name")
table.add_column("Markets")
table.add_column("Reason")
for exchange in available_exchanges:
name = Text(exchange["name"])
if exchange["supported"]:
name.append(" (Official)", style="italic")
name.append(" (Supported)", style="italic")
name.stylize("green bold")
classname = Text(exchange["classname"])
if exchange["is_alias"]:
name.stylize("strike")
classname.stylize("strike")
classname.append(f" (use {exchange['alias_for']})", style="italic")
trade_modes = Text(
", ".join(
@@ -68,6 +74,7 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
table.add_row(
name,
classname,
trade_modes,
exchange["comment"],
style=None if exchange["valid"] else "red",

View File

@@ -1,5 +1,6 @@
# flake8: noqa: F401
from freqtrade.configuration.asyncio_config import asyncio_setup
from freqtrade.configuration.config_secrets import sanitize_config
from freqtrade.configuration.config_setup import setup_utils_configuration
from freqtrade.configuration.config_validation import validate_config_consistency

View File

@@ -0,0 +1,10 @@
import sys
def asyncio_setup() -> None: # pragma: no cover
# Set eventloop for win32 setups
if sys.platform == "win32":
import asyncio
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())

View File

@@ -36,11 +36,6 @@ CONF_SCHEMA = {
"type": ["integer", "number"],
"minimum": -1,
},
"new_pairs_days": {
"description": "Download data of new pairs for given number of days",
"type": "integer",
"default": 30,
},
"timeframe": {
"description": (
f"The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). {__IN_STRATEGY}"
@@ -185,6 +180,7 @@ CONF_SCHEMA = {
"type": "boolean",
"default": False,
},
# Lookahead analysis section
"minimum_trade_amount": {
"description": "Minimum amount for a trade - only used for lookahead-analysis",
"type": "number",
@@ -480,6 +476,12 @@ CONF_SCHEMA = {
"type": "number",
"minimum": 0,
},
"unlock_at": {
"description": (
"Time when trading will be unlocked regularly. Format: HH:MM"
),
"type": "string",
},
"trade_limit": {
"description": "Minimum number of trades required during lookback period.",
"type": "number",
@@ -501,6 +503,7 @@ CONF_SCHEMA = {
"required": ["method"],
},
},
# RPC section
"telegram": {
"description": "Telegram settings.",
"type": "object",
@@ -701,6 +704,7 @@ CONF_SCHEMA = {
},
"required": ["enabled", "listen_ip_address", "listen_port", "username", "password"],
},
# end of RPC section
"db_url": {
"description": "Database connection URL.",
"type": "string",
@@ -734,7 +738,7 @@ CONF_SCHEMA = {
"default": {},
"properties": {
"process_throttle_secs": {
"description": "Throttle time in seconds for processing.",
"description": "Minimum loop duration for one bot iteration in seconds.",
"type": "integer",
},
"interval": {
@@ -763,11 +767,26 @@ CONF_SCHEMA = {
"description": f"Enable position adjustment. {__IN_STRATEGY}",
"type": "boolean",
},
# Download data section
"new_pairs_days": {
"description": "Download data of new pairs for given number of days",
"type": "integer",
"default": 30,
},
"download_trades": {
"description": "Download trades data by default (instead of ohlcv data).",
"type": "boolean",
},
"max_entry_position_adjustment": {
"description": f"Maximum entry position adjustment allowed. {__IN_STRATEGY}",
"type": ["integer", "number"],
"minimum": -1,
},
"add_config_files": {
"description": "Additional configuration files to load.",
"type": "array",
"items": {"type": "string"},
},
"orderflow": {
"description": "Settings related to order flow.",
"type": "object",
@@ -853,6 +872,14 @@ CONF_SCHEMA = {
"items": {"type": "string"},
"uniqueItems": True,
},
"log_responses": {
"description": (
"Log responses from the exchange."
"Useful/required to debug issues with order processing."
),
"type": "boolean",
"default": False,
},
"unknown_fee_rate": {
"description": "Fee rate for unknown markets.",
"type": "number",

View File

@@ -14,12 +14,16 @@ def sanitize_config(config: Config, *, show_sensitive: bool = False) -> Config:
return config
keys_to_remove = [
"exchange.key",
"exchange.api_key",
"exchange.apiKey",
"exchange.secret",
"exchange.password",
"exchange.uid",
"exchange.account_id",
"exchange.accountId",
"exchange.wallet_address",
"exchange.walletAddress",
"exchange.private_key",
"exchange.privateKey",
"telegram.token",
"telegram.chat_id",
@@ -33,8 +37,10 @@ def sanitize_config(config: Config, *, show_sensitive: bool = False) -> Config:
nested_config = config
for nested_key in nested_keys[:-1]:
nested_config = nested_config.get(nested_key, {})
nested_config[nested_keys[-1]] = "REDACTED"
if nested_keys[-1] in nested_config:
nested_config[nested_keys[-1]] = "REDACTED"
else:
config[key] = "REDACTED"
if key in config:
config[key] = "REDACTED"
return config

View File

@@ -1,6 +1,7 @@
import logging
from collections import Counter
from copy import deepcopy
from datetime import datetime
from typing import Any, Dict
from jsonschema import Draft4Validator, validators
@@ -201,16 +202,32 @@ def _validate_protections(conf: Dict[str, Any]) -> None:
"""
for prot in conf.get("protections", []):
parsed_unlock_at = None
if (config_unlock_at := prot.get("unlock_at")) is not None:
try:
parsed_unlock_at = datetime.strptime(config_unlock_at, "%H:%M")
except ValueError:
raise ConfigurationError(f"Invalid date format for unlock_at: {config_unlock_at}.")
if "stop_duration" in prot and "stop_duration_candles" in prot:
raise ConfigurationError(
"Protections must specify either `stop_duration` or `stop_duration_candles`.\n"
f"Please fix the protection {prot.get('method')}"
f"Please fix the protection {prot.get('method')}."
)
if "lookback_period" in prot and "lookback_period_candles" in prot:
raise ConfigurationError(
"Protections must specify either `lookback_period` or `lookback_period_candles`.\n"
f"Please fix the protection {prot.get('method')}"
f"Please fix the protection {prot.get('method')}."
)
if parsed_unlock_at is not None and (
"stop_duration" in prot or "stop_duration_candles" in prot
):
raise ConfigurationError(
"Protections must specify either `unlock_at`, `stop_duration` or "
"`stop_duration_candles`.\n"
f"Please fix the protection {prot.get('method')}."
)

View File

@@ -42,6 +42,7 @@ HYPEROPT_LOSS_BUILTIN = [
AVAILABLE_PAIRLISTS = [
"StaticPairList",
"VolumePairList",
"PercentChangePairList",
"ProducerPairList",
"RemotePairList",
"MarketCapPairList",

View File

@@ -401,7 +401,15 @@ def analyze_trade_parallelism(results: pd.DataFrame, timeframe: str) -> pd.DataF
timeframe_freq = timeframe_to_resample_freq(timeframe)
dates = [
pd.Series(pd.date_range(row[1]["open_date"], row[1]["close_date"], freq=timeframe_freq))
pd.Series(
pd.date_range(
row[1]["open_date"],
row[1]["close_date"],
freq=timeframe_freq,
# Exclude right boundary - the date is the candle open date.
inclusive="left",
)
)
for row in results[["open_date", "close_date"]].iterrows()
]
deltas = [len(x) for x in dates]

View File

@@ -78,6 +78,8 @@ def populate_dataframe_with_trades(
# create columns for trades
_init_dataframe_with_trades_columns(dataframe)
if trades is None or trades.empty:
return dataframe, cached_grouped_trades
try:
start_time = time.time()
@@ -88,7 +90,7 @@ def populate_dataframe_with_trades(
max_candles = config_orderflow["max_candles"]
start_date = dataframe.tail(max_candles).date.iat[0]
# slice of trades that are before current ohlcv candles to make groupby faster
trades = trades.loc[trades.candle_start >= start_date]
trades = trades.loc[trades["candle_start"] >= start_date]
trades.reset_index(inplace=True, drop=True)
# group trades by candle start

View File

@@ -521,15 +521,12 @@ class DataProvider:
(pair, timeframe or self._config["timeframe"], _candle_type), copy=copy
)
elif self.runmode in (RunMode.BACKTEST, RunMode.HYPEROPT):
_candle_type = (
CandleType.from_string(candle_type)
if candle_type != ""
else self._config["candle_type_def"]
)
data_handler = get_datahandler(
self._config["datadir"], data_format=self._config["dataformat_trades"]
)
trades_df = data_handler.trades_load(pair, TradingMode.FUTURES)
trades_df = data_handler.trades_load(
pair, self._config.get("trading_mode", TradingMode.SPOT)
)
return trades_df
else:

View File

@@ -12,7 +12,7 @@ from datetime import datetime, timezone
from pathlib import Path
from typing import List, Optional, Tuple, Type
from pandas import DataFrame
from pandas import DataFrame, to_datetime
from freqtrade import misc
from freqtrade.configuration import TimeRange
@@ -32,6 +32,7 @@ logger = logging.getLogger(__name__)
class IDataHandler(ABC):
_OHLCV_REGEX = r"^([a-zA-Z_\d-]+)\-(\d+[a-zA-Z]{1,2})\-?([a-zA-Z_]*)?(?=\.)"
_TRADES_REGEX = r"^([a-zA-Z_\d-]+)\-(trades)?(?=\.)"
def __init__(self, datadir: Path) -> None:
self._datadir = datadir
@@ -166,6 +167,50 @@ class IDataHandler(ABC):
:param candle_type: Any of the enum CandleType (must match trading mode!)
"""
@classmethod
def trades_get_available_data(cls, datadir: Path, trading_mode: TradingMode) -> List[str]:
"""
Returns a list of all pairs with ohlcv data available in this datadir
:param datadir: Directory to search for ohlcv files
:param trading_mode: trading-mode to be used
:return: List of Tuples of (pair, timeframe, CandleType)
"""
if trading_mode == TradingMode.FUTURES:
datadir = datadir.joinpath("futures")
_tmp = [
re.search(cls._TRADES_REGEX, p.name)
for p in datadir.glob(f"*.{cls._get_file_extension()}")
]
return [
cls.rebuild_pair_from_filename(match[1])
for match in _tmp
if match and len(match.groups()) > 1
]
def trades_data_min_max(
self,
pair: str,
trading_mode: TradingMode,
) -> Tuple[datetime, datetime, int]:
"""
Returns the min and max timestamp for the given pair's trades data.
:param pair: Pair to get min/max for
:param trading_mode: Trading mode to use (used to determine the filename)
:return: (min, max, len)
"""
df = self._trades_load(pair, trading_mode)
if df.empty:
return (
datetime.fromtimestamp(0, tz=timezone.utc),
datetime.fromtimestamp(0, tz=timezone.utc),
0,
)
return (
to_datetime(df.iloc[0]["timestamp"], unit="ms", utc=True).to_pydatetime(),
to_datetime(df.iloc[-1]["timestamp"], unit="ms", utc=True).to_pydatetime(),
len(df),
)
@classmethod
def trades_get_pairs(cls, datadir: Path) -> List[str]:
"""
@@ -247,9 +292,13 @@ class IDataHandler(ABC):
:param timerange: Timerange to load trades for - currently not implemented
:return: List of trades
"""
trades = trades_df_remove_duplicates(
self._trades_load(pair, trading_mode, timerange=timerange)
)
try:
trades = self._trades_load(pair, trading_mode, timerange=timerange)
except Exception:
logger.exception(f"Error loading trades for {pair}")
return DataFrame(columns=DEFAULT_TRADES_COLUMNS)
trades = trades_df_remove_duplicates(trades)
trades = trades_convert_types(trades)
return trades

View File

@@ -39,6 +39,7 @@ from freqtrade.exchange.exchange_utils_timeframe import (
from freqtrade.exchange.gate import Gate
from freqtrade.exchange.hitbtc import Hitbtc
from freqtrade.exchange.htx import Htx
from freqtrade.exchange.hyperliquid import Hyperliquid
from freqtrade.exchange.idex import Idex
from freqtrade.exchange.kraken import Kraken
from freqtrade.exchange.kucoin import Kucoin

File diff suppressed because it is too large Load Diff

View File

@@ -1,8 +1,10 @@
"""Kucoin exchange subclass."""
"""Bitvavo exchange subclass."""
import logging
from typing import Dict
from ccxt import DECIMAL_PLACES
from freqtrade.exchange import Exchange
@@ -22,3 +24,11 @@ class Bitvavo(Exchange):
_ft_has: Dict = {
"ohlcv_candle_limit": 1440,
}
@property
def precisionMode(self) -> int:
"""
Exchange ccxt precisionMode
Override due to https://github.com/ccxt/ccxt/issues/20408
"""
return DECIMAL_PLACES

View File

@@ -88,7 +88,14 @@ from freqtrade.exchange.exchange_utils_timeframe import (
timeframe_to_seconds,
)
from freqtrade.exchange.exchange_ws import ExchangeWS
from freqtrade.exchange.types import OHLCVResponse, OrderBook, Ticker, Tickers
from freqtrade.exchange.types import (
CcxtBalances,
CcxtPosition,
OHLCVResponse,
OrderBook,
Ticker,
Tickers,
)
from freqtrade.misc import (
chunks,
deep_merge_dicts,
@@ -128,6 +135,7 @@ class Exchange:
# Check https://github.com/ccxt/ccxt/issues/10767 for removal of ohlcv_volume_currency
"ohlcv_volume_currency": "base", # "base" or "quote"
"tickers_have_quoteVolume": True,
"tickers_have_percentage": True,
"tickers_have_bid_ask": True, # bid / ask empty for fetch_tickers
"tickers_have_price": True,
"trades_limit": 1000, # Limit for 1 call to fetch_trades
@@ -315,7 +323,7 @@ class Exchange:
asyncio.set_event_loop(loop)
return loop
def validate_config(self, config):
def validate_config(self, config: Config) -> None:
# Check if timeframe is available
self.validate_timeframes(config.get("timeframe"))
@@ -329,6 +337,7 @@ class Exchange:
self.validate_pricing(config["exit_pricing"])
self.validate_pricing(config["entry_pricing"])
self.validate_orderflow(config["exchange"])
self.validate_freqai(config)
def _init_ccxt(
self, exchange_config: Dict[str, Any], sync: bool, ccxt_kwargs: Dict[str, Any]
@@ -352,14 +361,18 @@ class Exchange:
raise OperationalException(f"Exchange {name} is not supported by ccxt")
ex_config = {
"apiKey": exchange_config.get("apiKey", exchange_config.get("key")),
"apiKey": exchange_config.get(
"api_key", exchange_config.get("apiKey", exchange_config.get("key"))
),
"secret": exchange_config.get("secret"),
"password": exchange_config.get("password"),
"uid": exchange_config.get("uid", ""),
"accountId": exchange_config.get("accountId", ""),
"accountId": exchange_config.get("account_id", exchange_config.get("accountId", "")),
# DEX attributes:
"walletAddress": exchange_config.get("walletAddress"),
"privateKey": exchange_config.get("privateKey"),
"walletAddress": exchange_config.get(
"wallet_address", exchange_config.get("walletAddress")
),
"privateKey": exchange_config.get("private_key", exchange_config.get("privateKey")),
}
if ccxt_kwargs:
logger.info("Applying additional ccxt config: %s", ccxt_kwargs)
@@ -411,7 +424,17 @@ class Exchange:
@property
def precisionMode(self) -> int:
"""exchange ccxt precisionMode"""
"""Exchange ccxt precisionMode"""
return self._api.precisionMode
@property
def precision_mode_price(self) -> int:
"""
Exchange ccxt precisionMode used for price
Workaround for ccxt limitation to not have precisionMode for price
if it differs for an exchange
Might need to be updated if https://github.com/ccxt/ccxt/issues/20408 is fixed.
"""
return self._api.precisionMode
def additional_exchange_init(self) -> None:
@@ -541,7 +564,7 @@ class Exchange:
else:
return self._trades[pair_interval]
else:
return DataFrame()
return DataFrame(columns=DEFAULT_TRADES_COLUMNS)
def get_contract_size(self, pair: str) -> Optional[float]:
if self.trading_mode == TradingMode.FUTURES:
@@ -804,6 +827,13 @@ class Exchange:
f"Trade data not available for {self.name}. Can't use orderflow feature."
)
def validate_freqai(self, config: Config) -> None:
freqai_enabled = config.get("freqai", {}).get("enabled", False)
if freqai_enabled and not self._ft_has["ohlcv_has_history"]:
raise ConfigurationError(
f"Historic OHLCV data not available for {self.name}. Can't use freqAI."
)
def validate_required_startup_candles(self, startup_candles: int, timeframe: str) -> int:
"""
Checks if required startup_candles is more than ohlcv_candle_limit().
@@ -908,7 +938,10 @@ class Exchange:
For stoploss calculations, must use ROUND_UP for longs, and ROUND_DOWN for shorts.
"""
return price_to_precision(
price, self.get_precision_price(pair), self.precisionMode, rounding_mode=rounding_mode
price,
self.get_precision_price(pair),
self.precision_mode_price,
rounding_mode=rounding_mode,
)
def price_get_one_pip(self, pair: str, price: float) -> float:
@@ -1645,7 +1678,7 @@ class Exchange:
return order
@retrier
def get_balances(self) -> dict:
def get_balances(self) -> CcxtBalances:
try:
balances = self._api.fetch_balance()
# Remove additional info from ccxt results
@@ -1665,7 +1698,7 @@ class Exchange:
raise OperationalException(e) from e
@retrier
def fetch_positions(self, pair: Optional[str] = None) -> List[Dict]:
def fetch_positions(self, pair: Optional[str] = None) -> List[CcxtPosition]:
"""
Fetch positions from the exchange.
If no pair is given, all positions are returned.
@@ -1677,7 +1710,7 @@ class Exchange:
symbols = []
if pair:
symbols.append(pair)
positions: List[Dict] = self._api.fetch_positions(symbols)
positions: List[CcxtPosition] = self._api.fetch_positions(symbols)
self._log_exchange_response("fetch_positions", positions)
return positions
except ccxt.DDoSProtection as e:
@@ -2469,17 +2502,17 @@ class Exchange:
logger.debug("Refreshing candle (OHLCV) data for %d pairs", len(pair_list))
# Gather coroutines to run
input_coroutines, cached_pairs = self._build_ohlcv_dl_jobs(pair_list, since_ms, cache)
ohlcv_dl_jobs, cached_pairs = self._build_ohlcv_dl_jobs(pair_list, since_ms, cache)
results_df = {}
# Chunk requests into batches of 100 to avoid overwhelming ccxt Throttling
for input_coro in chunks(input_coroutines, 100):
for dl_jobs_batch in chunks(ohlcv_dl_jobs, 100):
async def gather_stuff(coro):
async def gather_coroutines(coro):
return await asyncio.gather(*coro, return_exceptions=True)
with self._loop_lock:
results = self.loop.run_until_complete(gather_stuff(input_coro))
results = self.loop.run_until_complete(gather_coroutines(dl_jobs_batch))
for res in results:
if isinstance(res, Exception):
@@ -2607,12 +2640,13 @@ class Exchange:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f"Could not fetch historical candle (OHLCV) data "
f"for pair {pair} due to {e.__class__.__name__}. "
f"for {pair}, {timeframe}, {candle_type} due to {e.__class__.__name__}. "
f"Message: {e}"
) from e
except ccxt.BaseError as e:
raise OperationalException(
f"Could not fetch historical candle (OHLCV) data for pair {pair}. Message: {e}"
f"Could not fetch historical candle (OHLCV) data for "
f"{pair}, {timeframe}, {candle_type}. Message: {e}"
) from e
async def _fetch_funding_rate_history(
@@ -2677,6 +2711,94 @@ class Exchange:
self._trades[(pair, timeframe, c_type)] = trades_df
return trades_df
async def _build_trades_dl_jobs(
self, pairwt: PairWithTimeframe, data_handler, cache: bool
) -> Tuple[PairWithTimeframe, Optional[DataFrame]]:
"""
Build coroutines to refresh trades for (they're then called through async.gather)
"""
pair, timeframe, candle_type = pairwt
since_ms = None
new_ticks: List = []
all_stored_ticks_df = DataFrame(columns=DEFAULT_TRADES_COLUMNS + ["date"])
first_candle_ms = self.needed_candle_for_trades_ms(timeframe, candle_type)
# refresh, if
# a. not in _trades
# b. no cache used
# c. need new data
is_in_cache = (pair, timeframe, candle_type) in self._trades
if (
not is_in_cache
or not cache
or self._now_is_time_to_refresh_trades(pair, timeframe, candle_type)
):
logger.debug(f"Refreshing TRADES data for {pair}")
# fetch trades since latest _trades and
# store together with existing trades
try:
until = None
from_id = None
if is_in_cache:
from_id = self._trades[(pair, timeframe, candle_type)].iloc[-1]["id"]
until = dt_ts() # now
else:
until = int(timeframe_to_prev_date(timeframe).timestamp()) * 1000
all_stored_ticks_df = data_handler.trades_load(
f"{pair}-cached", self.trading_mode
)
if not all_stored_ticks_df.empty:
if (
all_stored_ticks_df.iloc[-1]["timestamp"] > first_candle_ms
and all_stored_ticks_df.iloc[0]["timestamp"] <= first_candle_ms
):
# Use cache and populate further
last_cached_ms = all_stored_ticks_df.iloc[-1]["timestamp"]
from_id = all_stored_ticks_df.iloc[-1]["id"]
# only use cached if it's closer than first_candle_ms
since_ms = (
last_cached_ms
if last_cached_ms > first_candle_ms
else first_candle_ms
)
else:
# Skip cache, it's too old
all_stored_ticks_df = DataFrame(
columns=DEFAULT_TRADES_COLUMNS + ["date"]
)
# from_id overrules with exchange set to id paginate
[_, new_ticks] = await self._async_get_trade_history(
pair,
since=since_ms if since_ms else first_candle_ms,
until=until,
from_id=from_id,
)
except Exception:
logger.exception(f"Refreshing TRADES data for {pair} failed")
return pairwt, None
if new_ticks:
all_stored_ticks_list = all_stored_ticks_df[DEFAULT_TRADES_COLUMNS].values.tolist()
all_stored_ticks_list.extend(new_ticks)
trades_df = self._process_trades_df(
pair,
timeframe,
candle_type,
all_stored_ticks_list,
cache,
first_required_candle_date=first_candle_ms,
)
data_handler.trades_store(
f"{pair}-cached", trades_df[DEFAULT_TRADES_COLUMNS], self.trading_mode
)
return pairwt, trades_df
else:
logger.error(f"No new ticks for {pair}")
return pairwt, None
def refresh_latest_trades(
self,
pair_list: ListPairsWithTimeframes,
@@ -2697,90 +2819,25 @@ class Exchange:
self._config["datadir"], data_format=self._config["dataformat_trades"]
)
logger.debug("Refreshing TRADES data for %d pairs", len(pair_list))
since_ms = None
results_df = {}
for pair, timeframe, candle_type in set(pair_list):
new_ticks: List = []
all_stored_ticks_df = DataFrame(columns=DEFAULT_TRADES_COLUMNS + ["date"])
first_candle_ms = self.needed_candle_for_trades_ms(timeframe, candle_type)
# refresh, if
# a. not in _trades
# b. no cache used
# c. need new data
is_in_cache = (pair, timeframe, candle_type) in self._trades
if (
not is_in_cache
or not cache
or self._now_is_time_to_refresh_trades(pair, timeframe, candle_type)
):
logger.debug(f"Refreshing TRADES data for {pair}")
# fetch trades since latest _trades and
# store together with existing trades
try:
until = None
from_id = None
if is_in_cache:
from_id = self._trades[(pair, timeframe, candle_type)].iloc[-1]["id"]
until = dt_ts() # now
trades_dl_jobs = []
for pair_wt in set(pair_list):
trades_dl_jobs.append(self._build_trades_dl_jobs(pair_wt, data_handler, cache))
else:
until = int(timeframe_to_prev_date(timeframe).timestamp()) * 1000
all_stored_ticks_df = data_handler.trades_load(
f"{pair}-cached", self.trading_mode
)
async def gather_coroutines(coro):
return await asyncio.gather(*coro, return_exceptions=True)
if not all_stored_ticks_df.empty:
if (
all_stored_ticks_df.iloc[-1]["timestamp"] > first_candle_ms
and all_stored_ticks_df.iloc[0]["timestamp"] <= first_candle_ms
):
# Use cache and populate further
last_cached_ms = all_stored_ticks_df.iloc[-1]["timestamp"]
from_id = all_stored_ticks_df.iloc[-1]["id"]
# only use cached if it's closer than first_candle_ms
since_ms = (
last_cached_ms
if last_cached_ms > first_candle_ms
else first_candle_ms
)
else:
# Skip cache, it's too old
all_stored_ticks_df = DataFrame(
columns=DEFAULT_TRADES_COLUMNS + ["date"]
)
for dl_job_chunk in chunks(trades_dl_jobs, 100):
with self._loop_lock:
results = self.loop.run_until_complete(gather_coroutines(dl_job_chunk))
# from_id overrules with exchange set to id paginate
[_, new_ticks] = self.get_historic_trades(
pair,
since=since_ms if since_ms else first_candle_ms,
until=until,
from_id=from_id,
)
except Exception:
logger.exception(f"Refreshing TRADES data for {pair} failed")
for res in results:
if isinstance(res, Exception):
logger.warning(f"Async code raised an exception: {repr(res)}")
continue
if new_ticks:
all_stored_ticks_list = all_stored_ticks_df[
DEFAULT_TRADES_COLUMNS
].values.tolist()
all_stored_ticks_list.extend(new_ticks)
trades_df = self._process_trades_df(
pair,
timeframe,
candle_type,
all_stored_ticks_list,
cache,
first_required_candle_date=first_candle_ms,
)
results_df[(pair, timeframe, candle_type)] = trades_df
data_handler.trades_store(
f"{pair}-cached", trades_df[DEFAULT_TRADES_COLUMNS], self.trading_mode
)
else:
logger.error(f"No new ticks for {pair}")
pairwt, trades_df = res
if trades_df is not None:
results_df[pairwt] = trades_df
return results_df

View File

@@ -2,6 +2,7 @@
Exchange support utils
"""
import inspect
from datetime import datetime, timedelta, timezone
from math import ceil, floor
from typing import Any, Dict, List, Optional, Tuple
@@ -53,9 +54,9 @@ def available_exchanges(ccxt_module: Optional[CcxtModuleType] = None) -> List[st
return [x for x in exchanges if validate_exchange(x)[0]]
def validate_exchange(exchange: str) -> Tuple[bool, str, bool]:
def validate_exchange(exchange: str) -> Tuple[bool, str, Optional[ccxt.Exchange]]:
"""
returns: can_use, reason
returns: can_use, reason, exchange_object
with Reason including both missing and missing_opt
"""
try:
@@ -64,11 +65,10 @@ def validate_exchange(exchange: str) -> Tuple[bool, str, bool]:
ex_mod = getattr(ccxt.async_support, exchange.lower())()
if not ex_mod or not ex_mod.has:
return False, "", False
return False, "", None
result = True
reason = ""
is_dex = getattr(ex_mod, "dex", False)
missing = [
k
for k, v in EXCHANGE_HAS_REQUIRED.items()
@@ -87,19 +87,24 @@ def validate_exchange(exchange: str) -> Tuple[bool, str, bool]:
if missing_opt:
reason += f"{'. ' if reason else ''}missing opt: {', '.join(missing_opt)}. "
return result, reason, is_dex
return result, reason, ex_mod
def _build_exchange_list_entry(
exchange_name: str, exchangeClasses: Dict[str, Any]
) -> ValidExchangesType:
valid, comment, is_dex = validate_exchange(exchange_name)
valid, comment, ex_mod = validate_exchange(exchange_name)
result: ValidExchangesType = {
"name": exchange_name,
"name": getattr(ex_mod, "name", exchange_name),
"classname": exchange_name,
"valid": valid,
"supported": exchange_name.lower() in SUPPORTED_EXCHANGES,
"comment": comment,
"dex": is_dex,
"dex": getattr(ex_mod, "dex", False),
"is_alias": getattr(ex_mod, "alias", False),
"alias_for": inspect.getmro(ex_mod.__class__)[1]().id
if getattr(ex_mod, "alias", False)
else None,
"trade_modes": [{"trading_mode": "spot", "margin_mode": ""}],
}
if resolved := exchangeClasses.get(exchange_name.lower()):

View File

@@ -3,6 +3,8 @@
import logging
from typing import Dict
from ccxt import SIGNIFICANT_DIGITS
from freqtrade.exchange import Exchange
@@ -17,8 +19,15 @@ class Hyperliquid(Exchange):
_ft_has: Dict = {
# Only the most recent 5000 candles are available according to the
# exchange's API documentation.
"ohlcv_has_history": True,
"ohlcv_has_history": False,
"ohlcv_candle_limit": 5000,
"trades_has_history": False, # Trades endpoint doesn't seem available.
"exchange_has_overrides": {"fetchTrades": False},
}
@property
def precision_mode_price(self) -> int:
"""
Override the default precision mode for price.
"""
return SIGNIFICANT_DIGITS

View File

@@ -12,7 +12,7 @@ from freqtrade.enums import MarginMode, TradingMode
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
from freqtrade.exchange import Exchange
from freqtrade.exchange.common import retrier
from freqtrade.exchange.types import Tickers
from freqtrade.exchange.types import CcxtBalances, Tickers
logger = logging.getLogger(__name__)
@@ -57,7 +57,7 @@ class Kraken(Exchange):
return super().get_tickers(symbols=symbols, cached=cached)
@retrier
def get_balances(self) -> dict:
def get_balances(self) -> CcxtBalances:
if self._config["dry_run"]:
return {}

View File

@@ -34,6 +34,7 @@ class Okx(Exchange):
"stoploss_order_types": {"limit": "limit"},
"stoploss_on_exchange": True,
"trades_has_history": False, # Endpoint doesn't have a "since" parameter
"ws.enabled": True,
}
_ft_has_futures: Dict = {
"tickers_have_quoteVolume": False,
@@ -43,6 +44,7 @@ class Okx(Exchange):
PriceType.MARK: "index",
PriceType.INDEX: "mark",
},
"ws.enabled": True,
}
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [

View File

@@ -12,9 +12,13 @@ class Ticker(TypedDict):
last: Optional[float]
quoteVolume: Optional[float]
baseVolume: Optional[float]
percentage: Optional[float]
# Several more - only listing required.
Tickers = Dict[str, Ticker]
class OrderBook(TypedDict):
symbol: str
bids: List[Tuple[float, float]]
@@ -24,7 +28,24 @@ class OrderBook(TypedDict):
nonce: Optional[int]
Tickers = Dict[str, Ticker]
class CcxtBalance(TypedDict):
free: float
used: float
total: float
CcxtBalances = Dict[str, CcxtBalance]
class CcxtPosition(TypedDict):
symbol: str
side: str
contracts: float
leverage: float
collateral: Optional[float]
initialMargin: Optional[float]
liquidationPrice: Optional[float]
# pair, timeframe, candleType, OHLCV, drop last?,
OHLCVResponse = Tuple[str, str, CandleType, List, bool]

View File

@@ -374,6 +374,7 @@ class FreqtradeBot(LoggingMixin):
if trade.exchange != self.exchange.id:
continue
trade.precision_mode = self.exchange.precisionMode
trade.precision_mode_price = self.exchange.precision_mode_price
trade.amount_precision = self.exchange.get_precision_amount(trade.pair)
trade.price_precision = self.exchange.get_precision_price(trade.pair)
trade.contract_size = self.exchange.get_contract_size(trade.pair)
@@ -541,7 +542,11 @@ class FreqtradeBot(LoggingMixin):
)
else:
trade.exit_reason = prev_exit_reason
total = self.wallets.get_total(trade.base_currency) if trade.base_currency else 0
total = (
self.wallets.get_owned(trade.pair, trade.base_currency)
if trade.base_currency
else 0
)
if total < trade.amount:
if trade.fully_canceled_entry_order_count == len(trade.orders):
logger.warning(
@@ -992,6 +997,7 @@ class FreqtradeBot(LoggingMixin):
amount_precision=self.exchange.get_precision_amount(pair),
price_precision=self.exchange.get_precision_price(pair),
precision_mode=self.exchange.precisionMode,
precision_mode_price=self.exchange.precision_mode_price,
contract_size=self.exchange.get_contract_size(pair),
)
stoploss = self.strategy.stoploss if not self.edge else self.edge.get_stoploss(pair)

View File

@@ -15,6 +15,7 @@ if sys.version_info < (3, 9): # pragma: no cover
from freqtrade import __version__
from freqtrade.commands import Arguments
from freqtrade.configuration import asyncio_setup
from freqtrade.constants import DOCS_LINK
from freqtrade.exceptions import ConfigurationError, FreqtradeException, OperationalException
from freqtrade.loggers import setup_logging_pre
@@ -33,6 +34,7 @@ def main(sysargv: Optional[List[str]] = None) -> None:
return_code: Any = 1
try:
setup_logging_pre()
asyncio_setup()
arguments = Arguments(sysargv)
args = arguments.get_parsed_arg()

View File

@@ -128,7 +128,10 @@ def round_dict(d, n):
return {k: (round(v, n) if isinstance(v, float) else v) for k, v in d.items()}
def safe_value_fallback(obj: dict, key1: str, key2: Optional[str] = None, default_value=None):
DictMap = Union[Dict[str, Any], Mapping[str, Any]]
def safe_value_fallback(obj: DictMap, key1: str, key2: Optional[str] = None, default_value=None):
"""
Search a value in obj, return this if it's not None.
Then search key2 in obj - return that if it's not none - then use default_value.
@@ -142,10 +145,7 @@ def safe_value_fallback(obj: dict, key1: str, key2: Optional[str] = None, defaul
return default_value
dictMap = Union[Dict[str, Any], Mapping[str, Any]]
def safe_value_fallback2(dict1: dictMap, dict2: dictMap, key1: str, key2: str, default_value=None):
def safe_value_fallback2(dict1: DictMap, dict2: DictMap, key1: str, key2: str, default_value=None):
"""
Search a value in dict1, return this if it's not None.
Fall back to dict2 - return key2 from dict2 if it's not None.

View File

@@ -14,6 +14,7 @@ from freqtrade.loggers.set_log_levels import (
)
from freqtrade.optimize.backtesting import Backtesting
from freqtrade.optimize.base_analysis import BaseAnalysis, VarHolder
from freqtrade.resolvers import StrategyResolver
logger = logging.getLogger(__name__)
@@ -21,10 +22,19 @@ logger = logging.getLogger(__name__)
class RecursiveAnalysis(BaseAnalysis):
def __init__(self, config: Dict[str, Any], strategy_obj: Dict):
self._startup_candle = config.get("startup_candle", [199, 399, 499, 999, 1999])
self._startup_candle = list(
map(int, config.get("startup_candle", [199, 399, 499, 999, 1999]))
)
super().__init__(config, strategy_obj)
strat = StrategyResolver.load_strategy(config)
self._strat_scc = strat.startup_candle_count
if self._strat_scc not in self._startup_candle:
self._startup_candle.append(self._strat_scc)
self._startup_candle.sort()
self.partial_varHolder_array: List[VarHolder] = []
self.partial_varHolder_lookahead_array: List[VarHolder] = []
@@ -58,9 +68,13 @@ class RecursiveAnalysis(BaseAnalysis):
values_diff = compare_df.loc[indicator]
values_diff_self = values_diff.loc["self"]
values_diff_other = values_diff.loc["other"]
diff = (values_diff_other - values_diff_self) / values_diff_self * 100
self.dict_recursive[indicator][part.startup_candle] = f"{diff:.3f}%"
if values_diff_self and values_diff_other:
diff = (values_diff_other - values_diff_self) / values_diff_self * 100
str_diff = f"{diff:.3f}%"
else:
str_diff = "NaN"
self.dict_recursive[indicator][part.startup_candle] = str_diff
else:
logger.info("No variance on indicator(s) found due to recursive formula.")
@@ -174,7 +188,7 @@ class RecursiveAnalysis(BaseAnalysis):
start_date_partial = end_date_full - timedelta(minutes=int(timeframe_minutes))
for startup_candle in self._startup_candle:
self.fill_partial_varholder(start_date_partial, int(startup_candle))
self.fill_partial_varholder(start_date_partial, startup_candle)
# Restore verbosity, so it's not too quiet for the next strategy
restore_verbosity_for_bias_tester()

View File

@@ -17,9 +17,13 @@ class RecursiveAnalysisSubFunctions:
@staticmethod
def text_table_recursive_analysis_instances(recursive_instances: List[RecursiveAnalysis]):
startups = recursive_instances[0]._startup_candle
strat_scc = recursive_instances[0]._strat_scc
headers = ["Indicators"]
for candle in startups:
headers.append(str(candle))
if candle == strat_scc:
headers.append(f"{candle} (from strategy)")
else:
headers.append(str(candle))
data = []
for inst in recursive_instances:

View File

@@ -181,6 +181,7 @@ class Backtesting:
self.fee = max(fee for fee in fees if fee is not None)
logger.info(f"Using fee {self.fee:.4%} - worst case fee from exchange (lowest tier).")
self.precision_mode = self.exchange.precisionMode
self.precision_mode_price = self.exchange.precision_mode_price
if self.config.get("freqai_backtest_live_models", False):
from freqtrade.freqai.utils import get_timerange_backtest_live_models
@@ -329,15 +330,15 @@ class Backtesting:
else:
self.detail_data = {}
if self.trading_mode == TradingMode.FUTURES:
self.funding_fee_timeframe: str = self.exchange.get_option("funding_fee_timeframe")
self.funding_fee_timeframe_secs: int = timeframe_to_seconds(self.funding_fee_timeframe)
funding_fee_timeframe: str = self.exchange.get_option("funding_fee_timeframe")
self.funding_fee_timeframe_secs: int = timeframe_to_seconds(funding_fee_timeframe)
mark_timeframe: str = self.exchange.get_option("mark_ohlcv_timeframe")
# Load additional futures data.
funding_rates_dict = history.load_data(
datadir=self.config["datadir"],
pairs=self.pairlists.whitelist,
timeframe=self.funding_fee_timeframe,
timeframe=funding_fee_timeframe,
timerange=self.timerange,
startup_candles=0,
fail_without_data=True,
@@ -785,7 +786,7 @@ class Backtesting:
)
if rate is not None and rate != close_rate:
close_rate = price_to_precision(
rate, trade.price_precision, self.precision_mode
rate, trade.price_precision, self.precision_mode_price
)
# We can't place orders lower than current low.
# freqtrade does not support this in live, and the order would fill immediately
@@ -929,7 +930,9 @@ class Backtesting:
# We can't place orders higher than current high (otherwise it'd be a stop limit entry)
# which freqtrade does not support in live.
if new_rate is not None and new_rate != propose_rate:
propose_rate = price_to_precision(new_rate, price_precision, self.precision_mode)
propose_rate = price_to_precision(
new_rate, price_precision, self.precision_mode_price
)
if direction == "short":
propose_rate = max(propose_rate, row[LOW_IDX])
else:
@@ -1109,6 +1112,7 @@ class Backtesting:
amount_precision=precision_amount,
price_precision=precision_price,
precision_mode=self.precision_mode,
precision_mode_price=self.precision_mode_price,
contract_size=contract_size,
orders=[],
)
@@ -1332,10 +1336,9 @@ class Backtesting:
pair: str,
current_time: datetime,
end_date: datetime,
open_trade_count_start: int,
trade_dir: Optional[LongShort],
is_first: bool = True,
) -> int:
) -> None:
"""
NOTE: This method is used by Hyperopt at each iteration. Please keep it optimized.
@@ -1345,7 +1348,6 @@ class Backtesting:
# 1. Manage currently open orders of active trades
if self.manage_open_orders(t, current_time, row):
# Close trade
open_trade_count_start -= 1
LocalTrade.remove_bt_trade(t)
self.wallets.update()
@@ -1361,13 +1363,9 @@ class Backtesting:
and trade_dir is not None
and not PairLocks.is_pair_locked(pair, row[DATE_IDX], trade_dir)
):
if self.trade_slot_available(open_trade_count_start):
if self.trade_slot_available(LocalTrade.bt_open_open_trade_count):
trade = self._enter_trade(pair, row, trade_dir)
if trade:
# TODO: hacky workaround to avoid opening > max_open_trades
# This emulates previous behavior - not sure if this is correct
# Prevents entering if the trade-slot was freed in this candle
open_trade_count_start += 1
self.wallets.update()
else:
self._collate_rejected(pair, row)
@@ -1386,7 +1384,28 @@ class Backtesting:
order = trade.select_order(trade.exit_side, is_open=True)
if order:
self._process_exit_order(order, trade, current_time, row, pair)
return open_trade_count_start
def time_pair_generator(
self, start_date: datetime, end_date: datetime, increment: timedelta, pairs: List[str]
):
"""
Backtest time and pair generator
"""
current_time = start_date + increment
self.progress.init_step(
BacktestState.BACKTEST, int((end_date - start_date) / self.timeframe_td)
)
while current_time <= end_date:
is_first = True
# Pairs that have open trades should be processed first
new_pairlist = list(dict.fromkeys([t.pair for t in LocalTrade.bt_trades_open] + pairs))
for pair in new_pairlist:
yield current_time, pair, is_first
is_first = False
self.progress.increment()
current_time += increment
def backtest(self, processed: Dict, start_date: datetime, end_date: datetime) -> Dict[str, Any]:
"""
@@ -1411,87 +1430,75 @@ class Backtesting:
# Indexes per pair, so some pairs are allowed to have a missing start.
indexes: Dict = defaultdict(int)
current_time = start_date + self.timeframe_td
self.progress.init_step(
BacktestState.BACKTEST, int((end_date - start_date) / self.timeframe_td)
)
# Loop timerange and get candle for each pair at that point in time
while current_time <= end_date:
open_trade_count_start = LocalTrade.bt_open_open_trade_count
self.check_abort()
strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)(
current_time=current_time
)
for i, pair in enumerate(data):
row_index = indexes[pair]
row = self.validate_row(data, pair, row_index, current_time)
if not row:
for current_time, pair, is_first in self.time_pair_generator(
start_date, end_date, self.timeframe_td, list(data.keys())
):
if is_first:
self.check_abort()
strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)(
current_time=current_time
)
row_index = indexes[pair]
row = self.validate_row(data, pair, row_index, current_time)
if not row:
continue
row_index += 1
indexes[pair] = row_index
self.dataprovider._set_dataframe_max_index(self.required_startup + row_index)
self.dataprovider._set_dataframe_max_date(current_time)
current_detail_time: datetime = row[DATE_IDX].to_pydatetime()
trade_dir: Optional[LongShort] = self.check_for_trade_entry(row)
if (
(trade_dir is not None or len(LocalTrade.bt_trades_open_pp[pair]) > 0)
and self.timeframe_detail
and pair in self.detail_data
):
# Spread out into detail timeframe.
# Should only happen when we are either in a trade for this pair
# or when we got the signal for a new trade.
exit_candle_end = current_detail_time + self.timeframe_td
detail_data = self.detail_data[pair]
detail_data = detail_data.loc[
(detail_data["date"] >= current_detail_time)
& (detail_data["date"] < exit_candle_end)
].copy()
if len(detail_data) == 0:
# Fall back to "regular" data if no detail data was found for this candle
self.backtest_loop(row, pair, current_time, end_date, trade_dir)
continue
row_index += 1
indexes[pair] = row_index
self.dataprovider._set_dataframe_max_index(self.required_startup + row_index)
self.dataprovider._set_dataframe_max_date(current_time)
current_detail_time: datetime = row[DATE_IDX].to_pydatetime()
trade_dir: Optional[LongShort] = self.check_for_trade_entry(row)
if (
(trade_dir is not None or len(LocalTrade.bt_trades_open_pp[pair]) > 0)
and self.timeframe_detail
and pair in self.detail_data
):
# Spread out into detail timeframe.
# Should only happen when we are either in a trade for this pair
# or when we got the signal for a new trade.
exit_candle_end = current_detail_time + self.timeframe_td
detail_data = self.detail_data[pair]
detail_data = detail_data.loc[
(detail_data["date"] >= current_detail_time)
& (detail_data["date"] < exit_candle_end)
].copy()
if len(detail_data) == 0:
# Fall back to "regular" data if no detail data was found for this candle
open_trade_count_start = self.backtest_loop(
row, pair, current_time, end_date, open_trade_count_start, trade_dir
)
continue
detail_data.loc[:, "enter_long"] = row[LONG_IDX]
detail_data.loc[:, "exit_long"] = row[ELONG_IDX]
detail_data.loc[:, "enter_short"] = row[SHORT_IDX]
detail_data.loc[:, "exit_short"] = row[ESHORT_IDX]
detail_data.loc[:, "enter_tag"] = row[ENTER_TAG_IDX]
detail_data.loc[:, "exit_tag"] = row[EXIT_TAG_IDX]
is_first = True
current_time_det = current_time
for det_row in detail_data[HEADERS].values.tolist():
self.dataprovider._set_dataframe_max_date(current_time_det)
open_trade_count_start = self.backtest_loop(
det_row,
pair,
current_time_det,
end_date,
open_trade_count_start,
trade_dir,
is_first,
)
current_time_det += self.timeframe_detail_td
is_first = False
else:
self.dataprovider._set_dataframe_max_date(current_time)
open_trade_count_start = self.backtest_loop(
row, pair, current_time, end_date, open_trade_count_start, trade_dir
detail_data.loc[:, "enter_long"] = row[LONG_IDX]
detail_data.loc[:, "exit_long"] = row[ELONG_IDX]
detail_data.loc[:, "enter_short"] = row[SHORT_IDX]
detail_data.loc[:, "exit_short"] = row[ESHORT_IDX]
detail_data.loc[:, "enter_tag"] = row[ENTER_TAG_IDX]
detail_data.loc[:, "exit_tag"] = row[EXIT_TAG_IDX]
is_first = True
current_time_det = current_time
for det_row in detail_data[HEADERS].values.tolist():
self.dataprovider._set_dataframe_max_date(current_time_det)
self.backtest_loop(
det_row,
pair,
current_time_det,
end_date,
trade_dir,
is_first,
)
# Move time one configured time_interval ahead.
self.progress.increment()
current_time += self.timeframe_td
current_time_det += self.timeframe_detail_td
is_first = False
else:
self.dataprovider._set_dataframe_max_date(current_time)
self.backtest_loop(row, pair, current_time, end_date, trade_dir)
self.handle_left_open(LocalTrade.bt_trades_open_pp, data=data)
self.wallets.update()
results = trade_list_to_dataframe(LocalTrade.trades)
results = trade_list_to_dataframe(LocalTrade.bt_trades)
return {
"results": results,
"config": self.strategy.config,

View File

@@ -17,7 +17,6 @@ import rapidjson
from joblib import Parallel, cpu_count, delayed, dump, load, wrap_non_picklable_objects
from joblib.externals import cloudpickle
from pandas import DataFrame
from rich.align import Align
from rich.console import Console
from freqtrade.constants import DATETIME_PRINT_FORMAT, FTHYPT_FILEVERSION, LAST_BT_RESULT_FN, Config
@@ -80,7 +79,7 @@ class Hyperopt:
self.max_open_trades_space: List[Dimension] = []
self.dimensions: List[Dimension] = []
self._hyper_out: HyperoptOutput = HyperoptOutput()
self._hyper_out: HyperoptOutput = HyperoptOutput(streaming=True)
self.config = config
self.min_date: datetime
@@ -168,7 +167,9 @@ class Hyperopt:
cloudpickle.register_pickle_by_value(sys.modules[modules.__module__])
self.hyperopt_pickle_magic(modules.__bases__)
def _get_params_dict(self, dimensions: List[Dimension], raw_params: List[Any]) -> Dict:
def _get_params_dict(
self, dimensions: List[Dimension], raw_params: List[Any]
) -> Dict[str, Any]:
# Ensure the number of dimensions match
# the number of parameters in the list.
if len(raw_params) != len(dimensions):
@@ -317,7 +318,7 @@ class Hyperopt:
+ self.max_open_trades_space
)
def assign_params(self, params_dict: Dict, category: str) -> None:
def assign_params(self, params_dict: Dict[str, Any], category: str) -> None:
"""
Assign hyperoptable parameters
"""
@@ -404,7 +405,12 @@ class Hyperopt:
)
def _get_results_dict(
self, backtesting_results, min_date, max_date, params_dict, processed: Dict[str, DataFrame]
self,
backtesting_results: Dict[str, Any],
min_date: datetime,
max_date: datetime,
params_dict: Dict[str, Any],
processed: Dict[str, DataFrame],
) -> Dict[str, Any]:
params_details = self._get_params_details(params_dict)
@@ -628,7 +634,7 @@ class Hyperopt:
# Define progressbar
with get_progress_tracker(
console=console,
cust_objs=[Align.center(self._hyper_out.table)],
cust_callables=[self._hyper_out],
) as pbar:
task = pbar.add_task("Epochs", total=self.total_epochs)

View File

@@ -1,6 +1,8 @@
import sys
from typing import List, Optional, Union
from os import get_terminal_size
from typing import Any, List, Optional
from rich.align import Align
from rich.console import Console
from rich.table import Table
from rich.text import Text
@@ -11,7 +13,16 @@ from freqtrade.util import fmt_coin
class HyperoptOutput:
def __init__(self):
def __init__(self, streaming=False) -> None:
self._results: List[Any] = []
self._streaming = streaming
self.__init_table()
def __call__(self, *args: Any, **kwds: Any) -> Any:
return Align.center(self.table)
def __init_table(self) -> None:
"""Initialize table"""
self.table = Table(
title="Hyperopt results",
)
@@ -26,17 +37,6 @@ class HyperoptOutput:
self.table.add_column("Objective", justify="right")
self.table.add_column("Max Drawdown (Acct)", justify="right")
def _add_row(self, data: List[Union[str, Text]]):
"""Add single row"""
row_to_add: List[Union[str, Text]] = [r if isinstance(r, Text) else str(r) for r in data]
self.table.add_row(*row_to_add)
def _add_rows(self, data: List[List[Union[str, Text]]]):
"""add multiple rows"""
for row in data:
self._add_row(row)
def print(self, console: Optional[Console] = None, *, print_colorized=True):
if not console:
console = Console(
@@ -55,8 +55,28 @@ class HyperoptOutput:
) -> None:
"""Format one or multiple rows and add them"""
stake_currency = config["stake_currency"]
self._results.extend(results)
for r in results:
max_rows: Optional[int] = None
if self._streaming:
try:
ts = get_terminal_size()
# Get terminal size.
# Account for header, borders, and for the progress bar.
# This assumes that lines don't wrap.
if ts.columns < 148:
# If the terminal is too small, we can't display the table properly.
# We will halve the number of rows to display.
max_rows = -(int(ts.lines / 2) - 6)
else:
max_rows = -(ts.lines - 6)
except OSError:
# If we can't get the terminal size, we will just display the last 10 rows.
pass
self.__init_table()
for r in self._results[max_rows:]:
self.table.add_row(
*[
# "Best":

View File

@@ -147,6 +147,9 @@ def migrate_trades_and_orders_table(
price_precision = get_column_def(cols, "price_precision", "null")
precision_mode = get_column_def(cols, "precision_mode", "null")
contract_size = get_column_def(cols, "contract_size", "null")
precision_mode_price = get_column_def(
cols, "precision_mode_price", get_column_def(cols, "precision_mode", "null")
)
# Schema migration necessary
with engine.begin() as connection:
@@ -177,7 +180,7 @@ def migrate_trades_and_orders_table(
timeframe, open_trade_value, close_profit_abs,
trading_mode, leverage, liquidation_price, is_short,
interest_rate, funding_fees, funding_fee_running, realized_profit,
amount_precision, price_precision, precision_mode, contract_size,
amount_precision, price_precision, precision_mode, precision_mode_price, contract_size,
max_stake_amount
)
select id, lower(exchange), pair, {base_currency} base_currency,
@@ -207,8 +210,8 @@ def migrate_trades_and_orders_table(
{funding_fees} funding_fees, {funding_fee_running} funding_fee_running,
{realized_profit} realized_profit,
{amount_precision} amount_precision, {price_precision} price_precision,
{precision_mode} precision_mode, {contract_size} contract_size,
{max_stake_amount} max_stake_amount
{precision_mode} precision_mode, {precision_mode_price} precision_mode_price,
{contract_size} contract_size, {max_stake_amount} max_stake_amount
from {trade_back_name}
"""
)
@@ -348,8 +351,8 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
# if ('orders' not in previous_tables
# or not has_column(cols_orders, 'funding_fee')):
migrating = False
# if not has_column(cols_trades, 'funding_fee_running'):
if not has_column(cols_orders, "ft_order_tag"):
if not has_column(cols_trades, "precision_mode_price"):
# if not has_column(cols_orders, "ft_order_tag"):
migrating = True
logger.info(
f"Running database migration for trades - "

View File

@@ -373,12 +373,12 @@ class LocalTrade:
use_db: bool = False
# Trades container for backtesting
trades: List["LocalTrade"] = []
trades_open: List["LocalTrade"] = []
bt_trades: List["LocalTrade"] = []
bt_trades_open: List["LocalTrade"] = []
# Copy of trades_open - but indexed by pair
bt_trades_open_pp: Dict[str, List["LocalTrade"]] = defaultdict(list)
bt_open_open_trade_count: int = 0
total_profit: float = 0
bt_total_profit: float = 0
realized_profit: float = 0
id: int = 0
@@ -433,6 +433,7 @@ class LocalTrade:
amount_precision: Optional[float] = None
price_precision: Optional[float] = None
precision_mode: Optional[int] = None
precision_mode_price: Optional[int] = None
contract_size: Optional[float] = None
# Leverage trading properties
@@ -730,6 +731,7 @@ class LocalTrade:
"amount_precision": self.amount_precision,
"price_precision": self.price_precision,
"precision_mode": self.precision_mode,
"precision_mode_price": self.precision_mode_price,
"contract_size": self.contract_size,
"has_open_orders": self.has_open_orders,
"orders": orders_json,
@@ -740,11 +742,11 @@ class LocalTrade:
"""
Resets all trades. Only active for backtesting mode.
"""
LocalTrade.trades = []
LocalTrade.trades_open = []
LocalTrade.bt_trades = []
LocalTrade.bt_trades_open = []
LocalTrade.bt_trades_open_pp = defaultdict(list)
LocalTrade.bt_open_open_trade_count = 0
LocalTrade.total_profit = 0
LocalTrade.bt_total_profit = 0
def adjust_min_max_rates(self, current_price: float, current_price_low: float) -> None:
"""
@@ -810,7 +812,7 @@ class LocalTrade:
stop_loss_norm = price_to_precision(
new_loss,
self.price_precision,
self.precision_mode,
self.precision_mode_price,
rounding_mode=ROUND_DOWN if self.is_short else ROUND_UP,
)
# no stop loss assigned yet
@@ -819,7 +821,7 @@ class LocalTrade:
self.initial_stop_loss = price_to_precision(
stop_loss_norm,
self.price_precision,
self.precision_mode,
self.precision_mode_price,
rounding_mode=ROUND_DOWN if self.is_short else ROUND_UP,
)
self.initial_stop_loss_pct = -1 * abs(stoploss)
@@ -1217,7 +1219,7 @@ class LocalTrade:
# with realized_profit.
close_profit = (close_profit_abs / total_stake) * self.leverage
else:
total_stake = total_stake + self._calc_open_trade_value(tmp_amount, price)
total_stake += self._calc_open_trade_value(tmp_amount, price)
max_stake_amount += tmp_amount * price
self.funding_fees = funding_fees
self.max_stake_amount = float(max_stake_amount)
@@ -1236,7 +1238,7 @@ class LocalTrade:
self.open_rate = float(current_stake / current_amount)
self.amount = current_amount_tr
self.stake_amount = float(current_stake) / (self.leverage or 1.0)
self.fee_open_cost = self.fee_open * float(current_stake)
self.fee_open_cost = self.fee_open * float(self.max_stake_amount)
self.recalc_open_trade_value()
if self.stop_loss_pct is not None and self.open_rate is not None:
self.adjust_stop_loss(self.open_rate, self.stop_loss_pct)
@@ -1405,7 +1407,7 @@ class LocalTrade:
Helper function to query Trades.
Returns a List of trades, filtered on the parameters given.
In live mode, converts the filter to a database query and returns all rows
In Backtest mode, uses filters on Trade.trades to get the result.
In Backtest mode, uses filters on Trade.bt_trades to get the result.
:param pair: Filter by pair
:param is_open: Filter by open/closed status
@@ -1418,13 +1420,13 @@ class LocalTrade:
# Offline mode - without database
if is_open is not None:
if is_open:
sel_trades = LocalTrade.trades_open
sel_trades = LocalTrade.bt_trades_open
else:
sel_trades = LocalTrade.trades
sel_trades = LocalTrade.bt_trades
else:
# Not used during backtesting, but might be used by a strategy
sel_trades = list(LocalTrade.trades + LocalTrade.trades_open)
sel_trades = list(LocalTrade.bt_trades + LocalTrade.bt_trades_open)
if pair:
sel_trades = [trade for trade in sel_trades if trade.pair == pair]
@@ -1439,24 +1441,24 @@ class LocalTrade:
@staticmethod
def close_bt_trade(trade):
LocalTrade.trades_open.remove(trade)
LocalTrade.bt_trades_open.remove(trade)
LocalTrade.bt_trades_open_pp[trade.pair].remove(trade)
LocalTrade.bt_open_open_trade_count -= 1
LocalTrade.trades.append(trade)
LocalTrade.total_profit += trade.close_profit_abs
LocalTrade.bt_trades.append(trade)
LocalTrade.bt_total_profit += trade.close_profit_abs
@staticmethod
def add_bt_trade(trade):
if trade.is_open:
LocalTrade.trades_open.append(trade)
LocalTrade.bt_trades_open.append(trade)
LocalTrade.bt_trades_open_pp[trade.pair].append(trade)
LocalTrade.bt_open_open_trade_count += 1
else:
LocalTrade.trades.append(trade)
LocalTrade.bt_trades.append(trade)
@staticmethod
def remove_bt_trade(trade):
LocalTrade.trades_open.remove(trade)
LocalTrade.bt_trades_open.remove(trade)
LocalTrade.bt_trades_open_pp[trade.pair].remove(trade)
LocalTrade.bt_open_open_trade_count -= 1
@@ -1562,6 +1564,7 @@ class LocalTrade:
amount_precision=data.get("amount_precision", None),
price_precision=data.get("price_precision", None),
precision_mode=data.get("precision_mode", None),
precision_mode_price=data.get("precision_mode_price", data.get("precision_mode", None)),
contract_size=data.get("contract_size", None),
)
for order in data["orders"]:
@@ -1695,6 +1698,9 @@ class Trade(ModelBase, LocalTrade):
)
price_precision: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore
precision_mode: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) # type: ignore
precision_mode_price: Mapped[Optional[int]] = mapped_column( # type: ignore
Integer, nullable=True
)
contract_size: Mapped[Optional[float]] = mapped_column(Float(), nullable=True) # type: ignore
# Leverage trading properties
@@ -1761,7 +1767,7 @@ class Trade(ModelBase, LocalTrade):
Helper function to query Trades.j
Returns a List of trades, filtered on the parameters given.
In live mode, converts the filter to a database query and returns all rows
In Backtest mode, uses filters on Trade.trades to get the result.
In Backtest mode, uses filters on Trade.bt_trades to get the result.
:return: unsorted List[Trade]
"""

View File

@@ -0,0 +1,329 @@
"""
Percent Change PairList provider
Provides dynamic pair list based on trade change
sorted based on percentage change in price over a
defined period or as coming from ticker
"""
import logging
from datetime import timedelta
from typing import Any, Dict, List, Optional
from cachetools import TTLCache
from pandas import DataFrame
from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date
from freqtrade.exchange.types import Ticker, Tickers
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter, SupportsBacktesting
from freqtrade.util import dt_now, format_ms_time
logger = logging.getLogger(__name__)
class PercentChangePairList(IPairList):
is_pairlist_generator = True
supports_backtesting = SupportsBacktesting.NO
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
if "number_assets" not in self._pairlistconfig:
raise OperationalException(
"`number_assets` not specified. Please check your configuration "
'for "pairlist.config.number_assets"'
)
self._stake_currency = self._config["stake_currency"]
self._number_pairs = self._pairlistconfig["number_assets"]
self._min_value = self._pairlistconfig.get("min_value", None)
self._max_value = self._pairlistconfig.get("max_value", None)
self._refresh_period = self._pairlistconfig.get("refresh_period", 1800)
self._pair_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period)
self._lookback_days = self._pairlistconfig.get("lookback_days", 0)
self._lookback_timeframe = self._pairlistconfig.get("lookback_timeframe", "1d")
self._lookback_period = self._pairlistconfig.get("lookback_period", 0)
self._sort_direction: Optional[str] = self._pairlistconfig.get("sort_direction", "desc")
self._def_candletype = self._config["candle_type_def"]
if (self._lookback_days > 0) & (self._lookback_period > 0):
raise OperationalException(
"Ambiguous configuration: lookback_days and lookback_period both set in pairlist "
"config. Please set lookback_days only or lookback_period and lookback_timeframe "
"and restart the bot."
)
# overwrite lookback timeframe and days when lookback_days is set
if self._lookback_days > 0:
self._lookback_timeframe = "1d"
self._lookback_period = self._lookback_days
# get timeframe in minutes and seconds
self._tf_in_min = timeframe_to_minutes(self._lookback_timeframe)
_tf_in_sec = self._tf_in_min * 60
# whether to use range lookback or not
self._use_range = (self._tf_in_min > 0) & (self._lookback_period > 0)
if self._use_range & (self._refresh_period < _tf_in_sec):
raise OperationalException(
f"Refresh period of {self._refresh_period} seconds is smaller than one "
f"timeframe of {self._lookback_timeframe}. Please adjust refresh_period "
f"to at least {_tf_in_sec} and restart the bot."
)
if not self._use_range and not (
self._exchange.exchange_has("fetchTickers")
and self._exchange.get_option("tickers_have_percentage")
):
raise OperationalException(
"Exchange does not support dynamic whitelist in this configuration. "
"Please edit your config and either remove PercentChangePairList, "
"or switch to using candles. and restart the bot."
)
candle_limit = self._exchange.ohlcv_candle_limit(
self._lookback_timeframe, self._config["candle_type_def"]
)
if self._lookback_period > candle_limit:
raise OperationalException(
"ChangeFilter requires lookback_period to not "
f"exceed exchange max request size ({candle_limit})"
)
@property
def needstickers(self) -> bool:
"""
Boolean property defining if tickers are necessary.
If no Pairlist requires tickers, an empty Dict is passed
as tickers argument to filter_pairlist
"""
return not self._use_range
def short_desc(self) -> str:
"""
Short whitelist method description - used for startup-messages
"""
return f"{self.name} - top {self._pairlistconfig['number_assets']} percent change pairs."
@staticmethod
def description() -> str:
return "Provides dynamic pair list based on percentage change."
@staticmethod
def available_parameters() -> Dict[str, PairlistParameter]:
return {
"number_assets": {
"type": "number",
"default": 30,
"description": "Number of assets",
"help": "Number of assets to use from the pairlist",
},
"min_value": {
"type": "number",
"default": None,
"description": "Minimum value",
"help": "Minimum value to use for filtering the pairlist.",
},
"max_value": {
"type": "number",
"default": None,
"description": "Maximum value",
"help": "Maximum value to use for filtering the pairlist.",
},
"sort_direction": {
"type": "option",
"default": "desc",
"options": ["", "asc", "desc"],
"description": "Sort pairlist",
"help": "Sort Pairlist ascending or descending by rate of change.",
},
**IPairList.refresh_period_parameter(),
"lookback_days": {
"type": "number",
"default": 0,
"description": "Lookback Days",
"help": "Number of days to look back at.",
},
"lookback_timeframe": {
"type": "string",
"default": "1d",
"description": "Lookback Timeframe",
"help": "Timeframe to use for lookback.",
},
"lookback_period": {
"type": "number",
"default": 0,
"description": "Lookback Period",
"help": "Number of periods to look back at.",
},
}
def gen_pairlist(self, tickers: Tickers) -> List[str]:
"""
Generate the pairlist
:param tickers: Tickers (from exchange.get_tickers). May be cached.
:return: List of pairs
"""
pairlist = self._pair_cache.get("pairlist")
if pairlist:
# Item found - no refresh necessary
return pairlist.copy()
else:
# Use fresh pairlist
# Check if pair quote currency equals to the stake currency.
_pairlist = [
k
for k in self._exchange.get_markets(
quote_currencies=[self._stake_currency], tradable_only=True, active_only=True
).keys()
]
# No point in testing for blacklisted pairs...
_pairlist = self.verify_blacklist(_pairlist, logger.info)
if not self._use_range:
filtered_tickers = [
v
for k, v in tickers.items()
if (
self._exchange.get_pair_quote_currency(k) == self._stake_currency
and (self._use_range or v.get("percentage") is not None)
and v["symbol"] in _pairlist
)
]
pairlist = [s["symbol"] for s in filtered_tickers]
else:
pairlist = _pairlist
pairlist = self.filter_pairlist(pairlist, tickers)
self._pair_cache["pairlist"] = pairlist.copy()
return pairlist
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
"""
Filters and sorts pairlist and returns the whitelist again.
Called on each bot iteration - please use internal caching if necessary
:param pairlist: pairlist to filter or sort
:param tickers: Tickers (from exchange.get_tickers). May be cached.
:return: new whitelist
"""
filtered_tickers: List[Dict[str, Any]] = [{"symbol": k} for k in pairlist]
if self._use_range:
# calculating using lookback_period
self.fetch_percent_change_from_lookback_period(filtered_tickers)
else:
# Fetching 24h change by default from supported exchange tickers
self.fetch_percent_change_from_tickers(filtered_tickers, tickers)
if self._min_value is not None:
filtered_tickers = [v for v in filtered_tickers if v["percentage"] > self._min_value]
if self._max_value is not None:
filtered_tickers = [v for v in filtered_tickers if v["percentage"] < self._max_value]
sorted_tickers = sorted(
filtered_tickers,
reverse=self._sort_direction == "desc",
key=lambda t: t["percentage"],
)
# Validate whitelist to only have active market pairs
pairs = self._whitelist_for_active_markets([s["symbol"] for s in sorted_tickers])
pairs = self.verify_blacklist(pairs, logmethod=logger.info)
# Limit pairlist to the requested number of pairs
pairs = pairs[: self._number_pairs]
return pairs
def fetch_candles_for_lookback_period(
self, filtered_tickers: List[Dict[str, str]]
) -> Dict[PairWithTimeframe, DataFrame]:
since_ms = (
int(
timeframe_to_prev_date(
self._lookback_timeframe,
dt_now()
+ timedelta(
minutes=-(self._lookback_period * self._tf_in_min) - self._tf_in_min
),
).timestamp()
)
* 1000
)
to_ms = (
int(
timeframe_to_prev_date(
self._lookback_timeframe, dt_now() - timedelta(minutes=self._tf_in_min)
).timestamp()
)
* 1000
)
# todo: utc date output for starting date
self.log_once(
f"Using change range of {self._lookback_period} candles, timeframe: "
f"{self._lookback_timeframe}, starting from {format_ms_time(since_ms)} "
f"till {format_ms_time(to_ms)}",
logger.info,
)
needed_pairs: ListPairsWithTimeframes = [
(p, self._lookback_timeframe, self._def_candletype)
for p in [s["symbol"] for s in filtered_tickers]
if p not in self._pair_cache
]
candles = self._exchange.refresh_ohlcv_with_cache(needed_pairs, since_ms)
return candles
def fetch_percent_change_from_lookback_period(self, filtered_tickers: List[Dict[str, Any]]):
# get lookback period in ms, for exchange ohlcv fetch
candles = self.fetch_candles_for_lookback_period(filtered_tickers)
for i, p in enumerate(filtered_tickers):
pair_candles = (
candles[(p["symbol"], self._lookback_timeframe, self._def_candletype)]
if (p["symbol"], self._lookback_timeframe, self._def_candletype) in candles
else None
)
# in case of candle data calculate typical price and change for candle
if pair_candles is not None and not pair_candles.empty:
current_close = pair_candles["close"].iloc[-1]
previous_close = pair_candles["close"].shift(self._lookback_period).iloc[-1]
pct_change = (
((current_close - previous_close) / previous_close) if previous_close > 0 else 0
)
# replace change with a range change sum calculated above
filtered_tickers[i]["percentage"] = pct_change
else:
filtered_tickers[i]["percentage"] = 0
def fetch_percent_change_from_tickers(self, filtered_tickers: List[Dict[str, Any]], tickers):
for i, p in enumerate(filtered_tickers):
# Filter out assets
if not self._validate_pair(
p["symbol"], tickers[p["symbol"]] if p["symbol"] in tickers else None
):
filtered_tickers.remove(p)
else:
filtered_tickers[i]["percentage"] = tickers[p["symbol"]]["percentage"]
def _validate_pair(self, pair: str, ticker: Optional[Ticker]) -> bool:
"""
Check if one price-step (pip) is > than a certain barrier.
:param pair: Pair that's currently validated
:param ticker: ticker dict as returned from ccxt.fetch_ticker
:return: True if the pair can stay, false if it should be removed
"""
if not ticker or "percentage" not in ticker or ticker["percentage"] is None:
self.log_once(
f"Removed {pair} from whitelist, because "
"ticker['percentage'] is empty (Usually no trade in the last 24h).",
logger.info,
)
return False
return True

View File

@@ -18,19 +18,19 @@ class CooldownPeriod(IProtection):
"""
LockReason to use
"""
return f"Cooldown period for {self.stop_duration_str}."
return f"Cooldown period for {self.unlock_reason_time_element}."
def short_desc(self) -> str:
"""
Short method description - used for startup-messages
Short method description - used for startup messages
"""
return f"{self.name} - Cooldown period of {self.stop_duration_str}."
return f"{self.name} - Cooldown period {self.unlock_reason_time_element}."
def _cooldown_period(self, pair: str, date_now: datetime) -> Optional[ProtectionReturn]:
"""
Get last trade for this pair
"""
look_back_until = date_now - timedelta(minutes=self._stop_duration)
look_back_until = date_now - timedelta(minutes=self._lookback_period)
# filters = [
# Trade.is_open.is_(False),
# Trade.close_date > look_back_until,
@@ -42,8 +42,8 @@ class CooldownPeriod(IProtection):
# Get latest trade
# Ignore type error as we know we only get closed trades.
trade = sorted(trades, key=lambda t: t.close_date)[-1] # type: ignore
self.log_once(f"Cooldown for {pair} for {self.stop_duration_str}.", logger.info)
until = self.calculate_lock_end([trade], self._stop_duration)
self.log_once(f"Cooldown for {pair} {self.unlock_reason_time_element}.", logger.info)
until = self.calculate_lock_end([trade])
return ProtectionReturn(
lock=True,

View File

@@ -32,15 +32,19 @@ class IProtection(LoggingMixin, ABC):
self._config = config
self._protection_config = protection_config
self._stop_duration_candles: Optional[int] = None
self._stop_duration: int = 0
self._lookback_period_candles: Optional[int] = None
self._unlock_at: Optional[str] = None
tf_in_min = timeframe_to_minutes(config["timeframe"])
if "stop_duration_candles" in protection_config:
self._stop_duration_candles = int(protection_config.get("stop_duration_candles", 1))
self._stop_duration = tf_in_min * self._stop_duration_candles
elif "unlock_at" in protection_config:
self._unlock_at = protection_config.get("unlock_at")
else:
self._stop_duration_candles = None
self._stop_duration = int(protection_config.get("stop_duration", 60))
if "lookback_period_candles" in protection_config:
self._lookback_period_candles = int(protection_config.get("lookback_period_candles", 1))
self._lookback_period = tf_in_min * self._lookback_period_candles
@@ -80,6 +84,16 @@ class IProtection(LoggingMixin, ABC):
else:
return f"{self._lookback_period} {plural(self._lookback_period, 'minute', 'minutes')}"
@property
def unlock_reason_time_element(self) -> str:
"""
Output configured unlock time or stop duration
"""
if self._unlock_at is not None:
return f"until {self._unlock_at}"
else:
return f"for {self.stop_duration_str}"
@abstractmethod
def short_desc(self) -> str:
"""
@@ -105,16 +119,23 @@ class IProtection(LoggingMixin, ABC):
If true, this pair will be locked with <reason> until <until>
"""
@staticmethod
def calculate_lock_end(trades: List[LocalTrade], stop_minutes: int) -> datetime:
def calculate_lock_end(self, trades: List[LocalTrade]) -> datetime:
"""
Get lock end time
Implicitly uses `self._stop_duration` or `self._unlock_at` depending on the configuration.
"""
max_date: datetime = max([trade.close_date for trade in trades if trade.close_date])
# coming from Database, tzinfo is not set.
if max_date.tzinfo is None:
max_date = max_date.replace(tzinfo=timezone.utc)
until = max_date + timedelta(minutes=stop_minutes)
if self._unlock_at is not None:
# unlock_at case with fixed hour of the day
hour, minutes = self._unlock_at.split(":")
unlock_at = max_date.replace(hour=int(hour), minute=int(minutes))
if unlock_at < max_date:
unlock_at += timedelta(days=1)
return unlock_at
until = max_date + timedelta(minutes=self._stop_duration)
return until

View File

@@ -36,7 +36,7 @@ class LowProfitPairs(IProtection):
"""
return (
f"{profit} < {self._required_profit} in {self.lookback_period_str}, "
f"locking for {self.stop_duration_str}."
f"locking {self.unlock_reason_time_element}."
)
def _low_profit(
@@ -70,7 +70,7 @@ class LowProfitPairs(IProtection):
f"within {self._lookback_period} minutes.",
logger.info,
)
until = self.calculate_lock_end(trades, self._stop_duration)
until = self.calculate_lock_end(trades)
return ProtectionReturn(
lock=True,

View File

@@ -39,7 +39,7 @@ class MaxDrawdown(IProtection):
"""
return (
f"{drawdown} passed {self._max_allowed_drawdown} in {self.lookback_period_str}, "
f"locking for {self.stop_duration_str}."
f"locking {self.unlock_reason_time_element}."
)
def _max_drawdown(self, date_now: datetime) -> Optional[ProtectionReturn]:
@@ -70,7 +70,8 @@ class MaxDrawdown(IProtection):
f" within {self.lookback_period_str}.",
logger.info,
)
until = self.calculate_lock_end(trades, self._stop_duration)
until = self.calculate_lock_end(trades)
return ProtectionReturn(
lock=True,

View File

@@ -38,7 +38,7 @@ class StoplossGuard(IProtection):
"""
return (
f"{self._trade_limit} stoplosses in {self._lookback_period} min, "
f"locking for {self._stop_duration} min."
f"locking {self.unlock_reason_time_element}."
)
def _stoploss_guard(
@@ -78,7 +78,7 @@ class StoplossGuard(IProtection):
f"stoplosses within {self._lookback_period} minutes.",
logger.info,
)
until = self.calculate_lock_end(trades, self._stop_duration)
until = self.calculate_lock_end(trades)
return ProtectionReturn(
lock=True,
until=until,

View File

@@ -182,7 +182,7 @@ def api_get_backtest():
ApiBG.bt["bt"].progress.action if ApiBG.bt["bt"] else str(BacktestState.STARTUP)
),
"progress": ApiBG.bt["bt"].progress.progress if ApiBG.bt["bt"] else 0,
"trade_count": len(LocalTrade.trades),
"trade_count": len(LocalTrade.bt_trades),
"status_msg": "Backtest running",
}

View File

@@ -67,7 +67,6 @@ class Balance(BaseModel):
stake: str
# Starting with 2.x
side: str
leverage: float
is_position: bool
position: float
is_bot_managed: bool

View File

@@ -58,7 +58,7 @@ async def channel_broadcaster(channel: WebSocketChannel, message_stream: Message
" consumers."
)
await channel.send(message, timeout=True)
await channel.send(message, use_timeout=True)
async def _process_consumer_request(request: Dict[str, Any], channel: WebSocketChannel, rpc: RPC):

View File

@@ -8,6 +8,7 @@ def asyncio_setup() -> None: # pragma: no cover
# Set eventloop for win32 setups
# Reverts a change done in uvicorn 0.15.0 - which now sets the eventloop
# via policy.
# TODO: is this workaround actually needed?
import sys
if sys.version_info >= (3, 8) and sys.platform == "win32":

View File

@@ -80,7 +80,7 @@ class WebSocketChannel:
self._send_high_limit = min(max(self.avg_send_time * 2, 1), 3)
async def send(
self, message: Union[WSMessageSchemaType, Dict[str, Any]], timeout: bool = False
self, message: Union[WSMessageSchemaType, Dict[str, Any]], use_timeout: bool = False
):
"""
Send a message on the wrapped websocket. If the sending
@@ -88,7 +88,7 @@ class WebSocketChannel:
disconnect the connection.
:param message: The message to send
:param timeout: Enforce send high limit, defaults to False
:param use_timeout: Enforce send high limit, defaults to False
"""
try:
_ = time.time()
@@ -96,7 +96,8 @@ class WebSocketChannel:
# a TimeoutError and bubble up to the
# message_endpoint to close the connection
await asyncio.wait_for(
self._wrapped_ws.send(message), timeout=self._send_high_limit if timeout else None
self._wrapped_ws.send(message),
timeout=self._send_high_limit if use_timeout else None,
)
total_time = time.time() - _
self._send_times.append(total_time)

View File

@@ -296,7 +296,10 @@ class RPC:
else:
trade_profit = 0.0
profit_str = f"{0.0:.2f}"
direction_str = ("S" if trade.is_short else "L") if nonspot else ""
leverage = f"{trade.leverage:.3g}"
direction_str = (
(f"S {leverage}x" if trade.is_short else f"L {leverage}x") if nonspot else ""
)
if self._fiat_converter:
fiat_profit = self._fiat_converter.convert_amount(
trade_profit, stake_currency, fiat_display_currency
@@ -742,7 +745,6 @@ class RPC:
"est_stake_bot": est_stake_bot if is_bot_managed else 0,
"stake": stake_currency,
"side": "long",
"leverage": 1,
"position": 0,
"is_bot_managed": is_bot_managed,
"is_position": False,
@@ -764,7 +766,6 @@ class RPC:
"est_stake": position.collateral,
"est_stake_bot": position.collateral,
"stake": stake_currency,
"leverage": position.leverage,
"side": position.side,
"is_bot_managed": True,
"is_position": True,

View File

@@ -1133,7 +1133,6 @@ class Telegram(RPCHandler):
curr_output = (
f"*{curr['currency']}:*\n"
f"\t`{curr['side']}: {curr['position']:.8f}`\n"
f"\t`Leverage: {curr['leverage']:.1f}`\n"
f"\t`Est. {curr['stake']}: "
f"{fmt_coin(curr['est_stake'], curr['stake'], False)}`\n"
)

View File

@@ -6,6 +6,7 @@ from freqtrade.exchange import (
timeframe_to_prev_date,
timeframe_to_seconds,
)
from freqtrade.persistence import Order, PairLocks, Trade
from freqtrade.strategy.informative_decorator import informative
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy.parameters import (
@@ -20,3 +21,27 @@ from freqtrade.strategy.strategy_helper import (
stoploss_from_absolute,
stoploss_from_open,
)
# Imports to be used for `from freqtrade.strategy import *`
__all__ = [
"IStrategy",
"Trade",
"Order",
"PairLocks",
"informative",
# Parameters
"BooleanParameter",
"CategoricalParameter",
"DecimalParameter",
"IntParameter",
"RealParameter",
# timeframe helpers
"timeframe_to_minutes",
"timeframe_to_next_date",
"timeframe_to_prev_date",
# Strategy helper functions
"merge_informative_pair",
"stoploss_from_absolute",
"stoploss_from_open",
]

View File

@@ -6,6 +6,7 @@
"refresh_period": 1800
}' %}
{
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": {{ max_open_trades }},
"stake_currency": "{{ stake_currency }}",
"stake_amount": {{ stake_amount }},

View File

@@ -1,15 +1,34 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
# --- Do not remove these libs ---
# --- Do not remove these imports ---
import numpy as np
import pandas as pd
from datetime import datetime, timedelta, timezone
from pandas import DataFrame
from datetime import datetime
from typing import Optional, Union
from typing import Dict, Optional, Union, Tuple
from freqtrade.strategy import (BooleanParameter, CategoricalParameter, DecimalParameter,
IntParameter, IStrategy, merge_informative_pair)
from freqtrade.strategy import (
IStrategy,
Trade,
Order,
PairLocks,
informative, # @informative decorator
# Hyperopt Parameters
BooleanParameter,
CategoricalParameter,
DecimalParameter,
IntParameter,
RealParameter,
# timeframe helpers
timeframe_to_minutes,
timeframe_to_next_date,
timeframe_to_prev_date,
# Strategy helper functions
merge_informative_pair,
stoploss_from_absolute,
stoploss_from_open,
)
# --------------------------------
# Add your lib to import here
@@ -40,7 +59,7 @@ class {{ strategy }}(IStrategy):
INTERFACE_VERSION = 3
# Optimal timeframe for the strategy.
timeframe = '5m'
timeframe = "5m"
# Can this strategy go short?
can_short: bool = False
@@ -78,8 +97,8 @@ class {{ strategy }}(IStrategy):
buy_rsi = IntParameter(10, 40, default=30, space="buy")
sell_rsi = IntParameter(60, 90, default=70, space="sell")
{{ attributes | indent(4) }}
{{ plot_config | indent(4) }}
{{- attributes | indent(4) }}
{{- plot_config | indent(4) }}
def informative_pairs(self):
"""
@@ -105,7 +124,7 @@ class {{ strategy }}(IStrategy):
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
"""
{{ indicators | indent(8) }}
{{- indicators | indent(8) }}
return dataframe
@@ -119,9 +138,9 @@ class {{ strategy }}(IStrategy):
dataframe.loc[
(
{{ buy_trend | indent(16) }}
(dataframe['volume'] > 0) # Make sure Volume is not 0
(dataframe["volume"] > 0) # Make sure Volume is not 0
),
'enter_long'] = 1
"enter_long"] = 1
# Uncomment to use shorts (Only used in futures/margin mode. Check the documentation for more info)
"""
dataframe.loc[
@@ -144,9 +163,9 @@ class {{ strategy }}(IStrategy):
dataframe.loc[
(
{{ sell_trend | indent(16) }}
(dataframe['volume'] > 0) # Make sure Volume is not 0
(dataframe["volume"] > 0) # Make sure Volume is not 0
),
'exit_long'] = 1
"exit_long"] = 1
# Uncomment to use shorts (Only used in futures/margin mode. Check the documentation for more info)
"""
dataframe.loc[
@@ -157,4 +176,4 @@ class {{ strategy }}(IStrategy):
'exit_short'] = 1
"""
return dataframe
{{ additional_methods | indent(4) }}
{{- additional_methods | indent(4) }}

View File

@@ -1,24 +1,39 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
# flake8: noqa: F401
# isort: skip_file
# --- Do not remove these libs ---
import numpy as np # noqa
import pandas as pd # noqa
# --- Do not remove these imports ---
import numpy as np
import pandas as pd
from datetime import datetime, timedelta, timezone
from pandas import DataFrame
from typing import Optional, Union
from typing import Dict, Optional, Union, Tuple
from freqtrade.strategy import (
IStrategy,
Trade,
Order,
PairLocks,
informative, # @informative decorator
# Hyperopt Parameters
BooleanParameter,
CategoricalParameter,
DecimalParameter,
IStrategy,
IntParameter,
RealParameter,
# timeframe helpers
timeframe_to_minutes,
timeframe_to_next_date,
timeframe_to_prev_date,
# Strategy helper functions
merge_informative_pair,
stoploss_from_absolute,
stoploss_from_open,
)
# --------------------------------
# Add your lib to import here
import talib.abstract as ta
import freqtrade.vendor.qtpylib.indicators as qtpylib
from technical import qtpylib
# This class is a sample. Feel free to customize it.

View File

@@ -29,19 +29,22 @@
"import os\n",
"from pathlib import Path\n",
"\n",
"\n",
"# Change directory\n",
"# Modify this cell to insure that the output shows the correct path.\n",
"# Define all paths relative to the project root shown in the cell output\n",
"project_root = \"somedir/freqtrade\"\n",
"i=0\n",
"i = 0\n",
"try:\n",
" os.chdir(project_root)\n",
" assert Path('LICENSE').is_file()\n",
"except:\n",
" while i<4 and (not Path('LICENSE').is_file()):\n",
" os.chdir(Path(Path.cwd(), '../'))\n",
" i+=1\n",
" project_root = Path.cwd()\n",
" if not Path(\"LICENSE\").is_file():\n",
" i = 0\n",
" while i < 4 and (not Path(\"LICENSE\").is_file()):\n",
" os.chdir(Path(Path.cwd(), \"../\"))\n",
" i += 1\n",
" project_root = Path.cwd()\n",
"except FileNotFoundError:\n",
" print(\"Please define the project root relative to the current directory\")\n",
"print(Path.cwd())"
]
},
@@ -60,6 +63,7 @@
"source": [
"from freqtrade.configuration import Configuration\n",
"\n",
"\n",
"# Customize these according to your needs.\n",
"\n",
"# Initialize empty configuration object\n",
@@ -87,12 +91,14 @@
"from freqtrade.data.history import load_pair_history\n",
"from freqtrade.enums import CandleType\n",
"\n",
"candles = load_pair_history(datadir=data_location,\n",
" timeframe=config[\"timeframe\"],\n",
" pair=pair,\n",
" data_format = \"json\", # Make sure to update this to your data\n",
" candle_type=CandleType.SPOT,\n",
" )\n",
"\n",
"candles = load_pair_history(\n",
" datadir=data_location,\n",
" timeframe=config[\"timeframe\"],\n",
" pair=pair,\n",
" data_format=\"json\", # Make sure to update this to your data\n",
" candle_type=CandleType.SPOT,\n",
")\n",
"\n",
"# Confirm success\n",
"print(f\"Loaded {len(candles)} rows of data for {pair} from {data_location}\")\n",
@@ -114,14 +120,16 @@
"outputs": [],
"source": [
"# Load strategy using values set above\n",
"from freqtrade.resolvers import StrategyResolver\n",
"from freqtrade.data.dataprovider import DataProvider\n",
"from freqtrade.resolvers import StrategyResolver\n",
"\n",
"\n",
"strategy = StrategyResolver.load_strategy(config)\n",
"strategy.dp = DataProvider(config, None, None)\n",
"strategy.ft_bot_start()\n",
"\n",
"# Generate buy/sell signals using strategy\n",
"df = strategy.analyze_ticker(candles, {'pair': pair})\n",
"df = strategy.analyze_ticker(candles, {\"pair\": pair})\n",
"df.tail()"
]
},
@@ -148,7 +156,7 @@
"source": [
"# Report results\n",
"print(f\"Generated {df['enter_long'].sum()} entry signals\")\n",
"data = df.set_index('date', drop=False)\n",
"data = df.set_index(\"date\", drop=False)\n",
"data.tail()"
]
},
@@ -179,10 +187,13 @@
"source": [
"from freqtrade.data.btanalysis import load_backtest_data, load_backtest_stats\n",
"\n",
"\n",
"# if backtest_dir points to a directory, it'll automatically load the last backtest file.\n",
"backtest_dir = config[\"user_data_dir\"] / \"backtest_results\"\n",
"# backtest_dir can also point to a specific file\n",
"# backtest_dir = config[\"user_data_dir\"] / \"backtest_results/backtest-result-2020-07-01_20-04-22.json\""
"# backtest_dir = (\n",
"# config[\"user_data_dir\"] / \"backtest_results/backtest-result-2020-07-01_20-04-22.json\"\n",
"# )"
]
},
{
@@ -195,23 +206,24 @@
"# This contains all information used to generate the backtest result.\n",
"stats = load_backtest_stats(backtest_dir)\n",
"\n",
"strategy = 'SampleStrategy'\n",
"# All statistics are available per strategy, so if `--strategy-list` was used during backtest, this will be reflected here as well.\n",
"strategy = \"SampleStrategy\"\n",
"# All statistics are available per strategy, so if `--strategy-list` was used during backtest,\n",
"# this will be reflected here as well.\n",
"# Example usages:\n",
"print(stats['strategy'][strategy]['results_per_pair'])\n",
"print(stats[\"strategy\"][strategy][\"results_per_pair\"])\n",
"# Get pairlist used for this backtest\n",
"print(stats['strategy'][strategy]['pairlist'])\n",
"print(stats[\"strategy\"][strategy][\"pairlist\"])\n",
"# Get market change (average change of all pairs from start to end of the backtest period)\n",
"print(stats['strategy'][strategy]['market_change'])\n",
"print(stats[\"strategy\"][strategy][\"market_change\"])\n",
"# Maximum drawdown ()\n",
"print(stats['strategy'][strategy]['max_drawdown'])\n",
"print(stats[\"strategy\"][strategy][\"max_drawdown\"])\n",
"# Maximum drawdown start and end\n",
"print(stats['strategy'][strategy]['drawdown_start'])\n",
"print(stats['strategy'][strategy]['drawdown_end'])\n",
"print(stats[\"strategy\"][strategy][\"drawdown_start\"])\n",
"print(stats[\"strategy\"][strategy][\"drawdown_end\"])\n",
"\n",
"\n",
"# Get strategy comparison (only relevant if multiple strategies were compared)\n",
"print(stats['strategy_comparison'])\n"
"print(stats[\"strategy_comparison\"])"
]
},
{
@@ -242,23 +254,25 @@
"source": [
"# Plotting equity line (starting with 0 on day 1 and adding daily profit for each backtested day)\n",
"\n",
"import pandas as pd\n",
"import plotly.express as px\n",
"\n",
"from freqtrade.configuration import Configuration\n",
"from freqtrade.data.btanalysis import load_backtest_stats\n",
"import plotly.express as px\n",
"import pandas as pd\n",
"\n",
"\n",
"# strategy = 'SampleStrategy'\n",
"# config = Configuration.from_files([\"user_data/config.json\"])\n",
"# backtest_dir = config[\"user_data_dir\"] / \"backtest_results\"\n",
"\n",
"stats = load_backtest_stats(backtest_dir)\n",
"strategy_stats = stats['strategy'][strategy]\n",
"strategy_stats = stats[\"strategy\"][strategy]\n",
"\n",
"df = pd.DataFrame(columns=['dates','equity'], data=strategy_stats['daily_profit'])\n",
"df['equity_daily'] = df['equity'].cumsum()\n",
"df = pd.DataFrame(columns=[\"dates\", \"equity\"], data=strategy_stats[\"daily_profit\"])\n",
"df[\"equity_daily\"] = df[\"equity\"].cumsum()\n",
"\n",
"fig = px.line(df, x=\"dates\", y=\"equity_daily\")\n",
"fig.show()\n"
"fig.show()"
]
},
{
@@ -278,6 +292,7 @@
"source": [
"from freqtrade.data.btanalysis import load_trades_from_db\n",
"\n",
"\n",
"# Fetch trades from database\n",
"trades = load_trades_from_db(\"sqlite:///tradesv3.sqlite\")\n",
"\n",
@@ -303,8 +318,9 @@
"source": [
"from freqtrade.data.btanalysis import analyze_trade_parallelism\n",
"\n",
"\n",
"# Analyze the above\n",
"parallel_trades = analyze_trade_parallelism(trades, '5m')\n",
"parallel_trades = analyze_trade_parallelism(trades, \"5m\")\n",
"\n",
"parallel_trades.plot()"
]
@@ -324,22 +340,23 @@
"metadata": {},
"outputs": [],
"source": [
"from freqtrade.plot.plotting import generate_candlestick_graph\n",
"from freqtrade.plot.plotting import generate_candlestick_graph\n",
"\n",
"\n",
"# Limit graph period to keep plotly quick and reactive\n",
"\n",
"# Filter trades to one pair\n",
"trades_red = trades.loc[trades['pair'] == pair]\n",
"trades_red = trades.loc[trades[\"pair\"] == pair]\n",
"\n",
"data_red = data['2019-06-01':'2019-06-10']\n",
"data_red = data[\"2019-06-01\":\"2019-06-10\"]\n",
"# Generate candlestick graph\n",
"graph = generate_candlestick_graph(pair=pair,\n",
" data=data_red,\n",
" trades=trades_red,\n",
" indicators1=['sma20', 'ema50', 'ema55'],\n",
" indicators2=['rsi', 'macd', 'macdsignal', 'macdhist']\n",
" )\n",
"\n",
"\n"
"graph = generate_candlestick_graph(\n",
" pair=pair,\n",
" data=data_red,\n",
" trades=trades_red,\n",
" indicators1=[\"sma20\", \"ema50\", \"ema55\"],\n",
" indicators2=[\"rsi\", \"macd\", \"macdsignal\", \"macdhist\"],\n",
")"
]
},
{
@@ -352,7 +369,7 @@
"# graph.show()\n",
"\n",
"# Render graph in a separate window\n",
"graph.show(renderer=\"browser\")\n"
"graph.show(renderer=\"browser\")"
]
},
{
@@ -370,11 +387,12 @@
"source": [
"import plotly.figure_factory as ff\n",
"\n",
"\n",
"hist_data = [trades.profit_ratio]\n",
"group_labels = ['profit_ratio'] # name of the dataset\n",
"group_labels = [\"profit_ratio\"] # name of the dataset\n",
"\n",
"fig = ff.create_distplot(hist_data, group_labels, bin_size=0.01)\n",
"fig.show()\n"
"fig.show()"
]
},
{

View File

@@ -1,3 +1,3 @@
(qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi
(dataframe['tema'] <= dataframe['bb_middleband']) & # Guard: tema below BB middle
(dataframe['tema'] > dataframe['tema'].shift(1)) & # Guard: tema is raising
(qtpylib.crossed_above(dataframe["rsi"], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi
(dataframe["tema"] <= dataframe["bb_middleband"]) & # Guard: tema below BB middle
(dataframe["tema"] > dataframe["tema"].shift(1)) & # Guard: tema is raising

View File

@@ -1 +1 @@
(qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi
(qtpylib.crossed_above(dataframe["rsi"], self.buy_rsi.value)) & # Signal: RSI crosses above buy_rsi

View File

@@ -3,24 +3,24 @@
# ------------------------------------
# ADX
dataframe['adx'] = ta.ADX(dataframe)
dataframe["adx"] = ta.ADX(dataframe)
# # Plus Directional Indicator / Movement
# dataframe['plus_dm'] = ta.PLUS_DM(dataframe)
# dataframe['plus_di'] = ta.PLUS_DI(dataframe)
# dataframe["plus_dm"] = ta.PLUS_DM(dataframe)
# dataframe["plus_di"] = ta.PLUS_DI(dataframe)
# # Minus Directional Indicator / Movement
# dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
# dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# dataframe["minus_dm"] = ta.MINUS_DM(dataframe)
# dataframe["minus_di"] = ta.MINUS_DI(dataframe)
# # Aroon, Aroon Oscillator
# aroon = ta.AROON(dataframe)
# dataframe['aroonup'] = aroon['aroonup']
# dataframe['aroondown'] = aroon['aroondown']
# dataframe['aroonosc'] = ta.AROONOSC(dataframe)
# dataframe["aroonup"] = aroon["aroonup"]
# dataframe["aroondown"] = aroon["aroondown"]
# dataframe["aroonosc"] = ta.AROONOSC(dataframe)
# # Awesome Oscillator
# dataframe['ao'] = qtpylib.awesome_oscillator(dataframe)
# dataframe["ao"] = qtpylib.awesome_oscillator(dataframe)
# # Keltner Channel
# keltner = qtpylib.keltner_channel(dataframe)
@@ -36,58 +36,58 @@ dataframe['adx'] = ta.ADX(dataframe)
# )
# # Ultimate Oscillator
# dataframe['uo'] = ta.ULTOSC(dataframe)
# dataframe["uo"] = ta.ULTOSC(dataframe)
# # Commodity Channel Index: values [Oversold:-100, Overbought:100]
# dataframe['cci'] = ta.CCI(dataframe)
# dataframe["cci"] = ta.CCI(dataframe)
# RSI
dataframe['rsi'] = ta.RSI(dataframe)
dataframe["rsi"] = ta.RSI(dataframe)
# # Inverse Fisher transform on RSI: values [-1.0, 1.0] (https://goo.gl/2JGGoy)
# rsi = 0.1 * (dataframe['rsi'] - 50)
# dataframe['fisher_rsi'] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1)
# rsi = 0.1 * (dataframe["rsi"] - 50)
# dataframe["fisher_rsi"] = (np.exp(2 * rsi) - 1) / (np.exp(2 * rsi) + 1)
# # Inverse Fisher transform on RSI normalized: values [0.0, 100.0] (https://goo.gl/2JGGoy)
# dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1)
# dataframe["fisher_rsi_norma"] = 50 * (dataframe["fisher_rsi"] + 1)
# # Stochastic Slow
# stoch = ta.STOCH(dataframe)
# dataframe['slowd'] = stoch['slowd']
# dataframe['slowk'] = stoch['slowk']
# dataframe["slowd"] = stoch["slowd"]
# dataframe["slowk"] = stoch["slowk"]
# Stochastic Fast
stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd']
dataframe['fastk'] = stoch_fast['fastk']
dataframe["fastd"] = stoch_fast["fastd"]
dataframe["fastk"] = stoch_fast["fastk"]
# # Stochastic RSI
# Please read https://github.com/freqtrade/freqtrade/issues/2961 before using this.
# STOCHRSI is NOT aligned with tradingview, which may result in non-expected results.
# stoch_rsi = ta.STOCHRSI(dataframe)
# dataframe['fastd_rsi'] = stoch_rsi['fastd']
# dataframe['fastk_rsi'] = stoch_rsi['fastk']
# dataframe["fastd_rsi"] = stoch_rsi["fastd"]
# dataframe["fastk_rsi"] = stoch_rsi["fastk"]
# MACD
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['macdhist'] = macd['macdhist']
dataframe["macd"] = macd["macd"]
dataframe["macdsignal"] = macd["macdsignal"]
dataframe["macdhist"] = macd["macdhist"]
# MFI
dataframe['mfi'] = ta.MFI(dataframe)
dataframe["mfi"] = ta.MFI(dataframe)
# # ROC
# dataframe['roc'] = ta.ROC(dataframe)
# dataframe["roc"] = ta.ROC(dataframe)
# Overlap Studies
# ------------------------------------
# Bollinger Bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_middleband'] = bollinger['mid']
dataframe['bb_upperband'] = bollinger['upper']
dataframe["bb_lowerband"] = bollinger["lower"]
dataframe["bb_middleband"] = bollinger["mid"]
dataframe["bb_upperband"] = bollinger["upper"]
dataframe["bb_percent"] = (
(dataframe["close"] - dataframe["bb_lowerband"]) /
(dataframe["bb_upperband"] - dataframe["bb_lowerband"])
@@ -112,95 +112,95 @@ dataframe["bb_width"] = (
# )
# # EMA - Exponential Moving Average
# dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3)
# dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5)
# dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10)
# dataframe['ema21'] = ta.EMA(dataframe, timeperiod=21)
# dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50)
# dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100)
# dataframe["ema3"] = ta.EMA(dataframe, timeperiod=3)
# dataframe["ema5"] = ta.EMA(dataframe, timeperiod=5)
# dataframe["ema10"] = ta.EMA(dataframe, timeperiod=10)
# dataframe["ema21"] = ta.EMA(dataframe, timeperiod=21)
# dataframe["ema50"] = ta.EMA(dataframe, timeperiod=50)
# dataframe["ema100"] = ta.EMA(dataframe, timeperiod=100)
# # SMA - Simple Moving Average
# dataframe['sma3'] = ta.SMA(dataframe, timeperiod=3)
# dataframe['sma5'] = ta.SMA(dataframe, timeperiod=5)
# dataframe['sma10'] = ta.SMA(dataframe, timeperiod=10)
# dataframe['sma21'] = ta.SMA(dataframe, timeperiod=21)
# dataframe['sma50'] = ta.SMA(dataframe, timeperiod=50)
# dataframe['sma100'] = ta.SMA(dataframe, timeperiod=100)
# dataframe["sma3"] = ta.SMA(dataframe, timeperiod=3)
# dataframe["sma5"] = ta.SMA(dataframe, timeperiod=5)
# dataframe["sma10"] = ta.SMA(dataframe, timeperiod=10)
# dataframe["sma21"] = ta.SMA(dataframe, timeperiod=21)
# dataframe["sma50"] = ta.SMA(dataframe, timeperiod=50)
# dataframe["sma100"] = ta.SMA(dataframe, timeperiod=100)
# Parabolic SAR
dataframe['sar'] = ta.SAR(dataframe)
dataframe["sar"] = ta.SAR(dataframe)
# TEMA - Triple Exponential Moving Average
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
dataframe["tema"] = ta.TEMA(dataframe, timeperiod=9)
# Cycle Indicator
# ------------------------------------
# Hilbert Transform Indicator - SineWave
hilbert = ta.HT_SINE(dataframe)
dataframe['htsine'] = hilbert['sine']
dataframe['htleadsine'] = hilbert['leadsine']
dataframe["htsine"] = hilbert["sine"]
dataframe["htleadsine"] = hilbert["leadsine"]
# Pattern Recognition - Bullish candlestick patterns
# ------------------------------------
# # Hammer: values [0, 100]
# dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe)
# dataframe["CDLHAMMER"] = ta.CDLHAMMER(dataframe)
# # Inverted Hammer: values [0, 100]
# dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe)
# dataframe["CDLINVERTEDHAMMER"] = ta.CDLINVERTEDHAMMER(dataframe)
# # Dragonfly Doji: values [0, 100]
# dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe)
# dataframe["CDLDRAGONFLYDOJI"] = ta.CDLDRAGONFLYDOJI(dataframe)
# # Piercing Line: values [0, 100]
# dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100]
# dataframe["CDLPIERCING"] = ta.CDLPIERCING(dataframe) # values [0, 100]
# # Morningstar: values [0, 100]
# dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
# dataframe["CDLMORNINGSTAR"] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100]
# # Three White Soldiers: values [0, 100]
# dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
# dataframe["CDL3WHITESOLDIERS"] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100]
# Pattern Recognition - Bearish candlestick patterns
# ------------------------------------
# # Hanging Man: values [0, 100]
# dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe)
# dataframe["CDLHANGINGMAN"] = ta.CDLHANGINGMAN(dataframe)
# # Shooting Star: values [0, 100]
# dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe)
# dataframe["CDLSHOOTINGSTAR"] = ta.CDLSHOOTINGSTAR(dataframe)
# # Gravestone Doji: values [0, 100]
# dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe)
# dataframe["CDLGRAVESTONEDOJI"] = ta.CDLGRAVESTONEDOJI(dataframe)
# # Dark Cloud Cover: values [0, 100]
# dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe)
# dataframe["CDLDARKCLOUDCOVER"] = ta.CDLDARKCLOUDCOVER(dataframe)
# # Evening Doji Star: values [0, 100]
# dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe)
# dataframe["CDLEVENINGDOJISTAR"] = ta.CDLEVENINGDOJISTAR(dataframe)
# # Evening Star: values [0, 100]
# dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe)
# dataframe["CDLEVENINGSTAR"] = ta.CDLEVENINGSTAR(dataframe)
# Pattern Recognition - Bullish/Bearish candlestick patterns
# ------------------------------------
# # Three Line Strike: values [0, -100, 100]
# dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe)
# dataframe["CDL3LINESTRIKE"] = ta.CDL3LINESTRIKE(dataframe)
# # Spinning Top: values [0, -100, 100]
# dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
# dataframe["CDLSPINNINGTOP"] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100]
# # Engulfing: values [0, -100, 100]
# dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
# dataframe["CDLENGULFING"] = ta.CDLENGULFING(dataframe) # values [0, -100, 100]
# # Harami: values [0, -100, 100]
# dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
# dataframe["CDLHARAMI"] = ta.CDLHARAMI(dataframe) # values [0, -100, 100]
# # Three Outside Up/Down: values [0, -100, 100]
# dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
# dataframe["CDL3OUTSIDE"] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100]
# # Three Inside Up/Down: values [0, -100, 100]
# dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
# dataframe["CDL3INSIDE"] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100]
# # Chart type
# # ------------------------------------
# # Heikin Ashi Strategy
# heikinashi = qtpylib.heikinashi(dataframe)
# dataframe['ha_open'] = heikinashi['open']
# dataframe['ha_close'] = heikinashi['close']
# dataframe['ha_high'] = heikinashi['high']
# dataframe['ha_low'] = heikinashi['low']
# dataframe["ha_open"] = heikinashi["open"]
# dataframe["ha_close"] = heikinashi["close"]
# dataframe["ha_high"] = heikinashi["high"]
# dataframe["ha_low"] = heikinashi["low"]
# Retrieve best bid and best ask from the orderbook
# ------------------------------------
"""
# first check if dataprovider is available
if self.dp:
if self.dp.runmode.value in ('live', 'dry_run'):
ob = self.dp.orderbook(metadata['pair'], 1)
dataframe['best_bid'] = ob['bids'][0][0]
dataframe['best_ask'] = ob['asks'][0][0]
if self.dp.runmode.value in ("live", "dry_run"):
ob = self.dp.orderbook(metadata["pair"], 1)
dataframe["best_bid"] = ob["bids"][0][0]
dataframe["best_ask"] = ob["asks"][0][0]
"""

View File

@@ -3,15 +3,15 @@
# ------------------------------------
# RSI
dataframe['rsi'] = ta.RSI(dataframe)
dataframe["rsi"] = ta.RSI(dataframe)
# Retrieve best bid and best ask from the orderbook
# ------------------------------------
"""
# first check if dataprovider is available
if self.dp:
if self.dp.runmode.value in ('live', 'dry_run'):
ob = self.dp.orderbook(metadata['pair'], 1)
dataframe['best_bid'] = ob['bids'][0][0]
dataframe['best_ask'] = ob['asks'][0][0]
if self.dp.runmode.value in ("live", "dry_run"):
ob = self.dp.orderbook(metadata["pair"], 1)
dataframe["best_bid"] = ob["bids"][0][0]
dataframe["best_ask"] = ob["asks"][0][0]
"""

View File

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

View File

@@ -1,3 +1,3 @@
(qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi
(dataframe['tema'] > dataframe['bb_middleband']) & # Guard: tema above BB middle
(dataframe['tema'] < dataframe['tema'].shift(1)) & # Guard: tema is falling
(qtpylib.crossed_above(dataframe["rsi"], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi
(dataframe["tema"] > dataframe["bb_middleband"]) & # Guard: tema above BB middle
(dataframe["tema"] < dataframe["tema"].shift(1)) & # Guard: tema is falling

View File

@@ -1 +1 @@
(qtpylib.crossed_above(dataframe['rsi'], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi
(qtpylib.crossed_above(dataframe["rsi"], self.sell_rsi.value)) & # Signal: RSI crosses above sell_rsi

View File

@@ -1,13 +1,13 @@
# Optional order type mapping.
order_types = {
'entry': 'limit',
'exit': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False
"entry": "limit",
"exit": "limit",
"stoploss": "market",
"stoploss_on_exchange": False
}
# Optional order time in force.
order_time_in_force = {
'entry': 'GTC',
'exit': 'GTC'
"entry": "GTC",
"exit": "GTC"
}

View File

@@ -13,9 +13,9 @@ def bot_loop_start(self, current_time: datetime, **kwargs) -> None:
"""
pass
def custom_entry_price(self, pair: str, trade: Optional['Trade'],
current_time: 'datetime', proposed_rate: float,
entry_tag: 'Optional[str]', side: str, **kwargs) -> float:
def custom_entry_price(self, pair: str, trade: Optional[Trade],
current_time: datetime, proposed_rate: float,
entry_tag: Optional[str], side: str, **kwargs) -> float:
"""
Custom entry price logic, returning the new entry price.
@@ -33,7 +33,7 @@ def custom_entry_price(self, pair: str, trade: Optional['Trade'],
"""
return proposed_rate
def adjust_entry_price(self, trade: 'Trade', order: 'Optional[Order]', pair: str,
def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str,
current_time: datetime, proposed_rate: float, current_order_rate: float,
entry_tag: Optional[str], side: str, **kwargs) -> float:
"""
@@ -61,8 +61,8 @@ def adjust_entry_price(self, trade: 'Trade', order: 'Optional[Order]', pair: str
"""
return current_order_rate
def custom_exit_price(self, pair: str, trade: 'Trade',
current_time: 'datetime', proposed_rate: float,
def custom_exit_price(self, pair: str, trade: Trade,
current_time: datetime, proposed_rate: float,
current_profit: float, exit_tag: Optional[str], **kwargs) -> float:
"""
Custom exit price logic, returning the new exit price.
@@ -104,7 +104,7 @@ def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: f
use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, after_fill: bool, **kwargs) -> float:
"""
Custom stoploss logic, returning the new distance relative to current_rate (as ratio).
@@ -126,8 +126,8 @@ def custom_stoploss(self, pair: str, trade: 'Trade', current_time: 'datetime', c
:return float: New stoploss value, relative to the current_rate
"""
def custom_exit(self, pair: str, trade: 'Trade', current_time: 'datetime', current_rate: float,
current_profit: float, **kwargs) -> 'Optional[Union[str, bool]]':
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, **kwargs) -> Optional[Union[str, bool]]:
"""
Custom exit signal logic indicating that specified position should be sold. Returning a
string or True from this method is equal to setting sell signal on a candle at specified
@@ -177,9 +177,9 @@ def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: f
"""
return True
def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount: float,
def confirm_trade_exit(self, pair: str, trade: Trade, order_type: str, amount: float,
rate: float, time_in_force: str, exit_reason: str,
current_time: 'datetime', **kwargs) -> bool:
current_time: datetime, **kwargs) -> bool:
"""
Called right before placing a regular exit order.
Timing for this function is critical, so avoid doing heavy computations or
@@ -206,7 +206,7 @@ def confirm_trade_exit(self, pair: str, trade: 'Trade', order_type: str, amount:
"""
return True
def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order',
def check_entry_timeout(self, pair: str, trade: Trade, order: Order,
current_time: datetime, **kwargs) -> bool:
"""
Check entry timeout function callback.
@@ -228,7 +228,7 @@ def check_entry_timeout(self, pair: str, trade: 'Trade', order: 'Order',
"""
return False
def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order',
def check_exit_timeout(self, pair: str, trade: Trade, order: Order,
current_time: datetime, **kwargs) -> bool:
"""
Check exit timeout function callback.
@@ -250,7 +250,7 @@ def check_exit_timeout(self, pair: str, trade: 'Trade', order: 'Order',
"""
return False
def adjust_trade_position(self, trade: 'Trade', current_time: datetime,
def adjust_trade_position(self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float,
min_stake: Optional[float], max_stake: float,
current_entry_rate: float, current_exit_rate: float,
@@ -302,7 +302,7 @@ def leverage(self, pair: str, current_time: datetime, current_rate: float,
return 1.0
def order_filled(self, pair: str, trade: 'Trade', order: 'Order',
def order_filled(self, pair: str, trade: Trade, order: Order,
current_time: datetime, **kwargs) -> None:
"""
Called right after an order fills.

View File

@@ -1,5 +1,5 @@
# Used for list-exchanges
from typing import List
from typing import List, Optional
from typing_extensions import TypedDict
@@ -11,8 +11,11 @@ class TradeModeType(TypedDict):
class ValidExchangesType(TypedDict):
name: str
classname: str
valid: bool
supported: bool
comment: str
dex: bool
is_alias: bool
alias_for: Optional[str]
trade_modes: List[TradeModeType]

View File

@@ -1,14 +1,18 @@
from typing import Union
from typing import Callable, List, Union
from rich.console import ConsoleRenderable, Group, RichCast
from rich.progress import Progress
class CustomProgress(Progress):
def __init__(self, *args, cust_objs=[], **kwargs) -> None:
def __init__(self, *args, cust_objs=[], cust_callables: List[Callable] = [], **kwargs) -> None:
self._cust_objs = cust_objs
self._cust_callables = cust_callables
super().__init__(*args, **kwargs)
def get_renderable(self) -> Union[ConsoleRenderable, RichCast, str]:
renderable = Group(*self._cust_objs, *self.get_renderables())
objs = [obj for obj in self._cust_objs]
for cust_call in self._cust_callables:
objs.append(cust_call())
renderable = Group(*objs, *self.get_renderables())
return renderable

View File

@@ -37,9 +37,11 @@ def print_rich_table(
row_to_add: List[Union[str, Text]] = [r if isinstance(r, Text) else str(r) for r in row]
table.add_row(*row_to_add)
console = Console(
width=200 if "pytest" in sys.modules else None,
)
width = None
if any(module in ["pytest", "ipykernel"] for module in sys.modules):
width = 200
console = Console(width=width)
console.print(table)
@@ -71,7 +73,9 @@ def print_df_rich_table(
row = [_format_value(x, floatfmt=".3f") for x in value_list]
table.add_row(*row)
console = Console(
width=200 if "pytest" in sys.modules else None,
)
width = None
if any(module in ["pytest", "ipykernel"] for module in sys.modules):
width = 200
console = Console(width=width)
console.print(table)

View File

@@ -29,7 +29,7 @@ class Wallet(NamedTuple):
class PositionWallet(NamedTuple):
symbol: str
position: float = 0
leverage: float = 0
leverage: Optional[float] = 0 # Don't use this - it's not guaranteed to be set
collateral: float = 0
side: str = "long"
@@ -66,6 +66,17 @@ class Wallets:
else:
return 0
def get_owned(self, pair: str, base_currency: str) -> float:
"""
Get currently owned value.
Designed to work across both spot and futures.
"""
if self._config.get("trading_mode", "spot") != TradingMode.FUTURES:
return self.get_total(base_currency) or 0
if pos := self._positions.get(pair):
return pos.position
return 0
def _update_dry(self) -> None:
"""
Update from database in dry-run mode
@@ -82,7 +93,7 @@ class Wallets:
tot_profit = Trade.get_total_closed_profit()
else:
# Backtest mode
tot_profit = LocalTrade.total_profit
tot_profit = LocalTrade.bt_total_profit
tot_profit += sum(trade.realized_profit for trade in open_trades)
tot_in_trades = sum(trade.stake_amount for trade in open_trades)
used_stake = 0.0
@@ -128,9 +139,9 @@ class Wallets:
if isinstance(balances[currency], dict):
self._wallets[currency] = Wallet(
currency,
balances[currency].get("free"),
balances[currency].get("used"),
balances[currency].get("total"),
balances[currency].get("free", 0),
balances[currency].get("used", 0),
balances[currency].get("total", 0),
)
# Remove currencies no longer in get_balances output
for currency in deepcopy(self._wallets):
@@ -138,7 +149,7 @@ class Wallets:
del self._wallets[currency]
positions = self._exchange.fetch_positions()
self._positions = {}
_parsed_positions = {}
for position in positions:
symbol = position["symbol"]
if position["side"] is None or position["collateral"] == 0.0:
@@ -146,14 +157,15 @@ class Wallets:
continue
size = self._exchange._contracts_to_amount(symbol, position["contracts"])
collateral = safe_value_fallback(position, "collateral", "initialMargin", 0.0)
leverage = position["leverage"]
self._positions[symbol] = PositionWallet(
leverage = position.get("leverage")
_parsed_positions[symbol] = PositionWallet(
symbol,
position=size,
leverage=leverage,
collateral=collateral,
side=position["side"],
)
self._positions = _parsed_positions
def update(self, require_update: bool = True) -> None:
"""

View File

@@ -1,7 +1,7 @@
from freqtrade_client.ft_rest_client import FtRestClient
__version__ = "2024.7.1"
__version__ = "2024.8"
if "dev" in __version__:
from pathlib import Path

View File

@@ -1,3 +1,3 @@
# Requirements for freqtrade client library
requests==2.32.3
python-rapidjson==1.18
python-rapidjson==1.20

View File

@@ -112,3 +112,12 @@ markdown_extensions:
custom_checkbox: true
- pymdownx.tilde
- mdx_truly_sane_lists
extra:
version:
provider: mike
alias: true
plugins:
- search:
enabled: true
- mike:
deploy_prefix: 'en'

View File

@@ -86,6 +86,7 @@ log_format = "%(asctime)s %(levelname)s %(message)s"
log_date_format = "%Y-%m-%d %H:%M:%S"
asyncio_mode = "auto"
asyncio_default_fixture_loop_scope = "function"
addopts = "--dist loadscope"
[tool.mypy]

View File

@@ -7,11 +7,11 @@
-r docs/requirements-docs.txt
coveralls==4.0.1
ruff==0.5.4
mypy==1.11.0
pre-commit==3.7.1
pytest==8.3.1
pytest-asyncio==0.23.8
ruff==0.6.2
mypy==1.11.2
pre-commit==3.8.0
pytest==8.3.2
pytest-asyncio==0.24.0
pytest-cov==5.0.0
pytest-mock==3.14.0
pytest-random-order==1.1.1
@@ -19,14 +19,14 @@ pytest-timeout==2.3.1
pytest-xdist==3.6.1
isort==5.13.2
# For datetime mocking
time-machine==2.14.2
time-machine==2.15.0
# Convert jupyter notebooks to markdown documents
nbconvert==7.16.4
# mypy types
types-cachetools==5.4.0.20240717
types-cachetools==5.5.0.20240820
types-filelock==3.2.7
types-requests==2.32.0.20240712
types-tabulate==0.9.0.20240106
types-python-dateutil==2.9.0.20240316
types-python-dateutil==2.9.0.20240821

View File

@@ -2,10 +2,10 @@
-r requirements-freqai.txt
# Required for freqai-rl
torch==2.3.1; sys_platform != 'darwin' or platform_machine != 'x86_64'
torch==2.2.2; sys_platform == 'darwin' and platform_machine == 'x86_64'
torch==2.4.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
gymnasium==0.29.1
stable_baselines3==2.3.2
sb3_contrib>=2.2.1
# Progress bar for stable-baselines3 and sb3-contrib
tqdm==4.66.4
tqdm==4.66.5

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