Compare commits

...

643 Commits

Author SHA1 Message Date
Matthias 13050f0d67 chore: bump version to 2024.12.1 2025-01-18 14:22:50 +01:00
Matthias 61e5b4755c chore: Bump ccxt to 4.4.48
fixes a critical issue with bybit
2025-01-18 14:22:28 +01:00
Matthias 466e834680 Merge pull request #11160 from freqtrade/new_release
New release 2024.12
2024-12-30 06:58:30 +01:00
Matthias 9e0b9962ce chore: bump version to 2024.12 2024-12-29 15:28:46 +01:00
Matthias 4d2863baf5 Merge branch 'stable' into new_release 2024-12-29 15:28:30 +01:00
Matthias 0032f9a960 chore: bump ccxt to 4.4.43
this adds support for myokx, the OKX EAA subsidiary.
2024-12-28 12:49:55 +01:00
Matthias 5ca31508fb chore: remove "former huobi" terminology 2024-12-28 12:46:57 +01:00
Matthias a58fde367c chore: remove "former OKEX" terminology 2024-12-28 12:46:35 +01:00
Matthias 3f41910eb4 docs: add myokx special case documentation 2024-12-28 12:10:25 +01:00
Matthias 9a201566f2 chore: fix typo in telegram help message 2024-12-28 12:05:55 +01:00
Matthias a180cb95dc chore: Improve list-exchanges output 2024-12-28 12:05:02 +01:00
Matthias fbb757fa2f chore: may myokx to okx - it's just an alias for european users 2024-12-28 12:03:37 +01:00
Matthias 6ee38adbb5 fix: round open_rate to tradable precisionThis prevents odd display issues with 16 decimals in case of multiple entries. 2024-12-27 19:18:10 +01:00
Matthias 6f3dca44a3 fix: apply hyperliquid market rate fix to fetch_orders 2024-12-27 18:16:37 +01:00
Matthias a4ae5b74a4 chore: add log_response after average modification 2024-12-27 18:04:29 +01:00
Matthias fc970a1af4 chore: Remove pin of matplotlib 2024-12-27 13:39:13 +01:00
Matthias b5748fee6e docs: add FAQ about resetting a bot's database 2024-12-26 13:54:21 +01:00
Matthias b092dfa2c9 Merge pull request #11149 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-12-26 12:10:19 +01:00
xmatthias 2ad1fbd771 chore: update pre-commit hooks 2024-12-26 03:06:05 +00:00
Matthias 1ca594bbcb chore: show "total" profit if necessary 2024-12-25 18:04:17 +01:00
Matthias e66611c965 chore: fix 2 minor potential bugs 2024-12-25 17:53:12 +01:00
Matthias c5a95dc682 refactor: reuse logic from get-status for get status-table 2024-12-25 17:40:02 +01:00
Matthias d7ccbfef20 test: improve test accuracy 2024-12-25 17:37:21 +01:00
Matthias 79f35bb553 chore: add docstring to rpc_status_table 2024-12-25 16:30:52 +01:00
Matthias 9e30e5c34c Merge pull request #11144 from freqtrade/deprecate/hdf5
Deprecate hdf5
2024-12-25 08:09:57 +01:00
Matthias 4963a9668f chore: fix typo 2024-12-24 15:35:34 +01:00
Matthias e59bd71c17 Merge pull request #11143 from freqtrade/feat/telegram_group_topics
Support Telegram group topics, add /tg_info command
2024-12-24 15:29:33 +01:00
Matthias 5750d05891 feat: deprecate hdf5 as data storage 2024-12-24 15:19:22 +01:00
Matthias 1810a911d4 chore: update chatID to be identical throughout docs. 2024-12-24 14:42:39 +01:00
Matthias 51ae0f0377 chore: check for update.message being present 2024-12-24 14:05:17 +01:00
Matthias 215b648edf Merge pull request #11141 from andrasmining/develop
Adding drawdown information for hyperopt-list --export-csv command & small fix stake currency source
2024-12-24 13:55:23 +01:00
Matthias d73f01701e chore: reduce verbosity for topic_id wrong check 2024-12-24 13:52:51 +01:00
Matthias 010b282679 docs: document /tg_info command 2024-12-24 13:46:29 +01:00
Matthias 6ef8291b2e test: add test for tg_info, update chat_id to be not 0 2024-12-24 13:38:34 +01:00
Andras Kovacs b820810afe hyperopt-list --export-csv command stake currency fix
Take stake currency from hyperopt result file instead of config
2024-12-24 13:33:58 +01:00
Matthias cef21860eb feat: add _tg_info endpoint for easier information gathering 2024-12-24 13:27:15 +01:00
Andras Kovacs 0c08656965 Merge branch 'develop' of https://github.com/andrasmining/freqtrade into develop 2024-12-24 12:54:21 +01:00
Andras Kovacs 59b1460e30 Adding drawdown information for hyperopt-list --export-csv command 2024-12-24 12:52:35 +01:00
Matthias b75ac1243a docs: document group-topic-id 2024-12-24 12:17:15 +01:00
Matthias 93af2b7b1d Merge pull request #11138 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-12-24 08:18:17 +01:00
xmatthias 7092edfe4c chore: update pre-commit hooks 2024-12-24 03:06:46 +00:00
Matthias 0663335011 chore: update config_schema with topic_id attribute 2024-12-23 20:12:21 +01:00
Matthias 6cc0c2be91 feat: add support for "topic_id" in telegram notifications 2024-12-23 20:11:24 +01:00
Matthias 4a256ed7ca chore: failed messages shouldn't return the wrapper but None 2024-12-23 19:57:44 +01:00
Matthias 2a526a815e fix: don't over-state stake-amount on first entry
closes #11135
2024-12-23 17:53:44 +01:00
Matthias 0a81658881 test: add test to confirm behavior in #11135 2024-12-23 17:53:44 +01:00
Matthias 5ac95a0507 test: close mock order for funding-fee test 2024-12-23 13:47:33 +01:00
Matthias 77e36ed313 Merge pull request #11125 from freqtrade/dependabot/pip/develop/technical-1.5.0
chore(deps): bump technical from 1.4.4 to 1.5.0
2024-12-23 11:16:12 +01:00
Matthias 52933e977b Merge pull request #11128 from freqtrade/dependabot/pip/develop/mypy-1.14.0
chore(deps-dev): bump mypy from 1.13.0 to 1.14.0
2024-12-23 09:06:20 +01:00
dependabot[bot] cf4c29a57f chore(deps): bump technical from 1.4.4 to 1.5.0
Bumps [technical](https://github.com/freqtrade/technical) from 1.4.4 to 1.5.0.
- [Release notes](https://github.com/freqtrade/technical/releases)
- [Commits](https://github.com/freqtrade/technical/compare/1.4.4...1.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 07:43:41 +00:00
Matthias 90cd381efa Merge pull request #11129 from freqtrade/dependabot/pip/develop/jinja2-3.1.5
chore(deps): bump jinja2 from 3.1.4 to 3.1.5
2024-12-23 08:42:28 +01:00
dependabot[bot] 9405f415d0 chore(deps-dev): bump mypy from 1.13.0 to 1.14.0
Bumps [mypy](https://github.com/python/mypy) from 1.13.0 to 1.14.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.13.0...v1.14.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 07:10:13 +00:00
Matthias 3dcc130903 Merge pull request #11131 from freqtrade/dependabot/pip/develop/ccxt-4.4.42
chore(deps): bump ccxt from 4.4.40 to 4.4.42
2024-12-23 08:09:13 +01:00
dependabot[bot] 9d022180e7 chore(deps): bump jinja2 from 3.1.4 to 3.1.5
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.4 to 3.1.5.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.4...3.1.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 07:04:05 +00:00
Matthias ba42d8d8c6 Merge pull request #11133 from freqtrade/dependabot/pip/develop/ruff-0.8.4
chore(deps-dev): bump ruff from 0.8.3 to 0.8.4
2024-12-23 08:03:01 +01:00
Matthias 117887ff3a Merge pull request #11122 from freqtrade/dependabot/github_actions/develop/astral-sh/setup-uv-5
chore(deps): bump astral-sh/setup-uv from 4 to 5
2024-12-23 08:01:42 +01:00
Matthias b62fa8190b Merge pull request #11123 from freqtrade/dependabot/pip/develop/mkdocs-c52ef7e6a7
chore(deps): bump mkdocs-material from 9.5.48 to 9.5.49 in the mkdocs group
2024-12-23 08:01:33 +01:00
Matthias eadd4a7bb2 Merge pull request #11127 from freqtrade/dependabot/pip/develop/psutil-6.1.1
chore(deps): bump psutil from 6.1.0 to 6.1.1
2024-12-23 08:01:19 +01:00
Matthias a93a1b8b48 Merge pull request #11130 from freqtrade/dependabot/pip/develop/urllib3-2.3.0
chore(deps): bump urllib3 from 2.2.3 to 2.3.0
2024-12-23 08:01:04 +01:00
Matthias 12cbe70371 Merge pull request #11126 from freqtrade/dependabot/pip/develop/pydantic-2.10.4
chore(deps): bump pydantic from 2.10.3 to 2.10.4
2024-12-23 08:00:44 +01:00
dependabot[bot] ed3367244c chore(deps-dev): bump ruff from 0.8.3 to 0.8.4
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.8.3 to 0.8.4.
- [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.8.3...0.8.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 03:47:30 +00:00
dependabot[bot] b8b69eecb8 chore(deps): bump ccxt from 4.4.40 to 4.4.42
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.40 to 4.4.42.
- [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.4.40...4.4.42)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 03:47:08 +00:00
dependabot[bot] 65acd5d827 chore(deps): bump urllib3 from 2.2.3 to 2.3.0
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.2.3 to 2.3.0.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.2.3...2.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 03:47:00 +00:00
dependabot[bot] 4d6732cd19 chore(deps): bump psutil from 6.1.0 to 6.1.1
Bumps [psutil](https://github.com/giampaolo/psutil) from 6.1.0 to 6.1.1.
- [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst)
- [Commits](https://github.com/giampaolo/psutil/compare/release-6.1.0...release-6.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 03:46:39 +00:00
dependabot[bot] dc5942115d chore(deps): bump pydantic from 2.10.3 to 2.10.4
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.10.3 to 2.10.4.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.10.3...v2.10.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 03:46:33 +00:00
dependabot[bot] c14492a6cf 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.48 to 9.5.49
- [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.48...9.5.49)

---
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-12-23 03:46:01 +00:00
dependabot[bot] 07682c396e chore(deps): bump astral-sh/setup-uv from 4 to 5
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 4 to 5.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/v4...v5)

---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-23 03:31:33 +00:00
Matthias 503305cbdb fix: properly pass timerange to data-download 2024-12-22 16:02:49 +01:00
Matthias 32f592444a Merge pull request #11119 from AchmadFathoni/fix_notebook
Fix KeyError: 'max_drawdown' in jupyter notebook
2024-12-21 19:52:03 +01:00
Achmad Fathoni 04813e452c Change unavailable "max_drawdown" key 2024-12-22 00:28:21 +07:00
Achmad Fathoni b50d9f5c59 Fix KeyError: 'max_drawdown' in jupyter notebook 2024-12-22 00:14:07 +07:00
Matthias 40cb70fca1 fix: one-side pairlocks not showing in UI 2024-12-21 14:30:47 +01:00
Matthias 0b0bbd4c42 chore: improve comment wording 2024-12-20 18:18:44 +01:00
Matthias 4704f2b08e chore: fix blosc version selectors 2024-12-20 06:38:15 +01:00
Matthias f9bd11f6fb chore: blosc2 only supports python 3.11+ 2024-12-20 06:29:42 +01:00
Matthias 0d73e961a4 chore: pin blosc2 for raspberry builds 2024-12-20 06:26:45 +01:00
Matthias e1f759e70e Merge pull request #11112 from froggleston/frog-docs-api-2
Add params to REST API table description, fix first column width
2024-12-19 21:06:03 +01:00
Robert Davey 66380566c8 Update docs/stylesheets/ft.extra.css
Co-authored-by: Matthias <xmatthias@outlook.com>
2024-12-19 19:35:28 +00:00
Matthias 8cb69e4d5a test: Update test for new liquidation price precision rounding 2024-12-19 19:37:55 +01:00
Matthias 8ee2c849b4 fix: round liquidation price to precision in a different spot
closes #11110
2024-12-19 19:37:35 +01:00
Matthias 564d874869 chore: improve typing 2024-12-19 19:25:00 +01:00
froggleston d38fcd2db4 Fix missing EOL 2024-12-19 16:24:07 +00:00
froggleston 06e20b38b5 Add params to REST API table description, fix first column width 2024-12-19 16:18:30 +00:00
Matthias c8b7449b25 Merge pull request #11111 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-12-19 08:10:34 +01:00
Matthias 6edfebdfed Merge pull request #11102 from froggleston/frog-docs-api-1
Add more detail to the REST API endpoint docs
2024-12-19 08:10:01 +01:00
Matthias c4b39a4fb1 docs: add suggestion to prefix all urls with base url. 2024-12-19 06:50:36 +01:00
xmatthias cdcca3da1c chore: update pre-commit hooks 2024-12-19 03:12:44 +00:00
Matthias 44d1f169c3 chore: type rpc._freqtradebot for improved DX 2024-12-17 20:39:04 +01:00
Matthias d805ee6f3d chore: improve type safety 2024-12-17 20:38:46 +01:00
Matthias ed2427852b test: adapt starting capital tests for tradable_balance_ratio 2024-12-17 20:32:11 +01:00
Matthias 5af894047e fix: starting balance should include tradable_balance_ratio 2024-12-17 20:08:40 +01:00
Matthias fced0c4952 Merge pull request #11105 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-12-17 06:30:46 +01:00
xmatthias 609893f7ec chore: update pre-commit hooks 2024-12-17 03:15:40 +00:00
Matthias 3f795a8918 chore: improve logic for live wallet update 2024-12-16 20:57:44 +01:00
Matthias a5907db5d8 chore: update pip-install command for windows ci 2024-12-16 19:02:21 +01:00
froggleston ad7b70a354 Add more detail to the REST API endpoint docs 2024-12-16 15:15:57 +00:00
Matthias d992772492 Merge pull request #11097 from freqtrade/dependabot/pip/develop/scikit-learn-1.6.0
chore(deps): bump scikit-learn from 1.5.2 to 1.6.0
2024-12-16 06:33:25 +01:00
Matthias 7ba879c441 Merge pull request #11096 from freqtrade/dependabot/pip/develop/matplotlib-3.10.0
chore(deps): bump matplotlib from 3.9.3 to 3.10.0
2024-12-16 06:32:08 +01:00
Matthias 3fa2238e9c Merge pull request #11095 from freqtrade/dependabot/pip/develop/pytest-c02b329de1
chore(deps-dev): bump pytest-asyncio from 0.24.0 to 0.25.0 in the pytest group
2024-12-16 06:31:41 +01:00
Matthias 274136dacb Merge pull request #11094 from freqtrade/dependabot/github_actions/develop/pypa/gh-action-pypi-publish-1.12.3
chore(deps): bump pypa/gh-action-pypi-publish from 1.12.2 to 1.12.3
2024-12-16 06:31:12 +01:00
Matthias d5d65cfd56 Merge pull request #11098 from freqtrade/dependabot/pip/develop/uvicorn-0.34.0
chore(deps): bump uvicorn from 0.32.1 to 0.34.0
2024-12-16 06:30:42 +01:00
Matthias 17c11d09fc Merge pull request #11099 from freqtrade/dependabot/pip/develop/janus-2.0.0
chore(deps): bump janus from 1.1.0 to 2.0.0
2024-12-16 06:30:16 +01:00
Matthias 6bf42173a1 Merge pull request #11100 from freqtrade/dependabot/pip/develop/ruff-0.8.3
chore(deps-dev): bump ruff from 0.8.2 to 0.8.3
2024-12-16 06:29:22 +01:00
dependabot[bot] c8649a77d2 chore(deps-dev): bump ruff from 0.8.2 to 0.8.3
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.8.2 to 0.8.3.
- [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.8.2...0.8.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 03:26:36 +00:00
dependabot[bot] d780d56b9a chore(deps): bump janus from 1.1.0 to 2.0.0
Bumps [janus](https://github.com/aio-libs/janus) from 1.1.0 to 2.0.0.
- [Release notes](https://github.com/aio-libs/janus/releases)
- [Changelog](https://github.com/aio-libs/janus/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/janus/compare/v1.1.0...v2.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 03:26:25 +00:00
dependabot[bot] 2587b549a0 chore(deps): bump uvicorn from 0.32.1 to 0.34.0
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.32.1 to 0.34.0.
- [Release notes](https://github.com/encode/uvicorn/releases)
- [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/uvicorn/compare/0.32.1...0.34.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 03:26:10 +00:00
dependabot[bot] dd805c4c25 chore(deps): bump scikit-learn from 1.5.2 to 1.6.0
Bumps [scikit-learn](https://github.com/scikit-learn/scikit-learn) from 1.5.2 to 1.6.0.
- [Release notes](https://github.com/scikit-learn/scikit-learn/releases)
- [Commits](https://github.com/scikit-learn/scikit-learn/compare/1.5.2...1.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 03:26:01 +00:00
dependabot[bot] 5cd1892dee chore(deps): bump matplotlib from 3.9.3 to 3.10.0
Bumps [matplotlib](https://github.com/matplotlib/matplotlib) from 3.9.3 to 3.10.0.
- [Release notes](https://github.com/matplotlib/matplotlib/releases)
- [Commits](https://github.com/matplotlib/matplotlib/compare/v3.9.3...v3.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 03:25:53 +00:00
dependabot[bot] f575e56e46 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.24.0 to 0.25.0
- [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases)
- [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.24.0...v0.25.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-12-16 03:25:42 +00:00
dependabot[bot] af7974adda chore(deps): bump pypa/gh-action-pypi-publish from 1.12.2 to 1.12.3
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.12.2 to 1.12.3.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.12.2...v1.12.3)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-16 03:25:38 +00:00
Matthias 685811ef63 feat: update backtest_api with new storage interface 2024-12-15 15:10:06 +01:00
Matthias 12ae36e99e test: use tmp_path instead of testdatadir for writing 2024-12-15 15:02:35 +01:00
Matthias abfe683763 test: fix tests data pollution 2024-12-15 15:02:16 +01:00
Matthias a1ab8de593 test: Update tests for new bt storage method 2024-12-15 13:20:25 +01:00
Matthias b04c5aec88 refactor: move backtest "storage" logic to storage file 2024-12-15 13:18:25 +01:00
Matthias b862927c8c chore: don't exclude tests from pyright 2024-12-15 13:07:39 +01:00
Matthias 5744e16a07 refactor: use 1 structure to save analysis signals 2024-12-14 19:50:59 +01:00
Matthias 11f0a59031 docs: slightly improve position adjust doc wording 2024-12-14 18:20:47 +01:00
Matthias 8dfbf342fd Merge pull request #11086 from freqtrade/fix-docs
add warning about loose logic
2024-12-14 14:13:11 +01:00
Matthias 5f947a86c7 docs: improve ordering in adjust trade position callback docs 2024-12-14 13:47:14 +01:00
Matthias 0f18e78bb4 Merge pull request #11084 from freqtrade/improve/orderflow_times
Improve orderflow execution times
2024-12-14 13:43:55 +01:00
Matthias d0c5d614f7 docs: add note about incompatibility between orderflow and freqAI 2024-12-14 13:08:13 +01:00
Matthias e2e1c610d8 Merge pull request #11087 from freqtrade/bump/ccxt
Bump ccxt to 4.4.40
2024-12-14 12:52:04 +01:00
Matthias 1e74aa3087 chore: bump ccxt to 4.4.40 2024-12-14 08:04:29 +01:00
Stefano Ariestasia f54a141918 fix typo 2024-12-14 14:41:32 +09:00
Stefano Ariestasia b54d8c53a7 add warning about loose logic 2024-12-14 14:37:16 +09:00
Matthias 016cabb7b2 chore: simplify imports 2024-12-13 14:59:35 +01:00
Matthias 9129266f54 feat: greatly improve performance of orderflow by not using apply 2024-12-13 14:28:26 +01:00
Matthias e8376d5b79 feat: add timeframe_to_DateOffset 2024-12-13 14:27:54 +01:00
Matthias cc7b1a5642 chore: clean up install_windows call 2024-12-13 14:24:27 +01:00
Matthias f11fe14cdd chore: remove system mode, set path globally 2024-12-13 14:24:27 +01:00
Matthias 219e6b420e chore: Try using uv venv creation 2024-12-13 14:24:27 +01:00
Matthias 3a6ef8c34b chore: use link mode for UV installs 2024-12-13 14:24:27 +01:00
Matthias 4f318a5981 chore: uv - different caching 2024-12-13 14:24:27 +01:00
Matthias f978a5c754 chore: uv - different caching 2024-12-13 14:24:27 +01:00
Matthias f378e10752 chore: link mode copy 2024-12-13 14:24:27 +01:00
Matthias 30909d13ba chore: uv: glob 2024-12-13 14:24:27 +01:00
Matthias 8cd03c9624 chore: uv - enable cache 2024-12-13 14:24:27 +01:00
Matthias 8f617d01d4 chore: further uv tests 2024-12-13 14:24:27 +01:00
Matthias 569ac2bc5b chore: move uv work 2024-12-13 14:24:27 +01:00
Matthias c1be014499 chore: package name lowercase ?? 2024-12-13 14:24:27 +01:00
Matthias 7d1ac0248b chore: only binary ... 2024-12-13 14:24:27 +01:00
Matthias aa8e504ef7 chore: remove non-existing UV argument 2024-12-13 14:24:27 +01:00
Matthias 6963141eb8 chore: fix aliasing 2024-12-13 14:24:27 +01:00
Matthias 3295b1c8de chore: try using UV for windows runs 2024-12-13 14:24:27 +01:00
Matthias 0159d25e48 chore: add missing dev-deps to pyproject.toml 2024-12-13 14:24:27 +01:00
Matthias abbfe04642 Merge pull request #11080 from stash86/docs-dca
add more explanation regarding reducing position partially
2024-12-12 20:11:39 +01:00
Matthias 8cf3c7b826 chore: use FtPrecise for stake_amount_filled calculation
closes #11073
2024-12-12 20:08:35 +01:00
Matthias 0fb927e709 Merge pull request #11081 from freqtrade/frog-docs-state-1
Add initial_state option to FAQ
2024-12-12 16:43:08 +01:00
Robert Davey c00b248d1a Add initial_state option to FAQ 2024-12-12 13:17:52 +00:00
Stefano d2b4d1e183 add more explanation regarding reducing position partially 2024-12-12 11:45:40 +00:00
Matthias 8fb9140c21 Merge pull request #11046 from freqtrade/fix/orderflow_cache
Fix orderflow cache
2024-12-12 06:49:58 +01:00
Matthias cb9d828ba0 Merge pull request #11079 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-12-12 06:28:12 +01:00
xmatthias 467c0dfffa chore: update pre-commit hooks 2024-12-12 03:16:05 +00:00
Matthias d887a4aa4a Merge branch 'develop' into fix/orderflow_cache 2024-12-11 19:28:06 +01:00
Matthias 34835752f8 chore: improve typing in percentChange Pairlist 2024-12-11 07:25:07 +01:00
Matthias b9aa78b987 fix: crash in PercentChange pairlist
Could happen if pairs were removed due to empty Ticker when the pairlist is not a generator.

closes #11074
2024-12-11 07:12:50 +01:00
Matthias d8fa782f13 test: Add test with faulty behavior
part of #11074
2024-12-11 07:07:47 +01:00
Matthias f3d7b249be test: Improve wallet tests 2024-12-10 21:05:05 +01:00
Matthias 097836d193 feat: improve logic for liquidation price calc 2024-12-10 21:05:04 +01:00
Matthias 7552ad5edb Merge pull request #11070 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-12-10 06:31:30 +01:00
xmatthias e9cf0a71d8 chore: update pre-commit hooks 2024-12-10 05:05:39 +00:00
Matthias cf731d7296 Merge pull request #11057 from freqtrade/dependabot/pip/develop/types-e0d1a7aad6
chore(deps-dev): bump the types group with 2 updates
2024-12-09 07:20:55 +01:00
Matthias 946e6fd4c2 Merge pull request #11066 from freqtrade/dependabot/pip/develop/pydantic-2.10.3
chore(deps): bump pydantic from 2.10.2 to 2.10.3
2024-12-09 07:10:14 +01:00
Matthias fe9d1a053e chore: bump pre-commit dependencies 2024-12-09 06:40:49 +01:00
Matthias 6b1ac499ba tests: more tests for cross wallet 2024-12-09 06:39:54 +01:00
Matthias 0fc0b2a1be test: add test for cross balance logic 2024-12-09 06:39:54 +01:00
Matthias 6887ed4bf7 feat: add cross margin balance logic 2024-12-09 06:39:54 +01:00
Matthias 8cb82df055 chore: simplify dry_wallets call 2024-12-09 06:39:54 +01:00
dependabot[bot] 6788214671 chore(deps): bump pydantic from 2.10.2 to 2.10.3
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.10.2 to 2.10.3.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.10.2...v2.10.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-09 05:32:50 +00:00
Matthias 4c2488ff65 Merge pull request #11065 from freqtrade/dependabot/pip/develop/fastapi-0.115.6
chore(deps): bump fastapi from 0.115.5 to 0.115.6
2024-12-09 06:31:58 +01:00
Matthias 05b101b612 Merge pull request #11064 from freqtrade/dependabot/pip/develop/ccxt-4.4.37
chore(deps): bump ccxt from 4.4.35 to 4.4.37
2024-12-09 06:29:55 +01:00
Matthias 65ab47dc63 Merge pull request #11063 from freqtrade/dependabot/pip/develop/python-telegram-bot-21.9
chore(deps): bump python-telegram-bot from 21.8 to 21.9
2024-12-09 06:29:21 +01:00
Matthias 23579df8da Merge pull request #11058 from freqtrade/dependabot/pip/develop/mkdocs-3ba6cc2ae5
chore(deps): bump mkdocs-material from 9.5.47 to 9.5.48 in the mkdocs group
2024-12-09 06:29:05 +01:00
Matthias 2b91be6237 Merge pull request #11061 from freqtrade/dependabot/pip/develop/ruff-0.8.2
chore(deps-dev): bump ruff from 0.8.1 to 0.8.2
2024-12-09 06:26:55 +01:00
dependabot[bot] f1fabb07bf chore(deps): bump fastapi from 0.115.5 to 0.115.6
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.115.5 to 0.115.6.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.115.5...0.115.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-09 03:47:54 +00:00
dependabot[bot] 8aea015452 chore(deps): bump ccxt from 4.4.35 to 4.4.37
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.35 to 4.4.37.
- [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.4.35...4.4.37)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-09 03:47:48 +00:00
dependabot[bot] 83471228e7 chore(deps): bump python-telegram-bot from 21.8 to 21.9
Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 21.8 to 21.9.
- [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases)
- [Changelog](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/CHANGES.rst)
- [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v21.8...v21.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-09 03:47:37 +00:00
dependabot[bot] e97a647b54 chore(deps-dev): bump ruff from 0.8.1 to 0.8.2
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.8.1 to 0.8.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.8.1...0.8.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-12-09 03:47:13 +00:00
dependabot[bot] f5dceb0b09 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.47 to 9.5.48
- [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.47...9.5.48)

---
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-12-09 03:46:15 +00:00
dependabot[bot] d4fbdeee94 chore(deps-dev): bump the types group with 2 updates
Bumps the types group with 2 updates: [types-tabulate](https://github.com/python/typeshed) and [types-python-dateutil](https://github.com/python/typeshed).


Updates `types-tabulate` from 0.9.0.20240106 to 0.9.0.20241207
- [Commits](https://github.com/python/typeshed/commits)

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

---
updated-dependencies:
- dependency-name: types-tabulate
  dependency-type: direct:development
  update-type: version-update:semver-patch
  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-12-09 03:45:29 +00:00
Matthias f529cfe8cf chore: fix wrong return typing 2024-12-08 16:58:31 +01:00
Matthias 4d36aaff39 feat: convert get_valid_pair_combination to generator 2024-12-08 16:54:17 +01:00
Matthias f07aec457a chore: remove unnecessary imports 2024-12-08 16:50:10 +01:00
Matthias d898c80e65 test: Add test for get_conversion_rate 2024-12-08 16:40:56 +01:00
Matthias 2ff0abc6e7 refactor: conversation rate retrieval to exchange class
for better reusability across the bot.
2024-12-08 15:44:46 +01:00
Matthias 0276e65f39 test: update rpc test for new behavior 2024-12-08 15:29:23 +01:00
Matthias 4cd8e6b444 chore: simplify rpc_balance method by relying on exchange cache 2024-12-08 15:22:40 +01:00
Matthias 58357a0746 chore: calculate total_stake only once 2024-12-08 14:41:26 +01:00
Matthias d0223e6f48 chore: simplify dry-run wallet update 2024-12-08 14:41:26 +01:00
Matthias 142ea68dce chore: fix oddity of price_to_precision 2024-12-08 14:41:26 +01:00
Matthias 654d2ab63d fix: Round stoploss_dist to price_precision 2024-12-08 14:41:26 +01:00
Matthias a30f281478 Merge pull request #11023 from freqtrade/dependabot/pip/develop/xgboost-2.1.3
chore(deps): bump xgboost from 2.0.3 to 2.1.3
2024-12-08 13:46:58 +01:00
Matthias ae1baf5789 test: mock price_to_precision for liquidation calculations 2024-12-08 13:35:46 +01:00
Matthias 3c88bdc60c fix: round liquidation price to price precision 2024-12-08 13:35:46 +01:00
Matthias 6d698e584b chore: return 0 total balances
total may be 0, while still having "free" balance (cross futures scenarios)
2024-12-08 13:35:46 +01:00
Matthias 193b989342 chore: Be more precise with binance account limitation 2024-12-08 13:35:46 +01:00
Matthias 8dc322d7f2 chore: don't allow negative stake amounts 2024-12-08 13:35:46 +01:00
Matthias eee5d710e7 chore: patch torch all the time -
"list"tests do load the modules as well - so they
need the same patch.
2024-12-08 10:06:23 +01:00
Matthias 489db37bbd Merge pull request #10979 from amalysh/develop
allow json in environment variables
2024-12-08 08:55:21 +01:00
Matthias 10b5d5e56b docs: add "list parsing" logic to documentation 2024-12-08 08:35:13 +01:00
Matthias 934bcf253e test: add tests for list parsing 2024-12-08 08:34:54 +01:00
Matthias 0683ba3a54 feat: limit environment-variable json parsing to lists 2024-12-08 08:30:45 +01:00
Matthias a85f200450 Merge pull request #11000 from freqtrade/feat/multi_wallet
Dry-run multi-wallet support
2024-12-07 17:49:32 +01:00
Matthias 98e0a5f101 chore: remove unused arguments in loss functions 2024-12-07 15:52:14 +01:00
Matthias fb9e11b7b5 chore: improve type safety 2024-12-07 13:21:18 +01:00
Matthias e77ca024d7 chore: improve naming, don't duplicate column list 2024-12-06 20:16:46 +01:00
Matthias 63f8217cd5 chore: ensure exif-data is removed from images 2024-12-06 20:11:46 +01:00
Matthias 9f5cb3a07e chore: add exif-stripper to ensure exif data is removed 2024-12-06 20:11:09 +01:00
Matthias e09d1b42f2 chore: eliminate duplicate trades data grouping 2024-12-06 20:09:43 +01:00
Matthias 267d9333a1 chore: remove pointless, very noisy log message. 2024-12-06 20:09:07 +01:00
Matthias ab39ac29e8 fix: ensure data type is maintained when data comes from cache. 2024-12-06 20:05:33 +01:00
Matthias 6c25feabf2 tests: assert type of orderflow object 2024-12-06 20:05:07 +01:00
Matthias 171157c100 chore: further simplify orderflow code 2024-12-06 07:03:24 +01:00
Matthias 3137d7cf2c chore: remove unnecessary config alias 2024-12-06 06:43:01 +01:00
Matthias 6ab528748e tests: Update orderflow tests 2024-12-06 06:40:15 +01:00
Matthias 621dfc136e fix: Improved caching
closes #11008
2024-12-06 06:38:49 +01:00
Matthias 9d07f5dc2e chore: reduce orderflow code duplication 2024-12-05 19:37:36 +01:00
Matthias 48dd86bd9b chore: use .at for assignments 2024-12-05 19:33:49 +01:00
Matthias 4879582896 feat: use dataframe.at directly to avoid intermediate series 2024-12-05 19:32:40 +01:00
Matthias 6584f86bce test: add Spy to improve test 2024-12-05 19:19:17 +01:00
Matthias 82d517fcbb test: improve orderflow test 2024-12-05 07:12:35 +01:00
Matthias ff371c43e0 tests: add test to ensure caching works
part of #11008
2024-12-05 07:11:58 +01:00
Matthias 0bf0e1808c chore: add todo for future cleanup 2024-12-05 06:58:25 +01:00
Matthias 6be5947f69 chore: Move local Import out of the loop 2024-12-05 06:37:19 +01:00
Matthias b1d7d7304d Merge pull request #11041 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-12-05 06:29:25 +01:00
xmatthias 1d0702e143 chore: update pre-commit hooks 2024-12-05 03:16:51 +00:00
Matthias 298ce335b9 chore: eliminate duplicate trades data grouping 2024-12-04 19:56:26 +01:00
Matthias e04f630f41 chore: further improve typing in orderflow 2024-12-04 07:10:53 +01:00
Matthias 7dfee16e25 chore: improved logic ordering in orderflow method 2024-12-04 07:09:09 +01:00
Matthias d6c21e2af6 chore: orderflow: better typing 2024-12-03 20:13:25 +01:00
Matthias ebae0a7248 chore: improve typing of new functionality 2024-12-03 19:44:26 +01:00
Matthias fe834f00a2 fix: import causing long startup time 2024-12-03 19:37:14 +01:00
Robert Caulk 3855d44bf5 Merge pull request #11005 from freqtrade/fix/freqai_bug
freqAI bug causing failures on 2nd backtest
2024-12-03 19:18:43 +01:00
Matthias ad8f621287 tests: Improve hyperopt loss tests 2024-12-03 19:17:01 +01:00
Matthias 18305a5bf6 docs: update hyperopt docs to include new argument 2024-12-03 19:16:48 +01:00
Matthias b0b73bf166 feat: add starting_balance as argument to hyperopt_loss_function 2024-12-03 19:15:57 +01:00
Matthias 7a8971b9b6 feat: use get_dry_run_wallet helper 2024-12-03 07:27:45 +01:00
Matthias c082e5f6a6 feat: add dry_run_wallet helper 2024-12-03 07:26:47 +01:00
Matthias ef0703f209 fix: Set timeframe for api calls
closes #11009
2024-12-03 06:53:39 +01:00
Matthias 196c122e0a Merge pull request #11031 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-12-03 06:30:30 +01:00
xmatthias 57d5a55ca0 chore: update pre-commit hooks 2024-12-03 03:17:35 +00:00
Matthias 94e09f9fa0 Merge pull request #11028 from xzmeng/fix-none
fix: check if days is None before conversion
2024-12-02 20:11:00 +01:00
Matthias 2f4bd6a0c9 chore: split pypi and pypi test jobs to enable proper attestation
Related: https://github.com/pypa/gh-action-pypi-publish/issues/283
2024-12-02 20:03:53 +01:00
Matthias 58d3555102 Merge pull request #11027 from xzmeng/fix-cond
fix: invert API download condition from `and` to `or`
2024-12-02 18:22:58 +01:00
Matthias b032c329e3 Merge pull request #11017 from freqtrade/dependabot/pip/develop/pyarrow-18.1.0
chore(deps): bump pyarrow from 18.0.0 to 18.1.0
2024-12-02 18:19:43 +01:00
Meng Xiangzhuo 73f00d2bc3 fix: check if days is None before conversion 2024-12-03 00:31:34 +08:00
Meng Xiangzhuo cf896bbb52 chore: add the missing space in log 2024-12-03 00:04:56 +08:00
Meng Xiangzhuo ba162e35c4 fix: invert logic and to or 2024-12-02 23:56:59 +08:00
Matthias 00e9178d5f chore: bump pyarrow wheel 2024-12-02 16:04:23 +01:00
Matthias 62b0995f6a Merge pull request #11020 from freqtrade/dependabot/pip/develop/ruff-0.8.1
chore(deps-dev): bump ruff from 0.8.0 to 0.8.1
2024-12-02 07:38:15 +01:00
Matthias 4107e5cc9a Merge pull request #11021 from freqtrade/dependabot/pip/develop/cryptography-44.0.0
chore(deps): bump cryptography from 43.0.3 to 44.0.0
2024-12-02 07:26:15 +01:00
Matthias c841146968 chore: Remove non-suppored callback from XGBRFRegressor
According to the XGBoost docs, this probably never worked
as it was never supported - but is now raising an error for user clarity.
2024-12-02 07:12:30 +01:00
dependabot[bot] 6c9e6cc198 chore(deps): bump xgboost from 2.0.3 to 2.1.3
Bumps [xgboost](https://github.com/dmlc/xgboost) from 2.0.3 to 2.1.3.
- [Release notes](https://github.com/dmlc/xgboost/releases)
- [Changelog](https://github.com/dmlc/xgboost/blob/master/NEWS.md)
- [Commits](https://github.com/dmlc/xgboost/compare/v2.0.3...v2.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 06:00:19 +00:00
dependabot[bot] 980bfddd8a chore(deps-dev): bump ruff from 0.8.0 to 0.8.1
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.8.0 to 0.8.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.8.0...0.8.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 05:37:46 +00:00
dependabot[bot] 06327e8f04 chore(deps): bump cryptography from 42.0.8 to 44.0.0
Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.8 to 44.0.0.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/42.0.8...44.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 05:37:30 +00:00
Matthias 2290b44f82 Merge pull request #11018 from freqtrade/dependabot/pip/develop/matplotlib-3.9.3
chore(deps): bump matplotlib from 3.9.2 to 3.9.3
2024-12-02 06:37:00 +01:00
Matthias c0cec2b9de Merge pull request #11015 from freqtrade/dependabot/pip/develop/python-telegram-bot-21.8
chore(deps): bump python-telegram-bot from 21.7 to 21.8
2024-12-02 06:36:46 +01:00
Matthias e376889f3d Merge pull request #11013 from freqtrade/dependabot/pip/develop/pytest-e05860fc06
chore(deps-dev): bump pytest from 8.3.3 to 8.3.4 in the pytest group
2024-12-02 06:36:33 +01:00
Matthias 8584cacb6e Merge pull request #11016 from freqtrade/dependabot/pip/develop/pydantic-2.10.2
chore(deps): bump pydantic from 2.10.1 to 2.10.2
2024-12-02 06:34:33 +01:00
Matthias 3ff19babf5 Merge pull request #11019 from freqtrade/dependabot/pip/develop/pyjwt-2.10.1
chore(deps): bump pyjwt from 2.10.0 to 2.10.1
2024-12-02 06:34:23 +01:00
Matthias 8b5aa08c57 Merge pull request #11014 from freqtrade/dependabot/pip/develop/mkdocs-16f9823690
chore(deps): bump mkdocs-material from 9.5.45 to 9.5.47 in the mkdocs group
2024-12-02 06:33:38 +01:00
dependabot[bot] cffcb2b985 chore(deps): bump pyjwt from 2.10.0 to 2.10.1
Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.10.0 to 2.10.1.
- [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.10.0...2.10.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 03:19:54 +00:00
dependabot[bot] 1da8b68906 chore(deps): bump matplotlib from 3.9.2 to 3.9.3
Bumps [matplotlib](https://github.com/matplotlib/matplotlib) from 3.9.2 to 3.9.3.
- [Release notes](https://github.com/matplotlib/matplotlib/releases)
- [Commits](https://github.com/matplotlib/matplotlib/compare/v3.9.2...v3.9.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 03:19:49 +00:00
dependabot[bot] d53f4fb81b chore(deps): bump pyarrow from 18.0.0 to 18.1.0
Bumps [pyarrow](https://github.com/apache/arrow) from 18.0.0 to 18.1.0.
- [Release notes](https://github.com/apache/arrow/releases)
- [Commits](https://github.com/apache/arrow/compare/apache-arrow-18.0.0...apache-arrow-18.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 03:19:42 +00:00
dependabot[bot] 02fcd3b489 chore(deps): bump pydantic from 2.10.1 to 2.10.2
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.10.1 to 2.10.2.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.10.1...v2.10.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 03:19:25 +00:00
dependabot[bot] 7b70a7a68a chore(deps): bump python-telegram-bot from 21.7 to 21.8
Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 21.7 to 21.8.
- [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases)
- [Changelog](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/CHANGES.rst)
- [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v21.7...v21.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-12-02 03:19:08 +00:00
dependabot[bot] 5060725ef5 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.45 to 9.5.47
- [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.45...9.5.47)

---
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-12-02 03:19:00 +00:00
dependabot[bot] 6bf3218665 chore(deps-dev): bump pytest from 8.3.3 to 8.3.4 in the pytest group
Bumps the pytest group with 1 update: [pytest](https://github.com/pytest-dev/pytest).


Updates `pytest` from 8.3.3 to 8.3.4
- [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.3...8.3.4)

---
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-12-02 03:18:44 +00:00
Matthias ab42f84daa Merge pull request #11011 from freqtrade/new_release
New release 2024.11
2024-12-01 20:11:57 +01:00
Matthias 29f86afaf4 Merge pull request #10858 from xzmeng/binance-public-data
Improve binance historic OHLCV download speed
2024-12-01 19:41:40 +01:00
Matthias ac5a3b9217 Merge pull request #11010 from freqtrade/bump/ta-lib
Bump ta lib to 0.4.34
2024-12-01 19:39:40 +01:00
Matthias d7d73364e9 chore: version bump 2024.12-dev 2024-12-01 19:38:37 +01:00
Matthias f83a57ae5c chore: version bump to 2024.11 2024-12-01 19:35:02 +01:00
Matthias 56d747818d Merge branch 'stable' into new_release 2024-12-01 19:34:50 +01:00
Matthias 64b50d13fa chore: Add typehint for consolidated method 2024-12-01 16:25:00 +01:00
Matthias 0ada6fbd46 test: Add test case for kraken balance rewards 2024-12-01 15:34:36 +01:00
Matthias 2c8840b7e0 fix(kraken): consolidate balances from "rewards"
These currencies show up as <currency>.F -
but can be traded normally - hence we should count them as "tradable" balance.

closes #11007
closes #10925
2024-12-01 15:33:53 +01:00
Matthias 1510c5517b chore: update ta-lib wheels for 0.4.34 2024-12-01 15:28:48 +01:00
Matthias f6e2fcbf15 chore: bump ta-lib to 0.4.34 2024-12-01 15:28:37 +01:00
Matthias 55247b4e12 chore: bump ccxt to 4.4.35 2024-12-01 15:19:45 +01:00
Matthias f31b26b1ab tests: Improve sample_order online testing 2024-11-30 18:12:32 +01:00
Matthias 2b350eb872 tests: improve assert accuracy 2024-11-30 13:50:12 +01:00
Matthias 88d77658f8 tests: extract strategy_safe_wrapper tests 2024-11-30 13:49:04 +01:00
Matthias cbbc386170 docs: update Hyperliquid docs to guide users to use API Wallets 2024-11-30 07:05:19 +01:00
Matthias 7f9226861a docs: slight formatting improvement 2024-11-30 07:04:19 +01:00
Matthias 162c79029f chore: slightly reorganize test file layout 2024-11-29 07:12:54 +01:00
Matthias 5f363f5c9d chore: properly remove ccxt reference 2024-11-29 07:00:42 +01:00
Matthias bd5877ad49 chore: add space in log message 2024-11-29 06:55:33 +01:00
Matthias 71cf7aedb5 chore: remove unnecessary test-file
that test is flaky for new pairs - and wouldn't run in production anyway.
2024-11-29 06:48:39 +01:00
Matthias fa1e0af19f chore: remove direct exchange connection 2024-11-29 06:46:55 +01:00
Matthias a6199680cd fix: freqAI bug causing failures on 2nd backtest 2024-11-28 19:53:41 +01:00
Matthias 9ff4298475 docs: update documentation for expanding pairs
This needs quoting otherwise zsh tries to expand the command,
causing failures.

closes #10996
2024-11-28 19:22:08 +01:00
Matthias 5793216d5c properly account for fee when assigning order.cost in backtesting
(although it's not relevant for operations, it shouldn't be obivously wrong).

closes #11003
2024-11-28 18:15:55 +01:00
Matthias d1710826e6 chore: improved naming 2024-11-28 07:27:29 +01:00
Matthias 675b996f57 chore: improved naming in test case 2024-11-28 07:20:32 +01:00
Matthias 21777a5804 chore: do more pinpointed imports 2024-11-28 07:09:57 +01:00
Matthias b1ca00b037 chore: don't shadow pandas builtin methods 2024-11-28 07:04:28 +01:00
Matthias e2a09f272a chore: Improve naming, remove unnecessary method 2024-11-28 06:55:15 +01:00
Matthias cf9c9fe278 Merge pull request #11001 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-11-28 06:53:38 +01:00
Matthias 0e77c89d79 chore: simplify code structure by moving conditional to parent method 2024-11-28 06:43:51 +01:00
xmatthias 7571ae19a0 chore: update pre-commit hooks 2024-11-28 03:14:33 +00:00
Matthias 69c1de7e4a chore: move fallback param to config directly 2024-11-27 18:20:44 +01:00
Matthias 09308e568d fix: increase code reliability by not relying on stake-currency to be in the dict 2024-11-27 07:24:11 +01:00
Matthias 671821aeb3 docs: Add documentation for dry-run-wallet as dict 2024-11-27 06:48:07 +01:00
Matthias 7369331e2d tests: add test for multi-pair dry-run wallets 2024-11-27 06:41:54 +01:00
Matthias e6e193f252 test: assert tickers is called a 2nd time if necessary 2024-11-27 06:41:54 +01:00
Matthias 2bb111605c feat: update rpc_balance to fetch spot tickers
Happens if there is no market for the given pair
2024-11-27 06:41:54 +01:00
Matthias 37aba6f7d5 feat: Allow fetch_tickers from different marketsegment 2024-11-27 06:41:54 +01:00
Matthias 3fc259bb9b feat: add non-trading balance to wallet 2024-11-27 06:41:54 +01:00
Matthias 15c1a8ee0b chore: reduce dict lookups, reuse attribute 2024-11-27 06:41:54 +01:00
Matthias b4b6de4e0d test: update test ... 2024-11-27 06:41:54 +01:00
Matthias 5b0be7e1a9 feat: support dict like dry_run_wallet 2024-11-27 06:41:54 +01:00
Matthias 92e64927b0 feat: allow object as dry-run balance 2024-11-27 06:41:54 +01:00
Matthias 58cde3253d docs: Fix rendering of $ signs 2024-11-27 06:41:45 +01:00
Matthias 47262b9350 docs: align character used for lists 2024-11-27 06:41:39 +01:00
Matthias 183909f474 chore: kwonly for cached arguments 2024-11-26 18:29:33 +01:00
Matthias 06bb43b13e chore: add missing docstring 2024-11-26 18:29:28 +01:00
Matthias 2ed8e1639f chore: move comment to where it belongs 2024-11-26 18:29:21 +01:00
Matthias e78d5b7787 chore: improved typing of rpc class 2024-11-26 18:29:17 +01:00
Matthias e0c6cc1563 feat: add log_responses to /balance endpoint
part of #10994
2024-11-26 10:23:03 +01:00
Matthias 472132c236 tests: update test for renamed prop 2024-11-26 07:08:55 +01:00
Matthias 1262aa19ec chore: make start_cap in wallets private 2024-11-26 06:37:35 +01:00
Matthias e9e3f7ef16 Merge pull request #10992 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-11-26 06:01:23 +01:00
xmatthias d9d4c478cc chore: update pre-commit hooks 2024-11-26 03:13:21 +00:00
Matthias db56fc158f test: update test market 2024-11-25 19:35:25 +01:00
Matthias af8d2490e2 feat: improve is_futures market check 2024-11-25 19:35:25 +01:00
Matthias fdc77bfd5b Merge pull request #10986 from freqtrade/dependabot/pip/develop/ruff-0.8.0
chore(deps-dev): bump ruff from 0.7.4 to 0.8.0
2024-11-25 07:55:15 +01:00
Matthias 5572d37c7f Merge pull request #10985 from freqtrade/dependabot/pip/develop/stable-baselines3-2.4.0
chore(deps): bump stable-baselines3 from 2.3.2 to 2.4.0
2024-11-25 07:39:01 +01:00
Matthias 57f2ab2f67 Merge pull request #10983 from freqtrade/dependabot/pip/develop/pydantic-2.10.1
chore(deps): bump pydantic from 2.9.2 to 2.10.1
2024-11-25 07:22:20 +01:00
Matthias 1cab52f555 Merge pull request #10988 from freqtrade/dependabot/pip/develop/numexpr-2.10.2
chore(deps): bump numexpr from 2.10.1 to 2.10.2
2024-11-25 07:07:41 +01:00
Matthias 748233ca70 chore: update imports to prevent shadowing internal exceptions 2024-11-25 06:39:18 +01:00
dependabot[bot] f76108c5b5 chore(deps): bump pydantic from 2.9.2 to 2.10.1
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.9.2 to 2.10.1.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.9.2...v2.10.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-25 05:31:31 +00:00
dependabot[bot] 09f94f7b56 chore(deps): bump stable-baselines3 from 2.3.2 to 2.4.0
Bumps [stable-baselines3](https://github.com/DLR-RM/stable-baselines3) from 2.3.2 to 2.4.0.
- [Release notes](https://github.com/DLR-RM/stable-baselines3/releases)
- [Commits](https://github.com/DLR-RM/stable-baselines3/compare/v2.3.2...v2.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-25 05:31:16 +00:00
Matthias a85b8d6edc Merge pull request #10984 from freqtrade/dependabot/pip/develop/uvicorn-0.32.1
chore(deps): bump uvicorn from 0.32.0 to 0.32.1
2024-11-25 06:30:54 +01:00
Matthias 52c9761e5b Merge pull request #10980 from freqtrade/dependabot/pip/develop/mkdocs-f608445ecc
chore(deps): bump mkdocs-material from 9.5.44 to 9.5.45 in the mkdocs group
2024-11-25 06:30:41 +01:00
Matthias c2dbb9cb43 Merge pull request #10981 from freqtrade/dependabot/pip/develop/tqdm-4.67.1
chore(deps): bump tqdm from 4.67.0 to 4.67.1
2024-11-25 06:30:26 +01:00
Matthias 59c3743f77 Merge pull request #10982 from freqtrade/dependabot/pip/develop/orjson-3.10.12
chore(deps): bump orjson from 3.10.11 to 3.10.12
2024-11-25 06:30:09 +01:00
dependabot[bot] 98422f8476 chore(deps): bump numexpr from 2.10.1 to 2.10.2
Bumps [numexpr](https://github.com/pydata/numexpr) from 2.10.1 to 2.10.2.
- [Release notes](https://github.com/pydata/numexpr/releases)
- [Changelog](https://github.com/pydata/numexpr/blob/master/RELEASE_NOTES.rst)
- [Commits](https://github.com/pydata/numexpr/compare/v2.10.1...v2.10.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-25 03:46:25 +00:00
dependabot[bot] 8c6283a687 chore(deps-dev): bump ruff from 0.7.4 to 0.8.0
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.7.4 to 0.8.0.
- [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.7.4...0.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-25 03:45:45 +00:00
dependabot[bot] 1fd492d838 chore(deps): bump uvicorn from 0.32.0 to 0.32.1
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.32.0 to 0.32.1.
- [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.32.0...0.32.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-25 03:45:25 +00:00
dependabot[bot] fc63a713fd chore(deps): bump orjson from 3.10.11 to 3.10.12
Bumps [orjson](https://github.com/ijl/orjson) from 3.10.11 to 3.10.12.
- [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.11...3.10.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-25 03:45:09 +00:00
dependabot[bot] e97b9820a4 chore(deps): bump tqdm from 4.67.0 to 4.67.1
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.67.0 to 4.67.1.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.67.0...v4.67.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-25 03:45:01 +00:00
dependabot[bot] 52f24af701 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.44 to 9.5.45
- [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.44...9.5.45)

---
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-11-25 03:44:56 +00:00
Alexander Malysh c3032feaf7 * allow json in env variables 2024-11-24 14:23:04 +01:00
Matthias b30c1523e3 chore: don't use e if not used 2024-11-24 10:36:31 +01:00
Matthias 6e1c4c2efd test: add test-case for improved error-handling 2024-11-24 10:36:09 +01:00
Matthias 3b542ea008 fix: add better error-handling for dataloading
closes #10978
2024-11-24 10:36:02 +01:00
Matthias 5841b56f2d chore: NotRequired from typing_extensions 2024-11-23 14:18:05 +01:00
Matthias 85ea29c93c fix: pydantic - typing_extensions type-dict 2024-11-23 14:05:52 +01:00
Matthias f7e979141b chore: simplify imports 2024-11-23 13:09:17 +01:00
Matthias e0bdc68a9e feat: support all parameters 2024-11-23 12:55:54 +01:00
Matthias e6cfde211b feat: enhance api_download capabilities 2024-11-22 16:18:28 +01:00
Matthias 978aaa5c4b chore: improve API server cleanup 2024-11-22 16:17:26 +01:00
Matthias b67beaa35a chore: simplify progress-tracker typing 2024-11-22 15:24:52 +01:00
Matthias 19f23106e7 tests: add test for download_data api method 2024-11-22 15:06:58 +01:00
Matthias d8a2c59b04 chore: Add proper typing to jobContainer response 2024-11-22 15:06:58 +01:00
Matthias dea785f562 feat: add progress_tasks to /background endpoint 2024-11-22 15:06:58 +01:00
Matthias ca42f86900 feat: set progressTasks in Callback correctly 2024-11-22 15:06:58 +01:00
Matthias de9294d1f4 feat: return progressTasks in API 2024-11-22 15:06:58 +01:00
Matthias 91b83519f5 feat: update types to support multiple progressbars 2024-11-22 15:06:58 +01:00
Matthias 3eb7f296aa fix: callback must be called after parent call 2024-11-22 15:06:58 +01:00
Matthias f3b0e404f0 chore: update to new syntax 2024-11-22 15:06:58 +01:00
Matthias 3ba3d7ffcf feat: increment api_version 2024-11-22 15:06:58 +01:00
Matthias 832fa88f08 feat: api-download-data shouldn't require stake-currency 2024-11-22 15:06:58 +01:00
Matthias 6e9d349ceb feat: api progressbar handling 2024-11-22 15:06:57 +01:00
Matthias 8bd1524abc feat: pass progressbar into download-data functions 2024-11-22 15:06:57 +01:00
Matthias 72f5633061 feat: add ProgressLike protocol 2024-11-22 15:06:57 +01:00
Matthias 3b25bdc1cb feat(data-dl): Simplify download-data calls 2024-11-22 15:06:57 +01:00
Matthias 3690609519 feat: simplify api_download_data code duplication 2024-11-22 15:06:57 +01:00
Matthias 43c9d14ac2 refactor: extract exchangeInit from download-data main 2024-11-22 15:06:57 +01:00
Matthias 83122fa58b feat: add initial download-data api endpoints 2024-11-22 15:06:57 +01:00
Matthias f23da7ea75 feat: Add initial data-download api schema 2024-11-22 15:06:57 +01:00
Matthias 7ef93e92e6 feat: Log if user has a Gate unified account 2024-11-21 20:57:20 +01:00
Matthias 945f9f8c47 docs: update unfilledtimeout.entry comment to remove false statement
closes #10966
2024-11-21 20:38:38 +01:00
Matthias 29b40d27e7 fix: hyperliquid doesn't provide "average" in fetch_order
therefore this needs to be filled from trades to have a good price
 for market orders
2024-11-21 20:31:56 +01:00
Matthias cd83ed699c chore: add rejected to canceled order states 2024-11-21 20:04:52 +01:00
Matthias 02fcded368 chore: bump ccxt to 4.4.33
closes #10942
2024-11-21 18:17:14 +01:00
Matthias 18b0dfe28c fix: add stop_price_prop for hyperliquid
temporary workaround for #10960
2024-11-21 06:45:03 +01:00
Matthias 69be62f1e6 Merge pull request #10965 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-11-21 06:28:15 +01:00
xmatthias 7136c5b414 chore: update pre-commit hooks 2024-11-21 03:10:53 +00:00
Matthias b637e7dde4 docs: add "impossible to load" FAQ entry 2024-11-20 07:00:29 +01:00
Matthias f545274a0e test: add test for bybit classic account behavior
related: #10872
2024-11-19 18:19:27 +01:00
Matthias e8c122d5ed fix: bybit classic market buy orders require price
closes #10872
2024-11-19 18:18:47 +01:00
Matthias 9457f63e01 Merge pull request #10954 from freqtrade/dependabot/pip/aiohttp-3.10.11
chore(deps): bump aiohttp from 3.10.10 to 3.10.11
2024-11-19 06:28:51 +01:00
Matthias df004a2c7a Merge pull request #10955 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-11-19 06:26:43 +01:00
xmatthias a698ee8bc8 chore: update pre-commit hooks 2024-11-19 03:12:02 +00:00
dependabot[bot] 1228ffe109 chore(deps): bump aiohttp from 3.10.10 to 3.10.11
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.10 to 3.10.11.
- [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.10...v3.10.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 21:04:54 +00:00
Matthias 076a10887b Merge pull request #10947 from freqtrade/dependabot/pip/develop/pycoingecko-3.2.0
chore(deps): bump pycoingecko from 3.1.0 to 3.2.0
2024-11-18 07:48:22 +01:00
Matthias fc3db2d29c chore: force pycoingecko to be > 3.2.0
that's the first version supporting demo API keys
2024-11-18 07:13:21 +01:00
Matthias 971cdbb918 chore: Simplify coingecko wrapper after update 2024-11-18 07:13:21 +01:00
dependabot[bot] 73adbe9e0c chore(deps): bump pycoingecko from 3.1.0 to 3.2.0
Bumps [pycoingecko](https://github.com/man-c/pycoingecko) from 3.1.0 to 3.2.0.
- [Changelog](https://github.com/man-c/pycoingecko/blob/master/CHANGELOG.md)
- [Commits](https://github.com/man-c/pycoingecko/compare/3.1.0...3.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 07:13:02 +01:00
Matthias 8363609637 chore: fix typo in comment 2024-11-18 07:05:13 +01:00
Matthias 705c3a408a Merge pull request #10953 from freqtrade/dependabot/pip/develop/fastapi-0.115.5
chore(deps): bump fastapi from 0.115.4 to 0.115.5
2024-11-18 06:35:26 +01:00
Matthias 228fc5ce73 Merge pull request #10949 from freqtrade/dependabot/pip/develop/pyjwt-2.10.0
chore(deps): bump pyjwt from 2.9.0 to 2.10.0
2024-11-18 06:35:04 +01:00
Matthias 09885535ac Merge pull request #10950 from freqtrade/dependabot/pip/develop/ruff-0.7.4
chore(deps-dev): bump ruff from 0.7.3 to 0.7.4
2024-11-18 06:34:42 +01:00
Matthias de139af5a5 Merge pull request #10951 from freqtrade/dependabot/pip/develop/websockets-14.1
chore(deps): bump websockets from 14.0 to 14.1
2024-11-18 06:33:56 +01:00
dependabot[bot] d9c7790e82 chore(deps): bump fastapi from 0.115.4 to 0.115.5
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.115.4 to 0.115.5.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.115.4...0.115.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 03:58:03 +00:00
dependabot[bot] aa03b7d9a0 chore(deps): bump websockets from 14.0 to 14.1
Bumps [websockets](https://github.com/python-websockets/websockets) from 14.0 to 14.1.
- [Release notes](https://github.com/python-websockets/websockets/releases)
- [Commits](https://github.com/python-websockets/websockets/compare/14.0...14.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 03:57:52 +00:00
dependabot[bot] 8f9d0019e6 chore(deps-dev): bump ruff from 0.7.3 to 0.7.4
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.7.3 to 0.7.4.
- [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.7.3...0.7.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 03:57:42 +00:00
dependabot[bot] 9e3fae1991 chore(deps): bump pyjwt from 2.9.0 to 2.10.0
Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.9.0 to 2.10.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.9.0...2.10.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-11-18 03:57:19 +00:00
Matthias 5dcb239253 Merge pull request #10944 from smith558/patch-1
Fix typo
2024-11-17 09:04:38 +01:00
Stanislav (Stanley) Modrak 9fcdea14d7 Fix typo 2024-11-16 20:52:31 +00:00
Matthias 85dbe83154 Merge pull request #10943 from emmanuel-ferdman/develop
docs: update `VolumePairList` reference
2024-11-16 17:07:16 +01:00
Matthias ee48da2a0a docs: slightly reword data message 2024-11-16 15:51:33 +01:00
Emmanuel Ferdman f477389159 docs: update VolumePairList reference
Signed-off-by: Emmanuel Ferdman <emmanuelferdman@gmail.com>
2024-11-16 06:26:56 -08:00
Matthias 3bb7f78255 chore: fix import sorting 2024-11-16 13:25:54 +01:00
Matthias 25aecfa9b2 docs: slightly update download-data docs 2024-11-16 13:07:23 +01:00
Matthias f821e504c3 tests: update test for new behavior 2024-11-16 12:58:31 +01:00
Matthias d66381863e feat: don't force-redownload all data
If the given timerange starts prior to the available data,
the bot shouldn't force-download everything unless forced via `--erase`
or via `--prepend`.
2024-11-16 12:58:23 +01:00
Matthias 68712c884e chore: fix line length 2024-11-16 12:51:13 +01:00
Matthias db9b31cb85 chore: don't use unsupported syntax 2024-11-16 12:40:03 +01:00
Matthias 7090d4e5ba fix: override gate funding fee limit to 90
(seems to be the new limit)
2024-11-16 11:56:05 +01:00
Matthias b2423fa390 chore: remove explicit ohlcv_candle_limit in favor of config option 2024-11-16 11:54:54 +01:00
Matthias 83b87cb812 feat: add explicit funding_fee_candle_limit for simplified overriding 2024-11-16 11:54:28 +01:00
Matthias 44dcb4cc78 Merge pull request #10839 from gaardiolor/hyperliquid
Hyperliquid
2024-11-16 11:18:03 +01:00
Matthias 64803a735d tests: update commands tests to early fail approach 2024-11-16 11:07:19 +01:00
Matthias 20e77bb5d9 test: update tests to recognize early failing 2024-11-16 11:02:22 +01:00
Matthias f11e5128ad tests: update tests for new dl-trades behavior 2024-11-16 10:58:25 +01:00
Matthias bbe13cafcb tests: Remove pointless test 2024-11-16 10:46:48 +01:00
Matthias 1acca87fbf chore: early-exit when no pairs are available 2024-11-16 10:46:23 +01:00
Matthias 2012bb7c53 chore: improve logging sequence 2024-11-16 10:43:01 +01:00
Matthias 14d384a30a feat: don't auto-delete trades data due to invalid timerange
trades-data is already slow enough to download
2024-11-16 10:34:24 +01:00
Matthias 927f5e2c8b tests: enhanced test for get_trade_history on binance 2024-11-16 10:10:30 +01:00
Matthias d59bedf977 feat: download-trades-data on binance has limited history
with from=0, we can get the initial data available.

closes #10941
2024-11-16 10:02:22 +01:00
Matthias c61a71a36e fix: percent_change from candle must provide percentage, not ratio
closes #10937
2024-11-16 08:50:41 +01:00
Matthias 46f2e693bc chore: bump ccxt to 4.4.31 2024-11-15 21:57:58 +01:00
Matthias cbc4bb5420 chore: use __all__ for hyperopt main export 2024-11-15 07:24:46 +01:00
Matthias 49e85e0680 docs: update migration docs to new syntax
only update the "after" parts.
No changs to highlighting, as the actual syntax used is not relevant for the strategy.
2024-11-15 07:21:22 +01:00
Matthias e8d724680a chore: improved log msg wording 2024-11-15 07:19:05 +01:00
Matthias 6d9ab5e9ff docs: update more typing wordings 2024-11-15 07:18:41 +01:00
Matthias a6d513a0e6 docs: update callback docs to new syntax 2024-11-15 07:15:31 +01:00
Matthias d565c40f4b docs: Updated syntax 2024-11-15 07:11:54 +01:00
Matthias 52b4ae5eae Docs: Align to 3.10 syntax 2024-11-15 07:10:31 +01:00
Meng Xiangzhuo 0f53dc1b7b chore: improve log level to warning when falling back to API 2024-11-15 12:08:50 +08:00
Matthias 5c4b9e59d1 chore: add Hyperliquid as supported exchange to docs 2024-11-14 07:08:27 +01:00
Matthias a02f63dcc3 chore: add hyperliquid to supported exchange list 2024-11-14 07:06:43 +01:00
Matthias a3a94e6ea4 chore: reorder hyperliquid docs, add note about order types 2024-11-14 07:06:09 +01:00
Matthias c039a45def fix: hyperliquid market orders don't exist and always require price 2024-11-14 07:04:39 +01:00
Matthias 3276dd6b94 Merge pull request #10931 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-11-14 06:34:32 +01:00
xmatthias de306de78b chore: update pre-commit hooks 2024-11-14 03:06:23 +00:00
xzmeng 19f96d60e3 refactor: streamline error handling by raising instead of returning 2024-11-14 09:47:23 +08:00
xzmeng bfdbf0248c refactor: rename asset_type to asset_type_url_segment 2024-11-14 07:51:58 +08:00
xzmeng 660863392b refactor: rename fetch_ohlcv to download_archive_ohlcv 2024-11-14 07:29:37 +08:00
xzmeng 8baa0f7310 chore: add user friendly warnings 2024-11-14 06:06:15 +08:00
xzmeng 6b18c4f24c feat: cancel uncompleted tasks before return 2024-11-14 05:24:31 +08:00
xzmeng 40f70a1cc0 chore: warning when fall back to rest API 2024-11-14 04:36:24 +08:00
Meng Xiangzhuo c869dfea3f chore: add more docstrings 2024-11-13 17:32:25 +08:00
Meng Xiangzhuo 39b4263b8b chore: improve comments 2024-11-13 17:14:49 +08:00
Meng Xiangzhuo cf7016b36d chore: remove unused code 2024-11-13 16:09:02 +08:00
Meng Xiangzhuo 1893ac7ed6 tests: patch thirty party libs from imported location 2024-11-13 13:08:29 +08:00
Meng Xiangzhuo a748d105ed chore: remove performance compare script 2024-11-13 12:45:46 +08:00
Meng Xiangzhuo fc307bcf5b tests: fix 2024-11-13 12:44:27 +08:00
Meng Xiangzhuo 03033a0684 refactor: use exchange.markets to avoid loading ccxt markets 2024-11-13 12:36:18 +08:00
Meng Xiangzhuo 7ca39a5c38 Merge branch 'develop' into binance-public-data 2024-11-13 12:14:50 +08:00
Meng Xiangzhuo d7555e1f29 feat: support proxy from environment variables 2024-11-13 12:03:41 +08:00
Meng Xiangzhuo 37726fba58 refactor: use CCXT for pair to symbol conversion 2024-11-13 11:55:10 +08:00
Meng Xiangzhuo c4cf582c9d refacotr: default to stop on 404 2024-11-13 11:18:32 +08:00
Meng Xiangzhuo acc53065e5 refactor: strip fast download logic into a method 2024-11-13 10:16:06 +08:00
Matthias 9360c109a9 docs: fix spelling 2024-11-12 19:32:42 +01:00
Matthias 5d9af5a7da test: update tests with new formatting 2024-11-12 19:27:17 +01:00
Matthias 88e0fed4c4 fix: Consistently format pair rate between entry and exit messages
closes #10922
2024-11-12 19:27:17 +01:00
Matthias 98dcc08c6d feat: add fmt_coin2 2024-11-12 19:27:17 +01:00
gaardiolor 702b50e7e9 Update exchanges.md - less details on the private key conversion 2024-11-12 18:22:19 +01:00
gaardiolor 8f946ea1b2 Update exchanges.md - further clarify private key best practices 2024-11-12 18:13:31 +01:00
Matthias 4e5ae0af84 Merge pull request #10919 from freqtrade/refactor/hyperopt
Restore hyperopt logging, refactor hyperopt layout
2024-11-12 18:13:02 +01:00
Matthias ca00871362 chore: adopt hyperopt class to new ruff formatting 2024-11-12 18:05:51 +01:00
Matthias a1d8658297 Merge branch 'develop' into pr/gaardiolor/10839 2024-11-12 18:05:25 +01:00
gaardiolor 31814e8bf9 Update docs/stoploss.md - clarify that stoploss_on_exchange is only supported for hyperliquid futures
Co-authored-by: Matthias <xmatthias@outlook.com>
2024-11-12 17:55:12 +01:00
Matthias 0028df2564 feat(hyperliquid): fix a few settings to make spot work properly 2024-11-12 06:58:21 +01:00
Matthias 9b3764212f chore: fix bad import 2024-11-12 06:30:55 +01:00
Matthias 8128b85706 Merge pull request #10921 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-11-12 06:24:15 +01:00
xmatthias d7c88a38fc chore: update pre-commit hooks 2024-11-12 03:04:16 +00:00
Matthias 0b0b221c02 chore: update advanced template to new formatting 2024-11-11 20:25:15 +01:00
Matthias d9b8f46282 docs: update doc samples to use lowercased tuple and dict 2024-11-11 20:24:01 +01:00
Matthias fbb64db3ae define log_queue globally 2024-11-11 20:14:37 +01:00
Matthias 33d8e67a87 docs: add note about logging 2024-11-11 20:12:31 +01:00
Matthias f05f173d23 chore: improved docstring for logging_mp_handle 2024-11-11 19:53:59 +01:00
Matthias 81a622a9fa chore: remove unnecessary log messages 2024-11-11 19:53:26 +01:00
Matthias 67415dd7e2 chore: improved module docstring 2024-11-11 19:53:04 +01:00
Matthias e8cd6b3be3 Merge pull request #10910 from freqtrade/dependabot/pip/develop/websockets-14.0
chore(deps): bump websockets from 13.1 to 14.0
2024-11-11 19:48:05 +01:00
Matthias 7e96e7af83 feat: add hyperopt log handlers to allow for multiprocessing logging 2024-11-11 19:43:37 +01:00
Matthias 119b73ead2 chore: improtve method sorting 2024-11-11 19:43:37 +01:00
Matthias 365c454da1 chore: Improve import comments 2024-11-11 19:43:37 +01:00
Matthias b6d1f9f754 test: update hyperopt tests for new structure 2024-11-11 19:43:37 +01:00
Matthias 6719d9670d feat: split hyperopt class
this ensures it's clear which parts are passed to workers
2024-11-11 19:43:37 +01:00
Matthias 62234878a1 test: update mocks for new layout 2024-11-11 19:43:37 +01:00
Matthias 851a9a7620 refactor: move hyperopt-auto 2024-11-11 19:43:37 +01:00
Matthias 61d9002cb1 refactor: move hyperopt-output 2024-11-11 19:43:37 +01:00
Matthias 84fc5dfcf7 refactor: move hyperopt-loss interface 2024-11-11 19:43:37 +01:00
Matthias e1b3ae208d chore: move hyperopt to it's own module 2024-11-11 19:43:37 +01:00
Matthias 60ec9739c2 chore: update websocket implementation to new asyncio api 2024-11-11 19:21:45 +01:00
Matthias c9fb3dfb1d chore: Update websocket imports to use "legacy" client
temporary - must be updated to the new implementation
2024-11-11 07:09:13 +01:00
Matthias 8230d1b515 Merge pull request #10913 from freqtrade/dependabot/pip/develop/ccxt-4.4.29
chore(deps): bump ccxt from 4.4.26 to 4.4.29
2024-11-11 06:29:59 +01:00
dependabot[bot] 838b4f4e52 chore(deps): bump websockets from 13.1 to 14.0
Bumps [websockets](https://github.com/python-websockets/websockets) from 13.1 to 14.0.
- [Release notes](https://github.com/python-websockets/websockets/releases)
- [Commits](https://github.com/python-websockets/websockets/compare/13.1...14.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-11-11 05:29:17 +00:00
Matthias eae5b154c9 Merge pull request #10908 from freqtrade/dependabot/pip/develop/mkdocs-54fce57b69
chore(deps): bump mkdocs-material from 9.5.43 to 9.5.44 in the mkdocs group
2024-11-11 06:28:51 +01:00
Matthias ebc870d2d4 Merge pull request #10911 from freqtrade/dependabot/pip/develop/packaging-24.2
chore(deps): bump packaging from 24.1 to 24.2
2024-11-11 06:28:15 +01:00
Matthias 986b1aadbf Merge pull request #10912 from freqtrade/dependabot/pip/develop/tqdm-4.67.0
chore(deps): bump tqdm from 4.66.6 to 4.67.0
2024-11-11 06:27:16 +01:00
Matthias 9ced73bf19 Merge pull request #10914 from freqtrade/dependabot/pip/develop/ruff-0.7.3
chore(deps-dev): bump ruff from 0.7.2 to 0.7.3
2024-11-11 06:26:38 +01:00
Matthias f0428d5c99 Merge pull request #10915 from freqtrade/dependabot/pip/develop/python-telegram-bot-21.7
chore(deps): bump python-telegram-bot from 21.6 to 21.7
2024-11-11 06:26:11 +01:00
Matthias 486a0d2c6e Merge pull request #10916 from freqtrade/dependabot/github_actions/develop/pypa/gh-action-pypi-publish-1.12.2
chore(deps): bump pypa/gh-action-pypi-publish from 1.11.0 to 1.12.2
2024-11-11 06:25:47 +01:00
dependabot[bot] 13cf6a1007 chore(deps): bump pypa/gh-action-pypi-publish from 1.11.0 to 1.12.2
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.11.0 to 1.12.2.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.11.0...v1.12.2)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-11 03:54:30 +00:00
dependabot[bot] 4c59bcc1bb chore(deps): bump python-telegram-bot from 21.6 to 21.7
Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 21.6 to 21.7.
- [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases)
- [Changelog](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/CHANGES.rst)
- [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v21.6...v21.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-11 03:11:15 +00:00
dependabot[bot] 057bb31d94 chore(deps-dev): bump ruff from 0.7.2 to 0.7.3
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.7.2 to 0.7.3.
- [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.7.2...0.7.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-11 03:11:00 +00:00
dependabot[bot] 6258c0671b chore(deps): bump ccxt from 4.4.26 to 4.4.29
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.26 to 4.4.29.
- [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.4.26...4.4.29)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-11 03:10:48 +00:00
dependabot[bot] b3b45458f6 chore(deps): bump tqdm from 4.66.6 to 4.67.0
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.6 to 4.67.0.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.66.6...v4.67.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-11 03:10:40 +00:00
dependabot[bot] 54047a98b3 chore(deps): bump packaging from 24.1 to 24.2
Bumps [packaging](https://github.com/pypa/packaging) from 24.1 to 24.2.
- [Release notes](https://github.com/pypa/packaging/releases)
- [Changelog](https://github.com/pypa/packaging/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pypa/packaging/compare/24.1...24.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-11 03:10:35 +00:00
dependabot[bot] 6411dcbf72 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.43 to 9.5.44
- [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.43...9.5.44)

---
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-11-11 03:10:03 +00:00
Matthias 1818215fee feat: add MultiMetricLoss to "builtin hyperopt loss" list 2024-11-10 10:11:15 +01:00
Matthias 1ffceef9f0 tests: Improve test further 2024-11-10 09:27:10 +01:00
Matthias be1426a5f6 feat: create strategy-dir if it doesn't exist 2024-11-10 09:18:56 +01:00
Matthias 3ebb819ba3 tests: update new-strategy test to use tmpdir 2024-11-10 09:18:56 +01:00
Matthias 5f16e534ee feat: add support for --strategy-path to new-strategy 2024-11-10 09:18:56 +01:00
Matthias 8f8e6f16b4 chore: remove unused mock imports 2024-11-10 09:18:56 +01:00
Matthias b5592b88fa Merge pull request #10898 from xzmeng/ruff310
chore: bump ruff target-version to 3.10
2024-11-10 09:06:28 +01:00
Matthias ba06269ed7 chore: update commented type 2024-11-10 08:47:24 +01:00
Matthias e450baeac4 Merge pull request #10902 from 14790897/develop
feat: enhance data fetching logic with pagination to increase market …
2024-11-09 18:39:47 +01:00
Matthias 3a81b6a08f chore: improve warning wording 2024-11-09 18:18:54 +01:00
Matthias b7a2efcd6a docs: update docs with new marketcaplist behavior 2024-11-09 18:12:48 +01:00
Meng Xiangzhuo a605d50d69 chore: revert minimum python version check 2024-11-10 00:22:32 +08:00
liuweiqing 27d894b8e9 chore: remove warnings 2024-11-09 18:40:32 +08:00
liuweiqing 14f1ef8e30 Merge branch 'develop' of https://github.com/freqtrade/freqtrade into develop 2024-11-09 18:23:30 +08:00
liuweiqing 7b471d59c5 chore: add warning when max_rank exceeds 250 in MarketCapPairList 2024-11-09 18:23:27 +08:00
Matthias 5ca102ffee Merge pull request #10901 from freqtrade/remove_brew
Attempt Remove brew hdf5 installs
2024-11-09 09:02:15 +01:00
Matthias 0e04f79b6d docs: add macos libomp to docs
closes #10900
2024-11-09 08:30:19 +01:00
Matthias c141b873c3 docs: Update install docs 2024-11-09 08:24:37 +01:00
Matthias 3cc97690a4 tests: add hyperliquid lev_prep test 2024-11-09 08:11:23 +01:00
Matthias 5b7f08137f tests: improve hyperliquid tests 2024-11-09 08:09:57 +01:00
liuweiqing 00318be59f chore: Convert page number to string to ensure type compatibility 2024-11-09 11:25:31 +08:00
Matthias 37c00d4eeb chore: keep libomp brew install 2024-11-08 07:59:47 +01:00
liuweiqing 3300d25e57 feat: enhance data fetching logic with pagination to increase market cap rank limit 2024-11-08 14:50:21 +08:00
Matthias f443b1426f chore: try removing all brew install steps
(hdf5 provides proper wheels now)
2024-11-08 07:03:03 +01:00
Matthias 96354ee1d8 docs: fix hyperliquid docs formatting 2024-11-08 06:45:45 +01:00
Meng Xiangzhuo 41b725714f chore: bump ruff target-version to 3.10 2024-11-08 04:37:33 +08:00
Matthias 12560e983c docs: Add note about custom-stoploss exit reasons 2024-11-07 19:57:41 +01:00
Matthias 44ff6d50ec docs: further clarify telegram docs 2024-11-07 07:22:54 +01:00
Matthias 29c1cca990 chore: don't modify original dict when initializing ccxt 2024-11-07 07:03:01 +01:00
Matthias 979f7ba78c docs: improved wording on telegram docs 2024-11-07 06:38:22 +01:00
Matthias b6dfe01628 chore: improve loudness call structure 2024-11-07 06:32:23 +01:00
Matthias d4cd5ba231 chore: Add loudness docstring 2024-11-07 06:29:23 +01:00
Matthias e7cf1e34cf docs: update docs to better explain options for telegram settings
closes #10895
2024-11-07 06:29:23 +01:00
Matthias c9718de24c feat: Update config_schema with support for exit_fill 2024-11-07 06:29:23 +01:00
Matthias c58b1f0fd2 feat: add "*" to define custom default for non-defined exit reasons 2024-11-07 06:29:23 +01:00
Matthias b720ae5d75 feat: Support individual exits for both exit-fill and exit messages 2024-11-07 06:29:23 +01:00
Matthias 3cea1a99b7 tests: add explicit notification enablement test 2024-11-07 06:29:23 +01:00
Matthias 03eb079a5e chore: slightly refactor telegram loudness control 2024-11-07 06:29:23 +01:00
Matthias 899db2de78 docs: improve formatting of telegram docs page 2024-11-07 06:29:23 +01:00
Matthias 22338d7058 Merge pull request #10896 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-11-07 06:29:11 +01:00
xmatthias db6d7714a4 chore: update pre-commit hooks 2024-11-07 03:07:11 +00:00
gaardiolor 2de4f425ff Update stoploss.md - add hyperliquid 2024-11-06 21:41:59 +01:00
gaardiolor a71ab6e2ae Update exchanges.md - add hyperliquid stoploss_on_exchange note 2024-11-06 21:39:56 +01:00
gaardiolor 0b6bf701ef Update exchanges.md - typo 2024-11-06 10:29:23 +01:00
gaardiolor 38eb4eed97 Reverted changes related to create_order_has_all_data 2024-11-05 22:37:12 +01:00
gaardiolor bdb54f56d9 Update exchanges.md - add Hyperliquid 2024-11-05 21:21:11 +01:00
Matthias 3eb2981fb4 chore: add explaining comment for "not fill" branch 2024-11-05 20:45:58 +01:00
Matthias 835269825d fix: improve stake-amount in order "created" message 2024-11-05 20:35:59 +01:00
Matthias 12ab1cb2c4 tests: add explicit test for stake_amount_filled 2024-11-05 07:19:49 +01:00
Matthias 07f0e3803a docs: add stake_amount_filled documentation 2024-11-05 07:19:39 +01:00
Matthias 3d307fe8ff feat: add stake_amount_filled order attribute 2024-11-05 07:19:09 +01:00
Matthias 5cb4a65cbd fix: use order.safe_amount for "order created" entry message 2024-11-05 07:06:07 +01:00
Matthias 84ce065f76 Merge pull request #10890 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-11-05 06:24:12 +01:00
xmatthias 6398d7ff23 chore: update pre-commit hooks 2024-11-05 03:05:06 +00:00
Matthias 7e3f8ad012 tests: update test to not test for non-existing property 2024-11-04 20:14:30 +01:00
Matthias 8703c87880 chore: improve a few more types 2024-11-04 19:40:11 +01:00
Matthias 7e010c88b9 chore: Improve type safety 2024-11-04 19:32:46 +01:00
Matthias dad921bf5b chore: use CcxtOrder in more places 2024-11-04 19:30:51 +01:00
Matthias e020bafc38 chore: simplify calculation by reducing dict lookups 2024-11-04 18:22:20 +01:00
Matthias ecf5e1ce6b chore: first step to improved ccxtOrder Typing 2024-11-04 18:16:00 +01:00
Matthias 31bcee1a13 Merge pull request #10887 from freqtrade/dependabot/pip/develop/pyarrow-18.0.0
chore(deps): bump pyarrow from 17.0.0 to 18.0.0
2024-11-04 09:55:54 +01:00
Matthias 6245127ce1 chore: update pyarrow armhf prebuilt wheel 2024-11-04 09:27:51 +01:00
Matthias d1a4b9ad85 Merge pull request #10885 from freqtrade/dependabot/pip/develop/pymdown-extensions-10.12
chore(deps): bump pymdown-extensions from 10.11.2 to 10.12
2024-11-04 07:48:05 +01:00
Matthias 167e615f67 Merge pull request #10886 from freqtrade/dependabot/pip/develop/ccxt-4.4.26
chore(deps): bump ccxt from 4.4.24 to 4.4.26
2024-11-04 07:37:56 +01:00
Matthias 9c1b0471ca Merge pull request #10884 from freqtrade/dependabot/pip/develop/torch-2.5.1
chore(deps): bump torch from 2.5.0 to 2.5.1
2024-11-04 07:25:27 +01:00
Matthias 96a8a94309 Merge pull request #10883 from freqtrade/dependabot/pip/develop/orjson-3.10.11
chore(deps): bump orjson from 3.10.10 to 3.10.11
2024-11-04 07:15:13 +01:00
Matthias d2960aae3c Merge pull request #10882 from freqtrade/dependabot/pip/develop/ruff-0.7.2
chore(deps-dev): bump ruff from 0.7.1 to 0.7.2
2024-11-04 07:11:01 +01:00
Matthias 3f1a23615a Merge pull request #10881 from freqtrade/dependabot/pip/develop/janus-1.1.0
chore(deps): bump janus from 1.0.0 to 1.1.0
2024-11-04 07:10:33 +01:00
dependabot[bot] 78de0f3167 chore(deps): bump pyarrow from 17.0.0 to 18.0.0
Bumps [pyarrow](https://github.com/apache/arrow) from 17.0.0 to 18.0.0.
- [Release notes](https://github.com/apache/arrow/releases)
- [Commits](https://github.com/apache/arrow/compare/go/v17.0.0...apache-arrow-18.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 05:40:36 +00:00
Matthias 718e900a39 Merge pull request #10879 from freqtrade/dependabot/pip/develop/rich-13.9.4
chore(deps): bump rich from 13.9.3 to 13.9.4
2024-11-04 06:39:56 +01:00
Matthias eff2a857c0 Merge pull request #10878 from freqtrade/dependabot/pip/develop/tqdm-4.66.6
chore(deps): bump tqdm from 4.66.5 to 4.66.6
2024-11-04 06:39:42 +01:00
Matthias cf2b5cc24d Merge pull request #10876 from freqtrade/dependabot/pip/develop/pytest-01f0e07c3f
chore(deps-dev): bump pytest-cov from 5.0.0 to 6.0.0 in the pytest group
2024-11-04 06:39:14 +01:00
dependabot[bot] 7859fadf6b chore(deps): bump pymdown-extensions from 10.11.2 to 10.12
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.11.2 to 10.12.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.11.2...10.12)

---
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-11-04 05:27:37 +00:00
Matthias 34588b7dc0 Merge pull request #10875 from freqtrade/dependabot/github_actions/develop/pypa/gh-action-pypi-publish-1.11.0
chore(deps): bump pypa/gh-action-pypi-publish from 1.10.3 to 1.11.0
2024-11-04 06:26:39 +01:00
Matthias f6999898d5 Merge pull request #10877 from freqtrade/dependabot/pip/develop/mkdocs-0610082a93
chore(deps): bump mkdocs-material from 9.5.42 to 9.5.43 in the mkdocs group
2024-11-04 06:26:25 +01:00
dependabot[bot] 821243ce7c chore(deps): bump ccxt from 4.4.24 to 4.4.26
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.24 to 4.4.26.
- [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.4.24...4.4.26)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 03:35:00 +00:00
dependabot[bot] 44d0abb111 chore(deps): bump torch from 2.2.2 to 2.5.1
Bumps [torch](https://github.com/pytorch/pytorch) from 2.2.2 to 2.5.1.
- [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.5.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 03:34:44 +00:00
dependabot[bot] 10fd2b291e chore(deps): bump orjson from 3.10.10 to 3.10.11
Bumps [orjson](https://github.com/ijl/orjson) from 3.10.10 to 3.10.11.
- [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.10...3.10.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 03:34:35 +00:00
dependabot[bot] 5034c7c312 chore(deps-dev): bump ruff from 0.7.1 to 0.7.2
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.7.1 to 0.7.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.7.1...0.7.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-11-04 03:34:25 +00:00
dependabot[bot] ff36ba7742 chore(deps): bump janus from 1.0.0 to 1.1.0
Bumps [janus](https://github.com/aio-libs/janus) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/aio-libs/janus/releases)
- [Changelog](https://github.com/aio-libs/janus/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/janus/compare/v1.0.0...v1.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 03:34:10 +00:00
dependabot[bot] 9b2fabfb3e chore(deps): bump rich from 13.9.3 to 13.9.4
Bumps [rich](https://github.com/Textualize/rich) from 13.9.3 to 13.9.4.
- [Release notes](https://github.com/Textualize/rich/releases)
- [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Textualize/rich/compare/v13.9.3...v13.9.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 03:33:48 +00:00
dependabot[bot] 4008eb20a3 chore(deps): bump tqdm from 4.66.5 to 4.66.6
Bumps [tqdm](https://github.com/tqdm/tqdm) from 4.66.5 to 4.66.6.
- [Release notes](https://github.com/tqdm/tqdm/releases)
- [Commits](https://github.com/tqdm/tqdm/compare/v4.66.5...v4.66.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 03:33:44 +00:00
dependabot[bot] e224ee8277 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.42 to 9.5.43
- [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.42...9.5.43)

---
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-11-04 03:33:34 +00:00
dependabot[bot] d236f97605 chore(deps-dev): bump pytest-cov from 5.0.0 to 6.0.0 in the pytest group
Bumps the pytest group with 1 update: [pytest-cov](https://github.com/pytest-dev/pytest-cov).


Updates `pytest-cov` from 5.0.0 to 6.0.0
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v5.0.0...v6.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 03:33:20 +00:00
dependabot[bot] 0efb966b68 chore(deps): bump pypa/gh-action-pypi-publish from 1.10.3 to 1.11.0
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.10.3 to 1.11.0.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.10.3...v1.11.0)

---
updated-dependencies:
- dependency-name: pypa/gh-action-pypi-publish
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 03:04:18 +00:00
Matthias 63dfa52ea8 chore: Fix missing space in log message 2024-11-03 13:43:30 +01:00
Matthias 74507e9981 docs: remove more --dmmp docs 2024-11-02 17:24:31 +01:00
Matthias d24bef7883 tests: remove --dmmp from tests 2024-11-02 16:54:54 +01:00
Matthias 59bf0c080a docs: update docs about removed --dmmp option 2024-11-02 16:54:45 +01:00
Matthias e8553cfac0 docs: Update documentation about dmmp 2024-11-02 16:50:20 +01:00
Matthias baadf62fc3 chore: remove --dmmp option 2024-11-02 16:49:26 +01:00
Meng Xiangzhuo cf0f232635 refactor: move download klines count message 2024-11-02 06:00:37 +08:00
Meng Xiangzhuo e2ee7f7b2f feat: fall back to rest API by catching all exceptions 2024-11-02 05:25:05 +08:00
Meng Xiangzhuo 76187d31cf feat: more binance fast download timeframes 2024-11-02 04:12:57 +08:00
Meng Xiangzhuo c3bbedbc56 refactor: create coroutines on demand
to avoid "coroutine was never awaited" warnings
2024-11-02 04:10:46 +08:00
gaardiolor c4312fbb31 _lev_prep: fixed typo. dry_run_liquidation_price: use wallet_balance instead of stake_amount 2024-11-01 19:14:37 +01:00
Matthias 1bf71b47df docs: add gate documentation about new permissions 2024-11-01 17:49:00 +01:00
Matthias 67f26fa1ac Merge pull request #10782 from freqtrade/feat/bt_reverse
Enable future positions to reverse their position
2024-11-01 09:29:35 +01:00
Matthias dafabd5b99 Merge pull request #10860 from xzmeng/fix-test-download-data-timerange
tests: fix failure when local timezone is not utc
2024-11-01 09:19:39 +01:00
Matthias 878adfb0c0 tests: expand tests to cover several times and timezones 2024-11-01 08:57:03 +01:00
Matthias 3da9f6d0e3 Merge pull request #10857 from freqtrade/ci_mypy_cache
Improved caching
2024-11-01 08:44:13 +01:00
Matthias f5fdd8a6ab Merge pull request #10866 from xzmeng/fix-strategy101-link
docs: fix strategy 101 link
2024-10-31 15:47:02 +01:00
Meng Xiangzhuo 47e4599fe2 docs: fix strategy 101 link 2024-10-31 22:20:45 +08:00
Matthias 81b432dc0e chore: bump dev-version to 2024.11-dev 2024-10-31 08:08:02 +01:00
Meng Xiangzhuo 3c76af9dab tests: fix windows 2024-10-31 04:18:37 +08:00
Meng Xiangzhuo ad12a9eb99 tests: setup windows asyncio loop 2024-10-31 03:29:21 +08:00
Meng Xiangzhuo 45bf046645 tests: add performance comparison 2024-10-31 01:51:54 +08:00
Meng Xiangzhuo a8516ff0f5 tests: fix failure when local timezone is not utc 2024-10-31 01:37:24 +08:00
Meng Xiangzhuo 2ceda2987c feat: add binance 1s ohlcv fast download 2024-10-30 23:51:25 +08:00
Matthias e99adc339e chore: re-use 3.12 cache for online tests 2024-10-30 15:55:50 +01:00
Meng Xiangzhuo 1aa863a92f tests: fix 2024-10-30 22:11:37 +08:00
Meng Xiangzhuo a417698fcd tests: fix test and improve coverage 2024-10-30 11:30:12 +08:00
Meng Xiangzhuo e49b5b03db feat: stop on 404 to prevent missing data 2024-10-30 07:59:06 +08:00
Meng Xiangzhuo b5f503dbbb tests: binance compare data.binance.vision to rest API 2024-10-30 07:30:03 +08:00
Meng Xiangzhuo 04d3633545 feat: implement fetch data from data.binance.vision 2024-10-30 07:29:52 +08:00
Meng Xiangzhuo 4e585c5c34 feat: implement Binance.get_historic_ohlcv detail 2024-10-30 07:29:38 +08:00
Meng Xiangzhuo 867aae868d refactor: move is_new_pair logic to Binance.get_historic_ohlcv 2024-10-30 07:29:19 +08:00
Matthias 04e8c3ceea chore: try improve caching 2024-10-29 19:31:08 +01:00
Matthias 22318eb80e chore: fix bad usage of variables 2024-10-29 18:14:53 +01:00
Matthias c03f8afca1 docs: document new behavior 2024-10-29 07:15:47 +01:00
Matthias 88f8e0b8b1 test: improve test for long/short switch 2024-10-29 07:15:47 +01:00
Matthias 8f6b831512 tests: adjust test_locked_pairs for new auto-lock 2024-10-29 07:15:47 +01:00
Matthias 85a2c12768 feat: Lock pairs in direction of the trade only 2024-10-29 07:15:47 +01:00
Matthias 1b8fd63853 tests: BT Detail tests should cover position switch 2024-10-29 07:15:47 +01:00
Matthias ae325ff124 tests: fix test based on changed behavior 2024-10-29 07:15:47 +01:00
Matthias 1698d96883 feat: Allow Backtesting to reverse position 2024-10-29 07:15:47 +01:00
gaardiolor cfa527e9af added tests for hyperliquid 2024-10-28 20:34:24 +01:00
gaardiolor 67a0040732 added hyperliquid to conftest 2024-10-28 19:32:43 +01:00
gaardiolor 3913b450b6 check for tickers_have_bid_ask 2024-10-28 19:30:59 +01:00
gaardiolor fd619de1d3 typo 2024-10-27 22:00:52 +01:00
gaardiolor 493fa8541b fixes related to ruff and mypy 2024-10-27 17:06:56 +01:00
gaardiolor a7681fc712 revert ccxt version bump 2024-10-27 12:27:54 +01:00
gaardiolor 7d1976ad05 fix ohlcv_candle_limit funding_rate candles
Co-authored-by: Matthias <xmatthias@outlook.com>
2024-10-27 12:09:20 +01:00
gaardiolor 4c78b8c6d1 updated dry_run_liquidation_price to match new signature
fixed comment
2024-10-26 14:13:57 +02:00
gaardiolor b88db55db3 Initial implementation of hyperliquid.
- Spot
- Swap (long, short, leverage, stoploss_on_exchange)
- dry_run_liquidation_price()
2024-10-25 17:47:53 +02:00
246 changed files with 21484 additions and 6802 deletions
+48 -18
View File
@@ -46,7 +46,7 @@ jobs:
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip
key: pip-${{ matrix.python-version }}-ubuntu
- name: TA binary *nix
if: steps.cache.outputs.cache-hit != 'true'
@@ -167,7 +167,7 @@ jobs:
uses: actions/cache@v4
with:
path: ~/Library/Caches/pip
key: ${{ matrix.os }}-${{ matrix.python-version }}-pip
key: pip-${{ matrix.os }}-${{ matrix.python-version }}
- name: TA binary *nix
if: steps.cache.outputs.cache-hit != 'true'
@@ -196,7 +196,7 @@ jobs:
rm /usr/local/bin/python3.11-config || true
rm /usr/local/bin/python3.12-config || true
brew install hdf5 c-blosc libomp
brew install libomp
- name: Installation (python)
run: |
@@ -276,14 +276,23 @@ jobs:
with:
python-version: ${{ matrix.python-version }}
- name: Pip cache (Windows)
uses: actions/cache@v4
- name: Install uv
uses: astral-sh/setup-uv@v5
with:
path: ~\AppData\Local\pip\Cache
key: ${{ matrix.os }}-${{ matrix.python-version }}-pip
enable-cache: true
cache-dependency-glob: "requirements**.txt"
cache-suffix: "${{ matrix.python-version }}"
prune-cache: false
- name: Installation
run: |
uv venv
.venv\Scripts\activate
# persist the venv path for future steps
"$(pwd)/.venv/Scripts" >> $env:GITHUB_PATH
function uvpipFunction { uv pip $args }
Set-Alias -name pip -value uvpipFunction
./build_helpers/install_windows.ps1
- name: Tests
@@ -420,7 +429,7 @@ jobs:
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip
key: pip-3.12-ubuntu
- name: TA binary *nix
if: steps.cache.outputs.cache-hit != 'true'
@@ -517,13 +526,40 @@ jobs:
ft_client/dist
retention-days: 10
deploy-pypi:
name: "Deploy to PyPI"
deploy-test-pypi:
name: "Publish Python 🐍 distribution 📦 to TestPyPI"
needs: [ build ]
runs-on: ubuntu-22.04
if: (github.event_name == 'release')
environment:
name: release
name: testpypi
url: https://test.pypi.org/p/freqtrade
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- name: Download artifact 📦
uses: actions/download-artifact@v4
with:
pattern: freqtrade*-build
path: dist
merge-multiple: true
- name: Publish to PyPI (Test)
uses: pypa/gh-action-pypi-publish@v1.12.3
with:
repository-url: https://test.pypi.org/legacy/
deploy-pypi:
name: "Publish Python 🐍 distribution 📦 to PyPI"
needs: [ build ]
runs-on: ubuntu-22.04
if: (github.event_name == 'release')
environment:
name: pypi
url: https://pypi.org/p/freqtrade
permissions:
id-token: write
@@ -538,14 +574,8 @@ jobs:
path: dist
merge-multiple: true
- name: Publish to PyPI (Test)
uses: pypa/gh-action-pypi-publish@v1.10.3
with:
repository-url: https://test.pypi.org/legacy/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@v1.10.3
uses: pypa/gh-action-pypi-publish@v1.12.3
deploy-docker:
+9 -4
View File
@@ -9,7 +9,7 @@ repos:
# stages: [push]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.13.0"
rev: "v1.14.0"
hooks:
- id: mypy
exclude: build_helpers
@@ -17,8 +17,8 @@ repos:
- types-cachetools==5.5.0.20240820
- types-filelock==3.2.7
- types-requests==2.32.0.20241016
- types-tabulate==0.9.0.20240106
- types-python-dateutil==2.9.0.20241003
- types-tabulate==0.9.0.20241207
- types-python-dateutil==2.9.0.20241206
- SQLAlchemy==2.0.36
# stages: [push]
@@ -31,7 +31,7 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.7.1'
rev: 'v0.8.4'
hooks:
- id: ruff
- id: ruff-format
@@ -56,6 +56,11 @@ repos:
.*\.md
)$
- repo: https://github.com/stefmolin/exif-stripper
rev: 0.6.1
hooks:
- id: strip-exif
- repo: https://github.com/codespell-project/codespell
rev: v2.3.0
hooks:
+5 -2
View File
@@ -32,15 +32,18 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even
- [X] [BingX](https://bingx.com/invite/0EM9RX)
- [X] [Bybit](https://bybit.com/)
- [X] [Gate.io](https://www.gate.io/ref/6266643)
- [X] [HTX](https://www.htx.com/) (Former Huobi)
- [X] [HTX](https://www.htx.com/)
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
- [X] [Kraken](https://kraken.com/)
- [X] [OKX](https://okx.com/) (Former OKEX)
- [X] [OKX](https://okx.com/)
- [X] [MyOKX](https://okx.com/) (OKX EEA)
- [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
### Supported Futures Exchanges (experimental)
- [X] [Binance](https://www.binance.com/)
- [X] [Gate.io](https://www.gate.io/ref/6266643)
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
- [X] [OKX](https://okx.com/)
- [X] [Bybit](https://bybit.com/)
+4 -5
View File
@@ -1,11 +1,10 @@
# vendored Wheels compiled via https://github.com/xmatthias/ta-lib-python/tree/ta_bundled_040
python -m pip install --upgrade pip wheel
python -m pip install --upgrade pip
python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
$pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
pip install --find-links=build_helpers\ --prefer-binary TA-Lib
pip install -U wheel "numpy<2"
pip install --only-binary ta-lib --find-links=build_helpers\ ta-lib
pip install -r requirements-dev.txt
pip install -e .
+27 -8
View File
@@ -102,8 +102,17 @@
},
"dry_run_wallet": {
"description": "Initial wallet balance for dry run mode.",
"type": "number",
"default": 1000
"type": [
"number",
"object"
],
"default": 1000,
"patternProperties": {
"^[a-zA-Z0-9]+$": {
"type": "number"
}
},
"additionalProperties": false
},
"cancel_open_orders_on_exit": {
"description": "Cancel open orders when exiting.",
@@ -592,7 +601,11 @@
"type": "string"
},
"chat_id": {
"description": "Telegram chat ID",
"description": "Telegram chat or group ID",
"type": "string"
},
"topic_id": {
"description": "Telegram topic ID - only applicable for group chats",
"type": "string"
},
"allow_custom_messages": {
@@ -682,12 +695,18 @@
},
"exit_fill": {
"description": "Telegram setting for exit fill signals.",
"type": "string",
"enum": [
"on",
"off",
"silent"
"type": [
"string",
"object"
],
"additionalProperties": {
"type": "string",
"enum": [
"on",
"off",
"silent"
]
},
"default": "on"
},
"exit_cancel": {
+5 -3
View File
@@ -37,8 +37,9 @@ class SuperDuperHyperOptLoss(IHyperOptLoss):
min_date: datetime,
max_date: datetime,
config: Config,
processed: Dict[str, DataFrame],
backtest_stats: Dict[str, Any],
processed: dict[str, DataFrame],
backtest_stats: dict[str, Any],
starting_balance: float,
**kwargs,
) -> float:
"""
@@ -70,6 +71,7 @@ Currently, the arguments are:
* `config`: Config object used (Note: Not all strategy-related parameters will be updated here if they are part of a hyperopt space).
* `processed`: Dict of Dataframes with the pair as keys containing the data used for backtesting.
* `backtest_stats`: Backtesting statistics using the same format as the backtesting file "strategy" substructure. Available fields can be seen in `generate_strategy_stats()` in `optimize_reports.py`.
* `starting_balance`: Starting balance used for backtesting.
This function needs to return a floating point number (`float`). Smaller numbers will be interpreted as better results. The parameters and balancing for this is up to you.
@@ -103,7 +105,7 @@ class MyAwesomeStrategy(IStrategy):
SKDecimal(0.01, 0.20, decimals=3, name='roi_p3'),
]
def generate_roi_table(params: Dict) -> Dict[int, float]:
def generate_roi_table(params: Dict) -> dict[int, float]:
roi_table = {}
roi_table[0] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
+1
View File
@@ -4,6 +4,7 @@ This guide walks you through utilizing public trade data for advanced orderflow
!!! Warning "Experimental Feature"
The orderflow feature is currently in beta and may be subject to changes in future releases. Please report any issues or feedback on the [Freqtrade GitHub repository](https://github.com/freqtrade/freqtrade/issues).
It's also currently not been tested with freqAI - and combining these two features is considered out of scope at this point.
!!! Warning "Performance"
Orderflow requires raw trades data. This data is rather large, and can cause a slow initial startup, when freqtrade needs to download the trades data for the last X candles. Additionally, enabling this feature will cause increased memory usage. Please ensure to have sufficient resources available.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

After

Width:  |  Height:  |  Size: 12 KiB

+21 -12
View File
@@ -10,12 +10,14 @@ To learn how to get data for the pairs and exchange you're interested in, head o
```
usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [-s NAME]
[--strategy-path PATH] [-i TIMEFRAME]
[--timerange TIMERANGE]
[--data-format-ohlcv {json,jsongz,hdf5}]
[--strategy-path PATH]
[--recursive-strategy-search]
[--freqaimodel NAME] [--freqaimodel-path PATH]
[-i TIMEFRAME] [--timerange TIMERANGE]
[--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}]
[--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[-p PAIRS [PAIRS ...]] [--eps] [--dmmp]
[-p PAIRS [PAIRS ...]] [--eps]
[--enable-protections]
[--dry-run-wallet DRY_RUN_WALLET]
[--timeframe-detail TIMEFRAME_DETAIL]
@@ -24,8 +26,9 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[--export-filename PATH]
[--breakdown {day,week,month} [{day,week,month} ...]]
[--cache {none,day,week,month}]
[--freqai-backtest-live-models]
optional arguments:
options:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
@@ -48,10 +51,6 @@ optional arguments:
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking).
--dmmp, --disable-max-market-positions
Disable applying `max_open_trades` during backtest
(same as setting `max_open_trades` to a very high
number).
--enable-protections, --enableprotections
Enable protections for backtesting.Will slow
backtesting down by a considerable amount, but will
@@ -80,10 +79,13 @@ optional arguments:
--cache {none,day,week,month}
Load a cached backtest result no older than specified
age (default: day).
--freqai-backtest-live-models
Run backtest with ready models.
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
--logfile FILE, --log-file FILE
Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
@@ -92,7 +94,7 @@ Common arguments:
`userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
-d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
@@ -102,6 +104,12 @@ Strategy arguments:
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
--recursive-strategy-search
Recursively search for a strategy in the strategies
folder.
--freqaimodel NAME Specify a custom freqaimodels.
--freqaimodel-path PATH
Specify additional lookup path for freqaimodels.
```
@@ -558,6 +566,7 @@ Since backtesting lacks some detailed information about what happens within a ca
- Stoploss
- ROI
- Trailing stoploss
- Position reversals (futures only) happen if an entry signal in the other direction than the closing trade triggers at the candle the existing trade closes.
Taking these assumptions, backtesting tries to mirror real trading as closely as possible. However, backtesting will **never** replace running a strategy in dry-run mode.
Also, keep in mind that past results don't guarantee future success.
@@ -572,7 +581,7 @@ These limits are usually listed in the exchange documentation as "trading rules"
Backtesting (as well as live and dry-run) does honor these limits, and will ensure that a stoploss can be placed below this value - so the value will be slightly higher than what the exchange specifies.
Freqtrade has however no information about historic limits.
This can lead to situations where trading-limits are inflated by using a historic price, resulting in minimum amounts > 50$.
This can lead to situations where trading-limits are inflated by using a historic price, resulting in minimum amounts > 50\$.
For example:
+44 -18
View File
@@ -39,13 +39,19 @@ Please note that Environment variables will overwrite corresponding settings in
Common example:
```
``` bash
FREQTRADE__TELEGRAM__CHAT_ID=<telegramchatid>
FREQTRADE__TELEGRAM__TOKEN=<telegramToken>
FREQTRADE__EXCHANGE__KEY=<yourExchangeKey>
FREQTRADE__EXCHANGE__SECRET=<yourExchangeSecret>
```
Json lists are parsed as json - so you can use the following to set a list of pairs:
``` bash
export FREQTRADE__EXCHANGE__PAIR_WHITELIST='["BTC/USDT", "ETH/USDT"]'
```
!!! Note
Environment variables detected are logged at startup - so if you can't find why a value is not what you think it should be based on the configuration, make sure it's not loaded from an environment variable.
@@ -54,7 +60,7 @@ FREQTRADE__EXCHANGE__SECRET=<yourExchangeSecret>
??? Warning "Loading sequence"
Environment variables are loaded after the initial configuration. As such, you cannot provide the path to the configuration through environment variables. Please use `--config path/to/config.json` for that.
This also applies to user_dir to some degree. while the user directory can be set through environment variables - the configuration will **not** be loaded from that location.
This also applies to `user_dir` to some degree. while the user directory can be set through environment variables - the configuration will **not** be loaded from that location.
### Multiple configuration files
@@ -146,10 +152,10 @@ Freqtrade can also load many options via command line (CLI) arguments (check out
The prevalence for all Options is as follows:
- CLI arguments override any other option
- [Environment Variables](#environment-variables)
- Configuration files are used in sequence (the last file wins) and override Strategy configurations.
- Strategy configurations are only used if they are not set via configuration or command-line arguments. These options are marked with [Strategy Override](#parameters-in-the-strategy) in the below table.
* CLI arguments override any other option
* [Environment Variables](#environment-variables)
* Configuration files are used in sequence (the last file wins) and override Strategy configurations.
* Strategy configurations are only used if they are not set via configuration or command-line arguments. These options are marked with [Strategy Override](#parameters-in-the-strategy) in the below table.
### Parameters table
@@ -168,7 +174,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `timeframe` | The timeframe to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). Usually missing in configuration, and specified in the strategy. [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** String
| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). <br> **Datatype:** String
| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in Dry Run mode.<br>*Defaults to `1000`.* <br> **Datatype:** Float
| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in Dry Run mode. [More information below](#dry-run-wallet)<br>*Defaults to `1000`.* <br> **Datatype:** Float or Dict
| `cancel_open_orders_on_exit` | Cancel open orders when the `/stop` RPC command is issued, `Ctrl+C` is pressed or the bot dies unexpectedly. When set to `true`, this allows you to use `/stop` to cancel unfilled and partially filled orders in the event of a market crash. It does not impact open positions. <br>*Defaults to `false`.* <br> **Datatype:** Boolean
| `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `true`.* <br> **Datatype:** Boolean
| `minimal_roi` | **Required.** Set the threshold as ratio the bot will use to exit a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict
@@ -183,7 +189,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `margin_mode` | When trading with leverage, this determines if the collateral owned by the trader will be shared or isolated to each trading pair [leverage documentation](leverage.md). <br> **Datatype:** String
| `liquidation_buffer` | A ratio specifying how large of a safety net to place between the liquidation price and the stoploss to prevent a position from reaching the liquidation price [leverage documentation](leverage.md). <br>*Defaults to `0.05`.* <br> **Datatype:** Float
| | **Unfilled timeout**
| `unfilledtimeout.entry` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled entry order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
| `unfilledtimeout.entry` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled entry order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
| `unfilledtimeout.exit` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled exit order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
| `unfilledtimeout.unit` | Unit to use in unfilledtimeout setting. Note: If you set unfilledtimeout.unit to "seconds", "internals.process_throttle_secs" must be inferior or equal to timeout [Strategy Override](#parameters-in-the-strategy). <br> *Defaults to `"minutes"`.* <br> **Datatype:** String
| `unfilledtimeout.exit_timeout_count` | How many times can exit orders time out. Once this number of timeouts is reached, an emergency exit is triggered. 0 to disable and allow unlimited order cancels. [Strategy Override](#parameters-in-the-strategy).<br>*Defaults to `0`.* <br> **Datatype:** Integer
@@ -225,6 +231,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `exchange.skip_open_order_update` | Skips open order updates on startup should the exchange cause problems. Only relevant in live conditions.<br>*Defaults to `false`*<br> **Datatype:** Boolean
| `exchange.unknown_fee_rate` | Fallback value to use when calculating trading fees. This can be useful for exchanges which have fees in non-tradable currencies. The value provided here will be multiplied with the "fee cost".<br>*Defaults to `None`<br> **Datatype:** float
| `exchange.log_responses` | Log relevant exchange responses. For debug mode only - use with care.<br>*Defaults to `false`*<br> **Datatype:** Boolean
| `exchange.only_from_ccxt` | Prevent data-download from data.binance.vision. Leaving this as false can greatly speed up downloads, but may be problematic if the site is not available.<br>*Defaults to `false`*<br> **Datatype:** Boolean
| `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
| | **Plugins**
| `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation of all possible configuration options.
@@ -295,10 +302,10 @@ Values set in the configuration file always overwrite values set in the strategy
* `order_time_in_force`
* `unfilledtimeout`
* `disable_dataframe_checks`
- `use_exit_signal`
* `use_exit_signal`
* `exit_profit_only`
- `exit_profit_offset`
- `ignore_roi_if_entry_signal`
* `exit_profit_offset`
* `ignore_roi_if_entry_signal`
* `ignore_buying_expired_candle_after`
* `position_adjustment_enable`
* `max_entry_position_adjustment`
@@ -311,18 +318,37 @@ There are several methods to configure how much of the stake currency the bot wi
The minimum stake amount will depend on exchange and pair and is usually listed in the exchange support pages.
Assuming the minimum tradable amount for XRP/USD is 20 XRP (given by the exchange), and the price is 0.6$, the minimum stake amount to buy this pair is `20 * 0.6 ~= 12`.
This exchange has also a limit on USD - where all orders must be > 10$ - which however does not apply in this case.
Assuming the minimum tradable amount for XRP/USD is 20 XRP (given by the exchange), and the price is 0.6\$, the minimum stake amount to buy this pair is `20 * 0.6 ~= 12`.
This exchange has also a limit on USD - where all orders must be > 10\$ - which however does not apply in this case.
To guarantee safe execution, freqtrade will not allow buying with a stake-amount of 10.1$, instead, it'll make sure that there's enough space to place a stoploss below the pair (+ an offset, defined by `amount_reserve_percent`, which defaults to 5%).
To guarantee safe execution, freqtrade will not allow buying with a stake-amount of 10.1\$, instead, it'll make sure that there's enough space to place a stoploss below the pair (+ an offset, defined by `amount_reserve_percent`, which defaults to 5%).
With a reserve of 5%, the minimum stake amount would be ~12.6$ (`12 * (1 + 0.05)`). If we take into account a stoploss of 10% on top of that - we'd end up with a value of ~14$ (`12.6 / (1 - 0.1)`).
With a reserve of 5%, the minimum stake amount would be ~12.6\$ (`12 * (1 + 0.05)`). If we take into account a stoploss of 10% on top of that - we'd end up with a value of ~14\$ (`12.6 / (1 - 0.1)`).
To limit this calculation in case of large stoploss values, the calculated minimum stake-limit will never be more than 50% above the real limit.
!!! Warning
Since the limits on exchanges are usually stable and are not updated often, some pairs can show pretty high minimum limits, simply because the price increased a lot since the last limit adjustment by the exchange. Freqtrade adjusts the stake-amount to this value, unless it's > 30% more than the calculated/desired stake-amount - in which case the trade is rejected.
#### Dry-run wallet
When running in dry-run mode, the bot will use a simulated wallet to execute trades. The starting balance of this wallet is defined by `dry_run_wallet` (defaults to 1000).
For more complex scenarios, you can also assign a dictionary to `dry_run_wallet` to define the starting balance for each currency.
```json
"dry_run_wallet": {
"BTC": 0.01,
"ETH": 2,
"USDT": 1000
}
```
Command line options (`--dry-run-wallet`) can be used to override the configuration value, but only for the float value, not for the dictionary. If you'd like to use the dictionary, please adjust the configuration file.
!!! Note
Balances not in stake-currency will not be used for trading, but are shown as part of the wallet balance.
On Cross-margin exchanges, the wallet balance may be used to calculate the available collateral for trading.
#### Tradable balance
By default, the bot assumes that the `complete amount - 1%` is at it's disposal, and when using [dynamic stake amount](#dynamic-stake-amount), it will split the complete balance into `max_open_trades` buckets per trade.
@@ -362,9 +388,9 @@ To overcome this, the option `amend_last_stake_amount` can be set to `True`, whi
In the example above this would mean:
- Trade1: 400 USDT
- Trade2: 400 USDT
- Trade3: 200 USDT
* Trade1: 400 USDT
* Trade2: 400 USDT
* Trade3: 200 USDT
!!! Note
This option only applies with [Static stake amount](#static-stake-amount) - since [Dynamic stake amount](#dynamic-stake-amount) divides the balances evenly.
+7 -7
View File
@@ -11,9 +11,8 @@ Without provided configuration, `--exchange` becomes mandatory.
You can use a relative timerange (`--days 20`) or an absolute starting point (`--timerange 20200101-`). For incremental downloads, the relative approach should be used.
!!! Tip "Tip: Updating existing data"
If you already have backtesting data available in your data-directory and would like to refresh this data up to today, freqtrade will automatically calculate the data missing for the existing pairs and the download will occur from the latest available point until "now", neither --days or --timerange parameters are required. Freqtrade will keep the available data and only download the missing data.
If you are updating existing data after inserting new pairs that you have no data for, use `--new-pairs-days xx` parameter. Specified number of days will be downloaded for new pairs while old pairs will be updated with missing data only.
If you use `--days xx` parameter alone - data for specified number of days will be downloaded for _all_ pairs. Be careful, if specified number of days is smaller than gap between now and last downloaded candle - freqtrade will delete all existing data to avoid gaps in candle data.
If you already have backtesting data available in your data-directory and would like to refresh this data up to today, freqtrade will automatically calculate the missing timerange for the existing pairs and the download will occur from the latest available point until "now", neither `--days` or `--timerange` parameters are required. Freqtrade will keep the available data and only download the missing data.
If you are updating existing data after inserting new pairs that you have no data for, use the `--new-pairs-days xx` parameter. Specified number of days will be downloaded for new pairs while old pairs will be updated with missing data only.
### Usage
@@ -90,7 +89,7 @@ Common arguments:
!!! Tip "Downloading all data for one quote currency"
Often, you'll want to download data for all pairs of a specific quote-currency. In such cases, you can use the following shorthand:
`freqtrade download-data --exchange binance --pairs .*/USDT <...>`. The provided "pairs" string will be expanded to contain all active pairs on the exchange.
`freqtrade download-data --exchange binance --pairs ".*/USDT" <...>`. The provided "pairs" string will be expanded to contain all active pairs on the exchange.
To also download data for inactive (delisted) pairs, add `--include-inactive-pairs` to the command.
!!! Note "Startup period"
@@ -117,16 +116,17 @@ freqtrade download-data --exchange binance --pairs ETH/USDT XRP/USDT BTC/USDT
or as regex (in this case, to download all active USDT pairs)
```bash
freqtrade download-data --exchange binance --pairs .*/USDT
freqtrade download-data --exchange binance --pairs ".*/USDT"
```
### Other Notes
* To use a different directory than the exchange specific default, use `--datadir user_data/data/some_directory`.
* To change the exchange used to download the historical data from, please use a different configuration file (you'll probably need to adjust rate limits etc.)
* To change the exchange used to download the historical data from, either use `--exchange <exchange>` - or specify a different configuration file.
* To use `pairs.json` from some other directory, use `--pairs-file some_other_dir/pairs.json`.
* To download historical candle (OHLCV) data for only 10 days, use `--days 10` (defaults to 30 days).
* To download historical candle (OHLCV) data from a fixed starting point, use `--timerange 20200101-` - which will download all data from January 1st, 2020.
* Given starting points are ignored if data is already available, downloading only missing data up to today.
* Use `--timeframes` to specify what timeframe download the historical candle (OHLCV) data for. Default is `--timeframes 1m 5m` which will download 1-minute and 5-minute data.
* To use exchange, timeframe and list of pairs as defined in your configuration file, use the `-c/--config` option. With this, the script uses the whitelist defined in the config as the list of currency pairs to download data for and does not require the pairs.json file. You can combine `-c/--config` with most other options.
@@ -162,7 +162,7 @@ Freqtrade currently supports the following data-formats:
* `feather` - a dataformat based on Apache Arrow
* `json` - plain "text" json files
* `jsongz` - a gzip-zipped version of json files
* `hdf5` - a high performance datastore
* `hdf5` - a high performance datastore (deprecated)
* `parquet` - columnar datastore (OHLCV only)
By default, both OHLCV data and trades data are stored in the `feather` format.
+7 -1
View File
@@ -81,4 +81,10 @@ version 2023.3 saw the removal of `populate_any_indicators` in favor of split me
## Removal of `protections` from configuration
Setting protections from the configuration via `"protections": [],` has been removed in 2024.10, after having raised deprecation warnings for over 3 years.
Setting protections from the configuration via `"protections": [],` has been removed in 2024.10, after having raised deprecation warnings for over 3 years.
## hdf5 data storage
Using hdf5 as data storage has been deprecated in 2024.12 and will be removed in 2025.1. We recommend switching to the feather data format.
Please use the [`convert-data` subcommand](data-download.md#sub-command-convert-data) to convert your existing data to one of the supported formats.
+2 -2
View File
@@ -162,7 +162,7 @@ Hopefully you also want to contribute this back upstream.
Whatever your motivations are - This should get you off the ground in trying to develop a new Pairlist Handler.
First of all, have a look at the [VolumePairList](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/pairlist/VolumePairList.py) Handler, and best copy this file with a name of your new Pairlist Handler.
First of all, have a look at the [VolumePairList](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/plugins/pairlist/VolumePairList.py) Handler, and best copy this file with a name of your new Pairlist Handler.
This is a simple Handler, which however serves as a good example on how to start developing.
@@ -226,7 +226,7 @@ In `VolumePairList`, this implements different methods of sorting, does early va
##### sample
``` python
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
def filter_pairlist(self, pairlist: list[str], tickers: dict) -> List[str]:
# Generate dynamic whitelist
pairs = self._calculate_pairlist(pairlist, tickers)
return pairs
+50 -3
View File
@@ -217,12 +217,12 @@ Kucoin supports [time_in_force](configuration.md#understand-order_time_in_force)
For Kucoin, it is suggested to add `"KCS/<STAKE>"` to your blacklist to avoid issues, unless you are willing to maintain enough extra `KCS` on the account or unless you're willing to disable using `KCS` for fees.
Kucoin accounts may use `KCS` for fees, and if a trade happens to be on `KCS`, further trades may consume this position and make the initial `KCS` trade unsellable as the expected amount is not there anymore.
## HTX (formerly Huobi)
## HTX
!!! Tip "Stoploss on Exchange"
HTX supports `stoploss_on_exchange` and uses `stop-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.
## OKX (former OKEX)
## OKX
OKX requires a passphrase for each api key, you will therefore need to add this key into the configuration so your exchange section looks as follows:
@@ -236,6 +236,9 @@ OKX requires a passphrase for each api key, you will therefore need to add this
}
```
If you've registered with OKX on the host my.okx.com (OKX EAA)- you will need to use `"myokx"` as the exchange name.
Using the wrong exchange will result in the error "OKX Error 50119: API key doesn't exist" - as the 2 are separate entities.
!!! Warning
OKX only provides 100 candles per api call. Therefore, the strategy will only have a pretty low amount of data available in backtesting mode.
@@ -252,6 +255,14 @@ OKX requires a passphrase for each api key, you will therefore need to add this
Gate.io allows the use of `POINT` to pay for fees. As this is not a tradable currency (no regular market available), automatic fee calculations will fail (and default to a fee of 0).
The configuration parameter `exchange.unknown_fee_rate` can be used to specify the exchange rate between Point and the stake currency. Obviously, changing the stake-currency will also require changes to this value.
Gate API keys require the following permissions on top of the market type you want to trade:
* "Spot Trade" _or_ "Perpetual Futures" (Read and Write) (either select both, or the one matching the market you want to trade)
* "Wallet" (read only)
* "Account" (read only)
Without these permissions, the bot will not start correctly and show errors like "permission missing".
## Bybit
Futures trading on bybit is currently supported for USDT markets, and will use isolated futures mode.
@@ -261,6 +272,7 @@ On startup, freqtrade will set the position mode to "One-way Mode" for the whole
As bybit doesn't provide funding rate history, the dry-run calculation is used for live trades as well.
API Keys for live futures trading must have the following permissions:
* Read-write
* Contract - Orders
* Contract - Positions
@@ -295,6 +307,41 @@ It's therefore required to pass the UID as well.
!!! Warning "Necessary Verification"
Bitmart requires Verification Lvl2 to successfully trade on the spot market through the API - even though trading via UI works just fine with just Lvl1 verification.
## Hyperliquid
!!! Tip "Stoploss on Exchange"
Hyperliquid supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it.
Hyperliquid is a Decentralized Exchange (DEX). Decentralized exchanges work a bit different compared to normal exchanges. Instead of authenticating private API calls using an API key, private API calls need to be signed with the private key of your wallet (We recommend using an api Wallet for this, generated either on Hyperliquid or in your wallet of choice).
This needs to be configured like this:
```json
"exchange": {
"name": "hyperliquid",
"walletAddress": "your_eth_wallet_address",
"privateKey": "your_api_private_key",
// ...
}
```
* walletAddress in hex format: `0x<40 hex characters>` - Can be easily copied from your wallet - and should be your wallet address, not your API Wallet Address.
* privateKey in hex format: `0x<64 hex characters>` - Use the key the API Wallet shows on creation.
Hyperliquid handles deposits and withdrawals on the Arbitrum One chain, a Layer 2 scaling solution built on top of Ethereum. Hyperliquid uses USDC as quote / collateral. The process of depositing USDC on Hyperliquid requires a couple of steps, see [how to start trading](https://hyperliquid.gitbook.io/hyperliquid-docs/onboarding/how-to-start-trading) for details on what steps are needed.
!!! Note "Hyperliquid general usage Notes"
Hyperliquid does not support market orders, however ccxt will simulate market orders by placing limit orders with a maximum slippage of 5%.
Unfortunately, hyperliquid only offers 5000 historic candles, so backtesting will either need to build candles historically (by waiting and downloading the data incrementally over time) - or will be limited to the last 5000 candles.
!!! Info "Some general best practices (non exhaustive)"
* Beware of supply chain attacks, like pip package poisoning etcetera. Whenever you use your private key, make sure your environment is safe.
* Don't use your actual wallet private key for trading. Use the Hyperliquid [API generator](https://app.hyperliquid.xyz/API) to create a separate API wallet.
* Don't store your actual wallet private key on the server you use for freqtrade. Use the API wallet private key instead. This key won't allow withdrawals, only trading.
* Always keep your mnemonic phrase and private key private.
* Don't use the same mnemonic as the one you had to backup when initializing a hardware wallet, using the same mnemonic basically deletes the security of your hardware wallet.
* Create a different software wallet, only transfer the funds you want to trade with to that wallet, and use that wallet to trade on Hyperliquid.
* If you have funds you don't want to use for trading (after making a profit for example), transfer them back to your hardware wallet.
## All exchanges
Should you experience constant errors with Nonce (like `InvalidNonce`), it is best to regenerate the API keys. Resetting Nonce is difficult and it's usually easier to regenerate the API keys.
@@ -304,7 +351,7 @@ Should you experience constant errors with Nonce (like `InvalidNonce`), it is be
* The Ocean (exchange id: `theocean`) exchange uses Web3 functionality and requires `web3` python package to be installed:
```shell
$ pip3 install web3
pip3 install web3
```
### Getting latest price / Incomplete candles
+24 -3
View File
@@ -40,6 +40,10 @@ This could be caused by the following reasons:
* The installation did not complete successfully.
* Please check the [Installation documentation](installation.md).
### The bot starts, but in STOPPED mode
Make sure you set the `initial_state` config option to `"running"` in your config.json
### I have waited 5 minutes, why hasn't the bot made any trades yet?
* Depending on the buy strategy, the amount of whitelisted coins, the
@@ -100,6 +104,19 @@ You can use the `/stopentry` command in Telegram to prevent future trade entry,
Please look at the [advanced setup documentation Page](advanced-setup.md#running-multiple-instances-of-freqtrade).
### I'm getting "Impossible to load Strategy" when starting the bot
This error message is shown when the bot cannot load the strategy.
Usually, you can use `freqtrade list-strategies` to list all available strategies.
The output of this command will also include a status column, showing if the strategy can be loaded.
Please check the following:
* Are you using the correct strategy name? The strategy name is case-sensitive and must correspond to the Strategy class name (not the filename!).
* Is the strategy in the `user_data/strategies` directory, and has the file-ending `.py`?
* Does the bot show other warnings before this error? Maybe you're missing some dependencies for the strategy - which would be highlighted in the log.
* In case of docker - is the strategy directory mounted correctly (check the volumes part of the docker-compose file)?
### I'm getting "Missing data fillup" messages in the log
This message is just a warning that the latest candles had missing candles in them.
@@ -116,6 +133,10 @@ This message is a warning that the candles had a price jump of > 30%.
This might be a sign that the pair stopped trading, and some token exchange took place (e.g. COCOS in 2021 - where price jumped from 0.0000154 to 0.01621).
This message is often accompanied by ["Missing data fillup"](#im-getting-missing-data-fillup-messages-in-the-log) - as trading on such pairs is often stopped for some time.
### I want to reset the bot's database
To reset the bot's database, you can either delete the database (by default `tradesv3.sqlite` or `tradesv3.dryrun.sqlite`), or use a different database url via `--db-url` (e.g. `sqlite:///mynewdatabase.sqlite`).
### I'm getting "Outdated history for pair xxx" in the log
The bot is trying to tell you that it got an outdated last candle (not the last complete candle).
@@ -146,9 +167,9 @@ The same fix should be applied in the configuration file, if order types are def
### I'm trying to start the bot live, but get an API permission error
Errors like `Invalid API-key, IP, or permissions for action` mean exactly what they actually say.
Your API key is either invalid (copy/paste error? check for leading/trailing spaces in the config), expired, or the IP you're running the bot from is not enabled in the Exchange's API console.
Usually, the permission "Spot Trading" (or the equivalent in the exchange you use) will be necessary.
Errors like `Invalid API-key, IP, or permissions for action` mean exactly what they actually say.
Your API key is either invalid (copy/paste error? check for leading/trailing spaces in the config), expired, or the IP you're running the bot from is not enabled in the Exchange's API console.
Usually, the permission "Spot Trading" (or the equivalent in the exchange you use) will be necessary.
Futures will usually have to be enabled specifically.
### How do I search the bot logs for something?
+7 -7
View File
@@ -293,10 +293,10 @@ class MyCoolPyTorchClassifier(BasePyTorchClassifier):
super().__init__(**kwargs)
config = self.freqai_info.get("model_training_parameters", {})
self.learning_rate: float = config.get("learning_rate", 3e-4)
self.model_kwargs: Dict[str, Any] = config.get("model_kwargs", {})
self.trainer_kwargs: Dict[str, Any] = config.get("trainer_kwargs", {})
self.model_kwargs: dict[str, Any] = config.get("model_kwargs", {})
self.trainer_kwargs: dict[str, Any] = config.get("trainer_kwargs", {})
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
def fit(self, data_dictionary: dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
:param data_dictionary: the dictionary holding all data for train, test,
@@ -359,10 +359,10 @@ class PyTorchMLPRegressor(BasePyTorchRegressor):
super().__init__(**kwargs)
config = self.freqai_info.get("model_training_parameters", {})
self.learning_rate: float = config.get("learning_rate", 3e-4)
self.model_kwargs: Dict[str, Any] = config.get("model_kwargs", {})
self.trainer_kwargs: Dict[str, Any] = config.get("trainer_kwargs", {})
self.model_kwargs: dict[str, Any] = config.get("model_kwargs", {})
self.trainer_kwargs: dict[str, Any] = config.get("trainer_kwargs", {})
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
def fit(self, data_dictionary: dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
n_features = data_dictionary["train_features"].shape[-1]
model = PyTorchMLPModel(
input_dim=n_features,
@@ -393,7 +393,7 @@ Here we create a `PyTorchMLPRegressor` class that implements the `fit` method. T
For example, if you are using a binary classifier to predict price movements as up or down, you can set the class names as follows:
```python
def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
self.freqai.class_names = ["down", "up"]
dataframe['&s-up_or_down'] = np.where(dataframe["close"].shift(-100) >
dataframe["close"], 'up', 'down')
+40 -20
View File
@@ -42,11 +42,11 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--recursive-strategy-search] [--freqaimodel NAME]
[--freqaimodel-path PATH] [-i TIMEFRAME]
[--timerange TIMERANGE]
[--data-format-ohlcv {json,jsongz,hdf5}]
[--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}]
[--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[-p PAIRS [PAIRS ...]] [--hyperopt-path PATH]
[--eps] [--dmmp] [--enable-protections]
[--eps] [--enable-protections]
[--dry-run-wallet DRY_RUN_WALLET]
[--timeframe-detail TIMEFRAME_DETAIL] [-e INT]
[--spaces {all,buy,sell,roi,stoploss,trailing,protection,trades,default} [{all,buy,sell,roi,stoploss,trailing,protection,trades,default} ...]]
@@ -55,15 +55,15 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--hyperopt-loss NAME] [--disable-param-export]
[--ignore-missing-spaces] [--analyze-per-epoch]
optional arguments:
options:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--data-format-ohlcv {json,jsongz,hdf5}
--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}
Storage format for downloaded candle (OHLCV) data.
(default: `json`).
(default: `feather`).
--max-open-trades INT
Override the value of the `max_open_trades`
configuration setting.
@@ -80,10 +80,6 @@ optional arguments:
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking).
--dmmp, --disable-max-market-positions
Disable applying `max_open_trades` during backtest
(same as setting `max_open_trades` to a very high
number).
--enable-protections, --enableprotections
Enable protections for backtesting.Will slow
backtesting down by a considerable amount, but will
@@ -133,7 +129,8 @@ optional arguments:
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
--logfile FILE, --log-file FILE
Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
@@ -142,7 +139,7 @@ Common arguments:
`userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
-d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
@@ -867,18 +864,15 @@ You can use the `--print-all` command line option if you would like to see all r
## Position stacking and disabling max market positions
In some situations, you may need to run Hyperopt (and Backtesting) with the
`--eps`/`--enable-position-staking` and `--dmmp`/`--disable-max-market-positions` arguments.
In some situations, you may need to run Hyperopt (and Backtesting) with the `--eps`/`--enable-position-staking` argument, or you may need to set `max_open_trades` to a very high number to disable the limit on the number of open trades.
By default, hyperopt emulates the behavior of the Freqtrade Live Run/Dry Run, where only one
open trade is allowed for every traded pair. The total number of trades open for all pairs
open trade per pair is allowed. The total number of trades open for all pairs
is also limited by the `max_open_trades` setting. During Hyperopt/Backtesting this may lead to
some potential trades to be hidden (or masked) by previously open trades.
potential trades being hidden (or masked) by already open trades.
The `--eps`/`--enable-position-stacking` argument allows emulation of buying the same pair multiple times,
while `--dmmp`/`--disable-max-market-positions` disables applying `max_open_trades`
during Hyperopt/Backtesting (which is equal to setting `max_open_trades` to a very high
number).
The `--eps`/`--enable-position-stacking` argument allows emulation of buying the same pair multiple times.
Using `--max-open-trades` with a very high number will disable the limit on the number of open trades.
!!! Note
Dry/live runs will **NOT** use position stacking - therefore it does make sense to also validate the strategy without this as it's closer to reality.
@@ -919,13 +913,39 @@ Your epochs should therefore be aligned to the possible values - or you should b
After you run Hyperopt for the desired amount of epochs, you can later list all results for analysis, select only best or profitable once, and show the details for any of the epochs previously evaluated. This can be done with the `hyperopt-list` and `hyperopt-show` sub-commands. The usage of these sub-commands is described in the [Utils](utils.md#list-hyperopt-results) chapter.
## Output debug messages from your strategy
If you want to output debug messages from your strategy, you can use the `logging` module. By default, Freqtrade will output all messages with a level of `INFO` or higher.
``` python
import logging
logger = logging.getLogger(__name__)
class MyAwesomeStrategy(IStrategy):
...
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
logger.info("This is a debug message")
...
```
!!! Note "using print"
Messages printed via `print()` will not be shown in the hyperopt output unless parallelism is disabled (`-j 1`).
It is recommended to use the `logging` module instead.
## Validate backtesting results
Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected.
To achieve same the results (number of trades, their durations, profit, etc.) as during Hyperopt, please use the same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
To achieve same the results (number of trades, their durations, profit, etc.) as during Hyperopt, please use the same configuration and parameters (timerange, timeframe, ...) used for hyperopt for Backtesting.
### Why do my backtest results not match my hyperopt results?
Should results not match, check the following factors:
* You may have added parameters to hyperopt in `populate_indicators()` where they will be calculated only once **for all epochs**. If you are, for example, trying to optimise multiple SMA timeperiod values, the hyperoptable timeperiod parameter should be placed in `populate_entry_trend()` which is calculated every epoch. See [Optimizing an indicator parameter](https://www.freqtrade.io/en/stable/hyperopt/#optimizing-an-indicator-parameter).
+3 -2
View File
@@ -352,7 +352,7 @@ The optional `bearer_token` will be included in the requests Authorization Heade
#### MarketCapPairList
`MarketCapPairList` employs sorting/filtering of pairs by their marketcap rank based of CoinGecko. It will only recognize coins up to the coin placed at rank 250. The returned pairlist will be sorted based of their marketcap ranks.
`MarketCapPairList` employs sorting/filtering of pairs by their marketcap rank based of CoinGecko. The returned pairlist will be sorted based of their marketcap ranks.
```json
"pairlists": [
@@ -366,7 +366,8 @@ The optional `bearer_token` will be included in the requests Authorization Heade
]
```
`number_assets` defines the maximum number of pairs returned by the pairlist. `max_rank` will determine the maximum rank used in creating/filtering the pairlist. It's expected that some coins within the top `max_rank` marketcap will not be included in the resulting pairlist since not all pairs will have active trading pairs in your preferred market/stake/exchange combination.
`number_assets` defines the maximum number of pairs returned by the pairlist. `max_rank` will determine the maximum rank used in creating/filtering the pairlist. It's expected that some coins within the top `max_rank` marketcap will not be included in the resulting pairlist since not all pairs will have active trading pairs in your preferred market/stake/exchange combination.
While using a `max_rank` bigger than 250 is supported, it's not recommended, as it'll cause multiple API calls to CoinGecko, which can lead to rate limit issues.
The `refresh_period` setting defines the interval (in seconds) at which the marketcap rank data will be refreshed. The default is 86,400 seconds (1 day). The pairlist cache (`refresh_period`) applies to both generating pairlists (when in the first position in the list) and filtering instances (when not in the first position in the list).
+9 -6
View File
@@ -28,7 +28,7 @@ Freqtrade is a free and open source crypto trading bot written in Python. It is
- Develop your Strategy: Write your strategy in python, using [pandas](https://pandas.pydata.org/). Example strategies to inspire you are available in the [strategy repository](https://github.com/freqtrade/freqtrade-strategies).
- Download market data: Download historical data of the exchange and the markets your may want to trade with.
- Backtest: Test your strategy on downloaded historical data.
- Optimize: Find the best parameters for your strategy using hyperoptimization which employs machining learning methods. You can optimize buy, sell, take profit (ROI), stop-loss and trailing stop-loss parameters for your strategy.
- Optimize: Find the best parameters for your strategy using hyperoptimization which employs machine learning methods. You can optimize buy, sell, take profit (ROI), stop-loss and trailing stop-loss parameters for your strategy.
- Select markets: Create your static list or use an automatic one based on top traded volumes and/or prices (not available during backtesting). You can also explicitly blacklist markets you don't want to trade.
- Run: Test your strategy with simulated money (Dry-Run mode) or deploy it with real money (Live-Trade mode).
- Run using Edge (optional module): The concept is to find the best historical [trade expectancy](edge.md#expectancy) by markets based on variation of the stop-loss and then allow/reject markets to trade. The sizing of the trade is based on a risk of a percentage of your capital.
@@ -40,21 +40,24 @@ Freqtrade is a free and open source crypto trading bot written in Python. It is
Please read the [exchange specific notes](exchanges.md) to learn about eventual, special configurations needed for each exchange.
- [X] [Binance](https://www.binance.com/)
- [X] [Bitmart](https://bitmart.com/)
- [X] [BingX](https://bingx.com/invite/0EM9RX)
- [X] [Bitmart](https://bitmart.com/)
- [X] [Bybit](https://bybit.com/)
- [X] [Gate.io](https://www.gate.io/ref/6266643)
- [X] [HTX](https://www.htx.com/) (Former Huobi)
- [X] [HTX](https://www.htx.com/)
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
- [X] [Kraken](https://kraken.com/)
- [X] [OKX](https://okx.com/) (Former OKEX)
- [X] [OKX](https://okx.com/)
- [X] [MyOKX](https://okx.com/) (OKX EEA)
- [ ] [potentially many others through <img alt="ccxt" width="30px" src="assets/ccxt-logo.svg" />](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
### Supported Futures Exchanges (experimental)
- [X] [Binance](https://www.binance.com/)
- [X] [Gate.io](https://www.gate.io/ref/6266643)
- [X] [OKX](https://okx.com/)
- [X] [Bybit](https://bybit.com/)
- [X] [Gate.io](https://www.gate.io/ref/6266643)
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
- [X] [OKX](https://okx.com/)
Please make sure to read the [exchange specific notes](exchanges.md), as well as the [trading with leverage](leverage.md) documentation before diving in.
+20 -13
View File
@@ -67,6 +67,18 @@ OS Specific steps are listed first, the common section below is necessary for al
sudo apt install -y python3-pip python3-venv python3-dev python3-pandas git curl
```
=== "MacOS"
#### Install necessary dependencies
Install [Homebrew](https://brew.sh/) if you don't have it already.
```bash
# install packages
brew install gettext libomp
```
!!! Note
The `setup.sh` script will install these dependencies for you - assuming brew is installed on your system.
=== "RaspberryPi/Raspbian"
The following assumes the latest [Raspbian Buster lite image](https://www.raspberrypi.org/downloads/raspbian/).
This image comes with python3.11 preinstalled, making it easy to get freqtrade up and running.
@@ -76,7 +88,7 @@ OS Specific steps are listed first, the common section below is necessary for al
```bash
sudo apt-get install python3-venv libatlas-base-dev cmake curl
# Use pywheels.org to speed up installation
# Use piwheels.org to speed up installation
sudo echo "[global]\nextra-index-url=https://www.piwheels.org/simple" > tee /etc/pip.conf
git clone https://github.com/freqtrade/freqtrade.git
@@ -150,9 +162,7 @@ Each time you open a new terminal, you must run `source .venv/bin/activate` to a
source ./.venv/bin/activate
```
### Congratulations
[You are ready](#you-are-ready), and run the bot
[You are now ready](#you-are-ready) to run the bot.
### Other options of /setup.sh script
@@ -220,7 +230,7 @@ cd ..
rm -rf ./ta-lib*
```
#### Setup Python virtual environment (virtualenv)
### Setup Python virtual environment (virtualenv)
You will run freqtrade in separated `virtual environment`
@@ -232,19 +242,18 @@ python3 -m venv .venv
source .venv/bin/activate
```
#### Install python dependencies
### Install python dependencies
```bash
python3 -m pip install --upgrade pip
python3 -m pip install -r requirements.txt
# install freqtrade
python3 -m pip install -e .
```
### Congratulations
[You are now ready](#you-are-ready) to run the bot.
[You are ready](#you-are-ready), and run the bot
#### (Optional) Post-installation Tasks
### (Optional) Post-installation Tasks
!!! Note
If you run the bot on a server, you should consider using [Docker](docker_quickstart.md) or a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout.
@@ -333,9 +342,7 @@ cd build_helpers
bash install_ta-lib.sh ${CONDA_PREFIX} nosudo
```
### Congratulations
[You are ready](#you-are-ready), and run the bot
[You are now ready](#you-are-ready) to run the bot.
### Important shortcuts
+3 -3
View File
@@ -1,7 +1,7 @@
markdown==3.7
mkdocs==1.6.1
mkdocs-material==9.5.42
mkdocs-material==9.5.49
mdx_truly_sane_lists==1.3
pymdown-extensions==10.11.2
jinja2==3.1.4
pymdown-extensions==10.12
jinja2==3.1.5
mike==2.1.3
+67 -53
View File
@@ -88,8 +88,9 @@ Make sure that the following 2 lines are available in your docker-compose file:
### Consuming the API
You can consume the API by using `freqtrade-client` (also available as `scripts/rest_client.py`).
This command can be installed independent of the bot by using `pip install freqtrade-client`.
We advise consuming the API by using the supported `freqtrade-client` package (also available as `scripts/rest_client.py`).
This command can be installed independent of any running freqtrade bot by using `pip install freqtrade-client`.
This module is designed to be lightweight, and only depends on the `requests` and `python-rapidjson` modules, skipping all heavy dependencies freqtrade otherwise needs.
@@ -144,57 +145,6 @@ This method will work for all arguments - check the "show" command for a list of
For a full list of available commands, please refer to the list below.
### Available endpoints
| Command | Description |
|----------|-------------|
| `ping` | Simple command testing the API Readiness - requires no authentication.
| `start` | Starts the trader.
| `stop` | Stops the trader.
| `stopbuy` | Stops the trader from opening new trades. Gracefully closes open trades according to their rules.
| `reload_config` | Reloads the configuration file.
| `trades` | List last trades. Limited to 500 trades per call.
| `trade/<tradeid>` | Get specific trade.
| `trades/<tradeid>` | DELETE - Remove trade from the database. Tries to close open orders. Requires manual handling of this trade on the exchange.
| `trades/<tradeid>/open-order` | DELETE - Cancel open order for this trade.
| `trades/<tradeid>/reload` | GET - Reload a trade from the Exchange. Only works in live, and can potentially help recover a trade that was manually sold on the exchange.
| `show_config` | Shows part of the current configuration with relevant settings to operation.
| `logs` | Shows last log messages.
| `status` | Lists all open trades.
| `count` | Displays number of trades used and available.
| `entries [pair]` | Shows profit statistics for each enter tags for given pair (or all pairs if pair isn't given). Pair is optional.
| `exits [pair]` | Shows profit statistics for each exit reasons for given pair (or all pairs if pair isn't given). Pair is optional.
| `mix_tags [pair]` | Shows profit statistics for each combinations of enter tag + exit reasons for given pair (or all pairs if pair isn't given). Pair is optional.
| `locks` | Displays currently locked pairs.
| `delete_lock <lock_id>` | Deletes (disables) the lock by id.
| `locks add <pair>, <until>, [side], [reason]` | Locks a pair until "until". (Until will be rounded up to the nearest timeframe).
| `profit` | Display a summary of your profit/loss from close trades and some stats about your performance.
| `forceexit <trade_id> [order_type] [amount]` | Instantly exits the given trade (ignoring `minimum_roi`), using the given order type ("market" or "limit", uses your config setting if not specified), and the chosen amount (full sell if not specified).
| `forceexit all` | Instantly exits all open trades (Ignoring `minimum_roi`).
| `forceenter <pair> [rate]` | Instantly enters the given pair. Rate is optional. (`force_entry_enable` must be set to True)
| `forceenter <pair> <side> [rate]` | Instantly longs or shorts the given pair. Rate is optional. (`force_entry_enable` must be set to True)
| `performance` | Show performance of each finished trade grouped by pair.
| `balance` | Show account balance per currency.
| `daily <n>` | Shows profit or loss per day, over the last n days (n defaults to 7).
| `weekly <n>` | Shows profit or loss per week, over the last n days (n defaults to 4).
| `monthly <n>` | Shows profit or loss per month, over the last n days (n defaults to 3).
| `stats` | Display a summary of profit / loss reasons as well as average holding times.
| `whitelist` | Show the current whitelist.
| `blacklist [pair]` | Show the current blacklist, or adds a pair to the blacklist.
| `edge` | Show validated pairs by Edge if it is enabled.
| `pair_candles` | Returns dataframe for a pair / timeframe combination while the bot is running. **Alpha**
| `pair_history` | Returns an analyzed dataframe for a given timerange, analyzed by a given strategy. **Alpha**
| `plot_config` | Get plot config from the strategy (or nothing if not configured). **Alpha**
| `strategies` | List strategies in strategy directory. **Alpha**
| `strategy <strategy>` | Get specific Strategy content. **Alpha**
| `available_pairs` | List available backtest data. **Alpha**
| `version` | Show version.
| `sysinfo` | Show information about the system load.
| `health` | Show bot health (last bot loop).
!!! Warning "Alpha status"
Endpoints labeled with *Alpha status* above may change at any time without notice.
Possible commands can be listed from the rest-client script using the `help` command.
``` bash
@@ -266,6 +216,14 @@ forceexit
health
Provides a quick health check of the running bot.
lock_add
Manually lock a specific pair
:param pair: Pair to lock
:param until: Lock until this date (format "2024-03-30 16:00:00Z")
:param side: Side to lock (long, short, *)
:param reason: Reason for the lock
locks
Return current locks
@@ -353,6 +311,62 @@ whitelist
```
### Available endpoints
If you wish to call the REST API manually via another route, e.g. directly via `curl`, the table below shows the relevant URL endpoints and parameters.
All endpoints in the below table need to be prefixed with the base URL of the API, e.g. `http://127.0.0.1:8080/api/v1/` - so the command becomes `http://127.0.0.1:8080/api/v1/<command>`.
| Endpoint | Method | Description / Parameters |
|-----------|--------|--------------------------|
| `/ping` | GET | Simple command testing the API Readiness - requires no authentication.
| `/start` | POST | Starts the trader.
| `/stop` | POST | Stops the trader.
| `/stopbuy` | POST | Stops the trader from opening new trades. Gracefully closes open trades according to their rules.
| `/reload_config` | POST | Reloads the configuration file.
| `/trades` | GET | List last trades. Limited to 500 trades per call.
| `/trade/<tradeid>` | GET | Get specific trade.<br/>*Params:*<br/>- `tradeid` (`int`)
| `/trades/<tradeid>` | DELETE | Remove trade from the database. Tries to close open orders. Requires manual handling of this trade on the exchange.<br/>*Params:*<br/>- `tradeid` (`int`)
| `/trades/<tradeid>/open-order` | DELETE | Cancel open order for this trade.<br/>*Params:*<br/>- `tradeid` (`int`)
| `/trades/<tradeid>/reload` | POST | Reload a trade from the Exchange. Only works in live, and can potentially help recover a trade that was manually sold on the exchange.<br/>*Params:*<br/>- `tradeid` (`int`)
| `/show_config` | GET | Shows part of the current configuration with relevant settings to operation.
| `/logs` | GET | Shows last log messages.
| `/status` | GET | Lists all open trades.
| `/count` | GET | Displays number of trades used and available.
| `/entries` | GET | Shows profit statistics for each enter tags for given pair (or all pairs if pair isn't given). Pair is optional.<br/>*Params:*<br/>- `pair` (`str`)
| `/exits` | GET | Shows profit statistics for each exit reasons for given pair (or all pairs if pair isn't given). Pair is optional.<br/>*Params:*<br/>- `pair` (`str`)
| `/mix_tags` | GET | Shows profit statistics for each combinations of enter tag + exit reasons for given pair (or all pairs if pair isn't given). Pair is optional.<br/>*Params:*<br/>- `pair` (`str`)
| `/locks` | GET | Displays currently locked pairs.
| `/locks` | POST | Locks a pair until "until". (Until will be rounded up to the nearest timeframe). Side is optional and is either `long` or `short` (default is `long`). Reason is optional.<br/>*Params:*<br/>- `<pair>` (`str`)<br/>- `<until>` (`datetime`)<br/>- `[side]` (`str`)<br/>- `[reason]` (`str`)
| `/locks/<lockid>` | DELETE | Deletes (disables) the lock by id.<br/>*Params:*<br/>- `lockid` (`int`)
| `/profit` | GET | Display a summary of your profit/loss from close trades and some stats about your performance.
| `/forceexit` | POST | Instantly exits the given trade (ignoring `minimum_roi`), using the given order type ("market" or "limit", uses your config setting if not specified), and the chosen amount (full sell if not specified). If `all` is supplied as the `tradeid`, then all currently open trades will be forced to exit.<br/>*Params:*<br/>- `<tradeid>` (`int` or `str`)<br/>- `<ordertype>` (`str`)<br/>- `[amount]` (`float`)
| `/forceenter` | POST | Instantly enters the given pair. Side is optional and is either `long` or `short` (default is `long`). Rate is optional. (`force_entry_enable` must be set to True)<br/>*Params:*<br/>- `<pair>` (`str`)<br/>- `<side>` (`str`)<br/>- `[rate]` (`float`)
| `/performance` | GET | Show performance of each finished trade grouped by pair.
| `/balance` | GET | Show account balance per currency.
| `/daily` | GET | Shows profit or loss per day, over the last n days (n defaults to 7).<br/>*Params:*<br/>- `<n>` (`int`)
| `/weekly` | GET | Shows profit or loss per week, over the last n days (n defaults to 4).<br/>*Params:*<br/>- `<n>` (`int`)
| `/monthly` | GET | Shows profit or loss per month, over the last n days (n defaults to 3).<br/>*Params:*<br/>- `<n>` (`int`)
| `/stats` | GET | Display a summary of profit / loss reasons as well as average holding times.
| `/whitelist` | GET | Show the current whitelist.
| `/blacklist` | GET | Show the current blacklist.
| `/blacklist` | POST | Adds the specified pair to the blacklist.<br/>*Params:*<br/>- `pair` (`str`)
| `/blacklist` | DELETE | Deletes the specified list of pairs from the blacklist.<br/>*Params:*<br/>- `[pair,pair]` (`list[str]`)
| `/edge` | GET | Show validated pairs by Edge if it is enabled.
| `/pair_candles` | GET | Returns dataframe for a pair / timeframe combination while the bot is running. **Alpha**
| `/pair_candles` | POST | Returns dataframe for a pair / timeframe combination while the bot is running, filtered by a provided list of columns to return. **Alpha**<br/>*Params:*<br/>- `<column_list>` (`list[str]`)
| `/pair_history` | GET | Returns an analyzed dataframe for a given timerange, analyzed by a given strategy. **Alpha**
| `/pair_history` | POST | Returns an analyzed dataframe for a given timerange, analyzed by a given strategy, filtered by a provided list of columns to return. **Alpha**<br/>*Params:*<br/>- `<column_list>` (`list[str]`)
| `/plot_config` | GET | Get plot config from the strategy (or nothing if not configured). **Alpha**
| `/strategies` | GET | List strategies in strategy directory. **Alpha**
| `/strategy/<strategy>` | GET | Get specific Strategy content by strategy class name. **Alpha**<br/>*Params:*<br/>- `<strategy>` (`str`)
| `/available_pairs` | GET | List available backtest data. **Alpha**
| `/version` | GET | Show version.
| `/sysinfo` | GET | Show information about the system load.
| `/health` | GET | Show bot health (last bot loop).
!!! Warning "Alpha status"
Endpoints labeled with *Alpha status* above may change at any time without notice.
### Message WebSocket
The API Server includes a websocket endpoint for subscribing to RPC messages from the freqtrade Bot.
+3 -2
View File
@@ -30,12 +30,13 @@ The Order-type will be ignored if only one mode is available.
|----------|-------------|
| Binance | limit |
| Binance Futures | market, limit |
| Bingx | market, limit |
| HTX (former Huobi) | limit |
| Bingx | market, limit |
| HTX | limit |
| kraken | market, limit |
| Gate | limit |
| Okx | limit |
| Kucoin | stop-limit, stop-market|
| Hyperliquid (futures only) | limit |
!!! Note "Tight stoploss"
<ins>Do not set too low/tight stoploss value when using stop loss on exchange!</ins>
+2 -2
View File
@@ -38,9 +38,9 @@ class AwesomeStrategy(IStrategy):
trade.set_custom_data(key='entry_type', value=trade_entry_type)
return super().bot_loop_start(**kwargs)
def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str,
def adjust_entry_price(self, trade: Trade, order: Order | None, pair: str,
current_time: datetime, proposed_rate: float, current_order_rate: float,
entry_tag: Optional[str], side: str, **kwargs) -> float:
entry_tag: str | None, side: str, **kwargs) -> float:
# 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'
+44 -32
View File
@@ -90,8 +90,8 @@ Called before entering a trade, makes it possible to manage your position size w
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,
leverage: float, entry_tag: Optional[str], side: str,
proposed_stake: float, min_stake: float | None, max_stake: float,
leverage: float, entry_tag: str | None, side: str,
**kwargs) -> float:
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
@@ -165,7 +165,8 @@ Called for open trade every iteration (roughly every 5 seconds) until a trade is
The usage of the custom stoploss method must be enabled by setting `use_custom_stoploss=True` on the strategy object.
The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss (before this method is called for the first time for a trade), and is still mandatory.
The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss (before this method is called for the first time for a trade), and is still mandatory.
As custom stoploss acts as regular, changing stoploss, it will behave similar to `trailing_stop` - and trades exiting due to this will have the exit_reason of `"trailing_stop_loss"`.
The method must return a stoploss value (float / number) as a percentage of the current price.
E.g. If the `current_rate` is 200 USD, then returning `0.02` will set the stoploss price 2% lower, at 196 USD.
@@ -212,7 +213,7 @@ class AwesomeStrategy(IStrategy):
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
"""
Custom stoploss logic, returning the new distance relative to current_rate (as ratio).
e.g. returning -0.05 would create a stoploss 5% below current_rate.
@@ -250,7 +251,7 @@ class AwesomeStrategy(IStrategy):
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
# Make sure you have the longest interval first - these conditions are evaluated from top to bottom.
if current_time - timedelta(minutes=120) > trade.open_date_utc:
@@ -276,7 +277,7 @@ class AwesomeStrategy(IStrategy):
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
if after_fill:
# After an additional order, start with a stoploss of 10% below the new open rate
@@ -305,7 +306,7 @@ class AwesomeStrategy(IStrategy):
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
if pair in ("ETH/BTC", "XRP/BTC"):
return -0.10
@@ -331,7 +332,7 @@ class AwesomeStrategy(IStrategy):
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
if current_profit < 0.04:
return None # return None to keep using the initial stoploss
@@ -363,7 +364,7 @@ class AwesomeStrategy(IStrategy):
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
# evaluate highest to lowest, so that highest possible stop is used
if current_profit > 0.40:
@@ -394,7 +395,7 @@ class AwesomeStrategy(IStrategy):
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
@@ -439,7 +440,7 @@ Stoploss values returned from `custom_stoploss()` must specify a percentage rela
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
# once the profit has risen above 10%, keep the stoploss at 7% above the open price
if current_profit > 0.10:
@@ -482,7 +483,7 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
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()
@@ -519,8 +520,8 @@ class AwesomeStrategy(IStrategy):
# ... populate_* methods
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: Trade | None, current_time: datetime, proposed_rate: float,
entry_tag: str | None, side: str, **kwargs) -> float:
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
timeframe=self.timeframe)
@@ -530,7 +531,7 @@ class AwesomeStrategy(IStrategy):
def custom_exit_price(self, pair: str, trade: Trade,
current_time: datetime, proposed_rate: float,
current_profit: float, exit_tag: Optional[str], **kwargs) -> float:
current_profit: float, exit_tag: str | None, **kwargs) -> float:
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
timeframe=self.timeframe)
@@ -662,7 +663,7 @@ class AwesomeStrategy(IStrategy):
# ... populate_* methods
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
time_in_force: str, current_time: datetime, entry_tag: str | None,
side: str, **kwargs) -> bool:
"""
Called right before placing a entry order.
@@ -766,6 +767,15 @@ Adjustment orders can be assigned with a tag by returning a 2 element Tuple, wit
Modifications to leverage are not possible, and the stake-amount returned is assumed to be before applying leverage.
!!! Danger "Loose Logic"
On dry and live run, this function will be called every `throttle_process_secs` (default to 5s). If you have a loose logic, for example your logic for extra entry is only to check RSI of last candle is below 30, then when such condition fulfilled, your bot will do extra re-entry every 5 secs until either it run out of money, it hit the `max_position_adjustment` limit, or a new candle with RSI more than 30 arrived.
Same thing also can happen with partial exit. So be sure to have a strict logic and/or check for the last filled order.
!!! Warning "Backtesting"
During backtesting this callback is called for each candle in `timeframe` or `timeframe_detail`, so run-time performance will be affected.
This can also cause deviating results between live and backtesting, since backtesting can adjust the trade only once per candle, whereas live could adjust the trade multiple times per candle.
### Increase position
The strategy is expected to return a positive **stake_amount** (in stake currency) between `min_stake` and `max_stake` if and when an additional entry order should be made (position is increased -> buy order for long trades, sell order for short trades).
@@ -775,16 +785,22 @@ If there are not enough funds in the wallet (the return value is above `max_stak
Additional entries are ignored once you have reached the maximum amount of extra entries that you have set on `max_entry_position_adjustment`, but the callback is called anyway looking for partial exits.
!!! 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.
### Decrease position
The strategy is expected to return a negative stake_amount (in stake currency) for a partial exit.
Returning the full owned stake at that point (`-trade.stake_amount`) results in a full exit.
Returning a value more than the above (so remaining stake_amount would become negative) will result in the bot ignoring the signal.
!!! 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.
For a partial exit, it's important to know that the formula used to calculate the amount of the coin for the partial exit order is `amount to be exited partially = negative_stake_amount * trade.amount / trade.stake_amount`, where `negative_stake_amount` is the value returned from the `adjust_trade_position` function. As seen in the formula, the formula doesn't care about current profit/loss of the position. It only cares about `trade.amount` and `trade.stake_amount` which aren't affected by the price movement at all.
For example, let's say you buy 2 SHITCOIN/USDT at open rate of 50, which means the trade's stake amount is 100 USDT. Now the price raises to 200 and you want to sell half of it. In that case, you have to return -50% of `trade.stake_amount` (0.5 * 100 USDT) which equals to -50. The bot will calculate the amount it needed to sell, which is `50 * 2 / 100` which equals 1 SHITCOIN/USDT. If you return -200 (50% of 2 * 200), the bot will ignore it since `trade.stake_amount` is only 100 USDT but you asked to sell 200 USDT which means you are asking to sell 4 SHITCOIN/USDT.
Back to the example above, since current rate is 200, the current USDT value of your trade is now 400 USDT. Let's say you want to partially sell 100 USDT to take out the initial investment and leave the profit in the trade hoping that the price keeps rising. In that case, you have to do a different approach. First, you need to calculate the exact amount you needed to sell. In this case, since you want to sell 100 USDT worth based of current rate, the exact amount you need to partially sell is `100 * 2 / 400` which equals 0.5 SHITCOIN/USDT. Since we know now the exact amount we want to sell (0.5), the value you need to return in the `adjust_trade_position` function is `-amount to be exited partially * trade.stake_amount / trade.amount`, which equals -25. The bot will sell 0.5 SHITCOIN/USDT, keeping 1.5 in trade. You will receive 100 USDT from the partial exit.
!!! Warning "Stoploss calculation"
Stoploss is still calculated from the initial opening price, not averaged price.
@@ -792,10 +808,6 @@ Returning a value more than the above (so remaining stake_amount would become ne
While `/stopentry` command stops the bot from entering new trades, the position adjustment feature will continue buying new orders on existing trades.
!!! Warning "Backtesting"
During backtesting this callback is called for each candle in `timeframe` or `timeframe_detail`, so run-time performance will be affected.
This can also cause deviating results between live and backtesting, since backtesting can adjust the trade only once per candle, whereas live could adjust the trade multiple times per candle.
!!! Warning "Performance with many position adjustments"
Position adjustments can be a good approach to increase a strategy's output - but it can also have drawbacks if using this feature extensively.
Each of the orders will be attached to the trade object for the duration of the trade - hence increasing memory usage.
@@ -820,8 +832,8 @@ class DigDeeperStrategy(IStrategy):
# This is called when placing the initial order (opening trade)
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: Optional[float], max_stake: float,
leverage: float, entry_tag: Optional[str], side: str,
proposed_stake: float, min_stake: float | None, max_stake: float,
leverage: float, entry_tag: str | None, side: str,
**kwargs) -> float:
# We need to leave most of the funds for possible further DCA orders
@@ -830,11 +842,11 @@ class DigDeeperStrategy(IStrategy):
def adjust_trade_position(self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float,
min_stake: Optional[float], max_stake: float,
min_stake: float | None, max_stake: float,
current_entry_rate: float, current_exit_rate: float,
current_entry_profit: float, current_exit_profit: float,
**kwargs
) -> Union[Optional[float], Tuple[Optional[float], Optional[str]]]:
) -> float | None | tuple[float | None, str | None]:
"""
Custom trade adjustment logic, returning the stake amount that a trade should be
increased or decreased.
@@ -890,7 +902,7 @@ class DigDeeperStrategy(IStrategy):
# Hope you have a deep wallet!
try:
# This returns first order stake size
stake_amount = filled_entries[0].stake_amount
stake_amount = filled_entries[0].stake_amount_filled
# This then calculates current safety order size
stake_amount = stake_amount * (1 + (count_of_entries * 0.25))
return stake_amount, "1/3rd_increase"
@@ -946,9 +958,9 @@ class AwesomeStrategy(IStrategy):
# ... populate_* methods
def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str,
def adjust_entry_price(self, trade: Trade, order: Order | None, pair: str,
current_time: datetime, proposed_rate: float, current_order_rate: float,
entry_tag: Optional[str], side: str, **kwargs) -> float:
entry_tag: str | None, side: str, **kwargs) -> float:
"""
Entry price re-adjustment logic, returning the user desired limit price.
This only executes when a order was already placed, still open (unfilled fully or partially)
@@ -1003,7 +1015,7 @@ For markets / exchanges that don't support leverage, this method is ignored.
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,
proposed_leverage: float, max_leverage: float, entry_tag: str | None, side: str,
**kwargs) -> float:
"""
Customize leverage for each new trade. This method is only called in futures mode.
+9 -6
View File
@@ -4,7 +4,7 @@ This page explains how to customize your strategies, add new indicators and set
If you haven't already, please familiarize yourself with:
- the [Freqtrade strategy 101](freqtrade-101.md), which provides a quick start to strategy development
- the [Freqtrade strategy 101](strategy-101.md), which provides a quick start to strategy development
- the [Freqtrade bot basics](bot-basics.md), which provides overall info on how the bot operates
## Develop your own strategy
@@ -582,11 +582,14 @@ When hyperopting, use of the hyperoptable parameter `.value` attribute is not su
??? info "Full documentation"
``` python
def informative(timeframe: str, asset: str = '',
fmt: Optional[Union[str, Callable[[KwArg(str)], str]]] = None,
*,
candle_type: Optional[CandleType] = None,
ffill: bool = True) -> Callable[[PopulateIndicators], PopulateIndicators]:
def informative(
timeframe: str,
asset: str = "",
fmt: str | Callable[[Any], str] | None = None,
*,
candle_type: CandleType | str | None = None,
ffill: bool = True,
) -> Callable[[PopulateIndicators], PopulateIndicators]:
"""
A decorator for populate_indicators_Nn(self, dataframe, metadata), allowing these functions to
define informative indicators.
+2 -2
View File
@@ -152,7 +152,7 @@ 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"])
# Maximum drawdown ()
print(stats["strategy"][strategy]["max_drawdown"])
print(stats["strategy"][strategy]["max_drawdown_abs"])
# Maximum drawdown start and end
print(stats["strategy"][strategy]["drawdown_start"])
print(stats["strategy"][strategy]["drawdown_end"])
@@ -215,7 +215,7 @@ trades.groupby("pair")["exit_reason"].value_counts()
```
## Analyze the loaded trades for trade parallelism
This can be useful to find the best `max_open_trades` parameter, when used with backtesting in conjunction with `--disable-max-market-positions`.
This can be useful to find the best `max_open_trades` parameter, when used with backtesting in conjunction with a very high `max_open_trades` setting.
`analyze_trade_parallelism()` returns a timeseries dataframe with an "open_trades" column, specifying the number of open trades for each candle.
+9 -9
View File
@@ -214,8 +214,8 @@ class AwesomeStrategy(IStrategy):
``` python hl_lines="4"
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,
entry_tag: Optional[str], side: str, **kwargs) -> float:
proposed_stake: float, min_stake: float | None, max_stake: float,
entry_tag: str | None, side: str, **kwargs) -> float:
# ...
return proposed_stake
```
@@ -237,7 +237,7 @@ After:
``` python hl_lines="4"
class AwesomeStrategy(IStrategy):
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
time_in_force: str, current_time: datetime, entry_tag: str | None,
side: str, **kwargs) -> bool:
return True
```
@@ -280,8 +280,8 @@ After:
``` python hl_lines="3"
class AwesomeStrategy(IStrategy):
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: Trade | None, current_time: datetime, proposed_rate: float,
entry_tag: str | None, side: str, **kwargs) -> float:
return proposed_rate
```
@@ -312,7 +312,7 @@ After:
``` python hl_lines="5 7"
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
# once the profit has risen above 10%, keep the stoploss at 7% above the open price
if current_profit > 0.10:
return stoploss_from_open(0.07, current_profit, is_short=trade.is_short)
@@ -329,7 +329,7 @@ After:
`order_time_in_force` attributes changed from `"buy"` to `"entry"` and `"sell"` to `"exit"`.
``` python
order_time_in_force: Dict = {
order_time_in_force: dict = {
"buy": "gtc",
"sell": "gtc",
}
@@ -338,7 +338,7 @@ After:
After:
``` python hl_lines="2 3"
order_time_in_force: Dict = {
order_time_in_force: dict = {
"entry": "GTC",
"exit": "GTC",
}
@@ -780,7 +780,7 @@ class MyCoolFreqaiModel(BaseRegressionModel):
def predict(
self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs
) -> Tuple[DataFrame, npt.NDArray[np.int_]]:
) -> tuple[DataFrame, npt.NDArray[np.int_]]:
# ... your custom stuff
+4
View File
@@ -15,3 +15,7 @@
.md-version__list {
font-weight: 500 !important;
}
#available-endpoints ~ .md-typeset__scrollwrap .md-typeset__table th:first-of-type {
width: 35% !important;
}
+98 -73
View File
@@ -45,22 +45,42 @@ Get your "Id", you will use it for the config parameter `chat_id`.
#### Use Group id
You can use bots in telegram groups by just adding them to the group. You can find the group id by first adding a [RawDataBot](https://telegram.me/rawdatabot) to your group. The Group id is shown as id in the `"chat"` section, which the RawDataBot will send to you:
To get the group ID, you can add the bot to the group, start freqtrade, and issue a `/tg_info` command.
This will return the group id to you, without having to use some random bot.
While "chat_id" is still required, it doesn't need to be set to this particular group id for this command.
The response will also contain the "topic_id" if necessary - both in a format ready to copy/paste into your configuration.
``` json
"chat":{
"id":-1001332619709
{
"enabled": true,
"token": "********",
"chat_id": "-1001332619709",
"topic_id": "122"
}
```
For the Freqtrade configuration, you can then use the full value (including `-` if it's there) as string:
For the Freqtrade configuration, you can then use the full value (including `-` ) as string:
```json
"chat_id": "-1001332619709"
```
!!! Warning "Using telegram groups"
When using telegram groups, you're giving every member of the telegram group access to your freqtrade bot and to all commands possible via telegram. Please make sure that you can trust everyone in the telegram group to avoid unpleasant surprises.
##### Group Topic ID
To use a specific topic in a group, you can use the `topic_id` parameter in the configuration. This will allow you to use the bot in a specific topic in a group.
Without this, the bot will always respond to the general channel in the group if topics are enabled for a group chat.
```json
"chat_id": "-1001332619709",
"topic_id": "3"
```
Similar to the group-id - you can use `/tg_info` from the topic/thread to get the correct topic-id.
## Control telegram noise
Freqtrade provides means to control the verbosity of your telegram bot.
@@ -93,9 +113,12 @@ Example configuration showing the different settings:
"trailing_stop_loss": "on",
"stop_loss": "on",
"stoploss_on_exchange": "on",
"custom_exit": "silent",
"partial_exit": "on"
"custom_exit": "silent", // custom_exit without specifying an exit reason
"partial_exit": "on",
// "custom_exit_message": "silent", // Disable individual custom exit reasons
"*": "off" // Disable all other exit reasons
},
// "exit": "off", // Simplistic configuration to disable all exit messages
"exit_cancel": "on",
"exit_fill": "off",
"protection_trigger": "off",
@@ -108,16 +131,16 @@ Example configuration showing the different settings:
},
```
`entry` notifications are sent when the order is placed, while `entry_fill` notifications are sent when the order is filled on the exchange.
`exit` notifications are sent when the order is placed, while `exit_fill` notifications are sent when the order is filled on the exchange.
`*_fill` notifications are off by default and must be explicitly enabled.
`protection_trigger` notifications are sent when a protection triggers and `protection_trigger_global` notifications trigger when global protections are triggered.
`strategy_msg` - Receive notifications from the strategy, sent via `self.dp.send_msg()` from the strategy [more details](strategy-customization.md#send-notification).
`show_candle` - show candle values as part of entry/exit messages. Only possible values are `"ohlc"` or `"off"`.
`balance_dust_level` will define what the `/balance` command takes as "dust" - Currencies with a balance below this will be shown.
`allow_custom_messages` completely disable strategy messages.
`reload` allows you to disable reload-buttons on selected messages.
* `entry` notifications are sent when the order is placed, while `entry_fill` notifications are sent when the order is filled on the exchange.
* `exit` notifications are sent when the order is placed, while `exit_fill` notifications are sent when the order is filled on the exchange.
Exit messages (`exit` and `exit_fill`) can be further controlled at individual exit reasons level, with the specific exit reason as the key. the default for all exit reasons is `on` - but can be configured via special `*` key - which will act as a wildcard for all exit reasons that are not explicitly defined.
* `*_fill` notifications are off by default and must be explicitly enabled.
* `protection_trigger` notifications are sent when a protection triggers and `protection_trigger_global` notifications trigger when global protections are triggered.
* `strategy_msg` - Receive notifications from the strategy, sent via `self.dp.send_msg()` from the strategy [more details](strategy-customization.md#send-notification).
* `show_candle` - show candle values as part of entry/exit messages. Only possible values are `"ohlc"` or `"off"`.
* `balance_dust_level` will define what the `/balance` command takes as "dust" - Currencies with a balance below this will be shown.
* `allow_custom_messages` completely disable strategy messages.
* `reload` allows you to disable reload-buttons on selected messages.
## Create a custom keyboard (command shortcut buttons)
@@ -238,16 +261,16 @@ Once all positions are sold, run `/stop` to completely stop the bot.
For each open trade, the bot will send you the following message.
Enter Tag is configurable via Strategy.
> **Trade ID:** `123` `(since 1 days ago)`
> **Current Pair:** CVC/BTC
> **Direction:** Long
> **Leverage:** 1.0
> **Amount:** `26.64180098`
> **Enter Tag:** Awesome Long Signal
> **Open Rate:** `0.00007489`
> **Current Rate:** `0.00007489`
> **Unrealized Profit:** `12.95%`
> **Stoploss:** `0.00007389 (-0.02%)`
> **Trade ID:** `123` `(since 1 days ago)`
> **Current Pair:** CVC/BTC
> **Direction:** Long
> **Leverage:** 1.0
> **Amount:** `26.64180098`
> **Enter Tag:** Awesome Long Signal
> **Open Rate:** `0.00007489`
> **Current Rate:** `0.00007489`
> **Unrealized Profit:** `12.95%`
> **Stoploss:** `0.00007389 (-0.02%)`
### /status table
@@ -274,34 +297,34 @@ current max
Return a summary of your profit/loss and performance.
> **ROI:** Close trades
> ∙ `0.00485701 BTC (2.2%) (15.2 Σ%)`
> ∙ `62.968 USD`
> **ROI:** All trades
> ∙ `0.00255280 BTC (1.5%) (6.43 Σ%)`
> ∙ `33.095 EUR`
>
> **Total Trade Count:** `138`
> **Bot started:** `2022-07-11 18:40:44`
> **First Trade opened:** `3 days ago`
> **Latest Trade opened:** `2 minutes ago`
> **Avg. Duration:** `2:33:45`
> **Best Performing:** `PAY/BTC: 50.23%`
> **Trading volume:** `0.5 BTC`
> **Profit factor:** `1.04`
> **Win / Loss:** `102 / 36`
> **Winrate:** `73.91%`
> **Expectancy (Ratio):** `4.87 (1.66)`
> **Max Drawdown:** `9.23% (0.01255 BTC)`
> **ROI:** Close trades
> ∙ `0.00485701 BTC (2.2%) (15.2 Σ%)`
> ∙ `62.968 USD`
> **ROI:** All trades
> ∙ `0.00255280 BTC (1.5%) (6.43 Σ%)`
> ∙ `33.095 EUR`
>
> **Total Trade Count:** `138`
> **Bot started:** `2022-07-11 18:40:44`
> **First Trade opened:** `3 days ago`
> **Latest Trade opened:** `2 minutes ago`
> **Avg. Duration:** `2:33:45`
> **Best Performing:** `PAY/BTC: 50.23%`
> **Trading volume:** `0.5 BTC`
> **Profit factor:** `1.04`
> **Win / Loss:** `102 / 36`
> **Winrate:** `73.91%`
> **Expectancy (Ratio):** `4.87 (1.66)`
> **Max Drawdown:** `9.23% (0.01255 BTC)`
The relative profit of `1.2%` is the average profit per trade.
The relative profit of `15.2 Σ%` is be based on the starting capital - so in this case, the starting capital was `0.00485701 * 1.152 = 0.00738 BTC`.
Starting capital is either taken from the `available_capital` setting, or calculated by using current wallet size - profits.
Profit Factor is calculated as gross profits / gross losses - and should serve as an overall metric for the strategy.
Expectancy corresponds to the average return per currency unit at risk, i.e. the winrate and the risk-reward ratio (the average gain of winning trades compared to the average loss of losing trades).
Expectancy Ratio is expected profit or loss of a subsequent trade based on the performance of all past trades.
Max drawdown corresponds to the backtesting metric `Absolute Drawdown (Account)` - calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`.
Bot started date will refer to the date the bot was first started. For older bots, this will default to the first trade's open date.
The relative profit of `1.2%` is the average profit per trade.
The relative profit of `15.2 Σ%` is be based on the starting capital - so in this case, the starting capital was `0.00485701 * 1.152 = 0.00738 BTC`.
**Starting capital(**) is either taken from the `available_capital` setting, or calculated by using current wallet size - profits.
**Profit Factor** is calculated as gross profits / gross losses - and should serve as an overall metric for the strategy.
**Expectancy** corresponds to the average return per currency unit at risk, i.e. the winrate and the risk-reward ratio (the average gain of winning trades compared to the average loss of losing trades).
**Expectancy Ratio** is expected profit or loss of a subsequent trade based on the performance of all past trades.
**Max drawdown** corresponds to the backtesting metric `Absolute Drawdown (Account)` - calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`.
**Bot started date** will refer to the date the bot was first started. For older bots, this will default to the first trade's open date.
### /forceexit <trade_id>
@@ -329,33 +352,34 @@ Note that for this to work, `force_entry_enable` needs to be set to true.
### /performance
Return the performance of each crypto-currency the bot has sold.
> Performance:
> 1. `RCN/BTC 0.003 BTC (57.77%) (1)`
> 2. `PAY/BTC 0.0012 BTC (56.91%) (1)`
> 3. `VIB/BTC 0.0011 BTC (47.07%) (1)`
> 4. `SALT/BTC 0.0010 BTC (30.24%) (1)`
> 5. `STORJ/BTC 0.0009 BTC (27.24%) (1)`
> ...
> Performance:
> 1. `RCN/BTC 0.003 BTC (57.77%) (1)`
> 2. `PAY/BTC 0.0012 BTC (56.91%) (1)`
> 3. `VIB/BTC 0.0011 BTC (47.07%) (1)`
> 4. `SALT/BTC 0.0010 BTC (30.24%) (1)`
> 5. `STORJ/BTC 0.0009 BTC (27.24%) (1)`
> ...
### /balance
Return the balance of all crypto-currency your have on the exchange.
> **Currency:** BTC
> **Available:** 3.05890234
> **Balance:** 3.05890234
> **Pending:** 0.0
> **Currency:** CVC
> **Available:** 86.64180098
> **Balance:** 86.64180098
> **Pending:** 0.0
> **Currency:** BTC
> **Available:** 3.05890234
> **Balance:** 3.05890234
> **Pending:** 0.0
>
> **Currency:** CVC
> **Available:** 86.64180098
> **Balance:** 86.64180098
> **Pending:** 0.0
### /daily <n>
Per default `/daily` will return the 7 last days. The example below if for `/daily 3`:
> **Daily Profit over the last 3 days:**
```
Day (count) USDT USD Profit %
-------------- ------------ ---------- ----------
@@ -370,6 +394,7 @@ Per default `/weekly` will return the 8 last weeks, including the current week.
from Monday. The example below if for `/weekly 3`:
> **Weekly Profit over the last 3 weeks (starting from Monday):**
```
Monday (count) Profit BTC Profit USD Profit %
------------- -------------- ------------ ----------
@@ -396,18 +421,18 @@ Month (count) Profit BTC Profit USD Profit %
Shows the current whitelist
> Using whitelist `StaticPairList` with 22 pairs
> Using whitelist `StaticPairList` with 22 pairs
> `IOTA/BTC, NEO/BTC, TRX/BTC, VET/BTC, ADA/BTC, ETC/BTC, NCASH/BTC, DASH/BTC, XRP/BTC, XVG/BTC, EOS/BTC, LTC/BTC, OMG/BTC, BTG/BTC, LSK/BTC, ZEC/BTC, HOT/BTC, IOTX/BTC, XMR/BTC, AST/BTC, XLM/BTC, NANO/BTC`
### /blacklist [pair]
Shows the current blacklist.
If Pair is set, then this pair will be added to the pairlist.
Also supports multiple pairs, separated by a space.
Also supports multiple pairs, separated by a space.
Use `/reload_config` to reset the blacklist.
> Using blacklist `StaticPairList` with 2 pairs
>`DODGE/BTC`, `HOT/BTC`.
> Using blacklist `StaticPairList` with 2 pairs
>`DODGE/BTC`, `HOT/BTC`.
### /edge
+1
View File
@@ -143,6 +143,7 @@ Most properties here can be None as they are dependent on the exchange response.
| `remaining` | float | Remaining amount |
| `cost` | float | Cost of the order - usually average * filled (*Exchange dependent on futures, may contain the cost with or without leverage and may be in contracts.*) |
| `stake_amount` | float | Stake amount used for this order. *Added in 2023.7.* |
| `stake_amount_filled` | float | Filled Stake amount used for this order. *Added in 2024.11.* |
| `order_date` | datetime | Order creation date **use `order_date_utc` instead** |
| `order_date_utc` | datetime | Order creation date (in UTC) |
| `order_fill_date` | datetime | Order fill date **use `order_fill_utc` instead** |
+1 -1
View File
@@ -1,6 +1,6 @@
"""Freqtrade bot"""
__version__ = "2024.10"
__version__ = "2024.12.1"
if "dev" in __version__:
from pathlib import Path
+6 -11
View File
@@ -5,7 +5,7 @@ This module contains the argument manager class
from argparse import ArgumentParser, Namespace, _ArgumentGroup
from functools import partial
from pathlib import Path
from typing import Any, Optional, Union
from typing import Any
from freqtrade.commands.cli_options import AVAILABLE_CLI_OPTIONS
from freqtrade.constants import DEFAULT_CONFIG
@@ -37,7 +37,6 @@ ARGS_COMMON_OPTIMIZE = [
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + [
"position_stacking",
"use_max_market_positions",
"enable_protections",
"dry_run_wallet",
"timeframe_detail",
@@ -53,7 +52,6 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + [
"hyperopt",
"hyperopt_path",
"position_stacking",
"use_max_market_positions",
"enable_protections",
"dry_run_wallet",
"timeframe_detail",
@@ -117,7 +115,7 @@ ARGS_CREATE_USERDIR = ["user_data_dir", "reset"]
ARGS_BUILD_CONFIG = ["config"]
ARGS_SHOW_CONFIG = ["user_data_dir", "config", "show_sensitive"]
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"]
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "strategy_path", "template"]
ARGS_CONVERT_DATA_TRADES = ["pairs", "format_from_trades", "format_to", "erase", "exchange"]
ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase", "exchange"]
@@ -242,8 +240,7 @@ ARGS_STRATEGY_UPDATER = ["strategy_list", "strategy_path", "recursive_strategy_s
ARGS_LOOKAHEAD_ANALYSIS = [
a
for a in ARGS_BACKTEST
if a
not in ("position_stacking", "use_max_market_positions", "backtest_cache", "backtest_breakdown")
if a not in ("position_stacking", "backtest_cache", "backtest_breakdown")
] + ["minimum_trade_amount", "targeted_trade_amount", "lookahead_analysis_exportfilename"]
ARGS_RECURSIVE_ANALYSIS = ["timeframe", "timerange", "dataformat_ohlcv", "pairs", "startup_candle"]
@@ -278,9 +275,9 @@ class Arguments:
Arguments Class. Manage the arguments received by the cli
"""
def __init__(self, args: Optional[list[str]]) -> None:
def __init__(self, args: list[str] | None) -> None:
self.args = args
self._parsed_arg: Optional[Namespace] = None
self._parsed_arg: Namespace | None = None
def get_parsed_arg(self) -> dict[str, Any]:
"""
@@ -322,9 +319,7 @@ class Arguments:
return parsed_arg
def _build_args(
self, optionlist: list[str], parser: Union[ArgumentParser, _ArgumentGroup]
) -> None:
def _build_args(self, optionlist: list[str], parser: ArgumentParser | _ArgumentGroup) -> None:
for val in optionlist:
opt = AVAILABLE_CLI_OPTIONS[val]
parser.add_argument(*opt.cli, dest=val, **opt.kwargs)
-8
View File
@@ -168,14 +168,6 @@ AVAILABLE_CLI_OPTIONS = {
action="store_true",
default=False,
),
"use_max_market_positions": Arg(
"--dmmp",
"--disable-max-market-positions",
help="Disable applying `max_open_trades` during backtest "
"(same as setting `max_open_trades` to a very high number).",
action="store_false",
default=True,
),
"backtest_show_pair_list": Arg(
"--show-pair-list",
help="Show backtesting pairlist sorted by profit.",
+8 -1
View File
@@ -86,7 +86,14 @@ def start_new_strategy(args: dict[str, Any]) -> None:
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
if "strategy" in args and args["strategy"]:
new_path = config["user_data_dir"] / USERPATH_STRATEGIES / (args["strategy"] + ".py")
if "strategy_path" in args and args["strategy_path"]:
strategy_dir = Path(args["strategy_path"])
else:
strategy_dir = config["user_data_dir"] / USERPATH_STRATEGIES
if not strategy_dir.is_dir():
logger.info(f"Creating strategy directory {strategy_dir}")
strategy_dir.mkdir(parents=True)
new_path = strategy_dir / (args["strategy"] + ".py")
if new_path.exists():
raise OperationalException(
+2 -3
View File
@@ -1,6 +1,5 @@
import logging
from pathlib import Path
from typing import Optional
import requests
@@ -24,7 +23,7 @@ def clean_ui_subdir(directory: Path):
p.rmdir()
def read_ui_version(dest_folder: Path) -> Optional[str]:
def read_ui_version(dest_folder: Path) -> str | None:
file = dest_folder / ".uiversion"
if not file.is_file():
return None
@@ -52,7 +51,7 @@ def download_and_install_ui(dest_folder: Path, dl_url: str, version: str):
f.write(version)
def get_ui_download_url(version: Optional[str] = None) -> tuple[str, str]:
def get_ui_download_url(version: str | None = None) -> tuple[str, str]:
base_url = "https://api.github.com/repos/freqtrade/frequi/"
# Get base UI Repo path
+1 -1
View File
@@ -15,7 +15,7 @@ def start_hyperopt_list(args: dict[str, Any]) -> None:
"""
from freqtrade.configuration import setup_utils_configuration
from freqtrade.data.btanalysis import get_latest_hyperopt_file
from freqtrade.optimize.hyperopt_output import HyperoptOutput
from freqtrade.optimize.hyperopt.hyperopt_output import HyperoptOutput
from freqtrade.optimize.hyperopt_tools import HyperoptTools
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
+2 -2
View File
@@ -1,7 +1,7 @@
import csv
import logging
import sys
from typing import Any, Union
from typing import Any
from freqtrade.enums import RunMode
from freqtrade.exceptions import ConfigurationError, OperationalException
@@ -87,7 +87,7 @@ def _print_objs_tabular(objs: list, print_colorized: bool) -> None:
from rich.text import Text
names = [s["name"] for s in objs]
objs_to_print: list[dict[str, Union[Text, str]]] = [
objs_to_print: list[dict[str, Text | str]] = [
{
"name": Text(s["name"] if s["name"] else "--"),
"location": s["location_rel"],
+2 -2
View File
@@ -17,7 +17,7 @@ def setup_optimize_configuration(args: dict[str, Any], method: RunMode) -> dict[
:return: Configuration
"""
from freqtrade.configuration import setup_utils_configuration
from freqtrade.util import fmt_coin
from freqtrade.util import fmt_coin, get_dry_run_wallet
config = setup_utils_configuration(args, method)
@@ -26,7 +26,7 @@ def setup_optimize_configuration(args: dict[str, Any], method: RunMode) -> dict[
RunMode.HYPEROPT: "hyperoptimization",
}
if method in no_unlimited_runmodes.keys():
wallet_size = config["dry_run_wallet"] * config["tradable_balance_ratio"]
wallet_size = get_dry_run_wallet(config) * config["tradable_balance_ratio"]
# tradable_balance_ratio
if (
config["stake_amount"] != constants.UNLIMITED_STAKE_AMOUNT
+13 -4
View File
@@ -85,8 +85,10 @@ CONF_SCHEMA = {
},
"dry_run_wallet": {
"description": "Initial wallet balance for dry run mode.",
"type": "number",
"type": ["number", "object"],
"default": DRY_RUN_WALLET,
"patternProperties": {r"^[a-zA-Z0-9]+$": {"type": "number"}},
"additionalProperties": False,
},
"cancel_open_orders_on_exit": {
"description": "Cancel open orders when exiting.",
@@ -458,7 +460,11 @@ CONF_SCHEMA = {
},
"token": {"description": "Telegram bot token.", "type": "string"},
"chat_id": {
"description": "Telegram chat ID",
"description": "Telegram chat or group ID",
"type": "string",
},
"topic_id": {
"description": "Telegram topic ID - only applicable for group chats",
"type": "string",
},
"allow_custom_messages": {
@@ -517,8 +523,11 @@ CONF_SCHEMA = {
},
"exit_fill": {
"description": "Telegram setting for exit fill signals.",
"type": "string",
"enum": TELEGRAM_SETTING_OPTIONS,
"type": ["string", "object"],
"additionalProperties": {
"type": "string",
"enum": TELEGRAM_SETTING_OPTIONS,
},
"default": "on",
},
"exit_cancel": {
+7 -10
View File
@@ -5,9 +5,10 @@ This module contains the configuration class
import ast
import logging
import warnings
from collections.abc import Callable
from copy import deepcopy
from pathlib import Path
from typing import Any, Callable, Optional
from typing import Any
from freqtrade import constants
from freqtrade.configuration.deprecated_settings import process_temporary_deprecated_settings
@@ -37,9 +38,9 @@ class Configuration:
Reuse this class for the bot, backtesting, hyperopt and every script that required configuration
"""
def __init__(self, args: dict[str, Any], runmode: Optional[RunMode] = None) -> None:
def __init__(self, args: dict[str, Any], runmode: RunMode | None = None) -> None:
self.args = args
self.config: Optional[Config] = None
self.config: Config | None = None
self.runmode = runmode
def get_config(self) -> Config:
@@ -241,11 +242,7 @@ class Configuration:
logstring="Parameter --enable-protections detected, enabling Protections. ...",
)
if "use_max_market_positions" in self.args and not self.args["use_max_market_positions"]:
config.update({"use_max_market_positions": False})
logger.info("Parameter --disable-max-market-positions detected ...")
logger.info("max_open_trades set to unlimited ...")
elif "max_open_trades" in self.args and self.args["max_open_trades"]:
if "max_open_trades" in self.args and self.args["max_open_trades"]:
config.update({"max_open_trades": self.args["max_open_trades"]})
logger.info(
"Parameter --max-open-trades detected, overriding max_open_trades to: %s ...",
@@ -455,8 +452,8 @@ class Configuration:
config: Config,
argname: str,
logstring: str,
logfun: Optional[Callable] = None,
deprecated_msg: Optional[str] = None,
logfun: Callable | None = None,
deprecated_msg: str | None = None,
) -> None:
"""
:param config: Configuration dictionary
@@ -3,7 +3,6 @@ Functions to handle deprecated settings
"""
import logging
from typing import Optional
from freqtrade.constants import Config
from freqtrade.exceptions import ConfigurationError, OperationalException
@@ -14,9 +13,9 @@ logger = logging.getLogger(__name__)
def check_conflicting_settings(
config: Config,
section_old: Optional[str],
section_old: str | None,
name_old: str,
section_new: Optional[str],
section_new: str | None,
name_new: str,
) -> None:
section_new_config = config.get(section_new, {}) if section_new else config
@@ -34,7 +33,7 @@ def check_conflicting_settings(
def process_removed_setting(
config: Config, section1: str, name1: str, section2: Optional[str], name2: str
config: Config, section1: str, name1: str, section2: str | None, name2: str
) -> None:
"""
:param section1: Removed section
@@ -54,9 +53,9 @@ def process_removed_setting(
def process_deprecated_setting(
config: Config,
section_old: Optional[str],
section_old: str | None,
name_old: str,
section_new: Optional[str],
section_new: str | None,
name_new: str,
) -> None:
check_conflicting_settings(config, section_old, name_old, section_new, name_new)
@@ -1,7 +1,6 @@
import logging
import shutil
from pathlib import Path
from typing import Optional
from freqtrade.configuration.detect_environment import running_in_docker
from freqtrade.constants import (
@@ -18,7 +17,7 @@ from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__)
def create_datadir(config: Config, datadir: Optional[str] = None) -> Path:
def create_datadir(config: Config, datadir: str | None = None) -> Path:
folder = Path(datadir) if datadir else Path(f"{config['user_data_dir']}/data")
if not datadir:
# set datadir
@@ -2,6 +2,8 @@ import logging
import os
from typing import Any
import rapidjson
from freqtrade.constants import ENV_VAR_PREFIX
from freqtrade.misc import deep_merge_dicts
@@ -20,6 +22,14 @@ def _get_var_typed(val):
return True
elif val.lower() in ("f", "false"):
return False
# try to convert from json
try:
value = rapidjson.loads(val)
# Limited to lists for now
if isinstance(value, list):
return value
except rapidjson.JSONDecodeError:
pass
# keep as string
return val
+2 -2
View File
@@ -7,7 +7,7 @@ import re
import sys
from copy import deepcopy
from pathlib import Path
from typing import Any, Optional
from typing import Any
import rapidjson
@@ -78,7 +78,7 @@ def load_config_file(path: str) -> dict[str, Any]:
def load_from_files(
files: list[str], base_path: Optional[Path] = None, level: int = 0
files: list[str], base_path: Path | None = None, level: int = 0
) -> dict[str, Any]:
"""
Recursively load configuration files if specified.
+7 -8
View File
@@ -5,7 +5,6 @@ This module contains the argument manager class
import logging
import re
from datetime import datetime, timezone
from typing import Optional
from typing_extensions import Self
@@ -25,24 +24,24 @@ class TimeRange:
def __init__(
self,
starttype: Optional[str] = None,
stoptype: Optional[str] = None,
starttype: str | None = None,
stoptype: str | None = None,
startts: int = 0,
stopts: int = 0,
):
self.starttype: Optional[str] = starttype
self.stoptype: Optional[str] = stoptype
self.starttype: str | None = starttype
self.stoptype: str | None = stoptype
self.startts: int = startts
self.stopts: int = stopts
@property
def startdt(self) -> Optional[datetime]:
def startdt(self) -> datetime | None:
if self.startts:
return datetime.fromtimestamp(self.startts, tz=timezone.utc)
return None
@property
def stopdt(self) -> Optional[datetime]:
def stopdt(self) -> datetime | None:
if self.stopts:
return datetime.fromtimestamp(self.stopts, tz=timezone.utc)
return None
@@ -120,7 +119,7 @@ class TimeRange:
self.starttype = "date"
@classmethod
def parse_timerange(cls, text: Optional[str]) -> Self:
def parse_timerange(cls, text: str | None) -> Self:
"""
Parse the value of the argument --timerange to determine what is the range desired
:param text: value from --timerange
+4 -3
View File
@@ -4,7 +4,7 @@
bot constants
"""
from typing import Any, Literal, Optional
from typing import Any, Literal
from freqtrade.enums import CandleType, PriceType
@@ -38,6 +38,7 @@ HYPEROPT_LOSS_BUILTIN = [
"MaxDrawDownHyperOptLoss",
"MaxDrawDownRelativeHyperOptLoss",
"ProfitDrawDownHyperOptLoss",
"MultiMetricHyperOptLoss",
]
AVAILABLE_PAIRLISTS = [
"StaticPairList",
@@ -97,7 +98,7 @@ DL_DATA_TIMEFRAMES = ["1m", "5m"]
ENV_VAR_PREFIX = "FREQTRADE__"
CANCELED_EXCHANGE_STATES = ("cancelled", "canceled", "expired")
CANCELED_EXCHANGE_STATES = ("cancelled", "canceled", "expired", "rejected")
NON_OPEN_EXCHANGE_STATES = CANCELED_EXCHANGE_STATES + ("closed",)
# Define decimals per coin for outputs
@@ -193,7 +194,7 @@ ListPairsWithTimeframes = list[PairWithTimeframe]
# Type for trades list
TradeList = list[list]
# ticks, pair, timeframe, CandleType
TickWithTimeframe = tuple[str, str, CandleType, Optional[int], Optional[int]]
TickWithTimeframe = tuple[str, str, CandleType, int | None, int | None]
ListTicksWithTimeframes = list[TickWithTimeframe]
LongShort = Literal["long", "short"]
+12 -14
View File
@@ -6,7 +6,7 @@ import logging
from copy import copy
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Literal, Optional, Union
from typing import Any, Literal
import numpy as np
import pandas as pd
@@ -53,7 +53,7 @@ BT_DATA_COLUMNS = [
]
def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str:
def get_latest_optimize_filename(directory: Path | str, variant: str) -> str:
"""
Get latest backtest export based on '.last_result.json'.
:param directory: Directory to search for last result
@@ -84,7 +84,7 @@ def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> s
return data[f"latest_{variant}"]
def get_latest_backtest_filename(directory: Union[Path, str]) -> str:
def get_latest_backtest_filename(directory: Path | str) -> str:
"""
Get latest backtest export based on '.last_result.json'.
:param directory: Directory to search for last result
@@ -97,7 +97,7 @@ def get_latest_backtest_filename(directory: Union[Path, str]) -> str:
return get_latest_optimize_filename(directory, "backtest")
def get_latest_hyperopt_filename(directory: Union[Path, str]) -> str:
def get_latest_hyperopt_filename(directory: Path | str) -> str:
"""
Get latest hyperopt export based on '.last_result.json'.
:param directory: Directory to search for last result
@@ -114,9 +114,7 @@ def get_latest_hyperopt_filename(directory: Union[Path, str]) -> str:
return "hyperopt_results.pickle"
def get_latest_hyperopt_file(
directory: Union[Path, str], predef_filename: Optional[str] = None
) -> Path:
def get_latest_hyperopt_file(directory: Path | str, predef_filename: str | None = None) -> Path:
"""
Get latest hyperopt export based on '.last_result.json'.
:param directory: Directory to search for last result
@@ -137,7 +135,7 @@ def get_latest_hyperopt_file(
return directory / get_latest_hyperopt_filename(directory)
def load_backtest_metadata(filename: Union[Path, str]) -> dict[str, Any]:
def load_backtest_metadata(filename: Path | str) -> dict[str, Any]:
"""
Read metadata dictionary from backtest results file without reading and deserializing entire
file.
@@ -154,7 +152,7 @@ def load_backtest_metadata(filename: Union[Path, str]) -> dict[str, Any]:
raise OperationalException("Unexpected error while loading backtest metadata.") from e
def load_backtest_stats(filename: Union[Path, str]) -> BacktestResultType:
def load_backtest_stats(filename: Path | str) -> BacktestResultType:
"""
Load backtest statistics file.
:param filename: pathlib.Path object, or string pointing to the file.
@@ -276,7 +274,7 @@ def get_backtest_market_change(filename: Path, include_ts: bool = True) -> pd.Da
def find_existing_backtest_stats(
dirname: Union[Path, str], run_ids: dict[str, str], min_backtest_date: Optional[datetime] = None
dirname: Path | str, run_ids: dict[str, str], min_backtest_date: datetime | None = None
) -> dict[str, Any]:
"""
Find existing backtest stats that match specified run IDs and load them.
@@ -345,7 +343,7 @@ def _load_backtest_data_df_compatibility(df: pd.DataFrame) -> pd.DataFrame:
return df
def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = None) -> pd.DataFrame:
def load_backtest_data(filename: Path | str, strategy: str | None = None) -> pd.DataFrame:
"""
Load backtest data file.
:param filename: pathlib.Path object, or string pointing to a file or directory
@@ -439,7 +437,7 @@ def evaluate_result_multi(
return df_final[df_final["open_trades"] > max_open_trades]
def trade_list_to_dataframe(trades: Union[list[Trade], list[LocalTrade]]) -> pd.DataFrame:
def trade_list_to_dataframe(trades: list[Trade] | list[LocalTrade]) -> pd.DataFrame:
"""
Convert list of Trade objects to pandas Dataframe
:param trades: List of trade objects
@@ -453,7 +451,7 @@ def trade_list_to_dataframe(trades: Union[list[Trade], list[LocalTrade]]) -> pd.
return df
def load_trades_from_db(db_url: str, strategy: Optional[str] = None) -> pd.DataFrame:
def load_trades_from_db(db_url: str, strategy: str | None = None) -> pd.DataFrame:
"""
Load trades from a DB (using dburl)
:param db_url: Sqlite url (default format sqlite:///tradesv3.dry-run.sqlite)
@@ -476,7 +474,7 @@ def load_trades(
db_url: str,
exportfilename: Path,
no_trades: bool = False,
strategy: Optional[str] = None,
strategy: str | None = None,
) -> pd.DataFrame:
"""
Based on configuration option 'trade_source':
+95 -102
View File
@@ -4,20 +4,31 @@ Functions to convert orderflow data from public_trades
import logging
import time
import typing
from collections import OrderedDict
from datetime import datetime
import numpy as np
import pandas as pd
from freqtrade.constants import DEFAULT_ORDERFLOW_COLUMNS, Config
from freqtrade.enums import RunMode
from freqtrade.exceptions import DependencyException
logger = logging.getLogger(__name__)
ORDERFLOW_ADDED_COLUMNS = [
"trades",
"orderflow",
"imbalances",
"stacked_imbalances_bid",
"stacked_imbalances_ask",
"max_delta",
"min_delta",
"bid",
"ask",
"delta",
"total_trades",
]
def _init_dataframe_with_trades_columns(dataframe: pd.DataFrame):
"""
@@ -25,53 +36,73 @@ def _init_dataframe_with_trades_columns(dataframe: pd.DataFrame):
:param dataframe: Dataframe to populate
"""
# Initialize columns with appropriate dtypes
dataframe["trades"] = np.nan
dataframe["orderflow"] = np.nan
dataframe["imbalances"] = np.nan
dataframe["stacked_imbalances_bid"] = np.nan
dataframe["stacked_imbalances_ask"] = np.nan
dataframe["max_delta"] = np.nan
dataframe["min_delta"] = np.nan
dataframe["bid"] = np.nan
dataframe["ask"] = np.nan
dataframe["delta"] = np.nan
dataframe["total_trades"] = np.nan
for column in ORDERFLOW_ADDED_COLUMNS:
dataframe[column] = np.nan
# Ensure the 'trades' column is of object type
dataframe["trades"] = dataframe["trades"].astype(object)
dataframe["orderflow"] = dataframe["orderflow"].astype(object)
dataframe["imbalances"] = dataframe["imbalances"].astype(object)
dataframe["stacked_imbalances_bid"] = dataframe["stacked_imbalances_bid"].astype(object)
dataframe["stacked_imbalances_ask"] = dataframe["stacked_imbalances_ask"].astype(object)
# Set columns to object type
for column in (
"trades",
"orderflow",
"imbalances",
"stacked_imbalances_bid",
"stacked_imbalances_ask",
):
dataframe[column] = dataframe[column].astype(object)
def timeframe_to_DateOffset(timeframe: str) -> pd.DateOffset:
"""
Translates the timeframe interval value written in the human readable
form ('1m', '5m', '1h', '1d', '1w', etc.) to the number
of seconds for one timeframe interval.
"""
from freqtrade.exchange import timeframe_to_seconds
timeframe_seconds = timeframe_to_seconds(timeframe)
timeframe_minutes = timeframe_seconds // 60
if timeframe_minutes < 1:
return pd.DateOffset(seconds=timeframe_seconds)
elif 59 < timeframe_minutes < 1440:
return pd.DateOffset(hours=timeframe_minutes // 60)
elif 1440 <= timeframe_minutes < 10080:
return pd.DateOffset(days=timeframe_minutes // 1440)
elif 10000 < timeframe_minutes < 43200:
return pd.DateOffset(weeks=1)
elif timeframe_minutes >= 43200 and timeframe_minutes < 525600:
return pd.DateOffset(months=1)
elif timeframe == "1y":
return pd.DateOffset(years=1)
else:
return pd.DateOffset(minutes=timeframe_minutes)
def _calculate_ohlcv_candle_start_and_end(df: pd.DataFrame, timeframe: str):
from freqtrade.exchange import timeframe_to_next_date, timeframe_to_resample_freq
from freqtrade.exchange import timeframe_to_resample_freq
timeframe_frequency = timeframe_to_resample_freq(timeframe)
# calculate ohlcv candle start and end
if df is not None and not df.empty:
timeframe_frequency = timeframe_to_resample_freq(timeframe)
dofs = timeframe_to_DateOffset(timeframe)
# calculate ohlcv candle start and end
df["datetime"] = pd.to_datetime(df["date"], unit="ms")
df["candle_start"] = df["datetime"].dt.floor(timeframe_frequency)
# used in _now_is_time_to_refresh_trades
df["candle_end"] = df["candle_start"].apply(
lambda candle_start: timeframe_to_next_date(timeframe, candle_start)
)
df["candle_end"] = df["candle_start"] + dofs
df.drop(columns=["datetime"], inplace=True)
def populate_dataframe_with_trades(
cached_grouped_trades: OrderedDict[tuple[datetime, datetime], pd.DataFrame],
cached_grouped_trades: pd.DataFrame | None,
config: Config,
dataframe: pd.DataFrame,
trades: pd.DataFrame,
) -> tuple[pd.DataFrame, OrderedDict[tuple[datetime, datetime], pd.DataFrame]]:
) -> tuple[pd.DataFrame, pd.DataFrame]:
"""
Populates a dataframe with trades
:param dataframe: Dataframe to populate
:param trades: Trades to populate with
:return: Dataframe with trades populated
"""
timeframe = config["timeframe"]
config_orderflow = config["orderflow"]
@@ -94,71 +125,52 @@ def populate_dataframe_with_trades(
# group trades by candle start
trades_grouped_by_candle_start = trades.groupby("candle_start", group_keys=False)
# Create Series to hold complex data
trades_series = pd.Series(index=dataframe.index, dtype=object)
orderflow_series = pd.Series(index=dataframe.index, dtype=object)
imbalances_series = pd.Series(index=dataframe.index, dtype=object)
stacked_imbalances_bid_series = pd.Series(index=dataframe.index, dtype=object)
stacked_imbalances_ask_series = pd.Series(index=dataframe.index, dtype=object)
trades_grouped_by_candle_start = trades.groupby("candle_start", group_keys=False)
candle_start: datetime
for candle_start, trades_grouped_df in trades_grouped_by_candle_start:
is_between = candle_start == dataframe["date"]
if is_between.any():
from freqtrade.exchange import timeframe_to_next_date
# there can only be one row with the same date
index = dataframe.index[is_between][0]
candle_next = timeframe_to_next_date(timeframe, typing.cast(datetime, candle_start))
if candle_next not in trades_grouped_by_candle_start.groups:
logger.warning(
f"candle at {candle_start} with {len(trades_grouped_df)} trades "
f"might be unfinished, because no finished trades at {candle_next}"
)
indices = dataframe.index[is_between].tolist()
# Add trades to each candle
trades_series.loc[indices] = [
trades_grouped_df.drop(columns=["candle_start", "candle_end"]).to_dict(
orient="records"
)
]
# Use caching mechanism
if (candle_start, candle_next) in cached_grouped_trades:
cache_entry = cached_grouped_trades[
(typing.cast(datetime, candle_start), candle_next)
]
# dataframe.loc[is_between] = cache_entry # doesn't take, so we need workaround:
# Create a dictionary of the column values to be assigned
update_dict = {c: cache_entry[c].iat[0] for c in cache_entry.columns}
# Assign the values using the update_dict
dataframe.loc[is_between, update_dict.keys()] = pd.DataFrame(
[update_dict], index=dataframe.loc[is_between].index
)
if (
cached_grouped_trades is not None
and (candle_start == cached_grouped_trades["date"]).any()
):
# Check if the trades are already in the cache
cache_idx = cached_grouped_trades.index[
cached_grouped_trades["date"] == candle_start
][0]
for col in ORDERFLOW_ADDED_COLUMNS:
dataframe.at[index, col] = cached_grouped_trades.at[cache_idx, col]
continue
dataframe.at[index, "trades"] = trades_grouped_df.drop(
columns=["candle_start", "candle_end"]
).to_dict(orient="records")
# Calculate orderflow for each candle
orderflow = trades_to_volumeprofile_with_total_delta_bid_ask(
trades_grouped_df, scale=config_orderflow["scale"]
)
orderflow_series.loc[indices] = [orderflow.to_dict(orient="index")]
dataframe.at[index, "orderflow"] = orderflow.to_dict(orient="index")
# orderflow_series.loc[[index]] = [orderflow.to_dict(orient="index")]
# Calculate imbalances for each candle's orderflow
imbalances = trades_orderflow_to_imbalances(
orderflow,
imbalance_ratio=config_orderflow["imbalance_ratio"],
imbalance_volume=config_orderflow["imbalance_volume"],
)
imbalances_series.loc[indices] = [imbalances.to_dict(orient="index")]
dataframe.at[index, "imbalances"] = imbalances.to_dict(orient="index")
stacked_imbalance_range = config_orderflow["stacked_imbalance_range"]
stacked_imbalances_bid_series.loc[indices] = [
stacked_imbalance_bid(
imbalances, stacked_imbalance_range=stacked_imbalance_range
)
]
stacked_imbalances_ask_series.loc[indices] = [
stacked_imbalance_ask(
imbalances, stacked_imbalance_range=stacked_imbalance_range
)
]
dataframe.at[index, "stacked_imbalances_bid"] = stacked_imbalance_bid(
imbalances, stacked_imbalance_range=stacked_imbalance_range
)
dataframe.at[index, "stacked_imbalances_ask"] = stacked_imbalance_ask(
imbalances, stacked_imbalance_range=stacked_imbalance_range
)
bid = np.where(
trades_grouped_df["side"].str.contains("sell"), trades_grouped_df["amount"], 0
@@ -168,39 +180,20 @@ def populate_dataframe_with_trades(
trades_grouped_df["side"].str.contains("buy"), trades_grouped_df["amount"], 0
)
deltas_per_trade = ask - bid
min_delta = deltas_per_trade.cumsum().min()
max_delta = deltas_per_trade.cumsum().max()
dataframe.loc[indices, "max_delta"] = max_delta
dataframe.loc[indices, "min_delta"] = min_delta
dataframe.at[index, "max_delta"] = deltas_per_trade.cumsum().max()
dataframe.at[index, "min_delta"] = deltas_per_trade.cumsum().min()
dataframe.loc[indices, "bid"] = bid.sum()
dataframe.loc[indices, "ask"] = ask.sum()
dataframe.loc[indices, "delta"] = (
dataframe.loc[indices, "ask"] - dataframe.loc[indices, "bid"]
dataframe.at[index, "bid"] = bid.sum()
dataframe.at[index, "ask"] = ask.sum()
dataframe.at[index, "delta"] = (
dataframe.at[index, "ask"] - dataframe.at[index, "bid"]
)
dataframe.loc[indices, "total_trades"] = len(trades_grouped_df)
dataframe.at[index, "total_trades"] = len(trades_grouped_df)
# Cache the result
cached_grouped_trades[(typing.cast(datetime, candle_start), candle_next)] = (
dataframe.loc[is_between].copy()
)
# Maintain cache size
if (
config.get("runmode") in (RunMode.DRY_RUN, RunMode.LIVE)
and len(cached_grouped_trades) > config_orderflow["cache_size"]
):
cached_grouped_trades.popitem(last=False)
else:
logger.debug(f"Found NO candles for trades starting with {candle_start}")
logger.debug(f"trades.groups_keys in {time.time() - start_time} seconds")
# Merge the complex data Series back into the DataFrame
dataframe["trades"] = trades_series
dataframe["orderflow"] = orderflow_series
dataframe["imbalances"] = imbalances_series
dataframe["stacked_imbalances_bid"] = stacked_imbalances_bid_series
dataframe["stacked_imbalances_ask"] = stacked_imbalances_ask_series
# Cache the entire dataframe
cached_grouped_trades = dataframe.tail(config_orderflow["cache_size"]).copy()
except Exception as e:
logger.exception("Error populating dataframe with trades")
+12 -12
View File
@@ -8,7 +8,7 @@ Common Interface for bot and strategy to access data.
import logging
from collections import deque
from datetime import datetime, timezone
from typing import Any, Optional
from typing import Any
from pandas import DataFrame, Timedelta, Timestamp, to_timedelta
@@ -40,17 +40,17 @@ class DataProvider:
def __init__(
self,
config: Config,
exchange: Optional[Exchange],
exchange: Exchange | None,
pairlists=None,
rpc: Optional[RPCManager] = None,
rpc: RPCManager | None = None,
) -> None:
self._config = config
self._exchange = exchange
self._pairlists = pairlists
self.__rpc = rpc
self.__cached_pairs: dict[PairWithTimeframe, tuple[DataFrame, datetime]] = {}
self.__slice_index: Optional[int] = None
self.__slice_date: Optional[datetime] = None
self.__slice_index: int | None = None
self.__slice_date: datetime | None = None
self.__cached_pairs_backtesting: dict[PairWithTimeframe, DataFrame] = {}
self.__producer_pairs_df: dict[
@@ -255,8 +255,8 @@ class DataProvider:
def get_producer_df(
self,
pair: str,
timeframe: Optional[str] = None,
candle_type: Optional[CandleType] = None,
timeframe: str | None = None,
candle_type: CandleType | None = None,
producer_name: str = "default",
) -> tuple[DataFrame, datetime]:
"""
@@ -349,7 +349,7 @@ class DataProvider:
return total_candles
def get_pair_dataframe(
self, pair: str, timeframe: Optional[str] = None, candle_type: str = ""
self, pair: str, timeframe: str | None = None, candle_type: str = ""
) -> DataFrame:
"""
Return pair candle (OHLCV) data, either live or cached historical -- depending
@@ -437,7 +437,7 @@ class DataProvider:
def refresh(
self,
pairlist: ListPairsWithTimeframes,
helping_pairs: Optional[ListPairsWithTimeframes] = None,
helping_pairs: ListPairsWithTimeframes | None = None,
) -> None:
"""
Refresh data, called with each cycle
@@ -471,7 +471,7 @@ class DataProvider:
return list(self._exchange._klines.keys())
def ohlcv(
self, pair: str, timeframe: Optional[str] = None, copy: bool = True, candle_type: str = ""
self, pair: str, timeframe: str | None = None, copy: bool = True, candle_type: str = ""
) -> DataFrame:
"""
Get candle (OHLCV) data for the given pair as DataFrame
@@ -497,7 +497,7 @@ class DataProvider:
return DataFrame()
def trades(
self, pair: str, timeframe: Optional[str] = None, copy: bool = True, candle_type: str = ""
self, pair: str, timeframe: str | None = None, copy: bool = True, candle_type: str = ""
) -> DataFrame:
"""
Get candle (TRADES) data for the given pair as DataFrame
@@ -529,7 +529,7 @@ class DataProvider:
)
return trades_df
def market(self, pair: str) -> Optional[dict[str, Any]]:
def market(self, pair: str) -> dict[str, Any] | None:
"""
Return market data for the pair
:param pair: Pair to get the data for
@@ -1,5 +1,4 @@
import logging
from typing import Optional
from pandas import DataFrame, read_feather, to_datetime
@@ -37,7 +36,7 @@ class FeatherDataHandler(IDataHandler):
)
def _ohlcv_load(
self, pair: str, timeframe: str, timerange: Optional[TimeRange], candle_type: CandleType
self, pair: str, timeframe: str, timerange: TimeRange | None, candle_type: CandleType
) -> DataFrame:
"""
Internal method used to load data for one pair from disk.
@@ -59,20 +58,25 @@ class FeatherDataHandler(IDataHandler):
)
if not filename.exists():
return DataFrame(columns=self._columns)
pairdata = read_feather(filename)
pairdata.columns = self._columns
pairdata = pairdata.astype(
dtype={
"open": "float",
"high": "float",
"low": "float",
"close": "float",
"volume": "float",
}
)
pairdata["date"] = to_datetime(pairdata["date"], unit="ms", utc=True)
return pairdata
try:
pairdata = read_feather(filename)
pairdata.columns = self._columns
pairdata = pairdata.astype(
dtype={
"open": "float",
"high": "float",
"low": "float",
"close": "float",
"volume": "float",
}
)
pairdata["date"] = to_datetime(pairdata["date"], unit="ms", utc=True)
return pairdata
except Exception as e:
logger.exception(
f"Error loading data from {filename}. Exception: {e}. Returning empty dataframe."
)
return DataFrame(columns=self._columns)
def ohlcv_append(
self, pair: str, timeframe: str, data: DataFrame, candle_type: CandleType
@@ -108,7 +112,7 @@ class FeatherDataHandler(IDataHandler):
raise NotImplementedError()
def _trades_load(
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> DataFrame:
"""
Load a pair from file, either .json.gz or .json
@@ -1,5 +1,4 @@
import logging
from typing import Optional
import numpy as np
import pandas as pd
@@ -45,7 +44,7 @@ class HDF5DataHandler(IDataHandler):
)
def _ohlcv_load(
self, pair: str, timeframe: str, timerange: Optional[TimeRange], candle_type: CandleType
self, pair: str, timeframe: str, timerange: TimeRange | None, candle_type: CandleType
) -> pd.DataFrame:
"""
Internal method used to load data for one pair from disk.
@@ -69,28 +68,36 @@ class HDF5DataHandler(IDataHandler):
)
if not filename.exists():
return pd.DataFrame(columns=self._columns)
where = []
if timerange:
if timerange.starttype == "date":
where.append(f"date >= Timestamp({timerange.startts * 1e9})")
if timerange.stoptype == "date":
where.append(f"date <= Timestamp({timerange.stopts * 1e9})")
try:
where = []
if timerange:
if timerange.starttype == "date":
where.append(f"date >= Timestamp({timerange.startts * 1e9})")
if timerange.stoptype == "date":
where.append(f"date <= Timestamp({timerange.stopts * 1e9})")
pairdata = pd.read_hdf(filename, key=key, mode="r", where=where)
pairdata = pd.read_hdf(filename, key=key, mode="r", where=where)
if list(pairdata.columns) != self._columns:
raise ValueError("Wrong dataframe format")
pairdata = pairdata.astype(
dtype={
"open": "float",
"high": "float",
"low": "float",
"close": "float",
"volume": "float",
}
)
pairdata = pairdata.reset_index(drop=True)
return pairdata
if list(pairdata.columns) != self._columns:
raise ValueError("Wrong dataframe format")
pairdata = pairdata.astype(
dtype={
"open": "float",
"high": "float",
"low": "float",
"close": "float",
"volume": "float",
}
)
pairdata = pairdata.reset_index(drop=True)
return pairdata
except ValueError:
raise
except Exception as e:
logger.exception(
f"Error loading data from {filename}. Exception: {e}. Returning empty dataframe."
)
return pd.DataFrame(columns=self._columns)
def ohlcv_append(
self, pair: str, timeframe: str, data: pd.DataFrame, candle_type: CandleType
@@ -134,7 +141,7 @@ class HDF5DataHandler(IDataHandler):
raise NotImplementedError()
def _trades_load(
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> pd.DataFrame:
"""
Load a pair from h5 file.
@@ -10,7 +10,6 @@ from abc import ABC, abstractmethod
from copy import deepcopy
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
from pandas import DataFrame, to_datetime
@@ -126,7 +125,7 @@ class IDataHandler(ABC):
@abstractmethod
def _ohlcv_load(
self, pair: str, timeframe: str, timerange: Optional[TimeRange], candle_type: CandleType
self, pair: str, timeframe: str, timerange: TimeRange | None, candle_type: CandleType
) -> DataFrame:
"""
Internal method used to load data for one pair from disk.
@@ -247,7 +246,7 @@ class IDataHandler(ABC):
@abstractmethod
def _trades_load(
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> DataFrame:
"""
Load a pair from file, either .json.gz or .json
@@ -282,7 +281,7 @@ class IDataHandler(ABC):
return False
def trades_load(
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> DataFrame:
"""
Load a pair from file, either .json.gz or .json
@@ -370,7 +369,7 @@ class IDataHandler(ABC):
timeframe: str,
candle_type: CandleType,
*,
timerange: Optional[TimeRange] = None,
timerange: TimeRange | None = None,
fill_missing: bool = True,
drop_incomplete: bool = False,
startup_candles: int = 0,
@@ -552,6 +551,13 @@ def get_datahandlerclass(datatype: str) -> type[IDataHandler]:
elif datatype == "hdf5":
from .hdf5datahandler import HDF5DataHandler
logger.warning(
"DEPRECATED: The hdf5 dataformat is deprecated and will be removed in the "
"next release. "
"Please use the convert-data command to convert your data to a supported format."
"We recommend using the feather format, as it is faster and is more space-efficient."
)
return HDF5DataHandler
elif datatype == "feather":
from .featherdatahandler import FeatherDataHandler
@@ -566,7 +572,7 @@ def get_datahandlerclass(datatype: str) -> type[IDataHandler]:
def get_datahandler(
datadir: Path, data_format: Optional[str] = None, data_handler: Optional[IDataHandler] = None
datadir: Path, data_format: str | None = None, data_handler: IDataHandler | None = None
) -> IDataHandler:
"""
:param datadir: Folder to save data
@@ -1,5 +1,4 @@
import logging
from typing import Optional
import numpy as np
from pandas import DataFrame, read_json, to_datetime
@@ -45,7 +44,7 @@ class JsonDataHandler(IDataHandler):
)
def _ohlcv_load(
self, pair: str, timeframe: str, timerange: Optional[TimeRange], candle_type: CandleType
self, pair: str, timeframe: str, timerange: TimeRange | None, candle_type: CandleType
) -> DataFrame:
"""
Internal method used to load data for one pair from disk.
@@ -119,7 +118,7 @@ class JsonDataHandler(IDataHandler):
raise NotImplementedError()
def _trades_load(
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> DataFrame:
"""
Load a pair from file, either .json.gz or .json
@@ -1,5 +1,4 @@
import logging
from typing import Optional
from pandas import DataFrame, read_parquet, to_datetime
@@ -35,7 +34,7 @@ class ParquetDataHandler(IDataHandler):
data.reset_index(drop=True).loc[:, self._columns].to_parquet(filename)
def _ohlcv_load(
self, pair: str, timeframe: str, timerange: Optional[TimeRange], candle_type: CandleType
self, pair: str, timeframe: str, timerange: TimeRange | None, candle_type: CandleType
) -> DataFrame:
"""
Internal method used to load data for one pair from disk.
@@ -57,20 +56,25 @@ class ParquetDataHandler(IDataHandler):
)
if not filename.exists():
return DataFrame(columns=self._columns)
pairdata = read_parquet(filename)
pairdata.columns = self._columns
pairdata = pairdata.astype(
dtype={
"open": "float",
"high": "float",
"low": "float",
"close": "float",
"volume": "float",
}
)
pairdata["date"] = to_datetime(pairdata["date"], unit="ms", utc=True)
return pairdata
try:
pairdata = read_parquet(filename)
pairdata.columns = self._columns
pairdata = pairdata.astype(
dtype={
"open": "float",
"high": "float",
"low": "float",
"close": "float",
"volume": "float",
}
)
pairdata["date"] = to_datetime(pairdata["date"], unit="ms", utc=True)
return pairdata
except Exception as e:
logger.exception(
f"Error loading data from {filename}. Exception: {e}. Returning empty dataframe."
)
return DataFrame(columns=self._columns)
def ohlcv_append(
self, pair: str, timeframe: str, data: DataFrame, candle_type: CandleType
@@ -106,7 +110,7 @@ class ParquetDataHandler(IDataHandler):
raise NotImplementedError()
def _trades_load(
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> DataFrame:
"""
Load a pair from file, either .json.gz or .json
+146 -120
View File
@@ -2,18 +2,11 @@ import logging
import operator
from datetime import datetime, timedelta
from pathlib import Path
from typing import Optional
from pandas import DataFrame, concat
from freqtrade.configuration import TimeRange
from freqtrade.constants import (
DATETIME_PRINT_FORMAT,
DEFAULT_DATAFRAME_COLUMNS,
DL_DATA_TIMEFRAMES,
DOCS_LINK,
Config,
)
from freqtrade.constants import DATETIME_PRINT_FORMAT, DL_DATA_TIMEFRAMES, DOCS_LINK, Config
from freqtrade.data.converter import (
clean_ohlcv_dataframe,
convert_trades_to_ohlcv,
@@ -25,8 +18,9 @@ from freqtrade.enums import CandleType, TradingMode
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import Exchange
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
from freqtrade.util import dt_now, dt_ts, format_ms_time, get_progress_tracker
from freqtrade.util import dt_now, dt_ts, format_ms_time
from freqtrade.util.migrations import migrate_data
from freqtrade.util.progress_tracker import CustomProgress, retrieve_progress_tracker
logger = logging.getLogger(__name__)
@@ -37,12 +31,12 @@ def load_pair_history(
timeframe: str,
datadir: Path,
*,
timerange: Optional[TimeRange] = None,
timerange: TimeRange | None = None,
fill_up_missing: bool = True,
drop_incomplete: bool = False,
startup_candles: int = 0,
data_format: Optional[str] = None,
data_handler: Optional[IDataHandler] = None,
data_format: str | None = None,
data_handler: IDataHandler | None = None,
candle_type: CandleType = CandleType.SPOT,
) -> DataFrame:
"""
@@ -79,13 +73,13 @@ def load_data(
timeframe: str,
pairs: list[str],
*,
timerange: Optional[TimeRange] = None,
timerange: TimeRange | None = None,
fill_up_missing: bool = True,
startup_candles: int = 0,
fail_without_data: bool = False,
data_format: str = "feather",
candle_type: CandleType = CandleType.SPOT,
user_futures_funding_rate: Optional[int] = None,
user_futures_funding_rate: int | None = None,
) -> dict[str, DataFrame]:
"""
Load ohlcv history data for a list of pairs.
@@ -137,8 +131,8 @@ def refresh_data(
timeframe: str,
pairs: list[str],
exchange: Exchange,
data_format: Optional[str] = None,
timerange: Optional[TimeRange] = None,
data_format: str | None = None,
timerange: TimeRange | None = None,
candle_type: CandleType,
) -> None:
"""
@@ -168,11 +162,11 @@ def refresh_data(
def _load_cached_data_for_updating(
pair: str,
timeframe: str,
timerange: Optional[TimeRange],
timerange: TimeRange | None,
data_handler: IDataHandler,
candle_type: CandleType,
prepend: bool = False,
) -> tuple[DataFrame, Optional[int], Optional[int]]:
) -> tuple[DataFrame, int | None, int | None]:
"""
Load cached data to download more data.
If timerange is passed in, checks whether data from an before the stored data will be
@@ -200,14 +194,21 @@ def _load_cached_data_for_updating(
candle_type=candle_type,
)
if not data.empty:
if not prepend and start and start < data.iloc[0]["date"]:
# Earlier data than existing data requested, redownload all
data = DataFrame(columns=DEFAULT_DATAFRAME_COLUMNS)
if prepend:
end = data.iloc[0]["date"]
else:
if prepend:
end = data.iloc[0]["date"]
else:
start = data.iloc[-1]["date"]
if start and start < data.iloc[0]["date"]:
# Earlier data than existing data requested, Update start date
logger.info(
f"{pair}, {timeframe}, {candle_type}: "
f"Requested start date {start:{DATETIME_PRINT_FORMAT}} earlier than local "
f"data start date {data.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}. "
f"Use `--prepend` to download data prior "
f"to {data.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}, or "
"`--erase` to redownload all data."
)
start = data.iloc[-1]["date"]
start_ms = int(start.timestamp() * 1000) if start else None
end_ms = int(end.timestamp() * 1000) if end else None
return data, start_ms, end_ms
@@ -220,8 +221,8 @@ def _download_pair_history(
exchange: Exchange,
timeframe: str = "5m",
new_pairs_days: int = 30,
data_handler: Optional[IDataHandler] = None,
timerange: Optional[TimeRange] = None,
data_handler: IDataHandler | None = None,
timerange: TimeRange | None = None,
candle_type: CandleType,
erase: bool = False,
prepend: bool = False,
@@ -284,6 +285,7 @@ def _download_pair_history(
candle_type=candle_type,
until_ms=until_ms if until_ms else None,
)
logger.info(f"Downloaded data for {pair} with length {len(new_dataframe)}.")
if data.empty:
data = new_dataframe
else:
@@ -322,21 +324,24 @@ def refresh_backtest_ohlcv_data(
timeframes: list[str],
datadir: Path,
trading_mode: str,
timerange: Optional[TimeRange] = None,
timerange: TimeRange | None = None,
new_pairs_days: int = 30,
erase: bool = False,
data_format: Optional[str] = None,
data_format: str | None = None,
prepend: bool = False,
progress_tracker: CustomProgress | None = None,
) -> list[str]:
"""
Refresh stored ohlcv data for backtesting and hyperopt operations.
Used by freqtrade download-data subcommand.
:return: List of pairs that are not available.
"""
progress_tracker = retrieve_progress_tracker(progress_tracker)
pairs_not_available = []
data_handler = get_datahandler(datadir, data_format)
candle_type = CandleType.get_default(trading_mode)
with get_progress_tracker() as progress:
with progress_tracker as progress:
tf_length = len(timeframes) if trading_mode != "futures" else len(timeframes) + 2
timeframe_task = progress.add_task("Timeframe", total=tf_length)
pair_task = progress.add_task("Downloading data...", total=len(pairs))
@@ -346,7 +351,7 @@ def refresh_backtest_ohlcv_data(
progress.update(timeframe_task, completed=0)
if pair not in exchange.markets:
pairs_not_available.append(pair)
pairs_not_available.append(f"{pair}: Pair not available on exchange.")
logger.info(f"Skipping pair {pair}...")
continue
for timeframe in timeframes:
@@ -404,7 +409,7 @@ def _download_trades_history(
pair: str,
*,
new_pairs_days: int = 30,
timerange: Optional[TimeRange] = None,
timerange: TimeRange | None = None,
data_handler: IDataHandler,
trading_mode: TradingMode,
) -> bool:
@@ -412,79 +417,74 @@ def _download_trades_history(
Download trade history from the exchange.
Appends to previously downloaded trades data.
"""
try:
until = None
since = 0
if timerange:
if timerange.starttype == "date":
since = timerange.startts * 1000
if timerange.stoptype == "date":
until = timerange.stopts * 1000
until = None
since = 0
if timerange:
if timerange.starttype == "date":
since = timerange.startts * 1000
if timerange.stoptype == "date":
until = timerange.stopts * 1000
trades = data_handler.trades_load(pair, trading_mode)
trades = data_handler.trades_load(pair, trading_mode)
# TradesList columns are defined in constants.DEFAULT_TRADES_COLUMNS
# DEFAULT_TRADES_COLUMNS: 0 -> timestamp
# DEFAULT_TRADES_COLUMNS: 1 -> id
# TradesList columns are defined in constants.DEFAULT_TRADES_COLUMNS
# DEFAULT_TRADES_COLUMNS: 0 -> timestamp
# DEFAULT_TRADES_COLUMNS: 1 -> id
if not trades.empty and since > 0 and since < trades.iloc[0]["timestamp"]:
# since is before the first trade
logger.info(
f"Start ({trades.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}) earlier than "
f"available data. Redownloading trades for {pair}..."
)
trades = trades_list_to_df([])
from_id = trades.iloc[-1]["id"] if not trades.empty else None
if not trades.empty and since < trades.iloc[-1]["timestamp"]:
# Reset since to the last available point
# - 5 seconds (to ensure we're getting all trades)
since = trades.iloc[-1]["timestamp"] - (5 * 1000)
logger.info(
f"Using last trade date -5s - Downloading trades for {pair} "
f"since: {format_ms_time(since)}."
)
if not since:
since = dt_ts(dt_now() - timedelta(days=new_pairs_days))
logger.debug(
"Current Start: %s",
"None" if trades.empty else f"{trades.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}",
if not trades.empty and since > 0 and since < trades.iloc[0]["timestamp"]:
# since is before the first trade
raise ValueError(
f"Start {format_ms_time(since)} earlier than "
f"available data ({trades.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}). "
f"Please use `--erase` if you'd like to redownload {pair}."
)
logger.debug(
"Current End: %s",
"None" if trades.empty else f"{trades.iloc[-1]['date']:{DATETIME_PRINT_FORMAT}}",
)
logger.info(f"Current Amount of trades: {len(trades)}")
# Default since_ms to 30 days if nothing is given
new_trades = exchange.get_historic_trades(
pair=pair,
since=since,
until=until,
from_id=from_id,
from_id = trades.iloc[-1]["id"] if not trades.empty else None
if not trades.empty and since < trades.iloc[-1]["timestamp"]:
# Reset since to the last available point
# - 5 seconds (to ensure we're getting all trades)
since = trades.iloc[-1]["timestamp"] - (5 * 1000)
logger.info(
f"Using last trade date -5s - Downloading trades for {pair} "
f"since: {format_ms_time(since)}."
)
new_trades_df = trades_list_to_df(new_trades[1])
trades = concat([trades, new_trades_df], axis=0)
# Remove duplicates to make sure we're not storing data we don't need
trades = trades_df_remove_duplicates(trades)
data_handler.trades_store(pair, trades, trading_mode)
logger.debug(
"New Start: %s",
"None" if trades.empty else f"{trades.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}",
)
logger.debug(
"New End: %s",
"None" if trades.empty else f"{trades.iloc[-1]['date']:{DATETIME_PRINT_FORMAT}}",
)
logger.info(f"New Amount of trades: {len(trades)}")
return True
if not since:
since = dt_ts(dt_now() - timedelta(days=new_pairs_days))
except Exception:
logger.exception(f'Failed to download and store historic trades for pair: "{pair}". ')
return False
logger.debug(
"Current Start: %s",
"None" if trades.empty else f"{trades.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}",
)
logger.debug(
"Current End: %s",
"None" if trades.empty else f"{trades.iloc[-1]['date']:{DATETIME_PRINT_FORMAT}}",
)
logger.info(f"Current Amount of trades: {len(trades)}")
# Default since_ms to 30 days if nothing is given
new_trades = exchange.get_historic_trades(
pair=pair,
since=since,
until=until,
from_id=from_id,
)
new_trades_df = trades_list_to_df(new_trades[1])
trades = concat([trades, new_trades_df], axis=0)
# Remove duplicates to make sure we're not storing data we don't need
trades = trades_df_remove_duplicates(trades)
data_handler.trades_store(pair, trades, trading_mode)
logger.debug(
"New Start: %s",
"None" if trades.empty else f"{trades.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}",
)
logger.debug(
"New End: %s",
"None" if trades.empty else f"{trades.iloc[-1]['date']:{DATETIME_PRINT_FORMAT}}",
)
logger.info(f"New Amount of trades: {len(trades)}")
return True
def refresh_backtest_trades_data(
@@ -496,20 +496,22 @@ def refresh_backtest_trades_data(
new_pairs_days: int = 30,
erase: bool = False,
data_format: str = "feather",
progress_tracker: CustomProgress | None = None,
) -> list[str]:
"""
Refresh stored trades data for backtesting and hyperopt operations.
Used by freqtrade download-data subcommand.
:return: List of pairs that are not available.
"""
progress_tracker = retrieve_progress_tracker(progress_tracker)
pairs_not_available = []
data_handler = get_datahandler(datadir, data_format=data_format)
with get_progress_tracker() as progress:
with progress_tracker as progress:
pair_task = progress.add_task("Downloading data...", total=len(pairs))
for pair in pairs:
progress.update(pair_task, description=f"Downloading trades [{pair}]")
if pair not in exchange.markets:
pairs_not_available.append(pair)
pairs_not_available.append(f"{pair}: Pair not available on exchange.")
logger.info(f"Skipping pair {pair}...")
continue
@@ -518,14 +520,22 @@ def refresh_backtest_trades_data(
logger.info(f"Deleting existing data for pair {pair}.")
logger.info(f"Downloading trades for pair {pair}.")
_download_trades_history(
exchange=exchange,
pair=pair,
new_pairs_days=new_pairs_days,
timerange=timerange,
data_handler=data_handler,
trading_mode=trading_mode,
)
try:
_download_trades_history(
exchange=exchange,
pair=pair,
new_pairs_days=new_pairs_days,
timerange=timerange,
data_handler=data_handler,
trading_mode=trading_mode,
)
except ValueError as e:
pairs_not_available.append(f"{pair}: {str(e)}")
except Exception:
logger.exception(
f'Failed to download and store historic trades for pair: "{pair}". '
)
progress.update(pair_task, advance=1)
return pairs_not_available
@@ -577,8 +587,24 @@ def validate_backtest_data(
def download_data_main(config: Config) -> None:
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
exchange = ExchangeResolver.load_exchange(config, validate=False)
download_data(config, exchange)
def download_data(
config: Config,
exchange: Exchange,
*,
progress_tracker: CustomProgress | None = None,
) -> None:
"""
Download data function. Used from both cli and API.
"""
timerange = TimeRange()
if "days" in config:
if "days" in config and config["days"] is not None:
time_since = (datetime.now() - timedelta(days=config["days"])).strftime("%Y%m%d")
timerange = TimeRange.parse_timerange(f"{time_since}-")
@@ -590,10 +616,6 @@ def download_data_main(config: Config) -> None:
pairs_not_available: list[str] = []
# Init exchange
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
exchange = ExchangeResolver.load_exchange(config, validate=False)
available_pairs = [
p
for p in exchange.get_markets(
@@ -605,17 +627,18 @@ def download_data_main(config: Config) -> None:
if "timeframes" not in config:
config["timeframes"] = DL_DATA_TIMEFRAMES
logger.info(
f"About to download pairs: {expanded_pairs}, "
f"intervals: {config['timeframes']} to {config['datadir']}"
)
if len(expanded_pairs) == 0:
logger.warning(
"No pairs available for download. "
"Please make sure you're using the correct Pair naming for your selected trade mode. \n"
f"More info: {DOCS_LINK}/bot-basics/#pair-naming"
)
return
logger.info(
f"About to download pairs: {expanded_pairs}, "
f"intervals: {config['timeframes']} to {config['datadir']}"
)
for timeframe in config["timeframes"]:
exchange.validate_timeframes(timeframe)
@@ -637,6 +660,7 @@ def download_data_main(config: Config) -> None:
erase=bool(config.get("erase")),
data_format=config["dataformat_trades"],
trading_mode=config.get("trading_mode", TradingMode.SPOT),
progress_tracker=progress_tracker,
)
if config.get("convert_trades") or not exchange.get_option("ohlcv_has_history", True):
@@ -672,10 +696,12 @@ def download_data_main(config: Config) -> None:
data_format=config["dataformat_ohlcv"],
trading_mode=config.get("trading_mode", "spot"),
prepend=config.get("prepend_data", False),
progress_tracker=progress_tracker,
)
finally:
if pairs_not_available:
logger.info(
f"Pairs [{','.join(pairs_not_available)}] not available "
f"on exchange {exchange.name}."
errors = "\n" + ("\n".join(pairs_not_available))
logger.warning(
f"Encountered a problem downloading the following pairs from {exchange.name}: "
f"{errors}"
)
+123 -23
View File
@@ -3,16 +3,20 @@
import logging
from datetime import datetime, timezone
from pathlib import Path
from typing import Optional
import ccxt
from pandas import DataFrame
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS
from freqtrade.enums import CandleType, MarginMode, PriceType, TradingMode
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
from freqtrade.exchange import Exchange
from freqtrade.exchange.binance_public_data import concat_safe, download_archive_ohlcv
from freqtrade.exchange.common import retrier
from freqtrade.exchange.exchange_types import FtHas, OHLCVResponse, Tickers
from freqtrade.exchange.exchange_types import FtHas, Tickers
from freqtrade.exchange.exchange_utils_timeframe import timeframe_to_msecs
from freqtrade.misc import deep_merge_dicts, json_load
from freqtrade.util.datetime_helpers import dt_from_ts, dt_ts
logger = logging.getLogger(__name__)
@@ -53,12 +57,18 @@ class Binance(Exchange):
(TradingMode.FUTURES, MarginMode.ISOLATED)
]
def get_tickers(self, symbols: Optional[list[str]] = None, cached: bool = False) -> Tickers:
tickers = super().get_tickers(symbols=symbols, cached=cached)
def get_tickers(
self,
symbols: list[str] | None = None,
*,
cached: bool = False,
market_type: TradingMode | None = None,
) -> Tickers:
tickers = super().get_tickers(symbols=symbols, cached=cached, market_type=market_type)
if self.trading_mode == TradingMode.FUTURES:
# Binance's future result has no bid/ask values.
# Therefore we must fetch that from fetch_bids_asks and combine the two results.
bidsasks = self.fetch_bids_asks(symbols, cached)
bidsasks = self.fetch_bids_asks(symbols, cached=cached)
tickers = deep_merge_dicts(bidsasks, tickers, allow_null_overrides=False)
return tickers
@@ -81,7 +91,10 @@ class Binance(Exchange):
"\nHedge Mode is not supported by freqtrade. "
"Please change 'Position Mode' on your binance futures account."
)
if assets_margin.get("multiAssetsMargin") is True:
if (
assets_margin.get("multiAssetsMargin") is True
and self.margin_mode != MarginMode.CROSS
):
msg += (
"\nMulti-Asset Mode is not supported by freqtrade. "
"Please change 'Asset Mode' on your binance futures account."
@@ -98,23 +111,24 @@ class Binance(Exchange):
except ccxt.BaseError as e:
raise OperationalException(e) from e
async def _async_get_historic_ohlcv(
def get_historic_ohlcv(
self,
pair: str,
timeframe: str,
since_ms: int,
candle_type: CandleType,
is_new_pair: bool = False,
raise_: bool = False,
until_ms: Optional[int] = None,
) -> OHLCVResponse:
until_ms: int | None = None,
) -> DataFrame:
"""
Overwrite to introduce "fast new pair" functionality by detecting the pair's listing date
Does not work for other exchanges, which don't return the earliest data when called with "0"
:param candle_type: Any of the enum CandleType (must match trading mode!)
"""
if is_new_pair:
x = await self._async_get_candle_history(pair, timeframe, candle_type, 0)
x = self.loop.run_until_complete(
self._async_get_candle_history(pair, timeframe, candle_type, 0)
)
if x and x[3] and x[3][0] and x[3][0][0] > since_ms:
# Set starting date to first available candle.
since_ms = x[3][0][0]
@@ -122,17 +136,89 @@ class Binance(Exchange):
f"Candle-data for {pair} available starting with "
f"{datetime.fromtimestamp(since_ms // 1000, tz=timezone.utc).isoformat()}."
)
if until_ms and since_ms >= until_ms:
logger.warning(
f"No available candle-data for {pair} before "
f"{dt_from_ts(until_ms).isoformat()}"
)
return DataFrame(columns=DEFAULT_DATAFRAME_COLUMNS)
return await super()._async_get_historic_ohlcv(
pair=pair,
timeframe=timeframe,
since_ms=since_ms,
is_new_pair=is_new_pair,
raise_=raise_,
candle_type=candle_type,
until_ms=until_ms,
if (
self._config["exchange"].get("only_from_ccxt", False)
or
# only download timeframes with significant improvements,
# otherwise fall back to rest API
not (
(candle_type == CandleType.SPOT and timeframe in ["1s", "1m", "3m", "5m"])
or (
candle_type == CandleType.FUTURES
and timeframe in ["1m", "3m", "5m", "15m", "30m"]
)
)
):
return super().get_historic_ohlcv(
pair=pair,
timeframe=timeframe,
since_ms=since_ms,
candle_type=candle_type,
is_new_pair=is_new_pair,
until_ms=until_ms,
)
else:
# Download from data.binance.vision
return self.get_historic_ohlcv_fast(
pair=pair,
timeframe=timeframe,
since_ms=since_ms,
candle_type=candle_type,
is_new_pair=is_new_pair,
until_ms=until_ms,
)
def get_historic_ohlcv_fast(
self,
pair: str,
timeframe: str,
since_ms: int,
candle_type: CandleType,
is_new_pair: bool = False,
until_ms: int | None = None,
) -> DataFrame:
"""
Fastly fetch OHLCV data by leveraging https://data.binance.vision.
"""
df = self.loop.run_until_complete(
download_archive_ohlcv(
candle_type=candle_type,
pair=pair,
timeframe=timeframe,
since_ms=since_ms,
until_ms=until_ms,
markets=self.markets,
)
)
# download the remaining data from rest API
if df.empty:
rest_since_ms = since_ms
else:
rest_since_ms = dt_ts(df.iloc[-1].date) + timeframe_to_msecs(timeframe)
# make sure since <= until
if until_ms and rest_since_ms > until_ms:
rest_df = DataFrame()
else:
rest_df = super().get_historic_ohlcv(
pair=pair,
timeframe=timeframe,
since_ms=rest_since_ms,
candle_type=candle_type,
is_new_pair=is_new_pair,
until_ms=until_ms,
)
all_df = concat_safe([df, rest_df])
return all_df
def funding_fee_cutoff(self, open_date: datetime):
"""
Funding fees are only charged at full hours (usually every 4-8h).
@@ -144,9 +230,7 @@ class Binance(Exchange):
"""
return open_date.minute == 0 and open_date.second < 15
def fetch_funding_rates(
self, symbols: Optional[list[str]] = None
) -> dict[str, dict[str, float]]:
def fetch_funding_rates(self, symbols: list[str] | None = None) -> dict[str, dict[str, float]]:
"""
Fetch funding rates for the given symbols.
:param symbols: List of symbols to fetch funding rates for
@@ -177,7 +261,7 @@ class Binance(Exchange):
leverage: float,
wallet_balance: float, # Or margin balance
open_trades: list,
) -> Optional[float]:
) -> float | None:
"""
Important: Must be fetching data from cached values as this is used by backtesting!
MARGIN: https://www.binance.com/en/support/faq/f6b010588e55413aa58b7d63ee0125ed
@@ -263,3 +347,19 @@ class Binance(Exchange):
return self.get_leverage_tiers()
else:
return {}
async def _async_get_trade_history_id_startup(
self, pair: str, since: int | None
) -> tuple[list[list], str]:
"""
override for initial call
Binance only provides a limited set of historic trades data.
Using from_id=0, we can get the earliest available trades.
So if we don't get any data with the provided "since", we can assume to
download all available data.
"""
t, from_id = await self._async_fetch_trades(pair, since=since)
if not t:
return [], "0"
return t, from_id
File diff suppressed because it is too large Load Diff
+281
View File
@@ -0,0 +1,281 @@
"""
Fetch daily-archived OHLCV data from https://data.binance.vision/
"""
import asyncio
import logging
import zipfile
from datetime import date, timedelta
from io import BytesIO
from typing import Any
import aiohttp
import pandas as pd
from pandas import DataFrame
from freqtrade.enums import CandleType
from freqtrade.misc import chunks
from freqtrade.util.datetime_helpers import dt_from_ts, dt_now
logger = logging.getLogger(__name__)
class Http404(Exception):
def __init__(self, msg, date, url):
super().__init__(msg)
self.date = date
self.url = url
class BadHttpStatus(Exception):
"""Not 200/404"""
pass
async def download_archive_ohlcv(
candle_type: CandleType,
pair: str,
timeframe: str,
*,
since_ms: int,
until_ms: int | None,
markets: dict[str, Any],
stop_on_404: bool = True,
) -> DataFrame:
"""
Fetch OHLCV data from https://data.binance.vision
The function makes its best effort to download data within the time range
[`since_ms`, `until_ms`] -- including `since_ms`, but excluding `until_ms`.
If `stop_one_404` is True, this returned DataFrame is guaranteed to start from `since_ms`
with no gaps in the data.
:candle_type: Currently only spot and futures are supported
:pair: symbol name in CCXT convention
:since_ms: the start timestamp of data, including itself
:until_ms: the end timestamp of data, excluding itself
:param until_ms: `None` indicates the timestamp of the latest available data
:markets: the CCXT markets dict, when it's None, the function will load the markets data
from a new `ccxt.binance` instance
:param stop_on_404: Stop to download the following data when a 404 returned
:return: the date range is between [since_ms, until_ms), return an empty DataFrame if no data
available in the time range
"""
try:
if candle_type == CandleType.SPOT:
asset_type_url_segment = "spot"
elif candle_type == CandleType.FUTURES:
asset_type_url_segment = "futures/um"
else:
raise ValueError(f"Unsupported CandleType: {candle_type}")
symbol = markets[pair]["id"]
start = dt_from_ts(since_ms)
end = dt_from_ts(until_ms) if until_ms else dt_now()
# We use two days ago as the last available day because the daily archives are daily
# uploaded and have several hours delay
last_available_date = dt_now() - timedelta(days=2)
end = min(end, last_available_date)
if start >= end:
return DataFrame()
df = await _download_archive_ohlcv(
asset_type_url_segment, symbol, pair, timeframe, start, end, stop_on_404
)
logger.debug(
f"Downloaded data for {pair} from https://data.binance.vision with length {len(df)}."
)
except Exception as e:
logger.warning(
"An exception occurred during fast download from Binance, falling back to "
"the slower REST API, this can take more time.",
exc_info=e,
)
df = DataFrame()
if not df.empty:
# only return the data within the requested time range
return df.loc[(df["date"] >= start) & (df["date"] < end)]
else:
return df
def concat_safe(dfs) -> DataFrame:
if all(df is None for df in dfs):
return DataFrame()
else:
return pd.concat(dfs)
async def _download_archive_ohlcv(
asset_type_url_segment: str,
symbol: str,
pair: str,
timeframe: str,
start: date,
end: date,
stop_on_404: bool,
) -> DataFrame:
# daily dataframes, `None` indicates missing data in that day (when `stop_on_404` is False)
dfs: list[DataFrame | None] = []
# the current day being processing, starting at 1.
current_day = 0
connector = aiohttp.TCPConnector(limit=100)
async with aiohttp.ClientSession(connector=connector, trust_env=True) as session:
# the HTTP connections has been throttled by TCPConnector
for dates in chunks(list(date_range(start, end)), 1000):
tasks = [
asyncio.create_task(
get_daily_ohlcv(asset_type_url_segment, symbol, timeframe, date, session)
)
for date in dates
]
for task in tasks:
current_day += 1
try:
df = await task
except Http404 as e:
if stop_on_404:
logger.debug(f"Failed to download {e.url} due to 404.")
# A 404 error on the first day indicates missing data
# on https://data.binance.vision, we provide the warning and the advice.
# https://github.com/freqtrade/freqtrade/blob/acc53065e5fa7ab5197073276306dc9dc3adbfa3/tests/exchange_online/test_binance_compare_ohlcv.py#L7
if current_day == 1:
logger.warning(
f"Fast download is unavailable due to missing data: "
f"{e.url}. Falling back to the slower REST API, "
"which may take more time."
)
if pair in ["BTC/USDT:USDT", "ETH/USDT:USDT", "BCH/USDT:USDT"]:
logger.warning(
f"To avoid the delay, you can first download {pair} using "
"`--timerange <start date>-20200101`, and then download the "
"remaining data with `--timerange 20200101-<end date>`."
)
else:
logger.warning(
f"Binance fast download for {pair} stopped at {e.date} due to "
f"missing data: {e.url}, falling back to rest API for the "
"remaining data, this can take more time."
)
await cancel_and_await_tasks(tasks[tasks.index(task) + 1 :])
return concat_safe(dfs)
else:
dfs.append(None)
except BaseException as e:
logger.warning(f"An exception raised: : {e}")
# Directly return the existing data, do not allow the gap within the data
await cancel_and_await_tasks(tasks[tasks.index(task) + 1 :])
return concat_safe(dfs)
else:
dfs.append(df)
return concat_safe(dfs)
async def cancel_and_await_tasks(unawaited_tasks):
"""Cancel and await the tasks"""
logger.debug("Try to cancel uncompleted download tasks.")
for task in unawaited_tasks:
task.cancel()
await asyncio.gather(*unawaited_tasks, return_exceptions=True)
logger.debug("All download tasks were awaited.")
def date_range(start: date, end: date):
date = start
while date <= end:
yield date
date += timedelta(days=1)
def binance_vision_zip_name(symbol: str, timeframe: str, date: date) -> str:
return f"{symbol}-{timeframe}-{date.strftime('%Y-%m-%d')}.zip"
def binance_vision_zip_url(
asset_type_url_segment: str, symbol: str, timeframe: str, date: date
) -> str:
"""
example urls:
https://data.binance.vision/data/spot/daily/klines/BTCUSDT/1s/BTCUSDT-1s-2023-10-27.zip
https://data.binance.vision/data/futures/um/daily/klines/BTCUSDT/1h/BTCUSDT-1h-2023-10-27.zip
"""
url = (
f"https://data.binance.vision/data/{asset_type_url_segment}/daily/klines/{symbol}"
f"/{timeframe}/{binance_vision_zip_name(symbol, timeframe, date)}"
)
return url
async def get_daily_ohlcv(
asset_type_url_segment: str,
symbol: str,
timeframe: str,
date: date,
session: aiohttp.ClientSession,
retry_count: int = 3,
retry_delay: float = 0.0,
) -> DataFrame:
"""
Get daily OHLCV from https://data.binance.vision
See https://github.com/binance/binance-public-data
:asset_type_url_segment: `spot` or `futures/um`
:symbol: binance symbol name, e.g. BTCUSDT
:timeframe: e.g. 1m, 1h
:date: the returned DataFrame will cover the entire day of `date` in UTC
:session: an aiohttp.ClientSession instance
:retry_count: times to retry before returning the exceptions
:retry_delay: the time to wait before every retry
:return: A dataframe containing columns date,open,high,low,close,volume
"""
url = binance_vision_zip_url(asset_type_url_segment, symbol, timeframe, date)
logger.debug(f"download data from binance: {url}")
retry = 0
while True:
if retry > 0:
sleep_secs = retry * retry_delay
logger.debug(
f"[{retry}/{retry_count}] retry to download {url} after {sleep_secs} seconds"
)
await asyncio.sleep(sleep_secs)
try:
async with session.get(url) as resp:
if resp.status == 200:
content = await resp.read()
logger.debug(f"Successfully downloaded {url}")
with zipfile.ZipFile(BytesIO(content)) as zipf:
with zipf.open(zipf.namelist()[0]) as csvf:
# https://github.com/binance/binance-public-data/issues/283
first_byte = csvf.read(1)[0]
if chr(first_byte).isdigit():
header = None
else:
header = 0
csvf.seek(0)
df = pd.read_csv(
csvf,
usecols=[0, 1, 2, 3, 4, 5],
names=["date", "open", "high", "low", "close", "volume"],
header=header,
)
df["date"] = pd.to_datetime(df["date"], unit="ms", utc=True)
return df
elif resp.status == 404:
logger.debug(f"Failed to download {url}")
raise Http404(f"404: {url}", date, url)
else:
raise BadHttpStatus(f"{resp.status} - {resp.reason}")
except Exception as e:
retry += 1
if isinstance(e, Http404) or retry > retry_count:
logger.debug(f"Failed to get data from {url}: {e}")
raise
+1 -2
View File
@@ -2,7 +2,6 @@
import logging
from datetime import datetime, timezone
from typing import Optional
from freqtrade.exchange import Exchange
@@ -17,7 +16,7 @@ class Bitpanda(Exchange):
"""
def get_trades_for_order(
self, order_id: str, pair: str, since: datetime, params: Optional[dict] = None
self, order_id: str, pair: str, since: datetime, params: dict | None = None
) -> list:
"""
Fetch Orders using the "fetch_my_trades" endpoint and filter them by order-id.
+20 -14
View File
@@ -2,16 +2,16 @@
import logging
from datetime import datetime, timedelta
from typing import Any, Optional
from typing import Any
import ccxt
from freqtrade.constants import BuySell
from freqtrade.enums import CandleType, MarginMode, PriceType, TradingMode
from freqtrade.enums import MarginMode, PriceType, TradingMode
from freqtrade.exceptions import DDosProtection, ExchangeError, OperationalException, TemporaryError
from freqtrade.exchange import Exchange
from freqtrade.exchange.common import retrier
from freqtrade.exchange.exchange_types import FtHas
from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
from freqtrade.util.datetime_helpers import dt_now, dt_ts
@@ -47,6 +47,7 @@ class Bybit(Exchange):
"ohlcv_has_history": True,
"mark_ohlcv_timeframe": "4h",
"funding_fee_timeframe": "8h",
"funding_fee_candle_limit": 200,
"stoploss_on_exchange": True,
"stoploss_order_types": {"limit": "limit", "market": "market"},
# bybit response parsing fails to populate stopLossPrice
@@ -114,14 +115,6 @@ class Bybit(Exchange):
except ccxt.BaseError as e:
raise OperationalException(e) from e
def ohlcv_candle_limit(
self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = None
) -> int:
if candle_type == CandleType.FUNDING_RATE:
return 200
return super().ohlcv_candle_limit(timeframe, candle_type, since_ms)
def _lev_prep(self, pair: str, leverage: float, side: BuySell, accept_fail: bool = False):
if self.trading_mode != TradingMode.SPOT:
params = {"leverage": leverage}
@@ -147,6 +140,17 @@ class Bybit(Exchange):
params["position_idx"] = 0
return params
def _order_needs_price(self, side: BuySell, ordertype: str) -> bool:
# Bybit requires price for market orders - but only for classic accounts,
# and only in spot mode
return (
ordertype != "market"
or (
side == "buy" and not self.unified_account and self.trading_mode == TradingMode.SPOT
)
or self._ft_has.get("marketOrderRequiresPrice", False)
)
def dry_run_liquidation_price(
self,
pair: str,
@@ -157,7 +161,7 @@ class Bybit(Exchange):
leverage: float,
wallet_balance: float, # Or margin balance
open_trades: list,
) -> Optional[float]:
) -> float | None:
"""
Important: Must be fetching data from cached values as this is used by backtesting!
PERPETUAL:
@@ -229,7 +233,9 @@ class Bybit(Exchange):
logger.warning(f"Could not update funding fees for {pair}.")
return 0.0
def fetch_orders(self, pair: str, since: datetime, params: Optional[dict] = None) -> list[dict]:
def fetch_orders(
self, pair: str, since: datetime, params: dict | None = None
) -> list[CcxtOrder]:
"""
Fetch all orders for a pair "since"
:param pair: Pair for the query
@@ -246,7 +252,7 @@ class Bybit(Exchange):
return orders
def fetch_order(self, order_id: str, pair: str, params: Optional[dict] = None) -> dict:
def fetch_order(self, order_id: str, pair: str, params: dict | None = None) -> CcxtOrder:
if self.exchange_has("fetchOrder"):
# Set acknowledged to True to avoid ccxt exception
params = {"acknowledged": True}
+6 -3
View File
@@ -1,8 +1,9 @@
import asyncio
import logging
import time
from collections.abc import Callable
from functools import wraps
from typing import Any, Callable, Optional, TypeVar, cast, overload
from typing import Any, TypeVar, cast, overload
from freqtrade.constants import ExchangeConfig
from freqtrade.exceptions import DDosProtection, RetryableOrderError, TemporaryError
@@ -46,6 +47,7 @@ MAP_EXCHANGE_CHILDCLASS = {
"binanceje": "binance",
"binanceusdm": "binance",
"okex": "okx",
"myokx": "okx",
"gateio": "gate",
"huboi": "htx",
}
@@ -57,6 +59,7 @@ SUPPORTED_EXCHANGES = [
"bybit",
"gate",
"htx",
"hyperliquid",
"kraken",
"okx",
]
@@ -172,7 +175,7 @@ def retrier(_func: F, *, retries=API_RETRY_COUNT) -> F: ...
def retrier(*, retries=API_RETRY_COUNT) -> Callable[[F], F]: ...
def retrier(_func: Optional[F] = None, *, retries=API_RETRY_COUNT):
def retrier(_func: F | None = None, *, retries=API_RETRY_COUNT):
def decorator(f: F) -> F:
@wraps(f)
def wrapper(*args, **kwargs):
@@ -185,7 +188,7 @@ def retrier(_func: Optional[F] = None, *, retries=API_RETRY_COUNT):
logger.warning(msg + f"Retrying still for {count} times.")
count -= 1
kwargs.update({"count": count})
if isinstance(ex, (DDosProtection, RetryableOrderError)):
if isinstance(ex, DDosProtection | RetryableOrderError):
# increasing backoff
backoff_delay = calculate_backoff(count + 1, retries)
logger.info(f"Applying DDosProtection backoff delay: {backoff_delay}")
+192 -118
View File
@@ -7,12 +7,12 @@ import asyncio
import inspect
import logging
import signal
from collections.abc import Coroutine
from collections.abc import Coroutine, Generator
from copy import deepcopy
from datetime import datetime, timedelta, timezone
from math import floor, isnan
from threading import Lock
from typing import Any, Literal, Optional, Union
from typing import Any, Literal, TypeGuard
import ccxt
import ccxt.pro as ccxt_pro
@@ -70,6 +70,7 @@ from freqtrade.exchange.common import (
)
from freqtrade.exchange.exchange_types import (
CcxtBalances,
CcxtOrder,
CcxtPosition,
FtHas,
OHLCVResponse,
@@ -169,7 +170,7 @@ class Exchange:
self,
config: Config,
*,
exchange_config: Optional[ExchangeConfig] = None,
exchange_config: ExchangeConfig | None = None,
validate: bool = True,
load_leverage_tiers: bool = False,
) -> None:
@@ -181,7 +182,7 @@ class Exchange:
self._api: ccxt.Exchange
self._api_async: ccxt_pro.Exchange
self._ws_async: ccxt_pro.Exchange = None
self._exchange_ws: Optional[ExchangeWS] = None
self._exchange_ws: ExchangeWS | None = None
self._markets: dict = {}
self._trading_fees: dict[str, Any] = {}
self._leverage_tiers: dict[str, list[dict]] = {}
@@ -198,9 +199,9 @@ class Exchange:
# Timestamp of last markets refresh
self._last_markets_refresh: int = 0
# Cache for 10 minutes ...
self._cache_lock = Lock()
self._fetch_tickers_cache: TTLCache = TTLCache(maxsize=2, ttl=60 * 10)
# Cache for 10 minutes ...
self._fetch_tickers_cache: TTLCache = TTLCache(maxsize=4, ttl=60 * 10)
# Cache values for 300 to avoid frequent polling of the exchange for prices
# Caching only applies to RPC methods, so prices for open trades are still
# refreshed once every iteration.
@@ -378,7 +379,7 @@ class Exchange:
logger.info("Applying additional ccxt config: %s", ccxt_kwargs)
if self._ccxt_params:
# Inject static options after the above output to not confuse users.
ccxt_kwargs = deep_merge_dicts(self._ccxt_params, ccxt_kwargs)
ccxt_kwargs = deep_merge_dicts(self._ccxt_params, deepcopy(ccxt_kwargs))
if ccxt_kwargs:
ex_config.update(ccxt_kwargs)
try:
@@ -452,28 +453,31 @@ class Exchange:
logger.info(f"API {endpoint}: {add_info_str}{response}")
def ohlcv_candle_limit(
self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = None
self, timeframe: str, candle_type: CandleType, since_ms: int | None = None
) -> int:
"""
Exchange ohlcv candle limit
Uses ohlcv_candle_limit_per_timeframe if the exchange has different limits
per timeframe (e.g. bittrex), otherwise falls back to ohlcv_candle_limit
TODO: this is most likely no longer needed since only bittrex needed this.
:param timeframe: Timeframe to check
:param candle_type: Candle-type
:param since_ms: Starting timestamp
:return: Candle limit as integer
"""
fallback_val = self._ft_has.get("ohlcv_candle_limit")
if candle_type == CandleType.FUNDING_RATE:
fallback_val = self._ft_has.get("funding_fee_candle_limit", fallback_val)
return int(
self._ft_has.get("ohlcv_candle_limit_per_timeframe", {}).get(
timeframe, str(self._ft_has.get("ohlcv_candle_limit"))
timeframe, str(fallback_val)
)
)
def get_markets(
self,
base_currencies: Optional[list[str]] = None,
quote_currencies: Optional[list[str]] = None,
base_currencies: list[str] | None = None,
quote_currencies: list[str] | None = None,
spot_only: bool = False,
margin_only: bool = False,
futures_only: bool = False,
@@ -522,6 +526,7 @@ class Exchange:
def market_is_future(self, market: dict[str, Any]) -> bool:
return (
market.get(self._ft_has["ccxt_futures_name"], False) is True
and market.get("type", False) == "swap"
and market.get("linear", False) is True
)
@@ -566,7 +571,7 @@ class Exchange:
else:
return DataFrame(columns=DEFAULT_TRADES_COLUMNS)
def get_contract_size(self, pair: str) -> Optional[float]:
def get_contract_size(self, pair: str) -> float | None:
if self.trading_mode == TradingMode.FUTURES:
market = self.markets.get(pair, {})
contract_size: float = 1.0
@@ -587,7 +592,7 @@ class Exchange:
trade["amount"] = trade["amount"] * contract_size
return trades
def _order_contracts_to_amount(self, order: dict) -> dict:
def _order_contracts_to_amount(self, order: CcxtOrder) -> CcxtOrder:
if "symbol" in order and order["symbol"] is not None:
contract_size = self.get_contract_size(order["symbol"])
if contract_size != 1:
@@ -700,16 +705,24 @@ class Exchange:
f"Available currencies are: {', '.join(quote_currencies)}"
)
def get_valid_pair_combination(self, curr_1: str, curr_2: str) -> str:
def get_valid_pair_combination(self, curr_1: str, curr_2: str) -> Generator[str, None, None]:
"""
Get valid pair combination of curr_1 and curr_2 by trying both combinations.
"""
for pair in [f"{curr_1}/{curr_2}", f"{curr_2}/{curr_1}"]:
yielded = False
for pair in (
f"{curr_1}/{curr_2}",
f"{curr_2}/{curr_1}",
f"{curr_1}/{curr_2}:{curr_2}",
f"{curr_2}/{curr_1}:{curr_1}",
):
if pair in self.markets and self.markets[pair].get("active"):
return pair
raise ValueError(f"Could not combine {curr_1} and {curr_2} to get a valid pair.")
yielded = True
yield pair
if not yielded:
raise ValueError(f"Could not combine {curr_1} and {curr_2} to get a valid pair.")
def validate_timeframes(self, timeframe: Optional[str]) -> None:
def validate_timeframes(self, timeframe: str | None) -> None:
"""
Check if timeframe from config is a supported timeframe on the exchange
"""
@@ -839,7 +852,7 @@ class Exchange:
def validate_trading_mode_and_margin_mode(
self,
trading_mode: TradingMode,
margin_mode: Optional[MarginMode], # Only None when trading_mode = TradingMode.SPOT
margin_mode: MarginMode | None, # Only None when trading_mode = TradingMode.SPOT
):
"""
Checks if freqtrade can perform trades using the configured
@@ -855,7 +868,7 @@ class Exchange:
f"Freqtrade does not support {mm_value} {trading_mode} on {self.name}"
)
def get_option(self, param: str, default: Optional[Any] = None) -> Any:
def get_option(self, param: str, default: Any | None = None) -> Any:
"""
Get parameter value from _ft_has
"""
@@ -872,7 +885,7 @@ class Exchange:
return self._ft_has["exchange_has_overrides"][endpoint]
return endpoint in self._api_async.has and self._api_async.has[endpoint]
def get_precision_amount(self, pair: str) -> Optional[float]:
def get_precision_amount(self, pair: str) -> float | None:
"""
Returns the amount precision of the exchange.
:param pair: Pair to get precision for
@@ -880,7 +893,7 @@ class Exchange:
"""
return self.markets.get(pair, {}).get("precision", {}).get("amount", None)
def get_precision_price(self, pair: str) -> Optional[float]:
def get_precision_price(self, pair: str) -> float | None:
"""
Returns the price precision of the exchange.
:param pair: Pair to get precision for
@@ -920,8 +933,8 @@ class Exchange:
return 1 / pow(10, precision)
def get_min_pair_stake_amount(
self, pair: str, price: float, stoploss: float, leverage: Optional[float] = 1.0
) -> Optional[float]:
self, pair: str, price: float, stoploss: float, leverage: float | None = 1.0
) -> float | None:
return self._get_stake_amount_limit(pair, price, stoploss, "min", leverage)
def get_max_pair_stake_amount(self, pair: str, price: float, leverage: float = 1.0) -> float:
@@ -939,8 +952,8 @@ class Exchange:
price: float,
stoploss: float,
limit: Literal["min", "max"],
leverage: Optional[float] = 1.0,
) -> Optional[float]:
leverage: float | None = 1.0,
) -> float | None:
isMin = limit == "min"
try:
@@ -997,20 +1010,20 @@ class Exchange:
self,
pair: str,
ordertype: str,
side: str,
side: BuySell,
amount: float,
rate: float,
leverage: float,
params: Optional[dict] = None,
params: dict | None = None,
stop_loss: bool = False,
) -> dict[str, Any]:
) -> CcxtOrder:
now = dt_now()
order_id = f"dry_run_{side}_{pair}_{now.timestamp()}"
# Rounding here must respect to contract sizes
_amount = self._contracts_to_amount(
pair, self.amount_to_precision(pair, self._amount_to_contracts(pair, amount))
)
dry_order: dict[str, Any] = {
dry_order: CcxtOrder = {
"id": order_id,
"symbol": pair,
"price": rate,
@@ -1026,14 +1039,13 @@ class Exchange:
"status": "open",
"fee": None,
"info": {},
"leverage": leverage,
}
if stop_loss:
dry_order["info"] = {"stopPrice": dry_order["price"]}
dry_order[self._ft_has["stop_price_prop"]] = dry_order["price"]
# Workaround to avoid filling stoploss orders immediately
dry_order["ft_order_type"] = "stoploss"
orderbook: Optional[OrderBook] = None
orderbook: OrderBook | None = None
if self.exchange_has("fetchL2OrderBook"):
orderbook = self.fetch_l2_order_book(pair, 20)
if ordertype == "limit" and orderbook:
@@ -1055,7 +1067,7 @@ class Exchange:
"filled": _amount,
"remaining": 0.0,
"status": "closed",
"cost": (dry_order["amount"] * average),
"cost": (_amount * average),
}
)
# market orders will always incurr taker fees
@@ -1072,9 +1084,9 @@ class Exchange:
def add_dry_order_fee(
self,
pair: str,
dry_order: dict[str, Any],
dry_order: CcxtOrder,
taker_or_maker: MakerTaker,
) -> dict[str, Any]:
) -> CcxtOrder:
fee = self.get_fee(pair, taker_or_maker=taker_or_maker)
dry_order.update(
{
@@ -1088,7 +1100,7 @@ class Exchange:
return dry_order
def get_dry_market_fill_price(
self, pair: str, side: str, amount: float, rate: float, orderbook: Optional[OrderBook]
self, pair: str, side: str, amount: float, rate: float, orderbook: OrderBook | None
) -> float:
"""
Get the market order fill price based on orderbook interpolation
@@ -1136,7 +1148,7 @@ class Exchange:
pair: str,
side: str,
limit: float,
orderbook: Optional[OrderBook] = None,
orderbook: OrderBook | None = None,
offset: float = 0.0,
) -> bool:
if not self.exchange_has("fetchL2OrderBook"):
@@ -1158,8 +1170,8 @@ class Exchange:
return False
def check_dry_limit_order_filled(
self, order: dict[str, Any], immediate: bool = False, orderbook: Optional[OrderBook] = None
) -> dict[str, Any]:
self, order: CcxtOrder, immediate: bool = False, orderbook: OrderBook | None = None
) -> CcxtOrder:
"""
Check dry-run limit order fill and update fee (if it filled).
"""
@@ -1186,7 +1198,7 @@ class Exchange:
return order
def fetch_dry_run_order(self, order_id) -> dict[str, Any]:
def fetch_dry_run_order(self, order_id) -> CcxtOrder:
"""
Return dry-run order
Only call if running in dry-run mode.
@@ -1248,7 +1260,7 @@ class Exchange:
leverage: float,
reduceOnly: bool = False,
time_in_force: str = "GTC",
) -> dict:
) -> CcxtOrder:
if self._config["dry_run"]:
dry_order = self.create_dry_run_order(
pair, ordertype, side, amount, self.price_to_precision(pair, rate), leverage
@@ -1306,7 +1318,7 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
def stoploss_adjust(self, stop_loss: float, order: dict, side: str) -> bool:
def stoploss_adjust(self, stop_loss: float, order: CcxtOrder, side: str) -> bool:
"""
Verify stop_loss against stoploss-order value (limit or price)
Returns True if adjustment is necessary.
@@ -1367,7 +1379,7 @@ class Exchange:
order_types: dict,
side: BuySell,
leverage: float,
) -> dict:
) -> CcxtOrder:
"""
creates a stoploss order.
requires `_ft_has['stoploss_order_types']` to be set as a dict mapping limit and market
@@ -1460,7 +1472,7 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
def fetch_order_emulated(self, order_id: str, pair: str, params: dict) -> dict:
def fetch_order_emulated(self, order_id: str, pair: str, params: dict) -> CcxtOrder:
"""
Emulated fetch_order if the exchange doesn't support fetch_order, but requires separate
calls for open and closed orders.
@@ -1494,7 +1506,7 @@ class Exchange:
raise OperationalException(e) from e
@retrier(retries=API_FETCH_ORDER_RETRY_COUNT)
def fetch_order(self, order_id: str, pair: str, params: Optional[dict] = None) -> dict:
def fetch_order(self, order_id: str, pair: str, params: dict | None = None) -> CcxtOrder:
if self._config["dry_run"]:
return self.fetch_dry_run_order(order_id)
if params is None:
@@ -1523,12 +1535,14 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
def fetch_stoploss_order(self, order_id: str, pair: str, params: Optional[dict] = None) -> dict:
def fetch_stoploss_order(
self, order_id: str, pair: str, params: dict | None = None
) -> CcxtOrder:
return self.fetch_order(order_id, pair, params)
def fetch_order_or_stoploss_order(
self, order_id: str, pair: str, stoploss_order: bool = False
) -> dict:
) -> CcxtOrder:
"""
Simple wrapper calling either fetch_order or fetch_stoploss_order depending on
the stoploss_order parameter
@@ -1540,7 +1554,7 @@ class Exchange:
return self.fetch_stoploss_order(order_id, pair)
return self.fetch_order(order_id, pair)
def check_order_canceled_empty(self, order: dict) -> bool:
def check_order_canceled_empty(self, order: CcxtOrder) -> bool:
"""
Verify if an order has been cancelled without being partially filled
:param order: Order dict as returned from fetch_order()
@@ -1549,7 +1563,7 @@ class Exchange:
return order.get("status") in NON_OPEN_EXCHANGE_STATES and order.get("filled") == 0.0
@retrier
def cancel_order(self, order_id: str, pair: str, params: Optional[dict] = None) -> dict:
def cancel_order(self, order_id: str, pair: str, params: dict | None = None) -> dict[str, Any]:
if self._config["dry_run"]:
try:
order = self.fetch_dry_run_order(order_id)
@@ -1577,19 +1591,17 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
def cancel_stoploss_order(
self, order_id: str, pair: str, params: Optional[dict] = None
) -> dict:
def cancel_stoploss_order(self, order_id: str, pair: str, params: dict | None = None) -> dict:
return self.cancel_order(order_id, pair, params)
def is_cancel_order_result_suitable(self, corder) -> bool:
def is_cancel_order_result_suitable(self, corder) -> TypeGuard[CcxtOrder]:
if not isinstance(corder, dict):
return False
required = ("fee", "status", "amount")
return all(corder.get(k, None) is not None for k in required)
def cancel_order_with_result(self, order_id: str, pair: str, amount: float) -> dict:
def cancel_order_with_result(self, order_id: str, pair: str, amount: float) -> CcxtOrder:
"""
Cancel order returning a result.
Creates a fake result if cancel order returns a non-usable result
@@ -1620,7 +1632,9 @@ class Exchange:
return order
def cancel_stoploss_order_with_result(self, order_id: str, pair: str, amount: float) -> dict:
def cancel_stoploss_order_with_result(
self, order_id: str, pair: str, amount: float
) -> CcxtOrder:
"""
Cancel stoploss order returning a result.
Creates a fake result if cancel order returns a non-usable result
@@ -1651,6 +1665,7 @@ class Exchange:
balances.pop("total", None)
balances.pop("used", None)
self._log_exchange_response("fetch_balances", balances)
return balances
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
@@ -1662,7 +1677,7 @@ class Exchange:
raise OperationalException(e) from e
@retrier
def fetch_positions(self, pair: Optional[str] = None) -> list[CcxtPosition]:
def fetch_positions(self, pair: str | None = None) -> list[CcxtPosition]:
"""
Fetch positions from the exchange.
If no pair is given, all positions are returned.
@@ -1686,7 +1701,7 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
def _fetch_orders_emulate(self, pair: str, since_ms: int) -> list[dict]:
def _fetch_orders_emulate(self, pair: str, since_ms: int) -> list[CcxtOrder]:
orders = []
if self.exchange_has("fetchClosedOrders"):
orders = self._api.fetch_closed_orders(pair, since=since_ms)
@@ -1696,7 +1711,9 @@ class Exchange:
return orders
@retrier(retries=0)
def fetch_orders(self, pair: str, since: datetime, params: Optional[dict] = None) -> list[dict]:
def fetch_orders(
self, pair: str, since: datetime, params: dict | None = None
) -> list[CcxtOrder]:
"""
Fetch all orders for a pair "since"
:param pair: Pair for the query
@@ -1712,7 +1729,9 @@ class Exchange:
if not params:
params = {}
try:
orders: list[dict] = self._api.fetch_orders(pair, since=since_ms, params=params)
orders: list[CcxtOrder] = self._api.fetch_orders(
pair, since=since_ms, params=params
)
except ccxt.NotSupported:
# Some exchanges don't support fetchOrders
# attempt to fetch open and closed orders separately
@@ -1757,7 +1776,7 @@ class Exchange:
raise OperationalException(e) from e
@retrier
def fetch_bids_asks(self, symbols: Optional[list[str]] = None, cached: bool = False) -> dict:
def fetch_bids_asks(self, symbols: list[str] | None = None, *, cached: bool = False) -> dict:
"""
:param symbols: List of symbols to fetch
:param cached: Allow cached result
@@ -1790,23 +1809,37 @@ class Exchange:
raise OperationalException(e) from e
@retrier
def get_tickers(self, symbols: Optional[list[str]] = None, cached: bool = False) -> Tickers:
def get_tickers(
self,
symbols: list[str] | None = None,
*,
cached: bool = False,
market_type: TradingMode | None = None,
) -> Tickers:
"""
:param symbols: List of symbols to fetch
:param cached: Allow cached result
:param market_type: Market type to fetch - either spot or futures.
:return: fetch_tickers result
"""
tickers: Tickers
if not self.exchange_has("fetchTickers"):
return {}
cache_key = f"fetch_tickers_{market_type}" if market_type else "fetch_tickers"
if cached:
with self._cache_lock:
tickers = self._fetch_tickers_cache.get("fetch_tickers") # type: ignore
tickers = self._fetch_tickers_cache.get(cache_key) # type: ignore
if tickers:
return tickers
try:
tickers = self._api.fetch_tickers(symbols)
# Re-map futures to swap
market_types = {
TradingMode.FUTURES: "swap",
}
params = {"type": market_types.get(market_type, market_type)} if market_type else {}
tickers = self._api.fetch_tickers(symbols, params)
with self._cache_lock:
self._fetch_tickers_cache["fetch_tickers"] = tickers
self._fetch_tickers_cache[cache_key] = tickers
return tickers
except ccxt.NotSupported as e:
raise OperationalException(
@@ -1830,7 +1863,39 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
# Pricing info
def get_conversion_rate(self, coin: str, currency: str) -> float | None:
"""
Quick and cached way to get conversion rate one currency to the other.
Can then be used as "rate * amount" to convert between currencies.
:param coin: Coin to convert
:param currency: Currency to convert to
:returns: Conversion rate from coin to currency
:raises: ExchangeErrors
"""
if coin == currency:
return 1.0
tickers = self.get_tickers(cached=True)
try:
for pair in self.get_valid_pair_combination(coin, currency):
ticker: Ticker | None = tickers.get(pair, None)
if not ticker:
tickers_other: Tickers = self.get_tickers(
cached=True,
market_type=(
TradingMode.SPOT
if self.trading_mode != TradingMode.SPOT
else TradingMode.FUTURES
),
)
ticker = tickers_other.get(pair, None)
if ticker:
rate: float | None = ticker.get("last", None)
if rate and pair.startswith(currency) and not pair.endswith(currency):
rate = 1.0 / rate
return rate
except ValueError:
return None
return None
@retrier
def fetch_ticker(self, pair: str) -> Ticker:
@@ -1850,7 +1915,7 @@ class Exchange:
@staticmethod
def get_next_limit_in_list(
limit: int, limit_range: Optional[list[int]], range_required: bool = True
limit: int, limit_range: list[int] | None, range_required: bool = True
):
"""
Get next greater value in the list.
@@ -1914,8 +1979,8 @@ class Exchange:
refresh: bool,
side: EntryExit,
is_short: bool,
order_book: Optional[OrderBook] = None,
ticker: Optional[Ticker] = None,
order_book: OrderBook | None = None,
ticker: Ticker | None = None,
) -> float:
"""
Calculates bid/ask target
@@ -1964,7 +2029,7 @@ class Exchange:
def _get_rate_from_ticker(
self, side: EntryExit, ticker: Ticker, conf_strategy: dict[str, Any], price_side: BidAsk
) -> Optional[float]:
) -> float | None:
"""
Get rate from ticker.
"""
@@ -2043,7 +2108,7 @@ class Exchange:
@retrier
def get_trades_for_order(
self, order_id: str, pair: str, since: datetime, params: Optional[dict] = None
self, order_id: str, pair: str, since: datetime, params: dict | None = None
) -> list:
"""
Fetch Orders using the "fetch_my_trades" endpoint and filter them by order-id.
@@ -2090,7 +2155,7 @@ class Exchange:
except ccxt.BaseError as e:
raise OperationalException(e) from e
def get_order_id_conditional(self, order: dict[str, Any]) -> str:
def get_order_id_conditional(self, order: CcxtOrder) -> str:
return order["id"]
@retrier
@@ -2139,7 +2204,7 @@ class Exchange:
raise OperationalException(e) from e
@staticmethod
def order_has_fee(order: dict) -> bool:
def order_has_fee(order: CcxtOrder) -> bool:
"""
Verifies if the passed in order dict has the needed keys to extract fees,
and that these keys (currency, cost) are not empty.
@@ -2158,7 +2223,7 @@ class Exchange:
def calculate_fee_rate(
self, fee: dict, symbol: str, cost: float, amount: float
) -> Optional[float]:
) -> float | None:
"""
Calculate fee rate if it's not given by the exchange.
:param fee: ccxt Fee dict - must contain cost / currency / rate
@@ -2186,10 +2251,13 @@ class Exchange:
# If cost is None or 0.0 -> falsy, return None
return None
try:
comb = self.get_valid_pair_combination(fee_curr, self._config["stake_currency"])
tick = self.fetch_ticker(comb)
fee_to_quote_rate = safe_value_fallback2(tick, tick, "last", "ask")
for comb in self.get_valid_pair_combination(
fee_curr, self._config["stake_currency"]
):
tick = self.fetch_ticker(comb)
fee_to_quote_rate = safe_value_fallback2(tick, tick, "last", "ask")
if tick:
break
except (ValueError, ExchangeError):
fee_to_quote_rate = self._config["exchange"].get("unknown_fee_rate", None)
if not fee_to_quote_rate:
@@ -2197,8 +2265,8 @@ class Exchange:
return round((fee_cost * fee_to_quote_rate) / cost, 8)
def extract_cost_curr_rate(
self, fee: dict, symbol: str, cost: float, amount: float
) -> tuple[float, str, Optional[float]]:
self, fee: dict[str, Any], symbol: str, cost: float, amount: float
) -> tuple[float, str, float | None]:
"""
Extract tuple of cost, currency, rate.
Requires order_has_fee to run first!
@@ -2223,7 +2291,7 @@ class Exchange:
since_ms: int,
candle_type: CandleType,
is_new_pair: bool = False,
until_ms: Optional[int] = None,
until_ms: int | None = None,
) -> DataFrame:
"""
Get candle history using asyncio and returns the list of candles.
@@ -2232,8 +2300,9 @@ class Exchange:
:param pair: Pair to download
:param timeframe: Timeframe to get data for
:param since_ms: Timestamp in milliseconds to get history from
:param until_ms: Timestamp in milliseconds to get history up to
:param candle_type: '', mark, index, premiumIndex, or funding_rate
:param is_new_pair: used by binance subclass to allow "fast" new pair downloading
:param until_ms: Timestamp in milliseconds to get history up to
:return: Dataframe with candle (OHLCV) data
"""
pair, _, _, data, _ = self.loop.run_until_complete(
@@ -2242,11 +2311,10 @@ class Exchange:
timeframe=timeframe,
since_ms=since_ms,
until_ms=until_ms,
is_new_pair=is_new_pair,
candle_type=candle_type,
)
)
logger.info(f"Downloaded data for {pair} with length {len(data)}.")
logger.debug(f"Downloaded data for {pair} from ccxt with length {len(data)}.")
return ohlcv_to_dataframe(data, timeframe, pair, fill_missing=False, drop_incomplete=True)
async def _async_get_historic_ohlcv(
@@ -2255,13 +2323,11 @@ class Exchange:
timeframe: str,
since_ms: int,
candle_type: CandleType,
is_new_pair: bool = False,
raise_: bool = False,
until_ms: Optional[int] = None,
until_ms: int | None = None,
) -> OHLCVResponse:
"""
Download historic ohlcv
:param is_new_pair: used by binance subclass to allow "fast" new pair downloading
:param candle_type: Any of the enum CandleType (must match trading mode!)
"""
@@ -2302,7 +2368,7 @@ class Exchange:
pair: str,
timeframe: str,
candle_type: CandleType,
since_ms: Optional[int],
since_ms: int | None,
cache: bool,
) -> Coroutine[Any, Any, OHLCVResponse]:
not_all_data = cache and self.required_candle_call_count > 1
@@ -2371,7 +2437,7 @@ class Exchange:
)
def _build_ohlcv_dl_jobs(
self, pair_list: ListPairsWithTimeframes, since_ms: Optional[int], cache: bool
self, pair_list: ListPairsWithTimeframes, since_ms: int | None, cache: bool
) -> tuple[list[Coroutine], list[PairWithTimeframe]]:
"""
Build Coroutines to execute as part of refresh_latest_ohlcv
@@ -2448,9 +2514,9 @@ class Exchange:
self,
pair_list: ListPairsWithTimeframes,
*,
since_ms: Optional[int] = None,
since_ms: int | None = None,
cache: bool = True,
drop_incomplete: Optional[bool] = None,
drop_incomplete: bool | None = None,
) -> dict[PairWithTimeframe, DataFrame]:
"""
Refresh in-memory OHLCV asynchronously and set `_klines` with the result
@@ -2544,7 +2610,7 @@ class Exchange:
pair: str,
timeframe: str,
candle_type: CandleType,
since_ms: Optional[int] = None,
since_ms: int | None = None,
) -> OHLCVResponse:
"""
Asynchronously get candle history data using fetch_ohlcv
@@ -2618,7 +2684,7 @@ class Exchange:
pair: str,
timeframe: str,
limit: int,
since_ms: Optional[int] = None,
since_ms: int | None = None,
) -> list[list]:
"""
Fetch funding rate history - used to selectively override this by subclasses.
@@ -2677,7 +2743,7 @@ class Exchange:
async def _build_trades_dl_jobs(
self, pairwt: PairWithTimeframe, data_handler, cache: bool
) -> tuple[PairWithTimeframe, Optional[DataFrame]]:
) -> tuple[PairWithTimeframe, DataFrame | None]:
"""
Build coroutines to refresh trades for (they're then called through async.gather)
"""
@@ -2821,7 +2887,7 @@ class Exchange:
@retrier_async
async def _async_fetch_trades(
self, pair: str, since: Optional[int] = None, params: Optional[dict] = None
self, pair: str, since: int | None = None, params: dict | None = None
) -> tuple[list[list], Any]:
"""
Asynchronously gets trade history using fetch_trades.
@@ -2880,8 +2946,16 @@ class Exchange:
else:
return trades[-1].get("timestamp")
async def _async_get_trade_history_id_startup(
self, pair: str, since: int | None
) -> tuple[list[list], str]:
"""
override for initial trade_history_id call
"""
return await self._async_fetch_trades(pair, since=since)
async def _async_get_trade_history_id(
self, pair: str, until: int, since: Optional[int] = None, from_id: Optional[str] = None
self, pair: str, until: int, since: int | None = None, from_id: str | None = None
) -> tuple[str, list[list]]:
"""
Asynchronously gets trade history using fetch_trades
@@ -2906,7 +2980,7 @@ class Exchange:
# of up to an hour.
# e.g. Binance returns the "last 1000" candles within a 1h time interval
# - so we will miss the first trades.
t, from_id = await self._async_fetch_trades(pair, since=since)
t, from_id = await self._async_get_trade_history_id_startup(pair, since=since)
trades.extend(t[x])
while True:
try:
@@ -2936,7 +3010,7 @@ class Exchange:
return (pair, trades)
async def _async_get_trade_history_time(
self, pair: str, until: int, since: Optional[int] = None
self, pair: str, until: int, since: int | None = None
) -> tuple[str, list[list]]:
"""
Asynchronously gets trade history using fetch_trades,
@@ -2977,9 +3051,9 @@ class Exchange:
async def _async_get_trade_history(
self,
pair: str,
since: Optional[int] = None,
until: Optional[int] = None,
from_id: Optional[str] = None,
since: int | None = None,
until: int | None = None,
from_id: str | None = None,
) -> tuple[str, list[list]]:
"""
Async wrapper handling downloading trades using either time or id based methods.
@@ -3008,9 +3082,9 @@ class Exchange:
def get_historic_trades(
self,
pair: str,
since: Optional[int] = None,
until: Optional[int] = None,
from_id: Optional[str] = None,
since: int | None = None,
until: int | None = None,
from_id: str | None = None,
) -> tuple[str, list]:
"""
Get trade history data using asyncio.
@@ -3039,7 +3113,7 @@ class Exchange:
return self.loop.run_until_complete(task)
@retrier
def _get_funding_fees_from_exchange(self, pair: str, since: Union[datetime, int]) -> float:
def _get_funding_fees_from_exchange(self, pair: str, since: datetime | int) -> float:
"""
Returns the sum of all funding fees that were exchanged for a pair within a timeframe
Dry-run handling happens as part of _calculate_funding_fees.
@@ -3170,8 +3244,8 @@ class Exchange:
file_dump_json(filename, data)
def load_cached_leverage_tiers(
self, stake_currency: str, cache_time: Optional[timedelta] = None
) -> Optional[dict[str, list[dict]]]:
self, stake_currency: str, cache_time: timedelta | None = None
) -> dict[str, list[dict]] | None:
"""
Load cached leverage tiers from disk
:param cache_time: The maximum age of the cache before it is considered outdated
@@ -3216,7 +3290,7 @@ class Exchange:
"maintAmt": float(info["cum"]) if "cum" in info else None,
}
def get_max_leverage(self, pair: str, stake_amount: Optional[float]) -> float:
def get_max_leverage(self, pair: str, stake_amount: float | None) -> float:
"""
Returns the maximum leverage that a pair can be traded at
:param pair: The base/quote currency pair being traded
@@ -3294,7 +3368,7 @@ class Exchange:
def _set_leverage(
self,
leverage: float,
pair: Optional[str] = None,
pair: str | None = None,
accept_fail: bool = False,
):
"""
@@ -3346,7 +3420,7 @@ class Exchange:
pair: str,
margin_mode: MarginMode,
accept_fail: bool = False,
params: Optional[dict] = None,
params: dict | None = None,
):
"""
Set's the margin mode on the exchange to cross or isolated for a specific pair
@@ -3381,7 +3455,7 @@ class Exchange:
amount: float,
is_short: bool,
open_date: datetime,
close_date: Optional[datetime] = None,
close_date: datetime | None = None,
) -> float:
"""
Fetches and calculates the sum of all funding fees that occurred for a pair
@@ -3434,7 +3508,7 @@ class Exchange:
@staticmethod
def combine_funding_and_mark(
funding_rates: DataFrame, mark_rates: DataFrame, futures_funding_rate: Optional[int] = None
funding_rates: DataFrame, mark_rates: DataFrame, futures_funding_rate: int | None = None
) -> DataFrame:
"""
Combine funding-rates and mark-rates dataframes
@@ -3475,7 +3549,7 @@ class Exchange:
is_short: bool,
open_date: datetime,
close_date: datetime,
time_in_ratio: Optional[float] = None,
time_in_ratio: float | None = None,
) -> float:
"""
calculates the sum of all funding fees that occurred for a pair during a futures trade
@@ -3533,8 +3607,8 @@ class Exchange:
stake_amount: float,
leverage: float,
wallet_balance: float,
open_trades: Optional[list] = None,
) -> Optional[float]:
open_trades: list | None = None,
) -> float | None:
"""
Set's the margin mode on the exchange to cross or isolated for a specific pair
"""
@@ -3582,7 +3656,7 @@ class Exchange:
leverage: float,
wallet_balance: float, # Or margin balance
open_trades: list,
) -> Optional[float]:
) -> float | None:
"""
Important: Must be fetching data from cached values as this is used by backtesting!
PERPETUAL:
@@ -3592,7 +3666,7 @@ class Exchange:
Wherein, "+" or "-" depends on whether the contract goes long or short:
"-" for long, and "+" for short.
okex: https://www.okx.com/support/hc/en-us/articles/
okx: https://www.okx.com/support/hc/en-us/articles/
360053909592-VI-Introduction-to-the-isolated-mode-of-Single-Multi-currency-Portfolio-margin
:param pair: Pair to calculate liquidation price for
@@ -3633,7 +3707,7 @@ class Exchange:
self,
pair: str,
notional_value: float,
) -> tuple[float, Optional[float]]:
) -> tuple[float, float | None]:
"""
Important: Must be fetching data from cached values as this is used by backtesting!
:param pair: Market symbol
+21 -18
View File
@@ -1,4 +1,4 @@
from typing import Optional, TypedDict
from typing import Any, Literal, TypedDict
from freqtrade.enums import CandleType
@@ -11,7 +11,7 @@ class FtHas(TypedDict, total=False):
# Stoploss on exchange
stoploss_on_exchange: bool
stop_price_param: str
stop_price_prop: str
stop_price_prop: Literal["stopPrice", "stopLossPrice"]
stop_price_type_field: str
stop_price_type_value_mapping: dict
stoploss_order_types: dict[str, str]
@@ -35,16 +35,17 @@ class FtHas(TypedDict, total=False):
trades_has_history: bool
trades_pagination_overlap: bool
# Orderbook
l2_limit_range: Optional[list[int]]
l2_limit_range: list[int] | None
l2_limit_range_required: bool
# Futures
ccxt_futures_name: str # usually swap
mark_ohlcv_price: str
mark_ohlcv_timeframe: str
funding_fee_timeframe: str
funding_fee_candle_limit: int
floor_leverage: bool
needs_trading_fees: bool
order_props_in_contracts: list[str]
order_props_in_contracts: list[Literal["amount", "cost", "filled", "remaining"]]
# Websocket control
ws_enabled: bool
@@ -52,14 +53,14 @@ class FtHas(TypedDict, total=False):
class Ticker(TypedDict):
symbol: str
ask: Optional[float]
askVolume: Optional[float]
bid: Optional[float]
bidVolume: Optional[float]
last: Optional[float]
quoteVolume: Optional[float]
baseVolume: Optional[float]
percentage: Optional[float]
ask: float | None
askVolume: float | None
bid: float | None
bidVolume: float | None
last: float | None
quoteVolume: float | None
baseVolume: float | None
percentage: float | None
# Several more - only listing required.
@@ -70,9 +71,9 @@ class OrderBook(TypedDict):
symbol: str
bids: list[tuple[float, float]]
asks: list[tuple[float, float]]
timestamp: Optional[int]
datetime: Optional[str]
nonce: Optional[int]
timestamp: int | None
datetime: str | None
nonce: int | None
class CcxtBalance(TypedDict):
@@ -89,10 +90,12 @@ class CcxtPosition(TypedDict):
side: str
contracts: float
leverage: float
collateral: Optional[float]
initialMargin: Optional[float]
liquidationPrice: Optional[float]
collateral: float | None
initialMargin: float | None
liquidationPrice: float | None
CcxtOrder = dict[str, Any]
# pair, timeframe, candleType, OHLCV, drop last?,
OHLCVResponse = tuple[str, str, CandleType, list, bool]
+24 -22
View File
@@ -5,7 +5,7 @@ Exchange support utils
import inspect
from datetime import datetime, timedelta, timezone
from math import ceil, floor
from typing import Any, Optional
from typing import Any
import ccxt
from ccxt import (
@@ -23,6 +23,7 @@ from freqtrade.exchange.common import (
BAD_EXCHANGES,
EXCHANGE_HAS_OPTIONAL,
EXCHANGE_HAS_REQUIRED,
MAP_EXCHANGE_CHILDCLASS,
SUPPORTED_EXCHANGES,
)
from freqtrade.exchange.exchange_utils_timeframe import timeframe_to_minutes, timeframe_to_prev_date
@@ -33,20 +34,18 @@ from freqtrade.util import FtPrecise
CcxtModuleType = Any
def is_exchange_known_ccxt(
exchange_name: str, ccxt_module: Optional[CcxtModuleType] = None
) -> bool:
def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType | None = None) -> bool:
return exchange_name in ccxt_exchanges(ccxt_module)
def ccxt_exchanges(ccxt_module: Optional[CcxtModuleType] = None) -> list[str]:
def ccxt_exchanges(ccxt_module: CcxtModuleType | None = None) -> list[str]:
"""
Return the list of all exchanges known to ccxt
"""
return ccxt_module.exchanges if ccxt_module is not None else ccxt.exchanges
def available_exchanges(ccxt_module: Optional[CcxtModuleType] = None) -> list[str]:
def available_exchanges(ccxt_module: CcxtModuleType | None = None) -> list[str]:
"""
Return exchanges available to the bot, i.e. non-bad exchanges in the ccxt list
"""
@@ -54,7 +53,7 @@ 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, Optional[ccxt.Exchange]]:
def validate_exchange(exchange: str) -> tuple[bool, str, ccxt.Exchange | None]:
"""
returns: can_use, reason, exchange_object
with Reason including both missing and missing_opt
@@ -93,21 +92,24 @@ def validate_exchange(exchange: str) -> tuple[bool, str, Optional[ccxt.Exchange]
def _build_exchange_list_entry(
exchange_name: str, exchangeClasses: dict[str, Any]
) -> ValidExchangesType:
exchange_name = exchange_name.lower()
valid, comment, ex_mod = validate_exchange(exchange_name)
mapped_exchange_name = MAP_EXCHANGE_CHILDCLASS.get(exchange_name, exchange_name).lower()
is_alias = getattr(ex_mod, "alias", False)
result: ValidExchangesType = {
"name": getattr(ex_mod, "name", exchange_name),
"classname": exchange_name,
"valid": valid,
"supported": exchange_name.lower() in SUPPORTED_EXCHANGES,
"supported": mapped_exchange_name in SUPPORTED_EXCHANGES and not is_alias,
"comment": comment,
"dex": getattr(ex_mod, "dex", False),
"is_alias": getattr(ex_mod, "alias", False),
"is_alias": is_alias,
"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()):
if resolved := exchangeClasses.get(mapped_exchange_name):
supported_modes = [{"trading_mode": "spot", "margin_mode": ""}] + [
{"trading_mode": tm.value, "margin_mode": mm.value}
for tm, mm in resolved["class"]._supported_trading_mode_margin_pairs
@@ -137,9 +139,7 @@ def list_available_exchanges(all_exchanges: bool) -> list[ValidExchangesType]:
return exchanges_valid
def date_minus_candles(
timeframe: str, candle_count: int, date: Optional[datetime] = None
) -> datetime:
def date_minus_candles(timeframe: str, candle_count: int, date: datetime | None = None) -> datetime:
"""
subtract X candles from a date.
:param timeframe: timeframe in string format (e.g. "5m")
@@ -166,7 +166,7 @@ def market_is_active(market: dict) -> bool:
return market.get("active", True) is not False
def amount_to_contracts(amount: float, contract_size: Optional[float]) -> float:
def amount_to_contracts(amount: float, contract_size: float | None) -> float:
"""
Convert amount to contracts.
:param amount: amount to convert
@@ -179,7 +179,7 @@ def amount_to_contracts(amount: float, contract_size: Optional[float]) -> float:
return amount
def contracts_to_amount(num_contracts: float, contract_size: Optional[float]) -> float:
def contracts_to_amount(num_contracts: float, contract_size: float | None) -> float:
"""
Takes num-contracts and converts it to contract size
:param num_contracts: number of contracts
@@ -194,7 +194,7 @@ def contracts_to_amount(num_contracts: float, contract_size: Optional[float]) ->
def amount_to_precision(
amount: float, amount_precision: Optional[float], precisionMode: Optional[int]
amount: float, amount_precision: float | None, precisionMode: int | None
) -> float:
"""
Returns the amount to buy or sell to a precision the Exchange accepts
@@ -224,9 +224,9 @@ def amount_to_precision(
def amount_to_contract_precision(
amount,
amount_precision: Optional[float],
precisionMode: Optional[int],
contract_size: Optional[float],
amount_precision: float | None,
precisionMode: int | None,
contract_size: float | None,
) -> float:
"""
Returns the amount to buy or sell to a precision the Exchange accepts
@@ -285,8 +285,8 @@ def __price_to_precision_significant_digits(
def price_to_precision(
price: float,
price_precision: Optional[float],
precisionMode: Optional[int],
price_precision: float | None,
precisionMode: int | None,
*,
rounding_mode: int = ROUND,
) -> float:
@@ -312,7 +312,9 @@ def price_to_precision(
decimal_to_precision(
price,
rounding_mode=rounding_mode,
precision=price_precision,
precision=int(price_precision)
if precisionMode != TICK_SIZE
else price_precision,
counting_mode=precisionMode,
)
)
@@ -1,5 +1,4 @@
from datetime import datetime, timezone
from typing import Optional
import ccxt
from ccxt import ROUND_DOWN, ROUND_UP
@@ -51,7 +50,7 @@ def timeframe_to_resample_freq(timeframe: str) -> str:
return resample_interval
def timeframe_to_prev_date(timeframe: str, date: Optional[datetime] = None) -> datetime:
def timeframe_to_prev_date(timeframe: str, date: datetime | None = None) -> datetime:
"""
Use Timeframe and determine the candle start date for this date.
Does not round when given a candle start date.
@@ -66,7 +65,7 @@ def timeframe_to_prev_date(timeframe: str, date: Optional[datetime] = None) -> d
return dt_from_ts(new_timestamp)
def timeframe_to_next_date(timeframe: str, date: Optional[datetime] = None) -> datetime:
def timeframe_to_next_date(timeframe: str, date: datetime | None = None) -> datetime:
"""
Use Timeframe and determine next candle.
:param timeframe: timeframe in string format (e.g. "5m")
+43 -8
View File
@@ -2,12 +2,15 @@
import logging
from datetime import datetime
from typing import Any, Optional
import ccxt
from freqtrade.constants import BuySell
from freqtrade.enums import MarginMode, PriceType, TradingMode
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
from freqtrade.exchange import Exchange
from freqtrade.exchange.exchange_types import FtHas
from freqtrade.exchange.common import retrier
from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
from freqtrade.misc import safe_value_fallback2
@@ -24,6 +27,8 @@ class Gate(Exchange):
may still not work as expected.
"""
unified_account = False
_ft_has: FtHas = {
"ohlcv_candle_limit": 1000,
"order_time_in_force": ["GTC", "IOC"],
@@ -38,6 +43,7 @@ class Gate(Exchange):
_ft_has_futures: FtHas = {
"needs_trading_fees": True,
"marketOrderRequiresPrice": False,
"funding_fee_candle_limit": 90,
"stop_price_type_field": "price_type",
"stop_price_type_value_mapping": {
PriceType.LAST: 0,
@@ -53,6 +59,35 @@ class Gate(Exchange):
(TradingMode.FUTURES, MarginMode.ISOLATED)
]
@retrier
def additional_exchange_init(self) -> None:
"""
Additional exchange initialization logic.
.api will be available at this point.
Must be overridden in child methods if required.
"""
try:
if not self._config["dry_run"]:
# TODO: This should work with 4.4.34 and later.
self._api.load_unified_status()
is_unified = self._api.options.get("unifiedAccount")
# Returns a tuple of bools, first for margin, second for Account
if is_unified:
self.unified_account = True
logger.info("Gate: Unified account.")
else:
self.unified_account = False
logger.info("Gate: Classic account.")
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f"Error in additional_exchange_init due to {e.__class__.__name__}. Message: {e}"
) from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
def _get_params(
self,
side: BuySell,
@@ -74,7 +109,7 @@ class Gate(Exchange):
return params
def get_trades_for_order(
self, order_id: str, pair: str, since: datetime, params: Optional[dict] = None
self, order_id: str, pair: str, since: datetime, params: dict | None = None
) -> list:
trades = super().get_trades_for_order(order_id, pair, since, params)
@@ -99,10 +134,12 @@ class Gate(Exchange):
}
return trades
def get_order_id_conditional(self, order: dict[str, Any]) -> str:
def get_order_id_conditional(self, order: CcxtOrder) -> str:
return safe_value_fallback2(order, order, "id_stop", "id")
def fetch_stoploss_order(self, order_id: str, pair: str, params: Optional[dict] = None) -> dict:
def fetch_stoploss_order(
self, order_id: str, pair: str, params: dict | None = None
) -> CcxtOrder:
order = self.fetch_order(order_id=order_id, pair=pair, params={"stop": True})
if order.get("status", "open") == "closed":
# Places a real order - which we need to fetch explicitly.
@@ -119,7 +156,5 @@ class Gate(Exchange):
return order1
return order
def cancel_stoploss_order(
self, order_id: str, pair: str, params: Optional[dict] = None
) -> dict:
def cancel_stoploss_order(self, order_id: str, pair: str, params: dict | None = None) -> dict:
return self.cancel_order(order_id=order_id, pair=pair, params={"stop": True})
+180 -7
View File
@@ -1,10 +1,15 @@
"""Hyperliquid exchange subclass"""
import logging
from copy import deepcopy
from datetime import datetime
from freqtrade.enums import TradingMode
from freqtrade.constants import BuySell
from freqtrade.enums import MarginMode, TradingMode
from freqtrade.exceptions import ExchangeError, OperationalException
from freqtrade.exchange import Exchange
from freqtrade.exchange.exchange_types import FtHas
from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
from freqtrade.util.datetime_helpers import dt_from_ts
logger = logging.getLogger(__name__)
@@ -16,20 +21,188 @@ class Hyperliquid(Exchange):
"""
_ft_has: FtHas = {
# Only the most recent 5000 candles are available according to the
# exchange's API documentation.
"ohlcv_has_history": False,
"ohlcv_candle_limit": 5000,
"trades_has_history": False, # Trades endpoint doesn't seem available.
"l2_limit_range": [20],
"trades_has_history": False,
"tickers_have_bid_ask": False,
"stoploss_on_exchange": False,
"exchange_has_overrides": {"fetchTrades": False},
"marketOrderRequiresPrice": True,
}
_ft_has_futures: FtHas = {
"stoploss_on_exchange": True,
"stoploss_order_types": {"limit": "limit"},
"stop_price_prop": "stopPrice",
"funding_fee_timeframe": "1h",
"funding_fee_candle_limit": 500,
}
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
(TradingMode.FUTURES, MarginMode.ISOLATED)
]
@property
def _ccxt_config(self) -> dict:
# Parameters to add directly to ccxt sync/async initialization.
# ccxt defaults to swap mode.
# ccxt Hyperliquid defaults to swap
config = {}
if self.trading_mode == TradingMode.SPOT:
config.update({"options": {"defaultType": "spot"}})
config.update(super()._ccxt_config)
return config
def get_max_leverage(self, pair: str, stake_amount: float | None) -> float:
# There are no leverage tiers
if self.trading_mode == TradingMode.FUTURES:
return self.markets[pair]["limits"]["leverage"]["max"]
else:
return 1.0
def _lev_prep(self, pair: str, leverage: float, side: BuySell, accept_fail: bool = False):
if self.trading_mode != TradingMode.SPOT:
# Hyperliquid expects leverage to be an int
leverage = int(leverage)
# Hyperliquid needs the parameter leverage.
# Don't use _set_leverage(), as this sets margin back to cross
self.set_margin_mode(pair, self.margin_mode, params={"leverage": leverage})
def dry_run_liquidation_price(
self,
pair: str,
open_rate: float, # Entry price of position
is_short: bool,
amount: float,
stake_amount: float,
leverage: float,
wallet_balance: float, # Or margin balance
open_trades: list,
) -> float | None:
"""
Optimized
Docs: https://hyperliquid.gitbook.io/hyperliquid-docs/trading/liquidations
Below can be done in fewer lines of code, but like this it matches the documentation.
Tested with 196 unique ccxt fetch_positions() position outputs
- Only first output per position where pnl=0.0
- Compare against returned liquidation price
Positions: 197 Average deviation: 0.00028980% Max deviation: 0.01309453%
Positions info:
{'leverage': {1.0: 23, 2.0: 155, 3.0: 8, 4.0: 7, 5.0: 4},
'side': {'long': 133, 'short': 64},
'symbol': {'BTC/USDC:USDC': 81,
'DOGE/USDC:USDC': 20,
'ETH/USDC:USDC': 53,
'SOL/USDC:USDC': 43}}
"""
# Defining/renaming variables to match the documentation
isolated_margin = wallet_balance
position_size = amount
price = open_rate
position_value = price * position_size
max_leverage = self.markets[pair]["limits"]["leverage"]["max"]
# Docs: The maintenance margin is half of the initial margin at max leverage,
# which varies from 3-50x. In other words, the maintenance margin is between 1%
# (for 50x max leverage assets) and 16.7% (for 3x max leverage assets)
# depending on the asset
# The key thing here is 'Half of the initial margin at max leverage'.
# A bit ambiguous, but this interpretation leads to accurate results:
# 1. Start from the position value
# 2. Assume max leverage, calculate the initial margin by dividing the position value
# by the max leverage
# 3. Divide this by 2
maintenance_margin_required = position_value / max_leverage / 2
# Docs: margin_available (isolated) = isolated_margin - maintenance_margin_required
margin_available = isolated_margin - maintenance_margin_required
# Docs: The maintenance margin is half of the initial margin at max leverage
# The docs don't explicitly specify maintenance leverage, but this works.
# Double because of the statement 'half of the initial margin at max leverage'
maintenance_leverage = max_leverage * 2
# Docs: l = 1 / MAINTENANCE_LEVERAGE (Using 'll' to comply with PEP8: E741)
ll = 1 / maintenance_leverage
# Docs: side = 1 for long and -1 for short
side = -1 if is_short else 1
# Docs: liq_price = price - side * margin_available / position_size / (1 - l * side)
liq_price = price - side * margin_available / position_size / (1 - ll * side)
if self.trading_mode == TradingMode.FUTURES:
return liq_price
else:
raise OperationalException(
"Freqtrade only supports isolated futures for leverage trading"
)
def get_funding_fees(
self, pair: str, amount: float, is_short: bool, open_date: datetime
) -> float:
"""
Fetch funding fees, either from the exchange (live) or calculates them
based on funding rate/mark price history
:param pair: The quote/base pair of the trade
:param is_short: trade direction
:param amount: Trade amount
:param open_date: Open date of the trade
:return: funding fee since open_date
:raises: ExchangeError if something goes wrong.
"""
# Hyperliquid does not have fetchFundingHistory
if self.trading_mode == TradingMode.FUTURES:
try:
return self._fetch_and_calculate_funding_fees(pair, amount, is_short, open_date)
except ExchangeError:
logger.warning(f"Could not update funding fees for {pair}.")
return 0.0
def _adjust_hyperliquid_order(
self,
order: dict,
) -> dict:
"""
Adjusts order response for Hyperliquid
:param order: Order response from Hyperliquid
:return: Adjusted order response
"""
if (
order["average"] is None
and order["status"] in ("canceled", "closed")
and order["filled"] > 0
):
# Hyperliquid does not fill the average price in the order response
# Fetch trades to calculate the average price to have the actual price
# the order was executed at
trades = self.get_trades_for_order(
order["id"], order["symbol"], since=dt_from_ts(order["timestamp"])
)
if trades:
total_amount = sum(t["amount"] for t in trades)
order["average"] = (
sum(t["price"] * t["amount"] for t in trades) / total_amount
if total_amount
else None
)
return order
def fetch_order(self, order_id: str, pair: str, params: dict | None = None) -> CcxtOrder:
order = super().fetch_order(order_id, pair, params)
order = self._adjust_hyperliquid_order(order)
self._log_exchange_response("fetch_order2", order)
return order
def fetch_orders(
self, pair: str, since: datetime, params: dict | None = None
) -> list[CcxtOrder]:
orders = super().fetch_orders(pair, since, params)
for idx, order in enumerate(deepcopy(orders)):
order2 = self._adjust_hyperliquid_order(order)
orders[idx] = order2
self._log_exchange_response("fetch_orders2", orders)
return orders
+32 -5
View File
@@ -2,7 +2,7 @@
import logging
from datetime import datetime
from typing import Any, Optional
from typing import Any
import ccxt
from pandas import DataFrame
@@ -50,11 +50,33 @@ class Kraken(Exchange):
return parent_check and market.get("darkpool", False) is False
def get_tickers(self, symbols: Optional[list[str]] = None, cached: bool = False) -> Tickers:
def get_tickers(
self,
symbols: list[str] | None = None,
*,
cached: bool = False,
market_type: TradingMode | None = None,
) -> Tickers:
# Only fetch tickers for current stake currency
# Otherwise the request for kraken becomes too large.
symbols = list(self.get_markets(quote_currencies=[self._config["stake_currency"]]))
return super().get_tickers(symbols=symbols, cached=cached)
return super().get_tickers(symbols=symbols, cached=cached, market_type=market_type)
def consolidate_balances(self, balances: CcxtBalances) -> CcxtBalances:
"""
Consolidate balances for the same currency.
Kraken returns ".F" balances if rewards is enabled.
"""
consolidated: CcxtBalances = {}
for currency, balance in balances.items():
base_currency = currency[:-2] if currency.endswith(".F") else currency
if base_currency in consolidated:
consolidated[base_currency]["free"] += balance["free"]
consolidated[base_currency]["used"] += balance["used"]
consolidated[base_currency]["total"] += balance["total"]
else:
consolidated[base_currency] = balance
return consolidated
@retrier
def get_balances(self) -> CcxtBalances:
@@ -68,6 +90,10 @@ class Kraken(Exchange):
balances.pop("free", None)
balances.pop("total", None)
balances.pop("used", None)
self._log_exchange_response("fetch_balances", balances)
# Consolidate balances
balances = self.consolidate_balances(balances)
orders = self._api.fetch_open_orders()
order_list = [
@@ -86,6 +112,7 @@ class Kraken(Exchange):
balances[bal]["used"] = sum(order[1] for order in order_list if order[0] == bal)
balances[bal]["free"] = balances[bal]["total"] - balances[bal]["used"]
self._log_exchange_response("fetch_balances2", balances)
return balances
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
@@ -99,7 +126,7 @@ class Kraken(Exchange):
def _set_leverage(
self,
leverage: float,
pair: Optional[str] = None,
pair: str | None = None,
accept_fail: bool = False,
):
"""
@@ -137,7 +164,7 @@ class Kraken(Exchange):
is_short: bool,
open_date: datetime,
close_date: datetime,
time_in_ratio: Optional[float] = None,
time_in_ratio: float | None = None,
) -> float:
"""
# ! This method will always error when run by Freqtrade because time_in_ratio is never
+2 -2
View File
@@ -4,7 +4,7 @@ import logging
from freqtrade.constants import BuySell
from freqtrade.exchange import Exchange
from freqtrade.exchange.exchange_types import FtHas
from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
logger = logging.getLogger(__name__)
@@ -47,7 +47,7 @@ class Kucoin(Exchange):
leverage: float,
reduceOnly: bool = False,
time_in_force: str = "GTC",
) -> dict:
) -> CcxtOrder:
res = super().create_order(
pair=pair,
ordertype=ordertype,
+10 -11
View File
@@ -1,6 +1,5 @@
import logging
from datetime import timedelta
from typing import Any, Optional
import ccxt
@@ -14,7 +13,7 @@ from freqtrade.exceptions import (
)
from freqtrade.exchange import Exchange, date_minus_candles
from freqtrade.exchange.common import API_RETRY_COUNT, retrier
from freqtrade.exchange.exchange_types import FtHas
from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
from freqtrade.misc import safe_value_fallback2
from freqtrade.util import dt_now, dt_ts
@@ -60,7 +59,7 @@ class Okx(Exchange):
_ccxt_params: dict = {"options": {"brokerId": "ffb5405ad327SUDE"}}
def ohlcv_candle_limit(
self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = None
self, timeframe: str, candle_type: CandleType, since_ms: int | None = None
) -> int:
"""
Exchange ohlcv candle limit
@@ -191,7 +190,7 @@ class Okx(Exchange):
params["posSide"] = self._get_posSide(side, True)
return params
def _convert_stop_order(self, pair: str, order_id: str, order: dict) -> dict:
def _convert_stop_order(self, pair: str, order_id: str, order: CcxtOrder) -> CcxtOrder:
if (
order.get("status", "open") == "closed"
and (real_order_id := order.get("info", {}).get("ordId")) is not None
@@ -209,7 +208,9 @@ class Okx(Exchange):
return order
@retrier(retries=API_RETRY_COUNT)
def fetch_stoploss_order(self, order_id: str, pair: str, params: Optional[dict] = None) -> dict:
def fetch_stoploss_order(
self, order_id: str, pair: str, params: dict | None = None
) -> CcxtOrder:
if self._config["dry_run"]:
return self.fetch_dry_run_order(order_id)
@@ -231,7 +232,7 @@ class Okx(Exchange):
return self._fetch_stop_order_fallback(order_id, pair)
def _fetch_stop_order_fallback(self, order_id: str, pair: str) -> dict:
def _fetch_stop_order_fallback(self, order_id: str, pair: str) -> CcxtOrder:
params2 = {"stop": True, "ordType": "conditional"}
for method in (
self._api.fetch_open_orders,
@@ -256,14 +257,12 @@ class Okx(Exchange):
raise OperationalException(e) from e
raise RetryableOrderError(f"StoplossOrder not found (pair: {pair} id: {order_id}).")
def get_order_id_conditional(self, order: dict[str, Any]) -> str:
def get_order_id_conditional(self, order: CcxtOrder) -> str:
if order.get("type", "") == "stop":
return safe_value_fallback2(order, order, "id_stop", "id")
return order["id"]
def cancel_stoploss_order(
self, order_id: str, pair: str, params: Optional[dict] = None
) -> dict:
def cancel_stoploss_order(self, order_id: str, pair: str, params: dict | None = None) -> dict:
params1 = {"stop": True}
# 'ordType': 'conditional'
#
@@ -273,7 +272,7 @@ class Okx(Exchange):
params=params1,
)
def _fetch_orders_emulate(self, pair: str, since_ms: int) -> list[dict]:
def _fetch_orders_emulate(self, pair: str, since_ms: int) -> list[CcxtOrder]:
orders = []
orders = self._api.fetch_closed_orders(pair, since=since_ms)
+3 -4
View File
@@ -2,7 +2,6 @@ import logging
import random
from abc import abstractmethod
from enum import Enum
from typing import Optional, Union
import gymnasium as gym
import numpy as np
@@ -140,7 +139,7 @@ class BaseEnvironment(gym.Env):
self._end_tick: int = len(self.prices) - 1
self._done: bool = False
self._current_tick: int = self._start_tick
self._last_trade_tick: Optional[int] = None
self._last_trade_tick: int | None = None
self._position = Positions.Neutral
self._position_history: list = [None]
self.total_reward: float = 0
@@ -173,8 +172,8 @@ class BaseEnvironment(gym.Env):
def tensorboard_log(
self,
metric: str,
value: Optional[Union[int, float]] = None,
inc: Optional[bool] = None,
value: int | float | None = None,
inc: bool | None = None,
category: str = "custom",
):
"""
@@ -2,9 +2,10 @@ import copy
import importlib
import logging
from abc import abstractmethod
from collections.abc import Callable
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Callable, Optional, Union
from typing import Any
import gymnasium as gym
import numpy as np
@@ -49,9 +50,9 @@ class BaseReinforcementLearningModel(IFreqaiModel):
)
th.set_num_threads(self.max_threads)
self.reward_params = self.freqai_info["rl_config"]["model_reward_parameters"]
self.train_env: Union[VecMonitor, SubprocVecEnv, gym.Env] = gym.Env()
self.eval_env: Union[VecMonitor, SubprocVecEnv, gym.Env] = gym.Env()
self.eval_callback: Optional[MaskableEvalCallback] = None
self.train_env: VecMonitor | SubprocVecEnv | gym.Env = gym.Env()
self.eval_env: VecMonitor | SubprocVecEnv | gym.Env = gym.Env()
self.eval_callback: MaskableEvalCallback | None = None
self.model_type = self.freqai_info["rl_config"]["model_type"]
self.rl_config = self.freqai_info["rl_config"]
self.df_raw: DataFrame = DataFrame()
+3 -3
View File
@@ -5,7 +5,7 @@ import random
import shutil
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Optional
from typing import Any
import numpy as np
import numpy.typing as npt
@@ -111,7 +111,7 @@ class FreqaiDataKitchen:
def set_paths(
self,
pair: str,
trained_timestamp: Optional[int] = None,
trained_timestamp: int | None = None,
) -> None:
"""
Set the paths to the data for the present coin/botloop
@@ -849,7 +849,7 @@ class FreqaiDataKitchen:
dataframe = strategy.set_freqai_targets(dataframe.copy(), metadata=metadata)
dataframe = self.remove_special_chars_from_feature_names(dataframe)
self.get_unique_classes_from_labels(dataframe)
self.get_unique_classes_from_labels(dataframe)
if self.config.get("reduce_df_footprint", False):
dataframe = reduce_dataframe_footprint(dataframe)
+5 -3
View File
@@ -5,7 +5,7 @@ from abc import ABC, abstractmethod
from collections import deque
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Literal, Optional
from typing import Any, Literal
import datasieve.transforms as ds
import numpy as np
@@ -106,7 +106,7 @@ class IFreqaiModel(ABC):
self._threads: list[threading.Thread] = []
self._stop_event = threading.Event()
self.metadata: dict[str, Any] = self.dd.load_global_metadata_from_disk()
self.data_provider: Optional[DataProvider] = None
self.data_provider: DataProvider | None = None
self.max_system_threads = max(int(psutil.cpu_count() * 2 - 2), 1)
self.can_short = True # overridden in start() with strategy.can_short
self.model: Any = None
@@ -294,7 +294,9 @@ class IFreqaiModel(ABC):
# tr_backtest is the backtesting time range e.g. the week directly
# following tr_train. Both of these windows slide through the
# entire backtest
for tr_train, tr_backtest in zip(dk.training_timeranges, dk.backtesting_timeranges):
for tr_train, tr_backtest in zip(
dk.training_timeranges, dk.backtesting_timeranges, strict=False
):
(_, _) = self.dd.get_pair_dict_info(pair)
train_it += 1
total_trains = len(dk.backtesting_timeranges)
@@ -1,6 +1,6 @@
import logging
from pathlib import Path
from typing import Any, Optional
from typing import Any
import torch as th
from stable_baselines3.common.callbacks import ProgressBarCallback
@@ -78,7 +78,7 @@ class ReinforcementLearner(BaseReinforcementLearningModel):
model = self.dd.model_dictionary[dk.pair]
model.set_env(self.train_env)
callbacks: list[Any] = [self.eval_callback, self.tensorboard_callback]
progressbar_callback: Optional[ProgressBarCallback] = None
progressbar_callback: ProgressBarCallback | None = None
if self.rl_config.get("progress_bar", False):
progressbar_callback = ProgressBarCallback()
callbacks.insert(0, progressbar_callback)
@@ -5,7 +5,6 @@ from xgboost import XGBRFRegressor
from freqtrade.freqai.base_models.BaseRegressionModel import BaseRegressionModel
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
from freqtrade.freqai.tensorboard import TBCallback
logger = logging.getLogger(__name__)
@@ -45,7 +44,12 @@ class XGBoostRFRegressor(BaseRegressionModel):
model = XGBRFRegressor(**self.model_training_parameters)
model.set_params(callbacks=[TBCallback(dk.data_path)])
# Callbacks are not supported for XGBRFRegressor, and version 2.1.x started to throw
# the following error:
# NotImplementedError: `early_stopping_rounds` and `callbacks` are not implemented
# for random forest.
# model.set_params(callbacks=[TBCallback(dk.data_path)])
model.fit(
X=X,
y=y,
@@ -55,6 +59,6 @@ class XGBoostRFRegressor(BaseRegressionModel):
xgb_model=xgb_model,
)
# set the callbacks to empty so that we can serialize to disk later
model.set_params(callbacks=[])
# model.set_params(callbacks=[])
return model
@@ -1,5 +1,5 @@
from enum import Enum
from typing import Any, Union
from typing import Any
from stable_baselines3.common.callbacks import BaseCallback
from stable_baselines3.common.logger import HParam
@@ -27,7 +27,7 @@ class TensorboardCallback(BaseCallback):
# "batch_size": self.model.batch_size,
# "n_steps": self.model.n_steps,
}
metric_dict: dict[str, Union[float, int]] = {
metric_dict: dict[str, float | int] = {
"eval/mean_reward": 0,
"rollout/ep_rew_mean": 0,
"rollout/ep_len_mean": 0,
+1 -1
View File
@@ -45,7 +45,7 @@ class TensorBoardCallback(BaseTensorBoardCallback):
return False
evals = ["validation", "train"]
for metric, eval_ in zip(evals_log.items(), evals):
for metric, eval_ in zip(evals_log.items(), evals, strict=False):
for metric_name, log in metric[1].items():
score = log[-1][0] if isinstance(log[-1], tuple) else log[-1]
self.writer.add_scalar(f"{eval_}-{metric_name}", score, epoch)
@@ -1,6 +1,6 @@
import logging
from pathlib import Path
from typing import Any, Optional
from typing import Any
import pandas as pd
import torch
@@ -50,8 +50,8 @@ class PyTorchModelTrainer(PyTorchTrainerInterface):
self.criterion = criterion
self.model_meta_data = model_meta_data
self.device = device
self.n_epochs: Optional[int] = kwargs.get("n_epochs", 10)
self.n_steps: Optional[int] = kwargs.get("n_steps", None)
self.n_epochs: int | None = kwargs.get("n_epochs", 10)
self.n_steps: int | None = kwargs.get("n_steps", None)
if self.n_steps is None and not self.n_epochs:
raise Exception("Either `n_steps` or `n_epochs` should be set.")
+1 -1
View File
@@ -107,7 +107,7 @@ def plot_feature_importance(
# Extract feature importance from model
models = {}
if "FreqaiMultiOutputRegressor" in str(model.__class__):
for estimator, label in zip(model.estimators_, dk.label_list):
for estimator, label in zip(model.estimators_, dk.label_list, strict=False):
models[label] = estimator
else:
models[dk.label_list[0]] = model
+54 -42
View File
@@ -9,7 +9,7 @@ from datetime import datetime, time, timedelta, timezone
from math import isclose
from threading import Lock
from time import sleep
from typing import Any, Optional
from typing import Any
from schedule import Scheduler
@@ -43,6 +43,7 @@ from freqtrade.exchange import (
timeframe_to_next_date,
timeframe_to_seconds,
)
from freqtrade.exchange.exchange_types import CcxtOrder
from freqtrade.leverage.liquidation_price import update_liquidation_prices
from freqtrade.misc import safe_value_fallback, safe_value_fallback2
from freqtrade.mixins import LoggingMixin
@@ -111,7 +112,7 @@ class FreqtradeBot(LoggingMixin):
self.trading_mode: TradingMode = self.config.get("trading_mode", TradingMode.SPOT)
self.margin_mode: MarginMode = self.config.get("margin_mode", MarginMode.NONE)
self.last_process: Optional[datetime] = None
self.last_process: datetime | None = None
# RPC runs in separate threads, can start handling external commands just after
# initialization, even before Freqtradebot has a chance to start its throttling,
@@ -325,7 +326,7 @@ class FreqtradeBot(LoggingMixin):
}
self.rpc.send_msg(msg)
def _refresh_active_whitelist(self, trades: Optional[list[Trade]] = None) -> list[str]:
def _refresh_active_whitelist(self, trades: list[Trade] | None = None) -> list[str]:
"""
Refresh active whitelist from pairlist or edge and extend it with
pairs that have open trades.
@@ -579,7 +580,7 @@ class FreqtradeBot(LoggingMixin):
logger.warning(
f"{trade} has a total of {trade.amount} {trade.base_currency}, "
f"but the Wallet shows a total of {total} {trade.base_currency}. "
f"Adjusting trade amount to {total}."
f"Adjusting trade amount to {total}. "
"This may however lead to further issues."
)
trade.amount = total
@@ -587,7 +588,7 @@ class FreqtradeBot(LoggingMixin):
logger.warning(
f"{trade} has a total of {trade.amount} {trade.base_currency}, "
f"but the Wallet shows a total of {total} {trade.base_currency}. "
"Refusing to adjust as the difference is too large."
"Refusing to adjust as the difference is too large. "
"This may however lead to further issues."
)
if prev_trade_amount != trade.amount:
@@ -862,14 +863,14 @@ class FreqtradeBot(LoggingMixin):
self,
pair: str,
stake_amount: float,
price: Optional[float] = None,
price: float | None = None,
*,
is_short: bool = False,
ordertype: Optional[str] = None,
enter_tag: Optional[str] = None,
trade: Optional[Trade] = None,
ordertype: str | None = None,
enter_tag: str | None = None,
trade: Trade | None = None,
mode: EntryExecuteMode = "initial",
leverage_: Optional[float] = None,
leverage_: float | None = None,
) -> bool:
"""
Executes an entry for the given pair
@@ -1078,13 +1079,13 @@ class FreqtradeBot(LoggingMixin):
def get_valid_enter_price_and_stake(
self,
pair: str,
price: Optional[float],
price: float | None,
stake_amount: float,
trade_side: LongShort,
entry_tag: Optional[str],
trade: Optional[Trade],
entry_tag: str | None,
trade: Trade | None,
mode: EntryExecuteMode,
leverage_: Optional[float],
leverage_: float | None,
) -> tuple[float, float, float]:
"""
Validate and eventually adjust (within limits) limit, amount and leverage
@@ -1180,7 +1181,7 @@ class FreqtradeBot(LoggingMixin):
self,
trade: Trade,
order: Order,
order_type: Optional[str],
order_type: str | None,
fill: bool = False,
sub_trade: bool = False,
) -> None:
@@ -1195,6 +1196,13 @@ class FreqtradeBot(LoggingMixin):
current_rate = self.exchange.get_rate(
trade.pair, side="entry", is_short=trade.is_short, refresh=False
)
stake_amount = trade.stake_amount
if not fill and trade.nr_of_successful_entries > 0:
# If we have open orders, we need to add the stake amount of the open orders
# as it's not yet included in the trade.stake_amount
stake_amount += sum(
o.stake_amount for o in trade.open_orders if o.ft_order_side == trade.entry_side
)
msg: RPCEntryMsg = {
"trade_id": trade.id,
@@ -1208,12 +1216,12 @@ class FreqtradeBot(LoggingMixin):
"limit": open_rate, # Deprecated (?)
"open_rate": open_rate,
"order_type": order_type or "unknown",
"stake_amount": trade.stake_amount,
"stake_amount": stake_amount,
"stake_currency": self.config["stake_currency"],
"base_currency": self.exchange.get_pair_base_currency(trade.pair),
"quote_currency": self.exchange.get_pair_quote_currency(trade.pair),
"fiat_currency": self.config.get("fiat_display_currency", None),
"amount": order.safe_amount_after_fee if fill else (order.amount or trade.amount),
"amount": order.safe_amount_after_fee if fill else (order.safe_amount or trade.amount),
"open_date": trade.open_date_utc or datetime.now(timezone.utc),
"current_rate": current_rate,
"sub_trade": sub_trade,
@@ -1295,7 +1303,7 @@ class FreqtradeBot(LoggingMixin):
logger.warning(
f"Unable to handle stoploss on exchange for {trade.pair}: {exception}"
)
# Check if we can sell our current pair
# Check if we can exit our current pair
if not trade.has_open_orders and trade.is_open and self.handle_trade(trade):
trades_closed += 1
@@ -1344,7 +1352,7 @@ class FreqtradeBot(LoggingMixin):
return False
def _check_and_execute_exit(
self, trade: Trade, exit_rate: float, enter: bool, exit_: bool, exit_tag: Optional[str]
self, trade: Trade, exit_rate: float, enter: bool, exit_: bool, exit_tag: str | None
) -> bool:
"""
Check and execute trade exit
@@ -1466,7 +1474,7 @@ class FreqtradeBot(LoggingMixin):
return False
def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: dict) -> None:
def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: CcxtOrder) -> None:
"""
Check to see if stoploss on exchange should be updated
in case of trailing stoploss on exchange
@@ -1504,7 +1512,7 @@ class FreqtradeBot(LoggingMixin):
f"Could not create trailing stoploss order for pair {trade.pair}."
)
def manage_trade_stoploss_orders(self, trade: Trade, stoploss_orders: list[dict]):
def manage_trade_stoploss_orders(self, trade: Trade, stoploss_orders: list[CcxtOrder]):
"""
Perform required actions according to existing stoploss orders of trade
:param trade: Corresponding Trade
@@ -1580,7 +1588,9 @@ class FreqtradeBot(LoggingMixin):
else:
self.replace_order(order, open_order, trade)
def handle_cancel_order(self, order: dict, order_obj: Order, trade: Trade, reason: str) -> None:
def handle_cancel_order(
self, order: CcxtOrder, order_obj: Order, trade: Trade, reason: str
) -> None:
"""
Check if current analyzed order timed out and cancel if necessary.
:param order: Order dict grabbed with exchange.fetch_order()
@@ -1602,7 +1612,7 @@ class FreqtradeBot(LoggingMixin):
self.emergency_exit(trade, order["price"], order["amount"])
def emergency_exit(
self, trade: Trade, price: float, sub_trade_amt: Optional[float] = None
self, trade: Trade, price: float, sub_trade_amt: float | None = None
) -> None:
try:
self.execute_trade_exit(
@@ -1632,7 +1642,7 @@ class FreqtradeBot(LoggingMixin):
)
trade.delete()
def replace_order(self, order: dict, order_obj: Optional[Order], trade: Trade) -> None:
def replace_order(self, order: CcxtOrder, order_obj: Order | None, trade: Trade) -> None:
"""
Check if current analyzed entry order should be replaced or simply cancelled.
To simply cancel the existing order(no replacement) adjust_entry_price() should return None
@@ -1736,10 +1746,10 @@ class FreqtradeBot(LoggingMixin):
def handle_cancel_enter(
self,
trade: Trade,
order: dict,
order: CcxtOrder,
order_obj: Order,
reason: str,
replacing: Optional[bool] = False,
replacing: bool | None = False,
) -> bool:
"""
entry cancel - cancel order
@@ -1820,7 +1830,9 @@ class FreqtradeBot(LoggingMixin):
)
return was_trade_fully_canceled
def handle_cancel_exit(self, trade: Trade, order: dict, order_obj: Order, reason: str) -> bool:
def handle_cancel_exit(
self, trade: Trade, order: CcxtOrder, order_obj: Order, reason: str
) -> bool:
"""
exit order cancel - cancel order and update trade
:return: True if exit order was cancelled, false otherwise
@@ -1931,9 +1943,9 @@ class FreqtradeBot(LoggingMixin):
limit: float,
exit_check: ExitCheckTuple,
*,
exit_tag: Optional[str] = None,
ordertype: Optional[str] = None,
sub_trade_amt: Optional[float] = None,
exit_tag: str | None = None,
ordertype: str | None = None,
sub_trade_amt: float | None = None,
) -> bool:
"""
Executes a trade exit for the given trade and limit
@@ -2042,10 +2054,10 @@ class FreqtradeBot(LoggingMixin):
def _notify_exit(
self,
trade: Trade,
order_type: Optional[str],
order_type: str | None,
fill: bool = False,
sub_trade: bool = False,
order: Optional[Order] = None,
order: Order | None = None,
) -> None:
"""
Sends rpc notification when a sell occurred.
@@ -2158,7 +2170,7 @@ class FreqtradeBot(LoggingMixin):
# Send the message
self.rpc.send_msg(msg)
def order_obj_or_raise(self, order_id: str, order_obj: Optional[Order]) -> Order:
def order_obj_or_raise(self, order_id: str, order_obj: Order | None) -> Order:
if not order_obj:
raise DependencyException(
f"Order_obj not found for {order_id}. This should not have happened."
@@ -2172,8 +2184,8 @@ class FreqtradeBot(LoggingMixin):
def update_trade_state(
self,
trade: Trade,
order_id: Optional[str],
action_order: Optional[dict[str, Any]] = None,
order_id: str | None,
action_order: CcxtOrder | None = None,
*,
stoploss_order: bool = False,
send_msg: bool = True,
@@ -2284,7 +2296,7 @@ class FreqtradeBot(LoggingMixin):
def handle_protections(self, pair: str, side: LongShort) -> None:
# Lock pair for one candle to prevent immediate re-entries
self.strategy.lock_pair(pair, datetime.now(timezone.utc), reason="Auto lock")
self.strategy.lock_pair(pair, datetime.now(timezone.utc), reason="Auto lock", side=side)
prot_trig = self.protections.stop_per_pair(pair, side=side)
if prot_trig:
msg: RPCProtectionMsg = {
@@ -2310,7 +2322,7 @@ class FreqtradeBot(LoggingMixin):
amount: float,
fee_abs: float,
order_obj: Order,
) -> Optional[float]:
) -> float | None:
"""
Applies the fee to amount (either from Order or from Trades).
Can eat into dust if more than the required asset is available.
@@ -2338,7 +2350,7 @@ class FreqtradeBot(LoggingMixin):
return fee_abs
return None
def handle_order_fee(self, trade: Trade, order_obj: Order, order: dict[str, Any]) -> None:
def handle_order_fee(self, trade: Trade, order_obj: Order, order: CcxtOrder) -> None:
# Try update amount (binance-fix)
try:
fee_abs = self.get_real_amount(trade, order, order_obj)
@@ -2347,7 +2359,7 @@ class FreqtradeBot(LoggingMixin):
except DependencyException as exception:
logger.warning("Could not update trade amount: %s", exception)
def get_real_amount(self, trade: Trade, order: dict, order_obj: Order) -> Optional[float]:
def get_real_amount(self, trade: Trade, order: CcxtOrder, order_obj: Order) -> float | None:
"""
Detect and update trade fee.
Calls trade.update_fee() upon correct detection.
@@ -2407,8 +2419,8 @@ class FreqtradeBot(LoggingMixin):
return True
def fee_detection_from_trades(
self, trade: Trade, order: dict, order_obj: Order, order_amount: float, trades: list
) -> Optional[float]:
self, trade: Trade, order: CcxtOrder, order_obj: Order, order_amount: float, trades: list
) -> float | None:
"""
fee-detection fallback to Trades.
Either uses provided trades list or the result of fetch_my_trades to get correct fee.
@@ -2419,7 +2431,7 @@ class FreqtradeBot(LoggingMixin):
)
if len(trades) == 0:
logger.info("Applying fee on amount for %s failed: myTrade-Dict empty found", trade)
logger.info("Applying fee on amount for %s failed: myTrade-dict empty found", trade)
return None
fee_currency = None
amount = 0
+5 -5
View File
@@ -1,4 +1,4 @@
from typing import Any, Optional
from typing import Any
from typing_extensions import TypedDict
@@ -26,7 +26,7 @@ class BacktestHistoryEntryType(BacktestMetadataType):
filename: str
strategy: str
notes: str
backtest_start_ts: Optional[int]
backtest_end_ts: Optional[int]
timeframe: Optional[str]
timeframe_detail: Optional[str]
backtest_start_ts: int | None
backtest_end_ts: int | None
timeframe: str | None
timeframe_detail: str | None
+1 -2
View File
@@ -1,5 +1,4 @@
# Used for list-exchanges
from typing import Optional
from typing_extensions import TypedDict
@@ -17,5 +16,5 @@ class ValidExchangesType(TypedDict):
comment: str
dex: bool
is_alias: bool
alias_for: Optional[str]
alias_for: str | None
trade_modes: list[TradeModeType]
+7 -5
View File
@@ -1,5 +1,4 @@
import logging
from typing import Optional
from freqtrade.enums import MarginMode
from freqtrade.exceptions import DependencyException
@@ -12,7 +11,7 @@ logger = logging.getLogger(__name__)
def update_liquidation_prices(
trade: Optional[LocalTrade] = None,
trade: LocalTrade | None = None,
*,
exchange: Exchange,
wallets: Wallets,
@@ -28,10 +27,13 @@ def update_liquidation_prices(
total_wallet_stake = 0.0
if dry_run:
# Parameters only needed for cross margin
total_wallet_stake = wallets.get_total(stake_currency)
total_wallet_stake = wallets.get_collateral()
logger.info("Updating liquidation price for all open trades.")
open_trades = Trade.get_open_trades()
logger.info(
"Updating liquidation price for all open trades. "
f"Collateral {total_wallet_stake} {stake_currency}."
)
open_trades: list[Trade] = Trade.get_open_trades()
for t in open_trades:
# TODO: This should be done in a batch update
t.set_liquidation_price(
+3 -3
View File
@@ -6,11 +6,11 @@ Read the documentation to know what cli arguments you need.
import logging
import sys
from typing import Any, Optional
from typing import Any
# check min. python version
if sys.version_info < (3, 10): # pragma: no cover
if sys.version_info < (3, 10): # pragma: no cover # noqa: UP036
sys.exit("Freqtrade requires Python version >= 3.10")
from freqtrade import __version__
@@ -24,7 +24,7 @@ from freqtrade.system import asyncio_setup, gc_set_threshold
logger = logging.getLogger("freqtrade")
def main(sysargv: Optional[list[str]] = None) -> None:
def main(sysargv: list[str] | None = None) -> None:
"""
This function will initiate the bot and start the trading loop.
:return: None
+4 -4
View File
@@ -7,7 +7,7 @@ import logging
from collections.abc import Iterator, Mapping
from io import StringIO
from pathlib import Path
from typing import Any, Optional, TextIO, Union
from typing import Any, TextIO
from urllib.parse import urlparse
import pandas as pd
@@ -129,10 +129,10 @@ def round_dict(d, n):
return {k: (round(v, n) if isinstance(v, float) else v) for k, v in d.items()}
DictMap = Union[dict[str, Any], Mapping[str, Any]]
DictMap = dict[str, Any] | Mapping[str, Any]
def safe_value_fallback(obj: DictMap, key1: str, key2: Optional[str] = None, default_value=None):
def safe_value_fallback(obj: DictMap, key1: str, key2: str | None = 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.
@@ -161,7 +161,7 @@ def safe_value_fallback2(dict1: DictMap, dict2: DictMap, key1: str, key2: str, d
return default_value
def plural(num: float, singular: str, plural: Optional[str] = None) -> str:
def plural(num: float, singular: str, plural: str | None = None) -> str:
return singular if (num == 1 or num == -1) else plural or singular + "s"
+1 -1
View File
@@ -1,4 +1,4 @@
from typing import Callable
from collections.abc import Callable
from cachetools import TTLCache, cached
@@ -1,7 +1,7 @@
import logging
import time
from pathlib import Path
from typing import Any, Union
from typing import Any
import pandas as pd
from rich.text import Text
@@ -10,7 +10,7 @@ from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException
from freqtrade.optimize.analysis.lookahead import LookaheadAnalysis
from freqtrade.resolvers import StrategyResolver
from freqtrade.util import print_rich_table
from freqtrade.util import get_dry_run_wallet, print_rich_table
logger = logging.getLogger(__name__)
@@ -21,7 +21,7 @@ class LookaheadAnalysisSubFunctions:
def text_table_lookahead_analysis_instances(
config: dict[str, Any],
lookahead_instances: list[LookaheadAnalysis],
caption: Union[str, None] = None,
caption: str | None = None,
):
headers = [
"filename",
@@ -163,7 +163,7 @@ class LookaheadAnalysisSubFunctions:
config["max_open_trades"] = len(config["pairs"])
min_dry_run_wallet = 1000000000
if config["dry_run_wallet"] < min_dry_run_wallet:
if get_dry_run_wallet(config) < min_dry_run_wallet:
logger.info(
"Dry run wallet was not set to 1 billion, pushing it up there "
"just to avoid false positives"
@@ -243,7 +243,7 @@ class LookaheadAnalysisSubFunctions:
# report the results
if lookaheadAnalysis_instances:
caption: Union[str, None] = None
caption: str | None = None
if any(
[
any(
+1 -2
View File
@@ -1,7 +1,6 @@
import hashlib
from copy import deepcopy
from pathlib import Path
from typing import Union
import rapidjson
@@ -38,7 +37,7 @@ def get_strategy_run_id(strategy) -> str:
return digest.hexdigest().lower()
def get_backtest_metadata_filename(filename: Union[Path, str]) -> Path:
def get_backtest_metadata_filename(filename: Path | str) -> Path:
"""Return metadata filename for specified backtest results file."""
filename = Path(filename)
return filename.parent / Path(f"{filename.stem}.meta{filename.suffix}")

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