Compare commits

..

584 Commits

Author SHA1 Message Date
Matthias bae2f98e5f chore: bump pyarrow wheel for 3.13 2026-02-01 17:55:37 +01:00
Matthias e88e3617c7 chore: bump pi image to trixie 2026-02-01 17:54:02 +01:00
Matthias 7cd97d9cfd chore: simplify docker install 2026-02-01 17:54:02 +01:00
Matthias 12a9f0e1b1 chore: bump armhf dockerfile to python 3.13 2026-02-01 17:54:02 +01:00
Matthias 6bb78edd96 chore: add pyarrow 3.13 armv7 wheel 2026-02-01 17:54:01 +01:00
Matthias 98b56a49c8 Merge pull request #12315 from x-mass/develop
fix: align _get_close_rate_for_roi with calc_profit_ratio logic in backtesting
2026-02-01 17:23:13 +01:00
Matthias 791fbe8054 Merge branch 'develop' into pr/x-mass/12315 2026-02-01 16:51:01 +01:00
Matthias cedfb2a51f chore: slightly improve docstring 2026-02-01 16:50:57 +01:00
Matthias 93b6c52aad test: Improved test setup 2026-02-01 16:44:10 +01:00
Matthias 0ef85e161e fix: problem in ohlcv_with_cache 2026-01-31 19:55:03 +01:00
Matthias 26aed91bbf test: improve test-case to highlight issue in ohlcv_with_cache 2026-01-31 19:54:49 +01:00
Matthias 3c2ddda203 test: improve test for ohlcv_with_cache 2026-01-31 19:46:30 +01:00
Matthias beec9123d4 chore: bump dev version to 2026.2-dev 2026-01-31 08:29:45 +01:00
Matthias 360f294f45 Merge pull request #12755 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2026-01-29 06:49:02 +01:00
Freqtrade Bot a64ab2629b chore: update pre-commit hooks 2026-01-29 03:45:25 +00:00
Matthias 5d88923e03 Merge pull request #12752 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2026-01-27 06:55:08 +01:00
Freqtrade Bot 84cbb1eb01 chore: update pre-commit hooks 2026-01-27 03:33:04 +00:00
Matthias 1c764d622d Merge pull request #12744 from freqtrade/dependabot/pip/develop/scipy-33c3de7653
chore(deps-dev): bump scipy-stubs from 1.17.0.0 to 1.17.0.1 in the scipy group
2026-01-26 07:32:01 +01:00
Matthias fb6627335b Merge pull request #12749 from freqtrade/dependabot/pip/develop/ccxt-4.5.34
chore(deps): bump ccxt from 4.5.33 to 4.5.34
2026-01-26 07:25:24 +01:00
Matthias 436d3b1bbc fix: binance.us should not use data.vision
closes #12743
2026-01-26 07:19:25 +01:00
Matthias 308c80c7b2 test: add test to skip download-data for binanceus 2026-01-26 07:19:24 +01:00
Matthias a4a69bd16d Merge pull request #12748 from freqtrade/dependabot/pip/develop/pyarrow-23.0.0
chore(deps): bump pyarrow from 22.0.0 to 23.0.0
2026-01-26 06:53:00 +01:00
Matthias 9bd68dd63a Merge pull request #12745 from freqtrade/dependabot/pip/develop/ruff-0.14.13
chore(deps-dev): bump ruff from 0.14.11 to 0.14.13
2026-01-26 06:44:07 +01:00
Matthias 670cfa2087 chore: bump scipy-stubs in pre-commit config 2026-01-26 06:19:13 +01:00
Matthias 7a69cfa62b chore: update pre-built pyarrlw armhf wheel 2026-01-26 06:16:45 +01:00
Matthias 142dd3fd25 Merge pull request #12746 from freqtrade/dependabot/pip/develop/plotly-6.5.2
chore(deps): bump plotly from 6.5.1 to 6.5.2
2026-01-26 06:14:44 +01:00
Matthias d102d3cac8 Merge pull request #12750 from freqtrade/dependabot/github_actions/develop/zizmorcore/zizmor-action-0.4.1
chore(deps): bump zizmorcore/zizmor-action from 0.3.0 to 0.4.1
2026-01-26 06:14:27 +01:00
Matthias 2b7bcdb594 Merge pull request #12747 from freqtrade/dependabot/github_actions/develop/actions-e6ee9d7de3
chore(deps): bump actions/checkout from 6.0.1 to 6.0.2 in the actions group
2026-01-26 06:13:48 +01:00
dependabot[bot] 9ccbf15080 chore(deps): bump zizmorcore/zizmor-action from 0.3.0 to 0.4.1
Bumps [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) from 0.3.0 to 0.4.1.
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](https://github.com/zizmorcore/zizmor-action/compare/e639db99335bc9038abc0e066dfcd72e23d26fb4...135698455da5c3b3e55f73f4419e481ab68cdd95)

---
updated-dependencies:
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.4.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 03:04:25 +00:00
dependabot[bot] 16717f06e9 chore(deps): bump ccxt from 4.5.33 to 4.5.34
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.5.33 to 4.5.34.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Commits](https://github.com/ccxt/ccxt/compare/v4.5.33...v4.5.34)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 03:04:23 +00:00
dependabot[bot] 7d77e16271 chore(deps): bump pyarrow from 22.0.0 to 23.0.0
Bumps [pyarrow](https://github.com/apache/arrow) from 22.0.0 to 23.0.0.
- [Release notes](https://github.com/apache/arrow/releases)
- [Commits](https://github.com/apache/arrow/compare/apache-arrow-22.0.0...apache-arrow-23.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 03:04:03 +00:00
dependabot[bot] f501619434 chore(deps): bump actions/checkout in the actions group
Bumps the actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 6.0.1 to 6.0.2
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/8e8c483db84b4bee98b60c0593521ed34d9990e8...de0fac2e4500dabe0009e67214ff5f5447ce83dd)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
  dependency-group: actions
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 03:03:59 +00:00
dependabot[bot] 7c9a618c72 chore(deps): bump plotly from 6.5.1 to 6.5.2
Bumps [plotly](https://github.com/plotly/plotly.py) from 6.5.1 to 6.5.2.
- [Release notes](https://github.com/plotly/plotly.py/releases)
- [Changelog](https://github.com/plotly/plotly.py/blob/main/CHANGELOG.md)
- [Commits](https://github.com/plotly/plotly.py/compare/v6.5.1...v6.5.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 03:03:53 +00:00
dependabot[bot] 16374ef7f1 chore(deps-dev): bump ruff from 0.14.11 to 0.14.13
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.11 to 0.14.13.
- [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.14.11...0.14.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 03:03:45 +00:00
dependabot[bot] bb4d2f8f1a chore(deps-dev): bump scipy-stubs in the scipy group
Bumps the scipy group with 1 update: [scipy-stubs](https://github.com/scipy/scipy-stubs).


Updates `scipy-stubs` from 1.17.0.0 to 1.17.0.1
- [Release notes](https://github.com/scipy/scipy-stubs/releases)
- [Commits](https://github.com/scipy/scipy-stubs/compare/v1.17.0.0...v1.17.0.1)

---
updated-dependencies:
- dependency-name: scipy-stubs
  dependency-version: 1.17.0.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: scipy
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-26 03:03:23 +00:00
Matthias 52fc180c75 feat: add typedDict for LeverageTiers - fix unbound upper bound for get_max_leverage 2026-01-25 11:12:20 +01:00
Matthias 87b313cd53 test: add Test case for get_max_leverage with open maxNotional 2026-01-25 11:09:05 +01:00
Matthias 74c88f77c0 test: allow maxNotional to be None 2026-01-25 10:56:58 +01:00
Matthias 6cb459f37b docs: update windows error link description 2026-01-25 10:28:45 +01:00
Matthias 00848c9a96 docs: fix typos in install docs 2026-01-25 10:02:44 +01:00
Matthias e8adf01b18 docs: improve ordering of script installation 2026-01-25 10:01:28 +01:00
Matthias d28fce7ed8 docs: combine installation guides 2026-01-25 09:59:54 +01:00
Matthias 390d504cf2 docs: add "updating" notes to install sections 2026-01-25 08:27:06 +01:00
Matthias 1799aa33f9 docs: add "why update?" section 2026-01-25 08:24:55 +01:00
Matthias dcff1c4317 docs: update problems updating wording, include promised links 2026-01-25 08:24:51 +01:00
Matthias 121bdab8fe docs: remove "experimental" from futures functionality 2026-01-25 08:08:07 +01:00
Matthias 9a48feea35 fix: use list for _OPENAPI_TAGS 2026-01-22 19:47:54 +01:00
Matthias 0955ccadfe feat: Describe all tags explicitly 2026-01-22 19:33:24 +01:00
Matthias c778e44e80 chore: improved tag namings 2026-01-22 07:09:26 +01:00
Matthias 18933a6e20 chore: rename info to Info 2026-01-22 07:04:27 +01:00
Matthias 182bdaceb5 feat: more openAPI tag description 2026-01-22 07:04:27 +01:00
Matthias 6ae4394628 feat: improved tags for openapi specs 2026-01-22 07:04:27 +01:00
Matthias 1bdb1539b1 refactor: move trading related API's to separate file 2026-01-22 07:04:27 +01:00
Matthias 2765277cd0 refactor: /freqaimodels to webserver section 2026-01-22 07:04:27 +01:00
Matthias 085a9be186 feat: add is_trading_mode api Depends 2026-01-22 07:04:27 +01:00
Matthias e279c0333f chore: add download-data tag to available-pairs 2026-01-22 07:04:27 +01:00
Matthias 5e7246d23b refactor: available_pairs is for webserver only 2026-01-22 07:04:27 +01:00
Matthias fe86163730 test: update test to use webserver only mode 2026-01-22 07:04:27 +01:00
Matthias b5d467c943 refactor: move some webserver-exclusive functions behind webserver gate 2026-01-22 07:04:27 +01:00
Matthias c9fd8a619a test: re-enable htx online tests 2026-01-22 06:55:35 +01:00
Matthias 690080e180 docs: add FAQ entry about sandbox accounts
closes #12733
2026-01-22 06:49:05 +01:00
Matthias 583431e9d5 Merge pull request #12734 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2026-01-22 06:34:30 +01:00
Freqtrade Bot 4b82f82d9c chore: update pre-commit hooks 2026-01-22 03:36:25 +00:00
Matthias 63d6001415 Merge pull request #12729 from alisalama/develop
Freqtrade Downloader - don't drop latest candle for Funding Rates
2026-01-21 18:05:34 +01:00
Ali Salama ad6cf90037 Applied ruff formatting 2026-01-21 10:56:25 +00:00
Ali Salama 558c65a85c Amend linting issues 2026-01-21 10:51:40 +00:00
Ali Salama 8cb72e1fa3 Fixed linting line length issue 2026-01-21 10:25:35 +00:00
Matthias 4870d45b04 Merge pull request #12731 from Cosica/fix_typo
Fix a typo in doc
2026-01-20 19:32:28 +01:00
Matthias 4436acea68 docs: Fix a few more markdown "gotchas" in advanced-bt 2026-01-20 18:19:21 +01:00
Matthias e8eb6d7018 docs: correct header indentation in advanced-hyperopt 2026-01-20 18:08:02 +01:00
Cosica f1a6e61dbd Fix typo in doc 2026-01-20 14:03:19 +08:00
Matthias 4abb871c16 Merge pull request #12730 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2026-01-20 06:27:08 +01:00
Freqtrade Bot a6eccaec69 chore: update pre-commit hooks 2026-01-20 03:32:09 +00:00
Ali Salama 1747724206 Always download latest candle for Funding Rates 2026-01-19 14:08:20 +00:00
Matthias 616900330b Merge pull request #12718 from freqtrade/dependabot/pip/develop/types-1b6322021f
chore(deps-dev): bump types-requests from 2.32.4.20250913 to 2.32.4.20260107 in the types group
2026-01-19 08:33:41 +01:00
Matthias bbe5e22730 Merge pull request #12720 from freqtrade/dependabot/pip/develop/jsonschema-4.26.0
chore(deps): bump jsonschema from 4.25.1 to 4.26.0
2026-01-19 07:29:48 +01:00
Matthias 8b396ae3a2 fix: set LogginMixin in reset_backtest for hyperopt silence 2026-01-19 07:22:33 +01:00
Matthias 7f511cbe82 chore: bump type-requests in pre-commit config 2026-01-19 07:19:57 +01:00
dependabot[bot] 12f5b9bc5b chore(deps-dev): bump types-requests in the types group
Bumps the types group with 1 update: [types-requests](https://github.com/typeshed-internal/stub_uploader).


Updates `types-requests` from 2.32.4.20250913 to 2.32.4.20260107
- [Commits](https://github.com/typeshed-internal/stub_uploader/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 06:10:31 +00:00
Matthias d152e3e47d Merge pull request #12719 from freqtrade/dependabot/pip/develop/scipy-48bf9b4f29
chore(deps): bump the scipy group with 2 updates
2026-01-19 07:09:05 +01:00
Matthias 39908c60e2 Merge pull request #12723 from freqtrade/dependabot/pip/develop/numpy-2.4.1
chore(deps): bump numpy from 2.4.0 to 2.4.1
2026-01-19 07:02:53 +01:00
Matthias 74a1e48a1c Merge pull request #12724 from freqtrade/dependabot/pip/develop/xgboost-3.1.3
chore(deps): bump xgboost from 3.1.2 to 3.1.3
2026-01-19 06:50:21 +01:00
Matthias b507aebf1c chore: bump scipy-stubs in pre-commit config 2026-01-19 06:40:20 +01:00
Matthias ad293c28d0 Merge pull request #12726 from freqtrade/dependabot/pip/develop/websockets-16.0
chore(deps): bump websockets from 15.0.1 to 16.0
2026-01-19 06:37:24 +01:00
Matthias e4c7b5511a Merge pull request #12722 from freqtrade/dependabot/pip/develop/ccxt-4.5.33
chore(deps): bump ccxt from 4.5.32 to 4.5.33
2026-01-19 06:34:22 +01:00
Matthias 85fd070899 Merge pull request #12725 from freqtrade/dependabot/github_actions/develop/astral-sh/setup-uv-7.2.0
chore(deps): bump astral-sh/setup-uv from 7.1.6 to 7.2.0
2026-01-19 06:33:05 +01:00
Matthias e658490593 Merge pull request #12727 from freqtrade/dependabot/pip/develop/ruff-0.14.11
chore(deps-dev): bump ruff from 0.14.10 to 0.14.11
2026-01-19 06:32:47 +01:00
Matthias 621e78e7ce Merge pull request #12721 from freqtrade/dependabot/pip/develop/plotly-6.5.1
chore(deps): bump plotly from 6.5.0 to 6.5.1
2026-01-19 06:31:44 +01:00
dependabot[bot] fc5801a0b7 chore(deps-dev): bump ruff from 0.14.10 to 0.14.11
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.10 to 0.14.11.
- [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.14.10...0.14.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 03:14:02 +00:00
dependabot[bot] 22cd1357f8 chore(deps): bump websockets from 15.0.1 to 16.0
Bumps [websockets](https://github.com/python-websockets/websockets) from 15.0.1 to 16.0.
- [Release notes](https://github.com/python-websockets/websockets/releases)
- [Commits](https://github.com/python-websockets/websockets/compare/15.0.1...16.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 03:13:48 +00:00
dependabot[bot] ead68e0a51 chore(deps): bump astral-sh/setup-uv from 7.1.6 to 7.2.0
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 7.1.6 to 7.2.0.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/681c641aba71e4a1c380be3ab5e12ad51f415867...61cb8a9741eeb8a550a1b8544337180c0fc8476b)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 03:13:46 +00:00
dependabot[bot] 80b87d2592 chore(deps): bump xgboost from 3.1.2 to 3.1.3
Bumps [xgboost](https://github.com/dmlc/xgboost) from 3.1.2 to 3.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/v3.1.2...v3.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 03:13:43 +00:00
dependabot[bot] 0ac43042cc chore(deps): bump numpy from 2.4.0 to 2.4.1
Bumps [numpy](https://github.com/numpy/numpy) from 2.4.0 to 2.4.1.
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst)
- [Commits](https://github.com/numpy/numpy/compare/v2.4.0...v2.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 03:13:37 +00:00
dependabot[bot] 590a69ca42 chore(deps): bump ccxt from 4.5.32 to 4.5.33
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.5.32 to 4.5.33.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Commits](https://github.com/ccxt/ccxt/compare/v4.5.32...v4.5.33)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 03:13:29 +00:00
dependabot[bot] e44a36cedd chore(deps): bump plotly from 6.5.0 to 6.5.1
Bumps [plotly](https://github.com/plotly/plotly.py) from 6.5.0 to 6.5.1.
- [Release notes](https://github.com/plotly/plotly.py/releases)
- [Changelog](https://github.com/plotly/plotly.py/blob/main/CHANGELOG.md)
- [Commits](https://github.com/plotly/plotly.py/compare/v6.5.0...v6.5.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 03:13:20 +00:00
dependabot[bot] 131ab77085 chore(deps): bump jsonschema from 4.25.1 to 4.26.0
Bumps [jsonschema](https://github.com/python-jsonschema/jsonschema) from 4.25.1 to 4.26.0.
- [Release notes](https://github.com/python-jsonschema/jsonschema/releases)
- [Changelog](https://github.com/python-jsonschema/jsonschema/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/python-jsonschema/jsonschema/compare/v4.25.1...v4.26.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 03:13:14 +00:00
dependabot[bot] ba07de8d00 chore(deps): bump the scipy group with 2 updates
Bumps the scipy group with 2 updates: [scipy](https://github.com/scipy/scipy) and [scipy-stubs](https://github.com/scipy/scipy-stubs).


Updates `scipy` from 1.16.3 to 1.17.0
- [Release notes](https://github.com/scipy/scipy/releases)
- [Commits](https://github.com/scipy/scipy/compare/v1.16.3...v1.17.0)

Updates `scipy-stubs` from 1.16.3.3 to 1.17.0.0
- [Release notes](https://github.com/scipy/scipy-stubs/releases)
- [Commits](https://github.com/scipy/scipy-stubs/compare/v1.16.3.3...v1.17.0.0)

---
updated-dependencies:
- dependency-name: scipy
  dependency-version: 1.17.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
  dependency-group: scipy
- dependency-name: scipy-stubs
  dependency-version: 1.17.0.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: scipy
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-19 03:12:58 +00:00
Matthias 6ee34aa482 fix: disable freqtrade logging in hyperopt subprocesses
closes #12717
2026-01-18 20:12:31 +01:00
Matthias d5686d548c chore: use dt_now to get the current date 2026-01-18 19:44:47 +01:00
Matthias 4917a0abe5 chore: add "package cleanup" step to devcontainer 2026-01-15 19:51:43 +01:00
Matthias 93f2cf5db7 test: update test startup time for mac
CI is slower than we thought it is.
2026-01-15 19:50:04 +01:00
Matthias 38a470098f test: shorten explanatory comment 2026-01-15 19:27:52 +01:00
Matthias b0eb5c77ec test: add additional test case for rpc_balance 2026-01-15 18:17:53 +01:00
Matthias 0d5bd94f43 test: switch futures position to ADA 2026-01-15 18:17:53 +01:00
Matthias 735284dccd test: extend rpc_balance test for short and long positions 2026-01-15 18:17:53 +01:00
Matthias deb0dd80c8 fix: futures balance calculation for shorts 2026-01-15 18:17:53 +01:00
Matthias 12f815e157 Merge pull request #12715 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2026-01-15 06:50:57 +01:00
Freqtrade Bot 059636f1ea chore: update pre-commit hooks 2026-01-15 03:33:01 +00:00
Matthias 793ca9c3da Merge pull request #12713 from freqtrade/dependabot/pip/filelock-3.20.3
chore(deps): bump filelock from 3.20.2 to 3.20.3
2026-01-14 06:32:03 +01:00
dependabot[bot] 323a28c0bc chore(deps): bump filelock from 3.20.2 to 3.20.3
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.20.2 to 3.20.3.
- [Release notes](https://github.com/tox-dev/py-filelock/releases)
- [Changelog](https://github.com/tox-dev/filelock/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/py-filelock/compare/3.20.2...3.20.3)

---
updated-dependencies:
- dependency-name: filelock
  dependency-version: 3.20.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-13 21:02:58 +00:00
Matthias 77567f6315 Merge pull request #12712 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2026-01-13 06:29:45 +01:00
Freqtrade Bot dda448c10d chore: update pre-commit hooks 2026-01-13 03:31:02 +00:00
Matthias b7df0de33c Merge pull request #12708 from freqtrade/dependabot/pip/develop/ccxt-4.5.32
chore(deps): bump ccxt from 4.5.31 to 4.5.32
2026-01-12 06:48:47 +01:00
Matthias a89ef23407 Merge pull request #12709 from freqtrade/dependabot/pip/develop/pymdown-extensions-10.20
chore(deps): bump pymdown-extensions from 10.19.1 to 10.20
2026-01-12 06:31:43 +01:00
Matthias b90538f47d Merge pull request #12710 from freqtrade/dependabot/pip/develop/filelock-3.20.2
chore(deps): bump filelock from 3.20.1 to 3.20.2
2026-01-12 06:31:22 +01:00
Matthias ee6b7f2f95 Merge pull request #12711 from freqtrade/dependabot/pip/develop/certifi-2026.1.4
chore(deps): bump certifi from 2025.11.12 to 2026.1.4
2026-01-12 06:31:04 +01:00
dependabot[bot] b4aa6231e0 chore(deps): bump certifi from 2025.11.12 to 2026.1.4
Bumps [certifi](https://github.com/certifi/python-certifi) from 2025.11.12 to 2026.1.4.
- [Commits](https://github.com/certifi/python-certifi/compare/2025.11.12...2026.01.04)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-12 03:04:46 +00:00
dependabot[bot] 6bd57cc3b5 chore(deps): bump filelock from 3.20.1 to 3.20.2
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.20.1 to 3.20.2.
- [Release notes](https://github.com/tox-dev/py-filelock/releases)
- [Changelog](https://github.com/tox-dev/filelock/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/py-filelock/compare/3.20.1...3.20.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-12 03:04:33 +00:00
dependabot[bot] 26db014782 chore(deps): bump pymdown-extensions from 10.19.1 to 10.20
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.19.1 to 10.20.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.19.1...10.20)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-12 03:04:28 +00:00
dependabot[bot] 8e3ca302b0 chore(deps): bump ccxt from 4.5.31 to 4.5.32
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.5.31 to 4.5.32.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Commits](https://github.com/ccxt/ccxt/compare/v4.5.31...v4.5.32)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-12 03:04:15 +00:00
Matthias 14aeb56ff9 test: update tests for new log message format 2026-01-10 18:15:22 +01:00
Matthias 9cbb8ec1c2 chore: improve incompatibility errors 2026-01-10 18:15:04 +01:00
Matthias faa5c4be39 docs: update Documentation to include point annotation 2026-01-09 20:00:33 +01:00
Matthias 45b6ec95a4 test: add tests for point annotations 2026-01-09 20:00:33 +01:00
Matthias 91b921cc88 feat: update types to support annotation point
part of #12698
2026-01-09 20:00:33 +01:00
Matthias 123897ea89 Merge pull request #12701 from freqtrade/fix/better_futures_wallets
Enhance futures wallet calculations
2026-01-09 19:43:17 +01:00
Matthias 31cf2dd46d test: improve rpc_balance handle error test 2026-01-09 06:49:17 +01:00
Matthias 06a53924d7 test: improve balance rpc test 2026-01-08 20:53:29 +01:00
Matthias 993771c5d3 chore: ensure alphabetical sort order for object loading
Attempts to fix random test failures ...
2026-01-08 20:39:22 +01:00
Matthias f08f19d1cb test: Change test to ADA (it's in markets)
Mock trades for balance test
2026-01-08 20:37:12 +01:00
Matthias 515912f14d chore: add explaining comment 2026-01-08 07:24:20 +01:00
Matthias df8465a9f3 chore: fix typo in docstring 2026-01-08 07:14:26 +01:00
Matthias 1333cb3181 feat: allow converstation rate without caching 2026-01-08 07:14:08 +01:00
Matthias 18fa981690 Merge pull request #12699 from stash86/main-stash
fix typo on example
2026-01-08 06:49:00 +01:00
Matthias 60425e6237 test: update rpc tests to new calculation mode 2026-01-08 06:47:17 +01:00
Matthias 3b5f1d52cd feat: Futures wallets should consider open PnL 2026-01-08 06:47:17 +01:00
Matthias 519721b58b Merge pull request #12700 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2026-01-08 06:31:42 +01:00
Freqtrade Bot 3ae7823983 chore: update pre-commit hooks 2026-01-08 03:33:00 +00:00
Stefano 7182e6960e fix typo on example 2026-01-08 08:49:16 +09:00
Matthias 8596042065 Merge pull request #12697 from freqtrade/dependabot/pip/urllib3-2.6.3
chore(deps): bump urllib3 from 2.6.2 to 2.6.3
2026-01-07 21:37:51 +01:00
dependabot[bot] c2e92250b5 chore(deps): bump urllib3 from 2.6.2 to 2.6.3
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.2 to 2.6.3.
- [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.6.2...2.6.3)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.6.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-07 19:47:04 +00:00
Matthias d2ed5c6736 Merge pull request #12695 from freqtrade/dependabot/pip/develop/psutil-7.2.1
chore(deps): bump psutil from 7.2.0 to 7.2.1
2026-01-07 20:19:30 +01:00
Matthias 8e023a7bf3 Merge pull request #12696 from freqtrade/dependabot/pip/develop/ccxt-4.5.31
chore(deps): bump ccxt from 4.5.30 to 4.5.31
2026-01-07 19:46:36 +01:00
dependabot[bot] b3711fdf6c chore(deps): bump ccxt from 4.5.30 to 4.5.31
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.5.30 to 4.5.31.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Commits](https://github.com/ccxt/ccxt/compare/v4.5.30...v4.5.31)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-07 16:04:28 +00:00
dependabot[bot] 681876e0e5 chore(deps): bump psutil from 7.2.0 to 7.2.1
Bumps [psutil](https://github.com/giampaolo/psutil) from 7.2.0 to 7.2.1.
- [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst)
- [Commits](https://github.com/giampaolo/psutil/compare/release-7.2.0...release-7.2.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-07 16:04:13 +00:00
Matthias c3502991d3 chore: use instance attribute throughout 2026-01-07 07:17:12 +01:00
Matthias 6cc22fcf74 fix: ensure defaults are applied to exchange configuration
closes #12694
2026-01-07 07:00:40 +01:00
Matthias 2f011e5421 docs: remove mentions of "isolated only" for futures 2026-01-07 06:33:19 +01:00
Matthias 4322acbbb8 refactor: rename wallets position variable 2026-01-06 20:00:04 +01:00
Matthias 50cba51008 chore: remove pointless exception handler 2026-01-06 19:05:32 +01:00
Matthias 7498f20baa feat: improve telegram balance output (remove trailing zeros) 2026-01-06 13:41:02 +01:00
Matthias a1b3e59491 fix: ensure wallet position leverage is always set 2026-01-06 13:00:11 +01:00
Matthias 4fcbce2ce5 Merge pull request #12685 from CutieCat6778/patch-1
Skip loop iteration if train_queue is empty
2026-01-06 10:15:50 +01:00
Matthias 22699ee6ed Merge pull request #12693 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2026-01-06 09:50:51 +01:00
Matthias aec5450036 chore: fix whitespace issue 2026-01-06 09:36:33 +01:00
Matthias 2c5a150a7b maint: remove non-needed permission 2026-01-06 09:35:22 +01:00
Matthias f845dc71b1 maint: fix pedantic zizmor problems 2026-01-06 09:33:00 +01:00
Matthias 61274252af maint: rename zizmor CI workflow
this will avoid schema collision with the zizmor.yml config file
2026-01-06 09:21:04 +01:00
Matthias 00d903cb3f maint: add explanatory comment for permission 2026-01-06 09:19:03 +01:00
Matthias 29f235441b maint: set base permissions for all workflows 2026-01-06 09:17:08 +01:00
Matthias 872b59c9cd maint: pin all actions by hash 2026-01-06 09:13:13 +01:00
Freqtrade Bot be7f5f4e26 chore: update pre-commit hooks 2026-01-06 08:36:01 +01:00
Matthias 82c1f096de maint: skip pip-audit in github actions 2026-01-06 08:35:40 +01:00
Matthias ed7887c426 Merge pull request #12692 from freqtrade/dependabot/pip/aiohttp-3.13.3
chore(deps): bump aiohttp from 3.13.2 to 3.13.3
2026-01-06 08:23:46 +01:00
dependabot[bot] e58f7b0344 chore(deps): bump aiohttp from 3.13.2 to 3.13.3
---
updated-dependencies:
- dependency-name: aiohttp
  dependency-version: 3.13.3
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-06 00:48:17 +00:00
Matthias 32eaae5dcf feat: add __repr__ to Wallets object 2026-01-05 18:06:05 +01:00
Matthias 38c74e5d1a docs: remove unnecessary `"-" in command 2026-01-05 13:09:18 +01:00
Matthias e89a31c6d1 Merge pull request #12688 from freqtrade/dependabot/pip/develop/technical-1.5.4
chore(deps): bump technical from 1.5.3 to 1.5.4
2026-01-05 08:25:25 +01:00
Matthias 3eb8de633c Merge pull request #12687 from freqtrade/dependabot/pip/develop/psutil-7.2.0
chore(deps): bump psutil from 7.1.3 to 7.2.0
2026-01-05 08:10:07 +01:00
Matthias 0ee3e4304f Merge pull request #12689 from freqtrade/dependabot/pip/develop/fastapi-0.128.0
chore(deps): bump fastapi from 0.127.0 to 0.128.0
2026-01-05 08:09:54 +01:00
dependabot[bot] 228dfe8ab2 chore(deps): bump fastapi from 0.127.0 to 0.128.0
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.127.0 to 0.128.0.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.127.0...0.128.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-05 03:29:12 +00:00
dependabot[bot] bfcc2aa153 chore(deps): bump technical from 1.5.3 to 1.5.4
Bumps [technical](https://github.com/freqtrade/technical) from 1.5.3 to 1.5.4.
- [Release notes](https://github.com/freqtrade/technical/releases)
- [Commits](https://github.com/freqtrade/technical/compare/1.5.3...1.5.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-05 03:28:55 +00:00
dependabot[bot] 39e2ecb6a2 chore(deps): bump psutil from 7.1.3 to 7.2.0
Bumps [psutil](https://github.com/giampaolo/psutil) from 7.1.3 to 7.2.0.
- [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst)
- [Commits](https://github.com/giampaolo/psutil/compare/release-7.1.3...release-7.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2026-01-05 03:28:42 +00:00
Thinh Nguyen 0fcfd933ea Skip loop iteration if train_queue is empty
Added a check to continue the loop if the training queue is empty.
2026-01-03 23:32:38 +01:00
Matthias b9a4918d61 test: reset converter before and after fiat conversions 2026-01-03 14:33:08 +01:00
Matthias 0d7127bb6a refactor: use metaclass for Singleton in FiatConverter 2026-01-03 14:06:17 +01:00
Matthias cfd048e74e test: mock fiat_convert entry method 2026-01-03 14:02:18 +01:00
Matthias c911332da3 test: simplify telegram fiat_convert mocking 2026-01-03 13:51:39 +01:00
Matthias 43a849d45d test: remove pointless mocks 2026-01-03 13:46:37 +01:00
Matthias 3ad502b073 test: mock object instead of global mock 2026-01-03 13:40:18 +01:00
Matthias 6d4ad393d5 test: cleanup unneeded mocks from test_rpc 2026-01-03 13:18:23 +01:00
Matthias 69cc238700 Merge pull request #12560 from stash86/main-stash
add more metrics on profit stat
2026-01-03 12:51:18 +01:00
Matthias 9a9b4e15f3 test: pandas warning tests to show warnings 2026-01-02 17:12:48 +01:00
Matthias fcab9460fb test: reduce startup time failure rates 2026-01-02 16:58:10 +01:00
Matthias 58130572a9 fix: use "triggered order" id where necessary
closes  #12680
2026-01-02 16:37:12 +01:00
Matthias f82d2fe796 chore: simplify conversion rate usage 2026-01-02 16:24:37 +01:00
Matthias 577464c12d Merge pull request #12506 from dev-starlight/develop
fix, Add UTF-8 encoding to read_text method
2026-01-02 13:56:25 +01:00
Matthias 00bd3e5a5c fix: strategy-updater should support non-english characters on windows 2026-01-02 12:42:12 +00:00
Matthias bc2be74b36 fix: further fixes for utf8 reading on windows 2026-01-02 12:20:09 +00:00
Matthias 3543437fc5 test: add test chinese comment to strategy_test file 2026-01-02 12:10:06 +00:00
Matthias 4b278941b8 test: small improvements to test clarity 2026-01-02 11:41:18 +01:00
Matthias 8a33b71351 test: update exchange api test 2026-01-02 11:40:10 +01:00
Matthias a09ca38de2 feat: list-exchanges to show futures "has" problematics 2026-01-02 11:05:02 +01:00
Matthias 4d4fee9874 feat: add futures validation to validate_exchange 2026-01-02 11:02:26 +01:00
Matthias f2dfe1788a feat: split futures "has" parameters to a separate variable 2026-01-02 10:40:57 +01:00
Matthias 0e82c0458a feat: improve output of list-exchanges 2026-01-02 10:33:12 +01:00
Matthias 2ec2626277 feat: blacklist kraken-futures - it's futures only
we know it won't work at the moment, and current exclusion is accidental
which could change with a ccxt update.
2026-01-02 10:33:12 +01:00
Matthias 04f4f327df refactor: improve punctuation handling on bad exchanges 2026-01-02 10:33:12 +01:00
Matthias 34ad58e2da feat: enable fetch_orders optional check 2026-01-02 10:33:12 +01:00
Matthias f78aa7b39a feat: has_optional should check for replacement methods 2026-01-02 10:33:12 +01:00
Matthias a174461952 test: add test for exchange_has validation 2026-01-02 10:33:12 +01:00
Matthias 6c164683b7 refactor: extract "has" checking logic to separate function 2026-01-02 10:33:12 +01:00
Matthias 9cdd8659ec fix: "missing" condition when no replacemet items are available 2026-01-02 10:33:12 +01:00
Matthias 795df94817 Merge pull request #12679 from freqtrade/maint/binance_mig
Remove binance futures pair naming migration
2026-01-02 10:18:57 +01:00
Matthias 40f87c0d30 test: remove binance pair naming tests 2026-01-02 08:42:25 +01:00
Matthias 1380940565 chore: remove binance pair naming migration 2026-01-02 08:42:14 +01:00
Matthias 595309c9ed chore: improve type-safety for Volumepairlist 2026-01-01 14:42:00 +01:00
Matthias 78dde7d1bc chore: don't use bitwise comparisons for bool comparisons 2026-01-01 14:41:49 +01:00
Matthias 2a0da08413 fix: double-space in log message 2026-01-01 14:01:25 +01:00
Matthias fd7aff6063 Merge pull request #12678 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2026-01-01 07:14:27 +01:00
Freqtrade Bot c41584a548 chore: update pre-commit hooks 2026-01-01 03:35:01 +00:00
Stefano 1e6d832f71 fix test 2025-12-31 10:40:09 +09:00
Stefano 6f2ba13b0c add to api schemas 2025-12-31 10:05:18 +09:00
Stefano 78141c7216 Merge branch 'freqtrade:develop' into main-stash 2025-12-31 10:02:04 +09:00
Matthias 8e91fea11f Merge pull request #12584 from igi01/feature/hyperliquid-hip3-support
Hyperliquid HIP3 support
2025-12-30 21:03:13 +01:00
Matthias 0f8c6d456e fix: only matching quote currencies should be tradable 2025-12-30 20:37:48 +01:00
Matthias 2a1b3ae7ba test: improve test to disable different stake currency markets 2025-12-30 20:37:29 +01:00
Matthias 0a94a6d332 test: refactor hyperliquid fake markets into a fixture 2025-12-30 20:31:49 +01:00
Matthias 75a3651493 test: ensure hyperliquid works without dex configured 2025-12-30 13:03:13 +01:00
Matthias 0dad2b36ab Merge branch 'develop' into feature/hyperliquid-hip3-support 2025-12-30 08:46:48 +01:00
Matthias 70834b6491 chore: update release template structure 2025-12-30 08:46:19 +01:00
Matthias 31269c7ce0 docs: Update docs date to 2025 2025-12-30 08:46:19 +01:00
Matthias f0a5bbe511 Merge pull request #12674 from freqtrade/dependabot/pip/develop/ccxt-4.5.30
chore(deps): bump ccxt from 4.5.29 to 4.5.30
2025-12-29 16:44:42 +01:00
dependabot[bot] 863124d67e chore(deps): bump ccxt from 4.5.29 to 4.5.30
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.5.29 to 4.5.30.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Commits](https://github.com/ccxt/ccxt/compare/v4.5.29...v4.5.30)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 15:28:15 +00:00
Matthias 6d1e4d8f45 Merge pull request #12665 from freqtrade/ci/docker_trixie
bump dockerfile base from bookworm to trixie
2025-12-29 14:40:47 +01:00
Matthias 5f12c81768 chore: bump version to 2026.1-dev 2025-12-29 13:35:51 +01:00
Matthias 31341afcef Merge branch 'stable' into develop 2025-12-29 12:56:03 +01:00
Matthias 383b305b74 Merge pull request #12671 from freqtrade/dependabot/pip/develop/humanize-4.15.0
chore(deps): bump humanize from 4.14.0 to 4.15.0
2025-12-29 12:26:06 +01:00
Matthias 84244e7ca7 Merge pull request #12669 from freqtrade/dependabot/pip/develop/numpy-2.4.0
chore(deps): bump numpy from 2.3.5 to 2.4.0
2025-12-29 12:01:25 +01:00
Matthias cc06e9baec Merge pull request #12672 from freqtrade/dependabot/pip/develop/uvicorn-0.40.0
chore(deps): bump uvicorn from 0.38.0 to 0.40.0
2025-12-29 08:11:33 +01:00
Matthias d5ac8a5b0b test: update test for new humanize behavior 2025-12-29 08:01:48 +01:00
Matthias 00a4a743d2 Merge pull request #12668 from freqtrade/dependabot/pip/develop/ruff-0.14.10
chore(deps-dev): bump ruff from 0.14.9 to 0.14.10
2025-12-29 06:52:35 +01:00
Matthias 650cb1ef8c Merge pull request #12667 from freqtrade/dependabot/pip/develop/gymnasium-1.2.3
chore(deps): bump gymnasium from 1.2.2 to 1.2.3
2025-12-29 06:46:15 +01:00
dependabot[bot] 76ea4ea14f chore(deps): bump uvicorn from 0.38.0 to 0.40.0
Bumps [uvicorn](https://github.com/Kludex/uvicorn) from 0.38.0 to 0.40.0.
- [Release notes](https://github.com/Kludex/uvicorn/releases)
- [Changelog](https://github.com/Kludex/uvicorn/blob/main/docs/release-notes.md)
- [Commits](https://github.com/Kludex/uvicorn/compare/0.38.0...0.40.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 05:42:07 +00:00
Matthias 90502a6ac0 Merge pull request #12666 from freqtrade/dependabot/pip/develop/mkdocs-5f79d4b5c7
chore(deps): bump mkdocs-material from 9.7.0 to 9.7.1 in the mkdocs group
2025-12-29 06:41:02 +01:00
Matthias f9fe909847 Merge pull request #12670 from freqtrade/dependabot/pip/develop/fastapi-0.127.0
chore(deps): bump fastapi from 0.125.0 to 0.127.0
2025-12-29 06:38:45 +01:00
dependabot[bot] 7085103e43 chore(deps): bump humanize from 4.14.0 to 4.15.0
Bumps [humanize](https://github.com/python-humanize/humanize) from 4.14.0 to 4.15.0.
- [Release notes](https://github.com/python-humanize/humanize/releases)
- [Commits](https://github.com/python-humanize/humanize/compare/4.14.0...4.15.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 03:27:49 +00:00
dependabot[bot] c5ee96b66a chore(deps): bump fastapi from 0.125.0 to 0.127.0
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.125.0 to 0.127.0.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.125.0...0.127.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 03:27:46 +00:00
dependabot[bot] d454a10dea chore(deps): bump numpy from 2.3.5 to 2.4.0
Bumps [numpy](https://github.com/numpy/numpy) from 2.3.5 to 2.4.0.
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst)
- [Commits](https://github.com/numpy/numpy/compare/v2.3.5...v2.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 03:27:37 +00:00
dependabot[bot] e3d03f586d chore(deps-dev): bump ruff from 0.14.9 to 0.14.10
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.9 to 0.14.10.
- [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.14.9...0.14.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 03:27:22 +00:00
dependabot[bot] e8d748535f chore(deps): bump gymnasium from 1.2.2 to 1.2.3
Bumps [gymnasium](https://github.com/Farama-Foundation/Gymnasium) from 1.2.2 to 1.2.3.
- [Release notes](https://github.com/Farama-Foundation/Gymnasium/releases)
- [Commits](https://github.com/Farama-Foundation/Gymnasium/compare/v1.2.2...v1.2.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 03:27:13 +00:00
dependabot[bot] 1cdb386374 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.7.0 to 9.7.1
- [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.7.0...9.7.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-29 03:27:02 +00:00
Matthias 8205cb25b8 chore: bump dockerfile base from bookworm to trixie 2025-12-28 14:16:02 +01:00
Matthias 511b817e84 test: test exception case for one dex call 2025-12-28 13:28:46 +01:00
Matthias de94170424 test: update and improve validation tests 2025-12-28 13:24:52 +01:00
Matthias 6e9b60c0b0 fix: use ConfigurationError instead of OperationalException 2025-12-28 13:24:43 +01:00
Matthias b8cf38cb6f test: fix a couple hyperliquid tests 2025-12-27 20:15:19 +01:00
Matthias b394705545 fix: improve check tightness in hyperliquid hip3 config 2025-12-27 20:10:19 +01:00
Matthias 9b99c5b3f3 chore: Add explanation for get_balances 2025-12-27 20:10:03 +01:00
Matthias e77e901df1 fix: don't ignore regular tradability rules 2025-12-27 18:25:19 +01:00
Matthias b26c37af62 test: test online to ensure HIP3 pairs are in the markets object 2025-12-27 17:33:39 +01:00
Matthias 414fe81b27 test: reduce test position data 2025-12-27 17:18:43 +01:00
Matthias 00de8077f7 test: fix hyperliquid positions test 2025-12-27 17:18:15 +01:00
Matthias 1dde1ad0ed test: fix and improve hip3 config validation test 2025-12-27 17:10:28 +01:00
Matthias 2708343a2d fix: only validate HIP3 in futures mode 2025-12-27 17:07:35 +01:00
Matthias 61eb3306dd docs: fix documentation formatting and wording 2025-12-27 16:59:32 +01:00
Matthias e71329c82b fix: DEX's are isolated margin only 2025-12-27 16:50:21 +01:00
Matthias dc4adccaaf feat: don't limit pairs in "list-pairs" mode 2025-12-27 16:48:53 +01:00
Matthias 45b9c8c53f chore: use parent methods for proper error handling 2025-12-27 16:48:25 +01:00
Matthias 5c02ce934d test: remove outdated TODO 2025-12-27 09:53:46 +01:00
Matthias 260512d678 chore: remove outdated todo, replaced with comment 2025-12-27 09:53:46 +01:00
Matthias 8b1b16cb08 test: cleanup unnecessary test fixture 2025-12-27 09:53:46 +01:00
Matthias cacd52db1c chore: remove unnecessary todo 2025-12-27 09:53:42 +01:00
Matthias 4e809947c7 chore: improve create_command_partials runtime by not calling subprocess several times 2025-12-26 20:02:24 +01:00
Matthias 89c4aa23f3 docs: update docs with new --eps helpstring 2025-12-26 14:03:30 +01:00
Matthias 1bd043a912 chore: improve cli docstring to clarify "--eps" 2025-12-26 13:37:52 +01:00
Matthias 3ce23f99de Merge pull request #12655 from freqtrade/dependabot/pip/develop/time-machine-3.2.0
chore(deps-dev): bump time-machine from 3.1.0 to 3.2.0
2025-12-25 10:00:00 +01:00
Matthias c5fa5c5c69 chore: be more specific in dependabot schedule 2025-12-25 08:52:43 +01:00
Matthias 21d0b60168 Merge pull request #12660 from freqtrade/dependabot/pip/develop/cachetools-6.2.4
chore(deps): bump cachetools from 6.2.3 to 6.2.4
2025-12-25 08:20:22 +01:00
Matthias ba3925cdcd Merge pull request #12657 from freqtrade/dependabot/pip/develop/ccxt-4.5.29
chore(deps): bump ccxt from 4.5.28 to 4.5.29
2025-12-25 08:20:02 +01:00
Matthias 9cf6d47a43 Merge pull request #12656 from freqtrade/dependabot/github_actions/develop/docker/setup-buildx-action-3.12.0
chore(deps): bump docker/setup-buildx-action from 3.11.1 to 3.12.0
2025-12-25 08:19:29 +01:00
Matthias e01993b9c1 Merge pull request #12659 from freqtrade/dependabot/pip/develop/pre-commit-4.5.1
chore(deps-dev): bump pre-commit from 4.5.0 to 4.5.1
2025-12-25 08:19:15 +01:00
Matthias c354a76ae2 Merge pull request #12658 from freqtrade/dependabot/pip/develop/fastapi-0.125.0
chore(deps): bump fastapi from 0.124.4 to 0.125.0
2025-12-25 08:18:56 +01:00
Matthias f296213088 Merge pull request #12661 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2025-12-25 08:18:24 +01:00
Freqtrade Bot d80dbc1644 chore: update pre-commit hooks 2025-12-25 03:32:57 +00:00
dependabot[bot] 91ee8652ef chore(deps): bump cachetools from 6.2.3 to 6.2.4
Bumps [cachetools](https://github.com/tkem/cachetools) from 6.2.3 to 6.2.4.
- [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/tkem/cachetools/compare/v6.2.3...v6.2.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-25 03:02:10 +00:00
dependabot[bot] 75dfc95221 chore(deps-dev): bump pre-commit from 4.5.0 to 4.5.1
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 4.5.0 to 4.5.1.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v4.5.0...v4.5.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-25 03:02:07 +00:00
dependabot[bot] 05fe35ce57 chore(deps): bump fastapi from 0.124.4 to 0.125.0
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.124.4 to 0.125.0.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.124.4...0.125.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-25 03:02:01 +00:00
dependabot[bot] 95e36013db chore(deps): bump ccxt from 4.5.28 to 4.5.29
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.5.28 to 4.5.29.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Commits](https://github.com/ccxt/ccxt/compare/v4.5.28...v4.5.29)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-25 03:01:56 +00:00
dependabot[bot] c5c96d9610 chore(deps): bump docker/setup-buildx-action from 3.11.1 to 3.12.0
Bumps [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) from 3.11.1 to 3.12.0.
- [Release notes](https://github.com/docker/setup-buildx-action/releases)
- [Commits](https://github.com/docker/setup-buildx-action/compare/e468171a9de216ec08956ac3ada2f0791b6bd435...8d2750c68a42422c14e847fe6c8ac0403b4cbd6f)

---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 3.12.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-25 03:01:43 +00:00
dependabot[bot] ad9a7279be chore(deps-dev): bump time-machine from 3.1.0 to 3.2.0
Bumps [time-machine](https://github.com/adamchainz/time-machine) from 3.1.0 to 3.2.0.
- [Changelog](https://github.com/adamchainz/time-machine/blob/main/docs/changelog.rst)
- [Commits](https://github.com/adamchainz/time-machine/compare/3.1.0...3.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-25 03:01:41 +00:00
Matthias dafb2fda7b Merge pull request #12505 from freqtrade/maint/python_3.14
CI against python 3.14
2025-12-24 15:36:55 +01:00
Matthias 04fea4ac29 Merge branch 'develop' into maint/python_3.14 2025-12-24 13:30:22 +01:00
Matthias 896250fe88 chore: add debugging output to debug psgallery error 2025-12-24 13:29:01 +01:00
Matthias 8485abc9b8 Merge pull request #12652 from freqtrade/maint/pip-audit
Add pip-audit
2025-12-24 13:17:43 +01:00
Matthias 2013a2edda chore: add recognition to the original tests author 2025-12-24 12:04:30 +01:00
Matthias 11eb0c0802 fix: re-add latest tag for docker hub - which aligns to the :devleop tag 2025-12-24 11:58:44 +01:00
Matthias 78709a9d76 test: properly wrap line 2025-12-24 11:39:00 +01:00
Matthias f55a90d98c chore: add pip-audit as dependency and test 2025-12-24 11:35:25 +01:00
Matthias 4b3ff02d49 chore: Add pip-audit action 2025-12-24 11:06:49 +01:00
Matthias ea52deb98e Merge pull request #12648 from hippocritical/develop
adjusted the error message of "more than 5 queries needed for exchange"
2025-12-23 13:22:58 +01:00
hippocritical 7b33242aa0 fixed tests according to failed tests on github 2025-12-23 10:57:47 +01:00
Matthias 46ee092119 Merge pull request #12638 from freqtrade/dependabot/pip/develop/scikit-learn-1.8.0
chore(deps): bump scikit-learn from 1.7.2 to 1.8.0
2025-12-23 07:05:37 +01:00
Matthias 931cd2e7ba chore: bump Dockerfile to latest python release 2025-12-23 07:01:50 +01:00
Matthias b6899264a8 chore: update classifiers to 3.14 2025-12-23 06:59:50 +01:00
dependabot[bot] ca47882fa9 chore(deps): bump scikit-learn from 1.7.2 to 1.8.0
Bumps [scikit-learn](https://github.com/scikit-learn/scikit-learn) from 1.7.2 to 1.8.0.
- [Release notes](https://github.com/scikit-learn/scikit-learn/releases)
- [Commits](https://github.com/scikit-learn/scikit-learn/compare/1.7.2...1.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-23 05:42:22 +00:00
Matthias c21ce5ef94 Merge branch 'develop' into maint/python_3.14 2025-12-23 06:38:25 +01:00
Matthias 9baa6eebbe Merge pull request #12649 from freqtrade/maint/remove_catboost
Remove catboost dependency
2025-12-23 06:32:30 +01:00
Matthias 0feec257e3 Merge pull request #12650 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-12-23 06:31:49 +01:00
Freqtrade Bot 802d42cc00 chore: update pre-commit hooks 2025-12-23 03:30:45 +00:00
hippocritical f546146d40 changed the wording in
if required_candle_call_count > 5:
as per matthias suggestion.

adjusted the elif part too, since this would have to be worded similarly.

If a native speaker thinks there is a better wording, be our guest.
2025-12-23 00:44:45 +01:00
Matthias d2c4bd1b50 docs: update formatting of link 2025-12-22 20:00:50 +01:00
Matthias 5e5495e050 docs: Add deprecation notice for Catboost models 2025-12-22 19:56:41 +01:00
Matthias 51f3f0d65a test: Remove catboost tests 2025-12-22 19:53:02 +01:00
hippocritical 0019867da8 didn't make a space in the end ... you can book that under typo ...
added a . at the end of another warning since all other messages do have the same structure.
2025-12-22 12:53:01 +01:00
hippocritical 335e632bbc adjusted the error message of "more than 5 queries needed for exchange" to be more verbose to the user.
Now they don't have to guess what the limit actually is to then work towards, helping to better understand the error message.
2025-12-22 12:45:31 +01:00
Matthias d0ac462dd3 Merge pull request #12647 from freqtrade/dependabot/pip/develop/mypy-1.19.1
chore(deps-dev): bump mypy from 1.19.0 to 1.19.1
2025-12-22 07:54:03 +01:00
dependabot[bot] fc113f7bbf chore(deps-dev): bump mypy from 1.19.0 to 1.19.1
Bumps [mypy](https://github.com/python/mypy) from 1.19.0 to 1.19.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.19.0...v1.19.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 06:32:34 +00:00
Matthias 4f6b1b6fd0 Merge pull request #12633 from freqtrade/dependabot/pip/develop/scipy-e8974a44ab
chore(deps-dev): bump scipy-stubs from 1.16.3.2 to 1.16.3.3 in the scipy group
2025-12-22 07:31:21 +01:00
Matthias 006d2305af chore: update samples and comments to use lightgbm / xgboost
instead of catboost
2025-12-22 07:16:24 +01:00
Matthias 5c9f140c5d feat: remove catboost models and dependency 2025-12-22 07:11:11 +01:00
Matthias c6f3b00819 chore: combine updates on actions from the actions/* org 2025-12-22 07:02:01 +01:00
Matthias 1780d2db23 chore: bump scipy-stubs in pre-commit-config 2025-12-22 06:59:35 +01:00
dependabot[bot] 78d602f7b4 chore(deps-dev): bump scipy-stubs in the scipy group
Bumps the scipy group with 1 update: [scipy-stubs](https://github.com/scipy/scipy-stubs).


Updates `scipy-stubs` from 1.16.3.2 to 1.16.3.3
- [Release notes](https://github.com/scipy/scipy-stubs/releases)
- [Commits](https://github.com/scipy/scipy-stubs/compare/v1.16.3.2...v1.16.3.3)

---
updated-dependencies:
- dependency-name: scipy-stubs
  dependency-version: 1.16.3.3
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: scipy
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 05:58:11 +00:00
Matthias 4823f66c4e Merge pull request #12635 from freqtrade/dependabot/pip/develop/sqlalchemy-2.0.45
chore(deps): bump sqlalchemy from 2.0.44 to 2.0.45
2025-12-22 06:56:47 +01:00
Matthias a3c19d10f7 chore: bump sqlalchemy in pre-commit-config 2025-12-22 06:38:34 +01:00
Matthias ebb318a0f0 Merge pull request #12645 from freqtrade/dependabot/pip/develop/ccxt-4.5.28
chore(deps): bump ccxt from 4.5.27 to 4.5.28
2025-12-22 06:37:37 +01:00
Matthias 35d6e6e832 Merge pull request #12643 from freqtrade/dependabot/pip/develop/ruff-0.14.9
chore(deps-dev): bump ruff from 0.14.8 to 0.14.9
2025-12-22 06:37:05 +01:00
Matthias 37bc93e0d3 Merge pull request #12642 from freqtrade/dependabot/github_actions/develop/codecov/codecov-action-5.5.2
chore(deps): bump codecov/codecov-action from 5.5.1 to 5.5.2
2025-12-22 06:36:50 +01:00
Matthias 2cd30b0604 Merge pull request #12636 from freqtrade/dependabot/github_actions/develop/peter-evans/create-pull-request-8.0.0
chore(deps): bump peter-evans/create-pull-request from 7.0.9 to 8.0.0
2025-12-22 06:35:48 +01:00
Matthias 2f30cda9d7 Merge pull request #12640 from freqtrade/dependabot/github_actions/develop/actions/upload-artifact-6
chore(deps): bump actions/upload-artifact from 5 to 6
2025-12-22 06:34:41 +01:00
Matthias 9e1c760222 Merge pull request #12641 from freqtrade/dependabot/pip/develop/urllib3-2.6.2
chore(deps): bump urllib3 from 2.6.0 to 2.6.2
2025-12-22 06:33:53 +01:00
Matthias 53884cae2f Merge pull request #12637 from freqtrade/dependabot/pip/develop/fastapi-0.124.4
chore(deps): bump fastapi from 0.124.0 to 0.124.4
2025-12-22 06:33:35 +01:00
Matthias 1b16b450b7 Merge pull request #12634 from freqtrade/dependabot/pip/develop/cachetools-6.2.3
chore(deps): bump cachetools from 6.2.2 to 6.2.3
2025-12-22 06:32:15 +01:00
Matthias 18ba3241ce Merge pull request #12639 from freqtrade/dependabot/github_actions/develop/actions/download-artifact-7
chore(deps): bump actions/download-artifact from 6 to 7
2025-12-22 06:31:56 +01:00
Matthias 6f8ad595b7 Merge pull request #12646 from freqtrade/dependabot/pip/develop/pymdown-extensions-10.19.1
chore(deps): bump pymdown-extensions from 10.18 to 10.19.1
2025-12-22 06:31:23 +01:00
Matthias 5479f204b5 Merge pull request #12644 from freqtrade/dependabot/github_actions/develop/astral-sh/setup-uv-7.1.6
chore(deps): bump astral-sh/setup-uv from 7.1.4 to 7.1.6
2025-12-22 06:31:09 +01:00
dependabot[bot] c54b94b5a4 chore(deps): bump pymdown-extensions from 10.18 to 10.19.1
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.18 to 10.19.1.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.18...10.19.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 03:02:39 +00:00
dependabot[bot] 5ed590d862 chore(deps): bump ccxt from 4.5.27 to 4.5.28
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.5.27 to 4.5.28.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Commits](https://github.com/ccxt/ccxt/compare/v4.5.27...v4.5.28)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 03:02:35 +00:00
dependabot[bot] 3f5ed399b5 chore(deps): bump astral-sh/setup-uv from 7.1.4 to 7.1.6
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 7.1.4 to 7.1.6.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/1e862dfacbd1d6d858c55d9b792c756523627244...681c641aba71e4a1c380be3ab5e12ad51f415867)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 03:02:26 +00:00
dependabot[bot] a9a686c64e chore(deps-dev): bump ruff from 0.14.8 to 0.14.9
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.8 to 0.14.9.
- [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.14.8...0.14.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 03:02:24 +00:00
dependabot[bot] 978ca8f852 chore(deps): bump codecov/codecov-action from 5.5.1 to 5.5.2
Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 5.5.1 to 5.5.2.
- [Release notes](https://github.com/codecov/codecov-action/releases)
- [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/codecov/codecov-action/compare/5a1091511ad55cbe89839c7260b706298ca349f7...671740ac38dd9b0130fbe1cec585b89eea48d3de)

---
updated-dependencies:
- dependency-name: codecov/codecov-action
  dependency-version: 5.5.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 03:02:16 +00:00
dependabot[bot] ea30f906f9 chore(deps): bump urllib3 from 2.6.0 to 2.6.2
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.6.0 to 2.6.2.
- [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.6.0...2.6.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 03:02:10 +00:00
dependabot[bot] 811112f0ce chore(deps): bump actions/upload-artifact from 5 to 6
Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 03:02:08 +00:00
dependabot[bot] 5ebffb27aa chore(deps): bump actions/download-artifact from 6 to 7
Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 03:02:03 +00:00
dependabot[bot] 0cb676a677 chore(deps): bump fastapi from 0.124.0 to 0.124.4
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.124.0 to 0.124.4.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.124.0...0.124.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 03:01:57 +00:00
dependabot[bot] 6a6dce323d chore(deps): bump peter-evans/create-pull-request from 7.0.9 to 8.0.0
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.9 to 8.0.0.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/84ae59a2cdc2258d6fa0732dd66352dddae2a412...98357b18bf14b5342f975ff684046ec3b2a07725)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-version: 8.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 03:01:51 +00:00
dependabot[bot] 0d62fab367 chore(deps): bump sqlalchemy from 2.0.44 to 2.0.45
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.44 to 2.0.45.
- [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases)
- [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst)
- [Commits](https://github.com/sqlalchemy/sqlalchemy/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 03:01:42 +00:00
dependabot[bot] a1b4a4dbfb chore(deps): bump cachetools from 6.2.2 to 6.2.3
Bumps [cachetools](https://github.com/tkem/cachetools) from 6.2.2 to 6.2.3.
- [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/tkem/cachetools/compare/v6.2.2...v6.2.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-22 03:01:32 +00:00
Ingo bcd033bd0d remove HIP3 dex quantity restrictions 2025-12-21 19:55:35 +01:00
Matthias 092da6aee5 chore(ci): Try to cleanup docker build host 2025-12-21 13:43:16 +01:00
Matthias 13c63c0bf5 fix: exception when backtesting in webserver mode
Running a futures backtest more than once without cache caused
the process to crash due to detail data not being loaded.
2025-12-21 13:31:10 +01:00
Matthias aa38a36b56 docs: add override_exchange_check to documentation 2025-12-21 12:58:13 +01:00
Ingo ce8732b0cd modified tests for hip3 assets + add new tests for hip3 assets 2025-12-21 08:03:51 +01:00
Ingo bcf6c244b0 Add config validation + market tradable checks 2025-12-21 08:03:51 +01:00
igi01 9023ca4f56 Merge branch 'freqtrade:develop' into feature/hyperliquid-hip3-support 2025-12-21 07:30:20 +01:00
Matthias e9847d9549 Merge pull request #12627 from freqtrade/unify/algo_stop_orders
refactor stoploss methods for exchanges with algo orders
2025-12-19 06:27:12 +01:00
Matthias 0beb76ce48 fix: function signature mismatch 2025-12-18 07:11:45 +01:00
Matthias 21d723b35d chore(ci): visualize disk sizes for docker build 2025-12-18 07:06:34 +01:00
Matthias f002ce67b1 chore: bump version to 2025.11.2 2025-12-18 07:02:17 +01:00
Matthias 5fdc8acbe9 test: switch to BTC/USDC in tests for now 2025-12-18 07:02:17 +01:00
Matthias 1811f9581f fix: allow set-leverage failures on followup orders 2025-12-18 06:56:07 +01:00
Matthias 66235f3198 fix: improve binance stoploss "triggered" behavior 2025-12-18 06:55:47 +01:00
Matthias 6c8b8f642a Merge pull request #12629 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2025-12-18 06:29:05 +01:00
Ingo 29b7cbbb12 HIP3 dexes now fetched by market info of specific pairs 2025-12-18 05:31:55 +01:00
Ingo b2d5343a4e accidentaly deleted file and not the diff 2025-12-18 05:31:07 +01:00
Freqtrade Bot 6905b8059d chore: update pre-commit hooks 2025-12-18 03:28:09 +00:00
Ingo 301aff141a HIP3 dexes now fetched by market info of specific pairs 2025-12-17 21:58:10 +01:00
Ingo 245ca4bc2a accidentaly deleted file and not the diff 2025-12-17 21:56:59 +01:00
Ingo f32d25daac accidentaly deleted file and not the diff 2025-12-17 21:56:07 +01:00
Matthias 1143aba671 test: simplify test call 2025-12-17 07:18:19 +01:00
Matthias 278bd0e97d chore: improve variable naming 2025-12-17 07:18:19 +01:00
Matthias d5b296c75c test: fix gate stoploss test 2025-12-17 07:18:19 +01:00
Matthias c63fba2e57 chore: rename flag to better match what it does 2025-12-17 07:18:19 +01:00
Matthias c8d74baaa1 refactor: unify fetch_stoploss_order 2025-12-17 07:18:19 +01:00
Matthias a9ebff3965 Merge pull request #12628 from freqtrade/dependabot/pip/filelock-3.20.1
chore(deps): bump filelock from 3.20.0 to 3.20.1
2025-12-17 06:31:43 +01:00
dependabot[bot] 8859b9009a chore(deps): bump filelock from 3.20.0 to 3.20.1
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.20.0 to 3.20.1.
- [Release notes](https://github.com/tox-dev/py-filelock/releases)
- [Changelog](https://github.com/tox-dev/filelock/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/py-filelock/compare/3.20.0...3.20.1)

---
updated-dependencies:
- dependency-name: filelock
  dependency-version: 3.20.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-16 22:56:31 +00:00
Matthias d4aee7e433 test: add autospec=True to just changed tests 2025-12-16 20:48:15 +01:00
Matthias f0908a1043 test: adapt test to new call logic 2025-12-16 20:32:26 +01:00
Matthias cf6bf1b7b5 refactor: reduce code duplication for cancel_stop_orders 2025-12-16 20:32:26 +01:00
Matthias 84e9251fcd test: use object patching instead of direct assignment 2025-12-16 20:32:26 +01:00
Matthias 5800002d42 feat: add stoploss_fetch_requires_stop_param property 2025-12-16 20:32:26 +01:00
Matthias 6a98c19dab fix: improve binance stoploss "triggered" behavior 2025-12-16 20:12:34 +01:00
Matthias 025707d9ad chore: bump joblib to 1.5.3 2025-12-16 17:44:15 +01:00
Matthias 4540e2ceea Merge branch 'develop' into maint/python_3.14 2025-12-16 17:39:55 +01:00
Matthias eb3e8b82f9 Merge pull request #12626 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-12-16 06:28:58 +01:00
Freqtrade Bot 98da54ae82 chore: update pre-commit hooks 2025-12-16 03:30:35 +00:00
Matthias fa5210aa4b fix: function signature mismatch 2025-12-15 20:35:05 +01:00
Matthias 289e53ed5a fix: allow set-leverage failures on followup orders 2025-12-15 20:23:11 +01:00
igi01 789f4aa4f8 Delete requirements.txt
Accidentally pushed Version, which was used for testing
2025-12-15 19:01:24 +01:00
igi01 2045fa6427 Delete freqtrade/configuration/config_validation.py
validation will be included in hyperliquid.py
2025-12-15 18:59:54 +01:00
Matthias 8c62816972 Merge pull request #12619 from freqtrade/dependabot/pip/develop/python-rapidjson-1.23
chore(deps): bump python-rapidjson from 1.22 to 1.23
2025-12-15 07:11:04 +01:00
dependabot[bot] c0d40a03aa chore(deps): bump python-rapidjson from 1.22 to 1.23
Bumps [python-rapidjson](https://github.com/python-rapidjson/python-rapidjson) from 1.22 to 1.23.
- [Changelog](https://github.com/python-rapidjson/python-rapidjson/blob/master/CHANGES.rst)
- [Commits](https://github.com/python-rapidjson/python-rapidjson/compare/v1.22...v1.23)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 05:50:08 +00:00
Matthias 551831c901 Merge pull request #12620 from freqtrade/dependabot/pip/develop/fastapi-0.124.0
chore(deps): bump fastapi from 0.123.0 to 0.124.0
2025-12-15 06:49:45 +01:00
Matthias 67850fd2a2 Merge pull request #12623 from freqtrade/dependabot/pip/develop/orjson-3.11.5
chore(deps): bump orjson from 3.11.4 to 3.11.5
2025-12-15 06:49:00 +01:00
Matthias 715ef21da0 Merge pull request #12621 from freqtrade/dependabot/github_actions/develop/actions/checkout-6.0.1
chore(deps): bump actions/checkout from 6.0.0 to 6.0.1
2025-12-15 06:43:44 +01:00
Matthias b8defa5d45 Merge pull request #12622 from freqtrade/dependabot/pip/develop/stable-baselines3-2.7.1
chore(deps): bump stable-baselines3 from 2.7.0 to 2.7.1
2025-12-15 06:27:06 +01:00
Matthias 6d3dac828a Merge pull request #12618 from freqtrade/dependabot/pip/develop/pytest-80f5f76e78
chore(deps-dev): bump pytest from 9.0.1 to 9.0.2 in the pytest group
2025-12-15 06:25:26 +01:00
Matthias 968a9a3750 Merge pull request #12625 from freqtrade/dependabot/pip/develop/ruff-0.14.8
chore(deps-dev): bump ruff from 0.14.7 to 0.14.8
2025-12-15 06:24:26 +01:00
Matthias 03b5cdb240 Merge pull request #12624 from freqtrade/dependabot/pip/develop/pymdown-extensions-10.18
chore(deps): bump pymdown-extensions from 10.17.2 to 10.18
2025-12-15 06:24:14 +01:00
dependabot[bot] 7dc0d5d146 chore(deps-dev): bump ruff from 0.14.7 to 0.14.8
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.7 to 0.14.8.
- [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.14.7...0.14.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 03:02:48 +00:00
dependabot[bot] c20f511418 chore(deps): bump pymdown-extensions from 10.17.2 to 10.18
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.17.2 to 10.18.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.17.2...10.18)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 03:02:20 +00:00
dependabot[bot] 484b3ff2f3 chore(deps): bump orjson from 3.11.4 to 3.11.5
Bumps [orjson](https://github.com/ijl/orjson) from 3.11.4 to 3.11.5.
- [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.11.4...3.11.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 03:02:15 +00:00
dependabot[bot] 5d25adfc42 chore(deps): bump stable-baselines3 from 2.7.0 to 2.7.1
Bumps [stable-baselines3](https://github.com/DLR-RM/stable-baselines3) from 2.7.0 to 2.7.1.
- [Release notes](https://github.com/DLR-RM/stable-baselines3/releases)
- [Commits](https://github.com/DLR-RM/stable-baselines3/compare/v2.7.0...v2.7.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 03:02:07 +00:00
dependabot[bot] 1099fd2525 chore(deps): bump actions/checkout from 6.0.0 to 6.0.1
Bumps [actions/checkout](https://github.com/actions/checkout) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6.0.0...v6.0.1)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 03:02:03 +00:00
dependabot[bot] 293989f019 chore(deps): bump fastapi from 0.123.0 to 0.124.0
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.123.0 to 0.124.0.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.123.0...0.124.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 03:01:59 +00:00
dependabot[bot] 57d1e24275 chore(deps-dev): bump pytest from 9.0.1 to 9.0.2 in the pytest group
Bumps the pytest group with 1 update: [pytest](https://github.com/pytest-dev/pytest).


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

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-15 03:01:39 +00:00
Matthias e52b5aef45 Merge pull request #12617 from freqtrade/feat/exit_price
Add price parameter to force-exit API
2025-12-14 19:34:25 +01:00
Matthias 28517085ca chore: bump version to 2025.11.1 2025-12-14 19:29:21 +01:00
Matthias a16d2a1ef9 chore: fix typo 2025-12-14 18:29:43 +01:00
Matthias 13c86452e9 refactor: only assign order_type once 2025-12-14 18:29:37 +01:00
Matthias fe18731057 test: slightly improve custom_exit_rate test 2025-12-14 17:58:11 +01:00
Matthias e26529b695 feat: Don't run custom_exit_price callback when exiting with price 2025-12-14 17:58:11 +01:00
Matthias bac6219cc1 feat: add price to force-exit 2025-12-14 17:58:11 +01:00
Matthias 0ed3bdc747 test: add test for force exit API logic 2025-12-14 17:57:52 +01:00
Matthias 8af0631ff0 Merge pull request #12599 from freqtrade/fix/dynamic_funding_fees
Adjust to dynamic funding fees
2025-12-14 17:56:34 +01:00
Matthias 2e3d276304 docs: Add strategy docs to migrate funding fees 2025-12-14 17:13:03 +01:00
Matthias bd5630a104 test: simplify test mock 2025-12-14 16:19:37 +01:00
Matthias b406219515 test: add candle_types test 2025-12-14 15:51:06 +01:00
Matthias 31d3a19836 feat: support candle_type parameter via API download 2025-12-14 13:49:39 +01:00
Matthias 83b372a32d docs: add "Funding fee adjustment" to deprecated docs 2025-12-13 19:30:21 +01:00
Matthias 51e0b204b6 docs: improve download data docs 2025-12-13 18:30:30 +01:00
Matthias c9ed79d2c8 chore: bump ccxt to 4.5.27 2025-12-13 13:51:30 +01:00
Matthias 043574f558 fix: support binance algo orders
closes #12610
2025-12-13 13:48:02 +01:00
Matthias fc5680d95c test: further test simplifications 2025-12-13 13:47:50 +01:00
Matthias eabb7c98ce test: simplify some stoploss test setups 2025-12-13 13:43:37 +01:00
Matthias 6848f9197e Merge pull request #12613 from freqtrade/fix/binance
Fix binance futures stoploss Order handling
2025-12-13 08:28:21 +01:00
Matthias 6d2c30abca chore: bump ccxt to 4.5.27 2025-12-12 06:54:29 +01:00
Matthias 93bde7dc46 fix: support binance algo orders
closes #12610
2025-12-12 06:52:11 +01:00
Matthias 97e2e0a405 chore: remove hard-pin of pycares 2025-12-11 18:23:58 +01:00
Matthias 12206f028b chore: comment wording improvements 2025-12-11 07:16:36 +01:00
Matthias 4c7944ac77 chore: update comment wording 2025-12-11 07:09:23 +01:00
Matthias d4ced7b416 docs: improve doc wording 2025-12-11 07:05:58 +01:00
Matthias 451eef5c99 test: further test simplifications 2025-12-11 06:49:56 +01:00
Matthias ae8f059de0 Merge pull request #12611 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2025-12-11 06:49:18 +01:00
Matthias 878bd7cbc7 chore: pin pycares for now 2025-12-11 06:35:27 +01:00
Freqtrade Bot 6d017c9a6c chore: update pre-commit hooks 2025-12-11 03:29:24 +00:00
Matthias 4c3d9b8c70 test: simplify some stoploss test setups 2025-12-10 19:28:30 +01:00
Matthias 23a4260859 chore: simplify okx cancel stoploss method 2025-12-10 17:23:10 +01:00
Matthias d15d08a2d5 test: Improve refresh_backtest test 2025-12-09 20:18:12 +01:00
Matthias 6aeab16ce4 test: improve candle type verification test 2025-12-09 20:13:10 +01:00
Matthias 46538d9a5b fix: verify prog actually exists before using it 2025-12-09 19:34:58 +01:00
Matthias f0f48395c5 chore: update download-data help text 2025-12-09 18:25:54 +01:00
Matthias b3a1442a69 feat: allow varying help texts for different subcommands 2025-12-09 18:25:11 +01:00
Matthias bbafb1dabd fix: deduplicate list before downloading
This avoids duplicate downloads, for example on hyperliquid, which uses "futures" as mark candle type.
2025-12-09 13:54:05 +01:00
Matthias 645a9159e4 chore: hyperliquid doesn't have mark candles
it uses regular futures candles as multiplicator for funding fees.
2025-12-09 13:43:49 +01:00
Matthias 2d3ff2f8ca test: mark-test should use the candle's defined mark price attribute 2025-12-09 13:42:59 +01:00
Matthias 5919736904 Merge pull request #12607 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-12-09 07:23:50 +01:00
Freqtrade Bot 732610e200 chore: update pre-commit hooks 2025-12-09 03:27:07 +00:00
Matthias 309985503d test: fix funding_rate_history online test 2025-12-08 20:17:10 +01:00
Matthias 072ed705fc test: fix funding_fee online tests 2025-12-08 19:21:48 +01:00
Matthias 3689b52309 docs: add Section about very loose stoploss on exchange
closes #12598
2025-12-08 17:21:51 +01:00
Matthias 01fbf31405 chore: don't suggest binance.us supports futures
it doesn't.
2025-12-08 17:15:41 +01:00
Matthias 00f687f3f5 test: test futures data with online exchanges 2025-12-08 17:07:26 +01:00
Matthias 96849fcafe refactor: provide a non-failing check_candle_support method 2025-12-08 17:01:36 +01:00
Matthias 80d5b6e24c test: minor refactor in online tests 2025-12-08 16:23:43 +01:00
Matthias f33fd98c83 test: Add test for candle type verification 2025-12-08 14:43:12 +01:00
Matthias c7636734de feat: validate supported candle types when downloading data 2025-12-08 14:29:16 +01:00
Matthias 994e61fe42 feat: add (commented) validation for fetch_*_ohlcv methods 2025-12-08 13:47:26 +01:00
Matthias 359eba462b feat: add candle_types argument to download-data 2025-12-08 12:45:41 +01:00
Matthias 38e48c0c5e test: update refresh ohlcv data test 2025-12-08 11:55:58 +01:00
Matthias 1e187e0945 Merge pull request #12604 from freqtrade/dependabot/pip/develop/ccxt-4.5.25
chore(deps): bump ccxt from 4.5.24 to 4.5.26
2025-12-08 11:52:15 +01:00
dependabot[bot] e4fc5df1cf chore(deps): bump ccxt from 4.5.24 to 4.5.25
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.5.24 to 4.5.25.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Commits](https://github.com/ccxt/ccxt/compare/v4.5.24...v4.5.25)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 09:25:30 +00:00
Matthias cde886b884 chore: use str for safe usage of candle_type 2025-12-08 10:20:00 +01:00
Matthias 9f4e167455 chore: force keyword usage on refresh_backtest_ohlcv 2025-12-08 09:27:48 +01:00
Matthias f5e6504e22 test: add test for funding rate exchange fix 2025-12-08 09:11:49 +01:00
Matthias 0ec1066b34 test: add test for funding_rate fix 2025-12-08 08:48:37 +01:00
Matthias 62d4da3b94 test: add test for get_funding_rate_timeframe 2025-12-08 08:43:50 +01:00
Matthias 253950deb6 Merge pull request #12600 from freqtrade/dependabot/pip/develop/scipy-ea2b5522bf
chore(deps-dev): bump scipy-stubs from 1.16.3.1 to 1.16.3.2 in the scipy group
2025-12-08 08:38:40 +01:00
Matthias ed92d6beb9 Merge pull request #12601 from freqtrade/dependabot/pip/develop/ruff-0.14.7
chore(deps-dev): bump ruff from 0.14.6 to 0.14.7
2025-12-08 08:27:20 +01:00
Matthias ebb362d9fa chore: bump scipy-stubs in pre-commit config 2025-12-08 08:16:30 +01:00
dependabot[bot] f23fad420e chore(deps-dev): bump ruff from 0.14.6 to 0.14.7
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.6 to 0.14.7.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.14.6...0.14.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 07:11:06 +00:00
Matthias aaf23221ff Merge pull request #12602 from freqtrade/dependabot/pip/develop/fastapi-0.123.0
chore(deps): bump fastapi from 0.122.0 to 0.123.0
2025-12-08 08:10:21 +01:00
Matthias 156c1a99a9 Merge pull request #12603 from freqtrade/dependabot/pip/develop/mypy-1.19.0
chore(deps-dev): bump mypy from 1.18.2 to 1.19.0
2025-12-08 08:09:35 +01:00
dependabot[bot] cb55ef5c59 chore(deps-dev): bump mypy from 1.18.2 to 1.19.0
Bumps [mypy](https://github.com/python/mypy) from 1.18.2 to 1.19.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.18.2...v1.19.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 03:02:14 +00:00
dependabot[bot] 6540fbb8e7 chore(deps): bump fastapi from 0.122.0 to 0.123.0
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.122.0 to 0.123.0.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.122.0...0.123.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 03:02:07 +00:00
dependabot[bot] 412392aea9 chore(deps-dev): bump scipy-stubs in the scipy group
Bumps the scipy group with 1 update: [scipy-stubs](https://github.com/scipy/scipy-stubs).


Updates `scipy-stubs` from 1.16.3.1 to 1.16.3.2
- [Release notes](https://github.com/scipy/scipy-stubs/releases)
- [Commits](https://github.com/scipy/scipy-stubs/compare/v1.16.3.1...v1.16.3.2)

---
updated-dependencies:
- dependency-name: scipy-stubs
  dependency-version: 1.16.3.2
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: scipy
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-08 03:01:42 +00:00
Matthias e6030b7f59 chore: minor adjustments for clarity 2025-12-07 20:01:08 +01:00
Matthias c1c968666e chore: some minor cleanups 2025-12-07 18:19:22 +01:00
Matthias f8d6363d19 test: update further tests 2025-12-07 16:11:47 +01:00
Matthias acc69e0d2e test: fix a couple more tests 2025-12-07 16:02:30 +01:00
Matthias 5110d0bdde test: update a couple of tests for new behavior 2025-12-07 15:12:42 +01:00
Matthias 597cc0592b test: update funding_rate_migration test 2025-12-06 20:14:22 +01:00
Matthias 01b0a8fa42 fix: 1h should be the default for funding/mark candles 2025-12-06 16:15:32 +01:00
Matthias cf6b7a847b fix: bitget's minimal funding fee interval is 1h 2025-12-06 16:01:05 +01:00
Matthias 4897080827 fix: bybit's minimal funding fee interval to 1h 2025-12-06 15:53:27 +01:00
Matthias 3ca8e0fb5c feat: auto-adjust funding rate timeframe in dataprovider 2025-12-06 15:42:40 +01:00
Matthias 40f4ff04c2 feat: auto-fix invalid funding rate timeframe in informative decorator 2025-12-06 14:16:27 +01:00
Matthias 3bd911982f feat: add get_funding_rate_timeframe to dataprovider 2025-12-06 14:13:53 +01:00
Matthias 730383ab18 feat: auto-download correct funding rate timeframe 2025-12-06 14:13:41 +01:00
Matthias b70f10dca6 chore: simplify warning formatting 2025-12-06 14:06:17 +01:00
Matthias 17009ac59f chore: allow non-matching funding timeframe - as timeframe doesn't actually matter any longer. 2025-12-06 13:53:18 +01:00
Matthias 07fbf2b467 feat: support dynamic funding fees in dry/live mode 2025-12-06 13:46:27 +01:00
Matthias 3f0be5e41f fix: floor timestamp to seconds
no candle has more than second precision.
2025-12-06 13:42:16 +01:00
Matthias 2845568f61 feat: limit funding_fee renaming to rename from low to high. 2025-12-05 20:32:56 +01:00
Matthias e3229935f6 Merge pull request #12594 from stremblayiOS/fix-hyperliquid-fetch-positions
Fix IndexError in fetch_positions for Hyperliquid when no pair specified
2025-12-05 20:16:48 +01:00
Matthias b1ee115b77 Merge pull request #12593 from arawrdn/develop
Update README.md
2025-12-05 19:53:33 +01:00
Matthias d6060f04bc Merge pull request #12595 from freqtrade/dependabot/pip/urllib3-2.6.0
chore(deps): bump urllib3 from 2.5.0 to 2.6.0
2025-12-05 19:43:22 +01:00
dependabot[bot] 1ae5310d2f chore(deps): bump urllib3 from 2.5.0 to 2.6.0
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.5.0 to 2.6.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.5.0...2.6.0)

---
updated-dependencies:
- dependency-name: urllib3
  dependency-version: 2.6.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-05 18:27:27 +00:00
stremblayiOS 417a0817a7 Fix IndexError in fetch_positions for Hyperliquid when no pair specified
## Summary

Fix IndexError crash in fetch_positions() when initializing wallets on Hyperliquid exchange.

## Quick changelog

- Changed fetch_positions to pass None instead of empty list when no specific pair is requested
- Fixes compatibility with Hyperliquid CCXT implementation that expects None for all positions

## What's new?

When fetch_positions() is called without a specific pair parameter, the code was passing an empty list [] to the CCXT API.
For Hyperliquid exchange, this causes an IndexError because the exchange's implementation attempts to access symbols[0]
without checking if the list is empty.

The CCXT standard is to pass None (not an empty list) when requesting all positions. This change aligns the code with
the CCXT API convention and prevents the crash on Hyperliquid during wallet initialization.

Error that was occurring:
```
IndexError: list index out of range
  at /root/freqtrade/.venv/lib/python3.11/site-packages/ccxt/hyperliquid.py:3051
  market = self.market(symbols[0])
```

This change does not use AI-generated code.
2025-12-05 18:33:40 +01:00
Matthias 41a82eff21 fix: don't fill up funding fee data Data
Timeframes are arbitrary and may vary between pairs or
time ranges
2025-12-05 18:20:14 +01:00
Matthias d41acc77f7 fix: floor funding-rate to seconds to account for slight time offset 2025-12-05 18:20:14 +01:00
Matthias 271fc6b585 feat: don't fill up missing funding-fees after merge 2025-12-05 18:20:14 +01:00
Matthias 262f4ffa4c test: update bt tests for new timeout behavior 2025-12-05 07:10:17 +01:00
Matthias 338c1c5424 fix: compare timeout with <= instead of <
closes #12590

Backtesting assumes round dates, so a timeout at "candle length" needs
to timeout at the hour - not after the hour.
otherwise the timeout becomes either double (60 instead of 30) -
or longer by one "timeframe detail" (31 instead of 30).
2025-12-05 07:10:04 +01:00
0xward 8ca25b1757 Update README.md
docs: fix minor typo and inconsistency in README disclaimer (Dry-run to dry-run)
2025-12-05 09:50:16 +07:00
Matthias 68e54248fd fix: calculate meaningful price change properly weighting each pair
closes #12588
2025-12-04 21:28:31 +01:00
Matthias e792bafe21 test: update test to ensure rel_mean is reasonable 2025-12-04 21:28:31 +01:00
Matthias fe95581acd test: update rel_mean test to use USDT Pairs
This will allow highlighting a problem pointed out in #12588
2025-12-04 21:28:31 +01:00
Matthias 7f831f8a9c test: update tests for new test pairs 2025-12-04 21:28:31 +01:00
Matthias 3fb7382cc4 test: update test data to include USDT pairs 2025-12-04 21:28:31 +01:00
Matthias 83f70b3c27 Merge pull request #12585 from LMessi2000/fix/okx-price-type-mapping
fix(okx): correct PriceType.MARK and PriceType.INDEX mapping
2025-12-04 07:24:31 +01:00
Ingo 922d5893f8 newest ccxt 2025-12-04 07:06:52 +01:00
Matthias e6e14ab67f Merge branch 'develop' into fix/okx-price-type-mapping 2025-12-04 07:02:27 +01:00
Matthias 37792e7d86 Merge pull request #12589 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2025-12-04 07:01:44 +01:00
Matthias a63ea6adb8 test: switch to BTC/USDC in tests for now 2025-12-04 06:56:14 +01:00
Matthias 06a8f3d097 chore: bump ccxt to 4.5.24 2025-12-04 06:36:13 +01:00
Freqtrade Bot e76b9bb64a chore: update pre-commit hooks 2025-12-04 03:27:18 +00:00
LMessi2000 606dbe5db1 Revert "test(okx): add unit test for stop_price_type_value_mapping"
This reverts commit 81e4e0d301.
2025-12-03 22:17:59 +08:00
LMessi2000 81e4e0d301 test(okx): add unit test for stop_price_type_value_mapping 2025-12-03 20:58:12 +08:00
LMessi2000 a3e97527d1 fix(okx): correct PriceType.MARK and PriceType.INDEX mapping 2025-12-03 20:41:01 +08:00
Ingo 3ebc7bfcfc no parsing direct position call 2025-12-03 07:02:30 +01:00
Ingo c892b07eef compatible with HIP3 2025-12-03 06:22:44 +01:00
Ingo 7a23da4deb only use HIP3_dexes with hyperliquid validation 2025-12-03 05:32:31 +01:00
Ingo 670999abda compatible with HIP3 2025-12-03 05:30:36 +01:00
Stefano e2b5e209f6 Merge branch 'freqtrade:develop' into main-stash 2025-12-03 09:10:15 +09:00
Matthias 7137461891 feat: improved download-data logging 2025-12-02 21:16:37 +01:00
Matthias 10e7d279f0 test: improve funding rate combine test 2025-12-02 20:39:34 +01:00
Matthias 971cb94ce4 feat: limit returned columns for merged mark/funding calc to used columns 2025-12-02 20:24:52 +01:00
Matthias 19526f1df2 test: update tests to align to improved formatting 2025-12-02 19:12:24 +01:00
Matthias 9c73c8671e feat: improve format_date with fallback value 2025-12-02 19:12:24 +01:00
Matthias 0f5427f4a0 feat: round_value should support None 2025-12-02 19:12:24 +01:00
Matthias a013793b2f feat: improved trade repr format (used for logs) 2025-12-02 19:12:24 +01:00
Matthias 1a77f1b203 Merge pull request #12581 from freqtrade/fix/sloe_handling
Improve stoploss cancellation logic
2025-12-02 16:18:52 +01:00
Matthias faf552837a test: add test for stoploss cancel skips 2025-12-02 06:48:53 +01:00
Matthias 4b2d099258 fix: ensure stoploss on exchange is canceled once the trade closes. 2025-12-02 06:48:53 +01:00
Matthias 4e6ea1d2ba feat: don't cancel stoploss early if it's not necessary 2025-12-02 06:48:53 +01:00
Matthias 197623839d Merge pull request #12579 from freqtrade/dependabot/github_actions/develop/actions/checkout-6.0.0
chore(deps): bump actions/checkout from 5.0.1 to 6.0.0
2025-12-02 06:47:47 +01:00
Matthias 6d8fa76708 Merge pull request #12580 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-12-02 06:40:25 +01:00
Matthias d7eb49d5d0 chore: align usage of actions/checkout version pin 2025-12-02 06:31:32 +01:00
Freqtrade Bot 659509e085 chore: update pre-commit hooks 2025-12-02 03:26:42 +00:00
Matthias 2b729c2527 test: improve integration test to have proper return value 2025-12-01 20:11:03 +01:00
dependabot[bot] 38151423b8 chore(deps): bump actions/checkout from 5.0.1 to 6.0.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.1 to 6.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v5.0.1...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 18:48:43 +00:00
Matthias 2f990b773d chore: increase dependabot cooldown to 7 days 2025-12-01 19:08:50 +01:00
Matthias 20d253dd41 Merge pull request #12574 from freqtrade/dependabot/pip/develop/pre-commit-4.5.0
chore(deps-dev): bump pre-commit from 4.4.0 to 4.5.0
2025-12-01 08:16:40 +01:00
Matthias 6e2165b5e5 Merge pull request #12572 from freqtrade/dependabot/pip/develop/fastapi-0.122.0
chore(deps): bump fastapi from 0.121.3 to 0.122.0
2025-12-01 07:25:23 +01:00
Matthias 6860904bb9 Merge pull request #12573 from freqtrade/dependabot/pip/develop/ccxt-4.5.22
chore(deps): bump ccxt from 4.5.20 to 4.5.22
2025-12-01 07:16:12 +01:00
Matthias 450dc23374 Merge pull request #12571 from freqtrade/dependabot/pip/develop/pymdown-extensions-10.17.2
chore(deps): bump pymdown-extensions from 10.17.1 to 10.17.2
2025-12-01 07:10:42 +01:00
Matthias d781f09b46 Merge pull request #12566 from freqtrade/dependabot/pip/develop/scipy-e647f76feb
chore(deps-dev): bump scipy-stubs from 1.16.3.0 to 1.16.3.1 in the scipy group
2025-12-01 07:10:13 +01:00
dependabot[bot] dbde8332f7 chore(deps): bump fastapi from 0.121.3 to 0.122.0
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.121.3 to 0.122.0.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.121.3...0.122.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 06:00:27 +00:00
Matthias f5f1785b17 Merge pull request #12576 from freqtrade/dependabot/github_actions/develop/zizmorcore/zizmor-action-0.3.0
chore(deps): bump zizmorcore/zizmor-action from 0.2.0 to 0.3.0
2025-12-01 06:59:33 +01:00
Matthias 7d01928df0 Merge pull request #12567 from freqtrade/dependabot/pip/develop/pydantic-2.12.5
chore(deps): bump pydantic from 2.12.4 to 2.12.5
2025-12-01 06:58:53 +01:00
Matthias 31f712749b Merge pull request #12568 from freqtrade/dependabot/pip/develop/time-machine-3.1.0
chore(deps-dev): bump time-machine from 3.0.0 to 3.1.0
2025-12-01 06:58:36 +01:00
Matthias bcdf83100f Merge pull request #12577 from freqtrade/dependabot/github_actions/develop/actions/checkout-6.0.0
chore(deps): bump actions/checkout from 5.0.0 to 6.0.0
2025-12-01 06:58:02 +01:00
dependabot[bot] dc88ea0ed9 chore(deps-dev): bump pre-commit from 4.4.0 to 4.5.0
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 4.4.0 to 4.5.0.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v4.4.0...v4.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 05:57:42 +00:00
Matthias 543457a1ef Merge pull request #12575 from freqtrade/dependabot/github_actions/develop/peter-evans/create-pull-request-7.0.9
chore(deps): bump peter-evans/create-pull-request from 7.0.8 to 7.0.9
2025-12-01 06:57:05 +01:00
Matthias 5d781d0114 Merge pull request #12570 from freqtrade/dependabot/pip/develop/xgboost-3.1.2
chore(deps): bump xgboost from 3.1.1 to 3.1.2
2025-12-01 06:51:39 +01:00
Matthias 45b1dabe0a chore: bump scipy-stubs in pre-commit config 2025-12-01 06:44:00 +01:00
Matthias ffea373f86 Merge pull request #12578 from freqtrade/dependabot/github_actions/develop/astral-sh/setup-uv-7.1.4
chore(deps): bump astral-sh/setup-uv from 7.1.2 to 7.1.4
2025-12-01 06:41:44 +01:00
Matthias ba650de562 Merge pull request #12569 from freqtrade/dependabot/pip/develop/ruff-0.14.6
chore(deps-dev): bump ruff from 0.14.5 to 0.14.6
2025-12-01 06:41:20 +01:00
dependabot[bot] 39233ff240 chore(deps): bump astral-sh/setup-uv from 7.1.2 to 7.1.4
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 7.1.2 to 7.1.4.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41...1e862dfacbd1d6d858c55d9b792c756523627244)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 03:30:36 +00:00
dependabot[bot] 8950423418 chore(deps): bump actions/checkout from 5.0.0 to 6.0.0
Bumps [actions/checkout](https://github.com/actions/checkout) from 5.0.0 to 6.0.0.
- [Release notes](https://github.com/actions/checkout/releases)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: 6.0.0
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 03:30:21 +00:00
dependabot[bot] 760a2b9ff7 chore(deps): bump zizmorcore/zizmor-action from 0.2.0 to 0.3.0
Bumps [zizmorcore/zizmor-action](https://github.com/zizmorcore/zizmor-action) from 0.2.0 to 0.3.0.
- [Release notes](https://github.com/zizmorcore/zizmor-action/releases)
- [Commits](https://github.com/zizmorcore/zizmor-action/compare/e673c3917a1aef3c65c972347ed84ccd013ecda4...e639db99335bc9038abc0e066dfcd72e23d26fb4)

---
updated-dependencies:
- dependency-name: zizmorcore/zizmor-action
  dependency-version: 0.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 03:30:05 +00:00
dependabot[bot] a13f488b84 chore(deps): bump peter-evans/create-pull-request from 7.0.8 to 7.0.9
Bumps [peter-evans/create-pull-request](https://github.com/peter-evans/create-pull-request) from 7.0.8 to 7.0.9.
- [Release notes](https://github.com/peter-evans/create-pull-request/releases)
- [Commits](https://github.com/peter-evans/create-pull-request/compare/271a8d0340265f705b14b6d32b9829c1cb33d45e...84ae59a2cdc2258d6fa0732dd66352dddae2a412)

---
updated-dependencies:
- dependency-name: peter-evans/create-pull-request
  dependency-version: 7.0.9
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 03:29:53 +00:00
dependabot[bot] 09b1582704 chore(deps): bump ccxt from 4.5.20 to 4.5.22
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.5.20 to 4.5.22.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Commits](https://github.com/ccxt/ccxt/compare/v4.5.20...v4.5.22)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 03:06:04 +00:00
dependabot[bot] 61ab49a124 chore(deps): bump pymdown-extensions from 10.17.1 to 10.17.2
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.17.1 to 10.17.2.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.17.1...10.17.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 03:05:36 +00:00
dependabot[bot] 1a95b04d79 chore(deps): bump xgboost from 3.1.1 to 3.1.2
Bumps [xgboost](https://github.com/dmlc/xgboost) from 3.1.1 to 3.1.2.
- [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/v3.1.1...v3.1.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 03:05:23 +00:00
dependabot[bot] 691a6b0a72 chore(deps-dev): bump ruff from 0.14.5 to 0.14.6
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.5 to 0.14.6.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.14.5...0.14.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 03:05:16 +00:00
dependabot[bot] 1d0a902251 chore(deps-dev): bump time-machine from 3.0.0 to 3.1.0
Bumps [time-machine](https://github.com/adamchainz/time-machine) from 3.0.0 to 3.1.0.
- [Changelog](https://github.com/adamchainz/time-machine/blob/main/docs/changelog.rst)
- [Commits](https://github.com/adamchainz/time-machine/compare/3.0.0...3.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 03:04:53 +00:00
dependabot[bot] 1de9f5a2fb chore(deps): bump pydantic from 2.12.4 to 2.12.5
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.12.4 to 2.12.5.
- [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.12.4...v2.12.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 03:04:45 +00:00
dependabot[bot] 7a5342cc93 chore(deps-dev): bump scipy-stubs in the scipy group
Bumps the scipy group with 1 update: [scipy-stubs](https://github.com/scipy/scipy-stubs).


Updates `scipy-stubs` from 1.16.3.0 to 1.16.3.1
- [Release notes](https://github.com/scipy/scipy-stubs/releases)
- [Commits](https://github.com/scipy/scipy-stubs/compare/v1.16.3.0...v1.16.3.1)

---
updated-dependencies:
- dependency-name: scipy-stubs
  dependency-version: 1.16.3.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: scipy
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-12-01 03:04:40 +00:00
Matthias 52c73eabeb docs: add missing space in docs 2025-11-30 13:25:12 +01:00
Matthias 02de1f0a1e Merge pull request #12559 from freqtrade/feat/dry_stop
Enhance dry-run stoploss functionality
2025-11-29 13:44:45 +01:00
Matthias 3453bdf607 chore: bump version to 2025.12-dev 2025-11-29 13:10:16 +01:00
Matthias ac2723c3a0 test: add explicit test for dry stoploss order filling 2025-11-29 12:23:15 +01:00
Matthias 1536c09df3 test: improved test naming 2025-11-29 12:07:13 +01:00
Stefano 645f70e216 add None 2025-11-29 15:53:28 +09:00
Stefano b59db3c690 add more metrics on profit stat 2025-11-29 15:43:20 +09:00
Matthias fade66afd9 fix: ensure we always have a price when checking stops 2025-11-28 15:52:30 +01:00
Matthias f63484d0b0 feat: add dry-limit check for stoploss orders 2025-11-28 15:52:30 +01:00
Matthias 3543e96ec5 refactor: extract dry-market order slippage from function 2025-11-28 15:52:30 +01:00
Matthias c83ea0db4f chore: fix default behavior for crossed mode 2025-11-28 15:52:30 +01:00
Matthias ad256367be feat: dry-is-crossed should support stoploss 2025-11-28 15:52:30 +01:00
Matthias 73b427370b test: add tests for dry crossed stoploss 2025-11-28 15:52:30 +01:00
Matthias ca7234e33f test: fix dry-stop tests 2025-11-28 15:52:30 +01:00
Matthias cd7b267171 feat: record dry-run stop_price price separately 2025-11-28 15:52:30 +01:00
Matthias 6cef73947c chore: apply formatting 2025-11-27 06:45:06 +01:00
Matthias 33dbe32e5c Merge branch 'develop' into pr/dev-starlight/12506 2025-11-27 06:37:17 +01:00
dev-starlight 8caf7a888e Add UTF-8 encoding to read_text method 2025-11-11 17:02:36 +08:00
dev-starlight 033050e6cf read text encoding='utf-8' 2025-11-11 17:00:56 +08:00
Matthias 0340d36af2 chore(ci): run ci against python 3.14 2025-11-11 07:02:19 +01:00
Matthias ad7816d51e chore: skip catboost tests on 3.14 for now 2025-11-11 07:01:21 +01:00
Matthias 73127c8179 chore: limit catboost to python < 3.14 2025-11-11 06:52:26 +01:00
x-mass ea6d23bd2d fix: align _get_close_rate_for_roi with calc_profit_ratio logic in backtesting 2025-10-02 11:51:06 +00:00
164 changed files with 12627 additions and 10026 deletions
+2 -1
View File
@@ -46,8 +46,9 @@ runs:
id: tags
env:
BRANCH_NAME_INPUT: ${{ github.event.inputs.branch_name }}
EVENT_NAME: ${{ github.event_name }}
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
if [ "${EVENT_NAME}" = "workflow_dispatch" ]; then
BRANCH_NAME="${BRANCH_NAME_INPUT}"
else
BRANCH_NAME="${GITHUB_REF##*/}"
+15 -8
View File
@@ -2,7 +2,7 @@ version: 2
updates:
- package-ecosystem: docker
cooldown:
default-days: 4
default-days: 7
directories:
- "/"
- "/docker"
@@ -16,7 +16,7 @@ updates:
- package-ecosystem: devcontainers
directory: "/"
cooldown:
default-days: 4
default-days: 7
schedule:
interval: daily
open-pull-requests-limit: 10
@@ -24,13 +24,13 @@ updates:
- package-ecosystem: pip
directory: "/"
cooldown:
default-days: 4
default-days: 7
exclude:
- ccxt
schedule:
interval: weekly
time: "03:00"
timezone: "Etc/UTC"
interval: "cron"
# Monday at 03:00
cronjob: "0 3 * * 1"
open-pull-requests-limit: 15
target-branch: develop
groups:
@@ -51,8 +51,15 @@ updates:
- package-ecosystem: "github-actions"
directory: "/"
cooldown:
default-days: 4
default-days: 7
schedule:
interval: "weekly"
interval: "cron"
# Monday at 03:00
cronjob: "0 3 * * 1"
open-pull-requests-limit: 10
target-branch: develop
groups:
actions:
patterns:
# Combine updates for github provided actions
- "actions/*"
@@ -6,20 +6,25 @@ on:
# on demand
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true
permissions:
contents: read
jobs:
auto-update:
name: "Auto Update Binance Leverage Tiers"
runs-on: ubuntu-latest
environment:
name: develop
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@v6
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.12"
@@ -34,7 +39,7 @@ jobs:
run: python build_helpers/binance_update_lev_tiers.py
- uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
- uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
with:
token: ${{ secrets.REPO_SCOPED_TOKEN }}
add-paths: freqtrade/exchange/binance_leverage_tiers.json
+33 -33
View File
@@ -16,8 +16,8 @@ on:
concurrency:
group: "${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}"
cancel-in-progress: true
permissions:
repository-projects: read
permissions: {}
jobs:
tests:
name: "Tests and Linting"
@@ -25,20 +25,20 @@ jobs:
strategy:
matrix:
os: [ "ubuntu-22.04", "ubuntu-24.04", "macos-14", "macos-15" , "windows-2022", "windows-2025" ]
python-version: ["3.11", "3.12", "3.13"]
python-version: ["3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
with:
activate-environment: true
enable-cache: true
@@ -74,7 +74,7 @@ jobs:
run: |
pytest --random-order --cov=freqtrade --cov=freqtrade_client --cov-config=.coveragerc
- uses: codecov/codecov-action@5a1091511ad55cbe89839c7260b706298ca349f7 # v5.5.1
- uses: codecov/codecov-action@671740ac38dd9b0130fbe1cec585b89eea48d3de # v5.5.2
if: (runner.os == 'Linux' && matrix.python-version == '3.12' && matrix.os == 'ubuntu-24.04')
with:
fail_ci_if_error: true
@@ -87,12 +87,12 @@ jobs:
rm -rf codecov codecov.SHA256SUM codecov.SHA256SUM.sig
- name: Run json schema extract
# This should be kept before the repository check to ensure that the schema is up-to-date
# This must be kept before the repository check to ensure that the schema is up-to-date
run: |
python build_helpers/extract_config_json_schema.py
- name: Run command docs partials extract
# This should be kept before the repository check to ensure that the docs are up-to-date
# This must be kept before the repository check to ensure that the docs are up-to-date
if: ${{ (matrix.python-version == '3.13') }}
run: |
python build_helpers/create_command_partials.py
@@ -110,7 +110,7 @@ jobs:
fi
- name: Check for repository changes - Windows
if: ${{ runner.os == 'Windows' && (matrix.python-version != '3.13') }}
if: ${{ runner.os == 'Windows' }}
run: |
if (git status --porcelain) {
Write-Host "Repository is dirty, changes detected:"
@@ -159,6 +159,7 @@ jobs:
shell: powershell
run: |
$PSVersionTable
Get-PSRepository | Format-List *
Set-PSRepository psgallery -InstallationPolicy trusted
Install-Module -Name Pester -RequiredVersion 5.3.1 -Confirm:$false -Force -SkipPublisherCheck
$Error.clear()
@@ -177,12 +178,12 @@ jobs:
name: "Mypy Version Check"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 #v6.1.0
with:
python-version: "3.12"
@@ -195,11 +196,11 @@ jobs:
name: "Pre-commit checks"
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@v6
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.12"
- uses: pre-commit/action@2c7b3805fd2a0fd8c1884dcaebf91fc102a13ecd # v3.0.1
@@ -208,7 +209,7 @@ jobs:
name: "Documentation build"
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
@@ -217,7 +218,7 @@ jobs:
./tests/test_docs.sh
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.12"
@@ -240,17 +241,17 @@ jobs:
name: "Tests and Linting - Online tests"
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.12"
- name: Install uv
uses: astral-sh/setup-uv@85856786d1ce8acfbcc2f13a5f3fbd6b938f9f41 # v7.1.2
uses: astral-sh/setup-uv@61cb8a9741eeb8a550a1b8544337180c0fc8476b # v7.2.0
with:
activate-environment: true
enable-cache: true
@@ -274,6 +275,7 @@ jobs:
# Notify only once - when CI completes (and after deploy) in case it's successful
notify-complete:
name: "Notify CI Completion"
needs: [
build,
build-linux-online
@@ -281,8 +283,6 @@ jobs:
runs-on: ubuntu-22.04
# Discord notification can't handle schedule events
if: github.event_name != 'schedule' && github.repository == 'freqtrade/freqtrade'
permissions:
repository-projects: read
steps:
- name: Check user permission
@@ -320,12 +320,12 @@ jobs:
with:
jobs: ${{ toJSON(needs) }}
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.12"
@@ -335,7 +335,7 @@ jobs:
python -m build --sdist --wheel
- name: Upload artifacts 📦
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.1.0
with:
name: freqtrade-build
path: |
@@ -348,7 +348,7 @@ jobs:
python -m build --sdist --wheel ft_client
- name: Upload artifacts 📦
uses: actions/upload-artifact@v5
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.1.0
with:
name: freqtrade-client-build
path: |
@@ -364,15 +364,15 @@ jobs:
name: testpypi
url: https://test.pypi.org/p/freqtrade
permissions:
id-token: write
id-token: write # Needed for pypa/gh-action-pypi-publish
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Download artifact 📦
uses: actions/download-artifact@v6
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
pattern: freqtrade*-build
path: dist
@@ -393,15 +393,15 @@ jobs:
name: pypi
url: https://pypi.org/p/freqtrade
permissions:
id-token: write
id-token: write # Needed for pypa/gh-action-pypi-publish
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Download artifact 📦
uses: actions/download-artifact@v6
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
with:
pattern: freqtrade*-build
path: dist
@@ -419,7 +419,7 @@ jobs:
if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade'
uses: ./.github/workflows/docker-build.yml
permissions:
packages: write
packages: write # Needed to push package versions
contents: read
secrets:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
@@ -433,6 +433,6 @@ jobs:
# Only run on push, schedule, or release events
if: (github.event_name == 'push' || github.event_name == 'schedule') && github.repository == 'freqtrade/freqtrade'
permissions:
packages: write
packages: write # Needed to delete package versions
with:
package_name: 'freqtrade'
+5 -2
View File
@@ -11,6 +11,9 @@ on:
# disable permissions for all of the available permissions
permissions: {}
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
build-docs:
@@ -19,12 +22,12 @@ jobs:
name: Deploy Docs through mike
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: true
- name: Set up Python
uses: actions/setup-python@v6
uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: '3.12'
+16 -2
View File
@@ -17,14 +17,17 @@ concurrency:
group: "${{ github.workflow }}"
cancel-in-progress: true
permissions:
contents: read
jobs:
build-and-push:
name: "Build and Push Devcontainer Image"
permissions:
packages: write
packages: write # Needed to push package versions
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Login to GitHub Container Registry
@@ -40,3 +43,14 @@ jobs:
imageName: ghcr.io/${{ github.repository }}-devcontainer
cacheFrom: ghcr.io/${{ github.repository }}-devcontainer
push: always
packages-cleanup:
name: "Docker Package Cleanup"
uses: ./.github/workflows/packages-cleanup.yml
# Only run on push, schedule, or release events
if: (github.event_name == 'push' || github.event_name == 'schedule') && github.repository == 'freqtrade/freqtrade'
permissions:
packages: write # Needed to delete package versions
with:
package_name: 'freqtrade-devcontainer'
+23 -4
View File
@@ -17,6 +17,10 @@ on:
default: 'develop'
type: string
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
permissions:
contents: read
@@ -33,10 +37,21 @@ jobs:
if: github.repository == 'freqtrade/freqtrade'
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Visualize disk usage before build
run: df -h
- name: Cleanup some disk space
run: |
docker system prune -a --force || true
docker builder prune -af || true
- name: Visualize disk usage after cleanup
run: df -h
- name: Set docker tag names
id: tags
uses: ./.github/actions/docker-tags
@@ -54,7 +69,7 @@ jobs:
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 #v3.11.1
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f #v3.12.0
- name: Available platforms
run: echo ${PLATFORMS}
@@ -142,17 +157,20 @@ jobs:
run: |
docker images
- name: Visualize disk usage after build
run: df -h
deploy-arm:
name: "Deploy Docker ARM64"
permissions:
packages: write
packages: write # Needed to push package versions
needs: [ deploy-docker ]
# Only run on 64bit machines
runs-on: [self-hosted, linux, ARM64]
if: github.repository == 'freqtrade/freqtrade'
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
@@ -276,6 +294,7 @@ jobs:
docker buildx imagetools create \
--tag ${GHCR_IMAGE_NAME}:${TAG} \
--tag ${GHCR_IMAGE_NAME}:latest \
--tag ${IMAGE_NAME}:latest \
${IMAGE_NAME}:${TAG}
- name: Docker images
+6 -1
View File
@@ -4,14 +4,19 @@ on:
branches:
- stable
concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true
# disable permissions for all of the available permissions
permissions: {}
jobs:
dockerHubDescription:
name: "Update Docker Hub Description"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
+8 -2
View File
@@ -25,20 +25,26 @@ on:
default: true
type: boolean
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: false
env:
PACKAGE_NAME: "freqtrade"
permissions: {}
jobs:
deploy-docker:
name: "Delete Packages"
runs-on: ubuntu-24.04
if: github.repository == 'freqtrade/freqtrade'
permissions:
packages: write
packages: write # Needed to delete package versions
steps:
- name: "Delete untagged Package Versions"
uses: actions/delete-package-versions@v5
uses: actions/delete-package-versions@e5bc658cc4c965c472efe991f8beea3981499c55 # v5.0.0
with:
package-name: ${{ inputs.package_name || env.PACKAGE_NAME }}
package-type: 'container'
+8 -3
View File
@@ -9,15 +9,20 @@ on:
permissions:
contents: read
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true
jobs:
auto-update:
name: Auto-update pre-commit hooks
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@v6
- uses: actions/setup-python@83679a892e2d95755f2dac6acb0bfd1e9ac5d548 # v6.1.0
with:
python-version: "3.12"
@@ -28,7 +33,7 @@ jobs:
- name: Run auto-update
run: pre-commit autoupdate
- uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
- uses: peter-evans/create-pull-request@98357b18bf14b5342f975ff684046ec3b2a07725 # v8.0.0
with:
token: ${{ secrets.REPO_SCOPED_TOKEN }}
add-paths: .pre-commit-config.yaml
-30
View File
@@ -1,30 +0,0 @@
name: GitHub Actions Security Analysis with zizmor 🌈
on:
push:
branches:
- develop
- stable
pull_request:
branches:
- develop
- stable
permissions: {}
jobs:
zizmor:
name: Run zizmor 🌈
runs-on: ubuntu-latest
permissions:
security-events: write
# contents: read # only needed for private repos
# actions: read # only needed for private repos
steps:
- name: Checkout repository
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
with:
persist-credentials: false
- name: Run zizmor 🌈
uses: zizmorcore/zizmor-action@e673c3917a1aef3c65c972347ed84ccd013ecda4 # v0.2.0
+34
View File
@@ -0,0 +1,34 @@
name: GitHub Actions Security Analysis with zizmor 🌈
on:
push:
branches:
- develop
- stable
pull_request:
branches:
- develop
- stable
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: false
permissions: {}
jobs:
zizmor:
name: Run zizmor 🌈
runs-on: ubuntu-latest
permissions:
security-events: write # Required for upload-sarif (used by zizmor-action) to upload SARIF files.
# contents: read # Only needed for private repos. Needed to clone the repo.
# actions: read # Only needed for private repos. Needed for upload-sarif to read workflow run info.
steps:
- name: Checkout repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- name: Run zizmor 🌈
uses: zizmorcore/zizmor-action@135698455da5c3b3e55f73f4419e481ab68cdd95 # v0.4.1
+6 -6
View File
@@ -21,18 +21,18 @@ repos:
# stages: [push]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.18.2"
rev: "v1.19.1"
hooks:
- id: mypy
exclude: build_helpers
additional_dependencies:
- types-cachetools==6.2.0.20251022
- types-filelock==3.2.7
- types-requests==2.32.4.20250913
- types-requests==2.32.4.20260107
- types-tabulate==0.9.0.20241207
- types-python-dateutil==2.9.0.20251115
- scipy-stubs==1.16.3.0
- SQLAlchemy==2.0.44
- scipy-stubs==1.17.0.1
- SQLAlchemy==2.0.45
# stages: [push]
- repo: https://github.com/pycqa/isort
@@ -44,7 +44,7 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.14.6'
rev: 'v0.14.14'
hooks:
- id: ruff
- id: ruff-format
@@ -83,6 +83,6 @@ repos:
# Ensure github actions remain safe
- repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v1.16.3
rev: v1.22.0
hooks:
- id: zizmor
+5 -7
View File
@@ -1,4 +1,4 @@
FROM python:3.13.8-slim-bookworm AS base
FROM python:3.13.11-slim-trixie AS base
# Setup env
ENV LANG=C.UTF-8
@@ -16,7 +16,8 @@ RUN mkdir /freqtrade \
&& useradd -u 1000 -G sudo -U -m -s /bin/bash ftuser \
&& chown ftuser:ftuser /freqtrade \
# Allow sudoers
&& echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers
&& echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers \
&& pip install --upgrade pip
WORKDIR /freqtrade
@@ -24,14 +25,11 @@ WORKDIR /freqtrade
FROM base AS python-deps
RUN apt-get update \
&& apt-get -y install build-essential libssl-dev git libffi-dev libgfortran5 pkg-config cmake gcc \
&& apt-get clean \
&& pip install --upgrade pip wheel
&& apt-get clean
# Install dependencies
COPY --chown=ftuser:ftuser requirements.txt requirements-hyperopt.txt /freqtrade/
USER ftuser
RUN pip install --user --no-cache-dir "numpy<3.0" \
&& pip install --user --no-cache-dir -r requirements-hyperopt.txt
RUN pip install --user --no-cache-dir -r requirements-hyperopt.txt
# Copy dependencies to runtime-image
FROM base AS runtime-image
+5 -3
View File
@@ -15,7 +15,7 @@ This software is for educational purposes only. Do not risk money which
you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS
AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.
Always start by running a trading bot in Dry-run and do not engage money
Always start by running a trading bot in Dry-Run and do not engage money
before you understand how it works and what profit/loss you should
expect.
@@ -24,7 +24,9 @@ hesitate to read the source code and understand the mechanism of this bot.
## Supported Exchange marketplaces
Please read the [exchange specific notes](docs/exchanges.md) to learn about eventual, special configurations needed for each exchange.
Please read the [exchange-specific notes](docs/exchanges.md) to learn about special configurations that maybe needed for each exchange.
### Supported Spot Exchanges
- [X] [Binance](https://www.binance.com/)
- [X] [BingX](https://bingx.com/invite/0EM9RX)
@@ -39,7 +41,7 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even
- [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)
### Supported Futures Exchanges
- [X] [Binance](https://www.binance.com/)
- [X] [Bitget](https://www.bitget.com/)
+39 -5
View File
@@ -1,5 +1,7 @@
import os
import subprocess # noqa: S404, RUF100
import sys
from io import StringIO
from pathlib import Path
@@ -8,7 +10,20 @@ def _write_partial_file(filename: str, content: str):
f.write(f"``` output\n{content}\n```\n")
def _get_help_output(parser) -> str:
"""Capture the help output from a parser."""
output = StringIO()
parser.print_help(file=output)
return output.getvalue()
def extract_command_partials():
# Set terminal width to 80 columns for consistent output formatting
os.environ["COLUMNS"] = "80"
# Import Arguments here to avoid circular imports and ensure COLUMNS is set
from freqtrade.commands.arguments import Arguments
subcommands = [
"trade",
"create-userdir",
@@ -46,16 +61,35 @@ def extract_command_partials():
"recursive-analysis",
]
result = subprocess.run(["freqtrade", "--help"], capture_output=True, text=True)
# Build the Arguments class to get the parser with all subcommands
args = Arguments(None)
args._build_subcommands()
_write_partial_file("docs/commands/main.md", result.stdout)
# Get main help output
main_help = _get_help_output(args.parser)
_write_partial_file("docs/commands/main.md", main_help)
# Get subparsers from the main parser
# The subparsers are stored in _subparsers._group_actions[0].choices
subparsers_action = None
for action in args.parser._subparsers._group_actions:
if hasattr(action, "choices"):
subparsers_action = action
break
if subparsers_action is None:
raise RuntimeError("Could not find subparsers in the main parser")
for command in subcommands:
print(f"Running for {command}")
result = subprocess.run(["freqtrade", command, "--help"], capture_output=True, text=True)
_write_partial_file(f"docs/commands/{command}.md", result.stdout)
if command in subparsers_action.choices:
subparser = subparsers_action.choices[command]
help_output = _get_help_output(subparser)
_write_partial_file(f"docs/commands/{command}.md", help_output)
else:
print(f" Warning: subcommand '{command}' not found in parser")
# freqtrade-client still uses subprocess as requested
print("Running for freqtrade-client")
result_client = subprocess.run(["freqtrade-client", "--show"], capture_output=True, text=True)
+4 -4
View File
@@ -1,4 +1,4 @@
FROM python:3.11.13-slim-bookworm AS base
FROM python:3.13.11-slim-trixie AS base
# Setup env
ENV LANG=C.UTF-8
@@ -13,7 +13,7 @@ RUN mkdir /freqtrade \
&& apt-get update \
&& apt-get -y install sudo libatlas3-base libopenblas-dev curl sqlite3 libutf8proc-dev libsnappy-dev \
&& apt-get clean \
&& useradd -u 1000 -G sudo -U -m ftuser \
&& useradd -u 1000 -G sudo -U -m -s /bin/bash ftuser \
&& chown ftuser:ftuser /freqtrade \
# Allow sudoers
&& echo "ftuser ALL=(ALL) NOPASSWD: /bin/chown" >> /etc/sudoers \
@@ -24,12 +24,12 @@ WORKDIR /freqtrade
# Install dependencies
FROM base AS python-deps
RUN apt-get update \
&& apt-get -y install build-essential libssl-dev libffi-dev libgfortran5 pkg-config cmake gcc \
&& apt-get -y install build-essential libssl-dev git libffi-dev libgfortran5 pkg-config cmake gcc \
&& apt-get clean \
&& echo "[global]\nextra-index-url=https://www.piwheels.org/simple" > /etc/pip.conf
# Install TA-lib
COPY build_helpers/* /tmp/
COPY build_helpers/*.whl /tmp/
# Install dependencies
COPY --chown=ftuser:ftuser requirements.txt /freqtrade/
+28 -29
View File
@@ -41,7 +41,7 @@ ranging from the simplest (0) to the most detailed per pair, per buy and per sel
* 1: profit summaries grouped by enter_tag
* 2: profit summaries grouped by enter_tag and exit_tag
* 3: profit summaries grouped by pair and enter_tag
* 4: profit summaries grouped by pair, enter_ and exit_tag (this can get quite large)
* 4: profit summaries grouped by pair, enter_tag and exit_tag (this can get quite large)
* 5: profit summaries grouped by exit_tag
More options are available by running with the `-h` option.
@@ -52,11 +52,10 @@ By default, `backtesting-analysis` processes the most recent backtest results in
If you want to analyze results from an earlier backtest, use the `--backtest-filename` option to specify the desired file. This lets you revisit and re-analyze historical backtest outputs at any time by providing the filename of the relevant backtest result:
``` bash
freqtrade backtesting-analysis -c <config.json> --timeframe <tf> --strategy <strategy_name> --timerange <timerange> --export signals --backtest-filename backtest-result-2025-03-05_20-38-34.zip
freqtrade backtesting -c <config.json> --strategy <strategy_name> --timerange <timerange> --export signals --backtest-filename backtest-result-2025-03-05_20-38-34.zip
```
You should see some output similar to below in the logs with the name of the timestamped
filename that was exported:
You should see some output similar to below in the logs with the name of the timestamped filename that was exported:
```
2022-06-14 16:28:32,698 - freqtrade.misc - INFO - dumping json to "mystrat_backtest-2022-06-14_16-28-32.json"
@@ -64,14 +63,14 @@ filename that was exported:
You can then use that filename in `backtesting-analysis`:
```
freqtrade backtesting-analysis -c <config.json> --backtest-filename=mystrat_backtest-2022-06-14_16-28-32.json
``` bash
freqtrade backtesting-analysis -c <config.json> --backtest-filename=backtest-result-2025-03-05_20-38-34.zip
```
To use a result from a different results directory, you can use `--backtest-directory` to specify the directory
``` bash
freqtrade backtesting-analysis -c <config.json> --backtest-directory custom_results/ --backtest-filename mystrat_backtest-2022-06-14_16-28-32.json
freqtrade backtesting-analysis -c <config.json> --backtest-directory custom_results/ --backtest-filename backtest-result-2025-03-05_20-38-34.zip
```
### Tuning the buy tags and sell tags to display
@@ -85,7 +84,7 @@ To show only certain buy and sell tags in the displayed output, use the followin
For example:
```bash
``` bash
freqtrade backtesting-analysis -c <config.json> --analysis-groups 0 2 --enter-reason-list enter_tag_a enter_tag_b --exit-reason-list roi custom_exit_tag_a stop_loss
```
@@ -96,7 +95,7 @@ values present on signal candles to allow fine-grained investigation and tuning
indicators. To print out a column for a given set of indicators, use the `--indicator-list`
option:
```bash
``` bash
freqtrade backtesting-analysis -c <config.json> --analysis-groups 0 2 --enter-reason-list enter_tag_a enter_tag_b --exit-reason-list roi custom_exit_tag_a stop_loss --indicator-list rsi rsi_1h bb_lowerband ema_9 macd macdsignal
```
@@ -108,24 +107,24 @@ output.
The indicator values will be displayed for both entry and exit points. If `--indicator-list all` is specified,
only the indicators at the entry point will be shown to avoid excessively large lists, which could occur depending on the strategy.
There are a range of candle and trade-related fields that are included in the analysis so are
There are a range of candle and trade-related fields that are included in the analysis so are
automatically accessible by including them on the indicator-list, and these include:
- **open_date :** trade open datetime
- **close_date :** trade close datetime
- **min_rate :** minimum price seen throughout the position
- **max_rate :** maximum price seen throughout the position
- **open :** signal candle open price
- **close :** signal candle close price
- **high :** signal candle high price
- **low :** signal candle low price
- **volume :** signal candle volume
- **profit_ratio :** trade profit ratio
- **profit_abs :** absolute profit return of the trade
* **open_date :** trade open datetime
* **close_date :** trade close datetime
* **min_rate :** minimum price seen throughout the position
* **max_rate :** maximum price seen throughout the position
* **open :** signal candle open price
* **close :** signal candle close price
* **high :** signal candle high price
* **low :** signal candle low price
* **volume :** signal candle volume
* **profit_ratio :** trade profit ratio
* **profit_abs :** absolute profit return of the trade
#### Sample Output for Indicator Values
```bash
``` bash
freqtrade backtesting-analysis -c user_data/config.json --analysis-groups 0 --indicator-list chikou_span tenkan_sen
```
@@ -158,13 +157,13 @@ The `--indicator-list` option, by default, displays indicator values for both en
Example: Display indicator values at entry signals:
```bash
``` bash
freqtrade backtesting-analysis -c user_data/config.json --analysis-groups 0 --indicator-list chikou_span tenkan_sen --entry-only
```
Example: Display indicator values at exit signals:
```bash
``` bash
freqtrade backtesting-analysis -c user_data/config.json --analysis-groups 0 --indicator-list chikou_span tenkan_sen --exit-only
```
@@ -181,7 +180,7 @@ To show only trades between dates within your backtested timerange, supply the u
For example, if your backtest timerange was `20220101-20221231` but you only want to output trades in January:
```bash
``` bash
freqtrade backtesting-analysis -c <config.json> --timerange 20220101-20220201
```
@@ -189,7 +188,7 @@ freqtrade backtesting-analysis -c <config.json> --timerange 20220101-20220201
Use the `--rejected-signals` option to print out rejected signals.
```bash
``` bash
freqtrade backtesting-analysis -c <config.json> --rejected-signals
```
@@ -198,13 +197,13 @@ freqtrade backtesting-analysis -c <config.json> --rejected-signals
Some of the tabular outputs can become large, so printing them out to the terminal is not preferable.
Use the `--analysis-to-csv` option to disable printing out of tables to standard out and write them to CSV files.
```bash
``` bash
freqtrade backtesting-analysis -c <config.json> --analysis-to-csv
```
By default this will write one file per output table you specified in the `backtesting-analysis` command, e.g.
```bash
``` bash
freqtrade backtesting-analysis -c <config.json> --analysis-to-csv --rejected-signals --analysis-groups 0 1
```
@@ -216,6 +215,6 @@ This will write to `user_data/backtest_results`:
To override where the files will be written, also specify the `--analysis-csv-path` option.
```bash
``` bash
freqtrade backtesting-analysis -c <config.json> --analysis-to-csv --analysis-csv-path another/data/path/
```
+3 -4
View File
@@ -133,7 +133,7 @@ class MyAwesomeStrategy(IStrategy):
]
# Define a custom max_open_trades space
def max_open_trades_space(self) -> List[Dimension]:
def max_open_trades_space() -> List[Dimension]:
return [
Integer(-1, 10, name='max_open_trades'),
]
@@ -142,7 +142,7 @@ class MyAwesomeStrategy(IStrategy):
!!! Note
All overrides are optional and can be mixed/matched as necessary.
### Dynamic parameters
## Dynamic parameters
Parameters can also be defined dynamically, but must be available to the instance once the [`bot_start()` callback](strategy-callbacks.md#bot-start) has been called.
@@ -159,7 +159,7 @@ class MyAwesomeStrategy(IStrategy):
!!! Warning
Parameters created this way will not show up in the `list-strategies` parameter count.
### Overriding Base estimator
## Overriding Base estimator
You can define your own optuna sampler for Hyperopt by implementing `generate_estimator()` in the Hyperopt subclass.
@@ -208,7 +208,6 @@ Some research will be necessary to find additional Samplers (from optunahub) for
Obviously the same approach will work for all other Samplers optuna supports.
## Space options
For the additional spaces, scikit-optimize (in combination with Freqtrade) provides the following space types:
+3 -1
View File
@@ -43,7 +43,9 @@ options:
separated.
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking).
stacking). Only applicable to backtesting and
hyperopt. Results archived by this cannot be
reproduced in dry/live trading.
--enable-protections, --enableprotections
Enable protections for backtesting. Will slow
backtesting down by a considerable amount, but will
+6
View File
@@ -11,6 +11,7 @@ usage: freqtrade download-data [-h] [-v] [--no-color] [--logfile FILE] [-V]
[--data-format-ohlcv {json,jsongz,feather,parquet}]
[--data-format-trades {json,jsongz,feather,parquet}]
[--trading-mode {spot,margin,futures}]
[--candle-types {spot,futures,mark,index,premiumIndex,funding_rate} [{spot,futures,mark,index,premiumIndex,funding_rate} ...]]
[--prepend]
options:
@@ -50,6 +51,11 @@ options:
`feather`).
--trading-mode, --tradingmode {spot,margin,futures}
Select Trading mode
--candle-types {spot,futures,mark,index,premiumIndex,funding_rate} [{spot,futures,mark,index,premiumIndex,funding_rate} ...]
Select candle type to download. Defaults to the
necessary candles for the selected trading mode (e.g.
'spot' or ('futures', 'funding_rate' and 'mark') for
futures).
--prepend Allow data prepending. (Data-appending is disabled)
Common arguments:
+3 -1
View File
@@ -41,7 +41,9 @@ options:
functions.
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking).
stacking). Only applicable to backtesting and
hyperopt. Results archived by this cannot be
reproduced in dry/live trading.
--enable-protections, --enableprotections
Enable protections for backtesting. Will slow
backtesting down by a considerable amount, but will
+1
View File
@@ -60,6 +60,7 @@ freqtrade download-data --exchange binance --pairs ".*/USDT"
* 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.
* When downloading futures data (`--trading-mode futures` or a configuration specifying futures mode), freqtrade will automatically download the necessary candle types (e.g. `mark` and `funding_rate` candles) unless specified otherwise via `--candle-types`.
??? Note "Permission denied errors"
If your configuration directory `user_data` was made by docker, you may get the following error:
+47
View File
@@ -98,3 +98,50 @@ Please use configuration based [log setup](advanced-setup.md#advanced-logging) i
The edge module has been deprecated in 2023.9 and removed in 2025.6.
All functionalities of edge have been removed, and having edge configured will result in an error.
## Adjustment to dynamic funding rate handling
With version 2025.12, the handling of dynamic funding rates has been adjusted to also support dynamic funding rates down to 1h funding intervals.
As a consequence, the mark and funding rate timeframes have been changed to 1h for every supported futures exchange.
As the timeframe for both mark and funding_fee candles has changed (usually from 8h to 1h) - already downloaded data will have to be adjusted or partially re-downloaded.
You can either re-download everything (`freqtrade download-data [...] --erase` - :warning: can take a long time) - or download the updated data selectively.
### Strategy
Most strategies should not need adjustments to continue to work as expected - however, strategies using `@informative("8h", candle_type="funding_rate")` or similar will have to switch the timeframe to 1h.
The same is true for `dp.get_pair_dataframe(metadata["pair"], "8h", candle_type="funding_rate")` - which will need to be switched to 1h.
freqtrade will auto-adjust the timeframe and return `funding_rates` despite the wrongly given timeframe. It'll issue a warning - and may still break your strategy.
### Selective data re-download
The script below should serve as an example - you may need to adjust the timeframe and exchange to your needs!
``` bash
# Cleanup no longer needed data
rm user_data/data/<exchange>/futures/*-mark*
rm user_data/data/<exchange>/futures/*-funding_rate*
# download new data (only required once to fix the mark and funding fee data)
freqtrade download-data -t 1h --trading-mode futures --candle-types funding_rate mark [...] --timerange <full timerange you've got other data for>
```
The result of the above will be that your funding_rates and mark data will have the 1h timeframe.
you can verify this with `freqtrade list-data --exchange <yourexchange> --show`.
!!! Note "Additional arguments"
Additional arguments to the above commands may be necessary, like configuration files or explicit user_data if they deviate from the default.
**Hyperliquid** is a special case now - which will no longer require 1h mark data - but will use regular candles instead (this data never existed and is identical to 1h futures candles). As we don't support download-data for hyperliquid (they don't provide historic data) - there won't be actions necessary for hyperliquid users.
## Catboost models in freqAI
CatBoost models have been removed with version 2025.12 and are no longer actively supported.
If you have existing bots using CatBoost models, you can still use them in your custom models by copy/pasting them from the git history (as linked below) and installing the Catboost library manually.
We do however recommend switching to other supported model libraries like LightGBM or XGBoost for better support and future compatibility.
* [CatboostRegressor](https://github.com/freqtrade/freqtrade/blob/c6f3b0081927e161a16b116cc47fb663f7831d30/freqtrade/freqai/prediction_models/CatboostRegressor.py)
* [CatboostClassifier](https://github.com/freqtrade/freqtrade/blob/c6f3b0081927e161a16b116cc47fb663f7831d30/freqtrade/freqai/prediction_models/CatboostClassifier.py)
* [CatboostClassifierMultiTarget](https://github.com/freqtrade/freqtrade/blob/c6f3b0081927e161a16b116cc47fb663f7831d30/freqtrade/freqai/prediction_models/CatboostClassifierMultiTarget.py)
+2 -3
View File
@@ -432,7 +432,6 @@ freqtrade download-data --timerange 20250625-20250801 --config tests/testdata/co
freqtrade backtesting --config tests/testdata/config.tests.usdt.json -s SampleStrategy --userdir user_data_bttest/ --cache none --timerange 20250701-20250801
```
## Continuous integration
This documents some decisions taken for the CI Pipeline.
@@ -464,10 +463,10 @@ git checkout -b new_release <commitid>
Determine if crucial bugfixes have been made between this commit and the current state, and eventually cherry-pick these.
* Merge the release branch (stable) into this branch.
* Edit `freqtrade/__init__.py` and add the version matching the current date (for example `2019.7` for July 2019). Minor versions can be `2019.7.1` should we need to do a second release that month. Version numbers must follow allowed versions from PEP0440 to avoid failures pushing to pypi.
* Edit `freqtrade/__init__.py` and add the version matching the current date (for example `2025.7` for July 2025). Minor versions can be `2025.7.1` should we need to do a second release that month. Version numbers must follow allowed versions from PEP0440 to avoid failures pushing to pypi.
* Commit this part.
* Push that branch to the remote and create a PR against the **stable branch**.
* Update develop version to next version following the pattern `2019.8-dev`.
* Update develop version to next version following the pattern `2025.8-dev`.
### Create changelog from git commits
+30
View File
@@ -428,6 +428,36 @@ Your balance and trades will now be used from your vault / subaccount - and no l
The Hyperliquid API does not provide historic data beyond the single call to fetch current data, so downloading data is not possible, as the downloaded data would not constitute proper historic data.
### HIP-3 DEXes
Hyperliquid supports HIP-3 decentralized exchanges (DEXes), which are independent exchanges built on top of the Hyperliquid infrastructure.
These DEXes operate similarly to the main Hyperliquid exchange but are community-created and managed.
To trade on HIP-3 DEXes with Freqtrade, you need to add them to your configuration using the `hip3_dexes` parameter:
```json
"exchange": {
"name": "hyperliquid",
"walletAddress": "your_master_wallet_address",
"privateKey": "your_api_private_key",
"hip3_dexes": ["dex_name_1", "dex_name_2"]
}
```
Replace `"dex_name_1"` and `"dex_name_2"` with the actual names of the HIP-3 DEXes you want to trade on (e.g. `vntl` and `xyz`).
!!! Warning "Performance and Rate Limit Impact"
Each HIP-3 DEX you add significantly impacts bot performance and rate limits.
* **Additional API Calls**: For each HIP-3 DEX configured, Freqtrade needs to make additional API calls.
* **Rate Limit Pressure**: Additional API calls contribute to Hyperliquid's strict rate limits. With multiple DEXes, you may hit rate limits faster, or rather, slow down bot operations due to enforced delays.
Please only add HIP-3 DEXes that you actively trade on. Monitor your logs for rate limit warnings or signs of slowed operations, and adjust your configuration accordingly.
Different HIP-3 DEXes may also use different quote currencies - so make sure to only add DEXes that are compatible with your stake currency to avoid unnecessary delays.
!!! Note
HIP-3 DEXes share the same wallet and free amount of collateral as your main Hyperliquid account. Trades on different DEXes will affect your overall account balance and margin.
## Bitvavo
If your account is required to use an operatorId, you can set it in the configuration file as follows:
+8 -1
View File
@@ -2,7 +2,7 @@
## Supported Markets
Freqtrade supports spot trading, as well as (isolated) futures trading for some selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges-experimental) for an up-to-date list of supported exchanges.
Freqtrade supports spot trading, as well as futures trading for some selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges-experimental) for an up-to-date list of supported exchanges.
### Can my bot open short positions?
@@ -29,6 +29,13 @@ You can however use the [`adjust_trade_position()` callback](strategy-callbacks.
Backtesting provides an option for this in `--eps` - however this is only there to highlight "hidden" signals, and will not work in live.
### Does freqtrade support sandbox accounts?
No, but you can use dry-run mode to simulate trading without risking real funds.
Sandbox markets are separate, simulated markets - which are not suitable to test your strategy in a realistic environment.
These markets usually have different order books, liquidity and trading behaviour (usually with very few participants) - which makes them unsuitable for realistic tests of your strategy.
### The bot does not start
Running the bot with `freqtrade trade --config config.json` shows the output `freqtrade: command not found`.
+6 -6
View File
@@ -200,15 +200,15 @@ If this value is set, FreqAI will initially use the predictions from the trainin
## Using different prediction models
FreqAI has multiple example prediction model libraries that are ready to be used as is via the flag `--freqaimodel`. These libraries include `CatBoost`, `LightGBM`, and `XGBoost` regression, classification, and multi-target models, and can be found in `freqai/prediction_models/`.
FreqAI has multiple example prediction model libraries that are ready to be used as is via the flag `--freqaimodel`. These libraries include `LightGBM`, and `XGBoost` regression, classification, and multi-target models, and can be found in `freqai/prediction_models/`.
Regression and classification models differ in what targets they predict - a regression model will predict a target of continuous values, for example what price BTC will be at tomorrow, whilst a classifier will predict a target of discrete values, for example if the price of BTC will go up tomorrow or not. This means that you have to specify your targets differently depending on which model type you are using (see details [below](#setting-model-targets)).
All of the aforementioned model libraries implement gradient boosted decision tree algorithms. They all work on the principle of ensemble learning, where predictions from multiple simple learners are combined to get a final prediction that is more stable and generalized. The simple learners in this case are decision trees. Gradient boosting refers to the method of learning, where each simple learner is built in sequence - the subsequent learner is used to improve on the error from the previous learner. If you want to learn more about the different model libraries you can find the information in their respective docs:
* CatBoost: https://catboost.ai/en/docs/
* LightGBM: https://lightgbm.readthedocs.io/en/v3.3.2/#
* XGBoost: https://xgboost.readthedocs.io/en/stable/#
* LightGBM: <https://lightgbm.readthedocs.io/en/v3.3.2/#>
* XGBoost: <https://xgboost.readthedocs.io/en/stable/#>
* CatBoost: <https://catboost.ai/en/docs/> (No longer actively supported since 2025.12)
There are also numerous online articles describing and comparing the algorithms. Some relatively lightweight examples would be [CatBoost vs. LightGBM vs. XGBoost — Which is the best algorithm?](https://towardsdatascience.com/catboost-vs-lightgbm-vs-xgboost-c80f40662924#:~:text=In%20CatBoost%2C%20symmetric%20trees%2C%20or,the%20same%20depth%20can%20differ.) and [XGBoost, LightGBM or CatBoost — which boosting algorithm should I use?](https://medium.com/riskified-technology/xgboost-lightgbm-or-catboost-which-boosting-algorithm-should-i-use-e7fda7bb36bc). Keep in mind that the performance of each model is highly dependent on the application and so any reported metrics might not be true for your particular use of the model.
@@ -219,7 +219,7 @@ Make sure to use unique names to avoid overriding built-in models.
#### Regressors
If you are using a regressor, you need to specify a target that has continuous values. FreqAI includes a variety of regressors, such as the `CatboostRegressor`via the flag `--freqaimodel CatboostRegressor`. An example of how you could set a regression target for predicting the price 100 candles into the future would be
If you are using a regressor, you need to specify a target that has continuous values. FreqAI includes a variety of regressors, such as the `LightGBMRegressor`via the flag `--freqaimodel LightGBMRegressor`. An example of how you could set a regression target for predicting the price 100 candles into the future would be
```python
df['&s-close_price'] = df['close'].shift(-100)
@@ -229,7 +229,7 @@ If you want to predict multiple targets, you need to define multiple labels usin
#### Classifiers
If you are using a classifier, you need to specify a target that has discrete values. FreqAI includes a variety of classifiers, such as the `CatboostClassifier` via the flag `--freqaimodel CatboostClassifier`. If you elects to use a classifier, the classes need to be set using strings. For example, if you want to predict if the price 100 candles into the future goes up or down you would set
If you are using a classifier, you need to specify a target that has discrete values. FreqAI includes a variety of classifiers, such as the `LightGBMClassifier` via the flag `--freqaimodel LightGBMClassifier`. If you elects to use a classifier, the classes need to be set using strings. For example, if you want to predict if the price 100 candles into the future goes up or down you would set
```python
df['&s-up_or_down'] = np.where( df["close"].shift(-100) > df["close"], 'up', 'down')
+1 -1
View File
@@ -107,7 +107,6 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
| `n_steps` | An alternative way of setting `n_epochs` - the number of training iterations to run. Iteration here refer to the number of times we call `optimizer.step()`. Ignored if `n_epochs` is set. A simplified version of the function: <br><br> n_epochs = n_steps / (n_obs / batch_size) <br><br> The motivation here is that `n_steps` is easier to optimize and keep stable across different n_obs - the number of data points. <br> <br> **Datatype:** int. optional. <br> Default: `None`.
| `batch_size` | The size of the batches to use during training. <br><br> **Datatype:** int. <br> Default: `64`.
### Additional parameters
| Parameter | Description |
@@ -116,3 +115,4 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
| `freqai.keras` | If the selected model makes use of Keras (typical for TensorFlow-based prediction models), this flag needs to be activated so that the model save/loading follows Keras standards. <br> **Datatype:** Boolean. <br> Default: `False`.
| `freqai.conv_width` | The width of a neural network input tensor. This replaces the need for shifting candles (`include_shifted_candles`) by feeding in historical data points as the second dimension of the tensor. Technically, this parameter can also be used for regressors, but it only adds computational overhead and does not change the model training/prediction. <br> **Datatype:** Integer. <br> Default: `2`.
| `freqai.reduce_df_footprint` | Recast all numeric columns to float32/int32, with the objective of reducing ram/disk usage and decreasing train/inference timing. This parameter is set in the main level of the Freqtrade configuration file (not inside FreqAI). <br> **Datatype:** Boolean. <br> Default: `False`.
| `freqai.override_exchange_check` | Override the exchange check to force FreqAI to use exchanges that may not have enough historic data. Turn this to True if you know your FreqAI model and strategy do not require historical data. <br> **Datatype:** Boolean. <br> Default: `False`.
+7 -7
View File
@@ -1,28 +1,28 @@
## Highlighted changes
# Highlighted changes
- ...
### How to update
## How to update
As always, you can update your bot using one of the following commands:
#### docker-compose
### docker-compose
```bash
docker-compose pull
docker-compose up -d
```
#### Installation via setup script
### Installation via setup script
```
``` bash
# Deactivate venv and run
./setup.sh --update
```
#### Plain native installation
### Plain native installation
```
``` bash
git pull
pip install -U -r requirements.txt
```
+3 -1
View File
@@ -37,6 +37,8 @@ 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.
### Supported Spot Exchanges
- [X] [Binance](https://www.binance.com/)
- [X] [BingX](https://bingx.com/invite/0EM9RX)
- [X] [Bitget](https://www.bitget.com/)
@@ -50,7 +52,7 @@ Please read the [exchange specific notes](exchanges.md) to learn about eventual,
- [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)
### Supported Futures Exchanges
- [X] [Binance](https://www.binance.com/)
- [X] [Bitget](https://www.bitget.com/)
+57 -18
View File
@@ -9,15 +9,25 @@ The freqtrade documentation describes various ways to install freqtrade
* [Manual Installation](#manual-installation)
* [Installation with Conda](#installation-with-conda)
Please consider using the prebuilt [docker images](docker_quickstart.md) to get started quickly while evaluating how freqtrade works.
Please consider using the prebuilt [docker images](docker_quickstart.md) to get started quickly.
!!! Note "Updating"
Keeping freqtrade updated is important to [ensure ongoing compatibility](updating.md#why-update) with exchange API's.
Please refer to the [updating guide](updating.md) for details on how to update your installation.
!!! Note "Windows users"
We **strongly** recommend that Windows users use [Docker](docker_quickstart.md) as this will work much easier and smoother (also more secure).
If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu/Linux instructions will work.
If you really want to install freqtrade natively on Windows, best use the [`./setup.ps1` installation script](#use-setupps1-windows).
Please also make sure to use the 64bit version of Python, as 32bit versions have severe memory limitations, which can negatively impact your experience with backtesting/hyperopt.
------
## Information
For Windows installation, please use the [windows installation guide](windows_installation.md).
The easiest way to install and run Freqtrade is to clone the bot Github repository and then run the `./setup.sh` script, if it's available for your platform.
The easiest way to install and run Freqtrade is to clone the bot Github repository and then run the `./setup.sh` (`./setup.ps1` for Windows) script, if it's available for your platform.
!!! Note "Version considerations"
When cloning the repository the default working branch has the name `develop`. This branch contains all last features (can be considered as relatively stable, thanks to automated tests).
@@ -152,20 +162,9 @@ If you are on Debian, Ubuntu or MacOS, freqtrade provides the script to install
./setup.sh -i
```
### Activate your virtual environment
#### Other options of /setup.sh script
Each time you open a new terminal, you must run `source .venv/bin/activate` to activate your virtual environment.
```bash
# activate virtual environment
source ./.venv/bin/activate
```
[You are now ready](#you-are-ready) to run the bot.
### Other options of /setup.sh script
You can as well update, configure and reset the codebase of your bot with `./script.sh`
You can also update, configure and reset the codebase of your bot with `./setup.sh`
```bash
# --update, Command git pull to update.
@@ -194,6 +193,34 @@ This option will pull the last version of your current branch and update your vi
This option will hard reset your branch (only if you are on either `stable` or `develop`) and recreate your virtualenv.
```
#### Activate your virtual environment
Each time you open a new terminal, you must run `source .venv/bin/activate` to activate your virtual environment.
```bash
# activate virtual environment
source ./.venv/bin/activate
```
### Use ./setup.ps1 (Windows)
The script will ask you a few questions to determine which parts should be installed.
```powershell
Set-ExecutionPolicy -ExecutionPolicy Bypass
cd freqtrade
. .\setup.ps1
```
#### Activate your virtual environment (Windows)
```powershell
# activate virtual environment
. .\.venv\Scripts\Activate.ps1
```
[You are now ready](#you-are-ready) to run the bot.
-----
## Manual Installation
@@ -337,7 +364,7 @@ conda deactivate
Happy trading!
-----
------
## You are ready
@@ -394,3 +421,15 @@ open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10
```
If this file is inexistent, then you're probably on a different version of MacOS, so you may need to consult the internet for specific resolution details.
### Windows Installation error
```bash
error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools
```
Unfortunately, many packages requiring compilation don't provide a pre-built wheel. It is therefore mandatory to have a C/C++ compiler installed and available for your python environment to use.
You can download the Visual C++ build tools from [the Visual Studio website](https://visualstudio.microsoft.com/visual-cpp-build-tools/) and install "Desktop development with C++" in it's default configuration. Unfortunately, this is a heavy download / dependency so you might want to consider WSL2 or [docker compose](docker_quickstart.md) first.
![Windows installation](assets/windows_install.png)
+1 -4
View File
@@ -1,8 +1,5 @@
# Trading with Leverage
!!! Warning "Beta feature"
This feature is still in it's testing phase. Should you notice something you think is wrong please let us know via Discord or via Github Issue.
!!! Note "Multiple bots on one account"
You can't run 2 bots on the same account with leverage. For leveraged / margin trading, freqtrade assumes it's the only user of the account, and all liquidation levels are calculated based on this assumption.
@@ -55,7 +52,7 @@ Perpetual swaps (also known as Perpetual Futures) are contracts traded at a pric
In addition to the gains/losses from the change in price of the futures contract, traders also exchange _funding fees_, which are gains/losses worth an amount that is derived from the difference in price between the futures contract and the underlying asset. The difference in price between a futures contract and the underlying asset varies between exchanges.
To trade in futures markets, you'll have to set `trading_mode` to "futures".
You will also have to pick a "margin mode" (explanation below) - with freqtrade currently only supporting isolated margin.
You will also have to pick a "margin mode" (explanation below).
``` json
"trading_mode": "futures",
+2 -2
View File
@@ -1,7 +1,7 @@
markdown==3.10
mkdocs==1.6.1
mkdocs-material==9.7.0
mkdocs-material==9.7.1
mdx_truly_sane_lists==1.3
pymdown-extensions==10.17.1
pymdown-extensions==10.20
jinja2==3.1.6
mike==2.1.3
+6 -1
View File
@@ -31,9 +31,14 @@ The Order-type will be ignored if only one mode is available.
--8<-- "includes/exchange-features.md"
!!! Note "Tight stoploss"
<ins>Do not set too low/tight stoploss value when using stop loss on exchange!</ins>
Do not set too low/tight stoploss value when using stop loss on exchange!
If set to low/tight you will have greater risk of missing fill on the order and stoploss will not work.
!!! Warning "Loose stoploss"
Using stoploss on exchange with a very wide stoploss (e.g. -1) may fail to place the stoploss order on exchange due to exchange limitations.
In that case, the bot will fallback to using the `emergency_exit` order type to place a market order as placing the stoploss order failed.
Freqtrade currently does not implement a limitation to avoid this situation, so please ensure your stoploss values are within reasonable limits for your exchange or disable stoploss on exchange.
### stoploss_on_exchange and stoploss_on_exchange_limit_ratio
Enable or Disable stop loss on exchange.
+18 -2
View File
@@ -634,7 +634,7 @@ class AwesomeStrategy(IStrategy):
## Custom order price rules
By default, freqtrade use the orderbook to automatically set an order price([Relevant documentation](configuration.md#prices-used-for-orders)), you also have the option to create custom order prices based on your strategy.
By default, freqtrade use the orderbook to automatically set an order price ([Relevant documentation](configuration.md#prices-used-for-orders)), you also have the option to create custom order prices based on your strategy.
You can use this feature by creating a `custom_entry_price()` function in your strategy file to customize entry prices and `custom_exit_price()` for exits.
@@ -644,7 +644,7 @@ Each of these methods are called right before placing an order on the exchange.
If your custom pricing function return None or an invalid value, price will fall back to `proposed_rate`, which is based on the regular pricing configuration.
!!! Note
Using custom_entry_price, the Trade object will be available as soon as the first entry order associated with the trade is created, for the first entry, `trade` parameter value will be `None`.
When using `custom_entry_price()`, the Trade object will be available as soon as the first entry order associated with the trade is created, for the first entry, `trade` parameter value will be `None`.
### Custom order entry and exit price example
@@ -1292,6 +1292,22 @@ Currently two types of annotations are supported, `area` and `line`.
}
```
#### Point
``` json
{
"type": "point", // Type of the annotation, currently only "point" is supported
"x": "2024-01-01 15:00:00", // Start date of the point
"y": 94000.2, // Price / y axis value
"color": "",
"z_level": 5, // z-level, higher values are drawn on top of lower values. Positions relative to the Chart elements need to be set in freqUI.
"label": "some label",
"size": 2, // Optional, line width in pixels. Defaults to 10
"symbol": "circle", // Optional, can be "circle", "rect", "roundRect", "triangle", "pin", "arrow", "none".
}
```
The below example will mark the chart with areas for the hours 8 and 15, with a grey color, highlighting the market open and close hours.
This is obviously a very basic example.
+14 -3
View File
@@ -6,6 +6,12 @@ To update your freqtrade installation, please use one of the below methods, corr
Breaking changes / changed behavior will be documented in the changelog that is posted alongside every release.
For the develop branch, please follow PR's to avoid being surprised by changes.
## Why update?
Keeping your bot updated not only ensures that you have the latest features and improvements, but is a requirement to keep your bot running smoothly.
Freqtrade is heavily dependent on the underlying exchange API's, which change pretty frequently if considered across exchanges.
To ensure ongoing compatibility, please make sure to update your bot regularly.
## Docker
!!! Note "Legacy installations using the `master` image"
@@ -38,7 +44,12 @@ pip install -e .
freqtrade install-ui
```
### Problems updating
## Problems updating
Update-problems usually come missing dependencies (you didn't follow the above instructions) - or from updated dependencies, which fail to install (for example TA-lib).
Please refer to the corresponding installation sections (common problems linked below)
Update-problems usually come missing dependencies (you didn't follow the above instructions) - or from dependencies which fail to install.
We try to make sure that heavy dependencies have wheels available for major platforms, but sometimes this is not possible.
Please refer to the corresponding installation sections (common problem sections linked below).
[Common installation problems](installation.md#troubleshooting)
[Common installation problems - windows](installation.md#windows-installation-error)
-54
View File
@@ -1,54 +0,0 @@
# Windows installation
We **strongly** recommend that Windows users use [Docker](docker_quickstart.md) as this will work much easier and smoother (also more secure).
If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work.
Otherwise, please follow the instructions below.
All instructions assume that python 3.11+ is installed and available.
## Clone the git repository
First of all clone the repository by running:
``` powershell
git clone https://github.com/freqtrade/freqtrade.git
```
Now, choose your installation method, either automatically via script (recommended) or manually following the corresponding instructions.
## Install freqtrade automatically
### Run the installation script
The script will ask you a few questions to determine which parts should be installed.
```powershell
Set-ExecutionPolicy -ExecutionPolicy Bypass
cd freqtrade
. .\setup.ps1
```
## Install freqtrade manually
!!! Note "64bit Python version"
Please make sure to use 64bit Windows and 64bit Python to avoid problems with backtesting or hyperopt due to the memory constraints 32bit applications have under Windows.
32bit python versions are no longer supported under Windows.
!!! Hint
Using the [Anaconda Distribution](https://www.anaconda.com/distribution/) under Windows can greatly help with installation problems. Check out the [Anaconda installation section](installation.md#installation-with-conda) in the documentation for more information.
### Error during installation on Windows
``` bash
error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools
```
Unfortunately, many packages requiring compilation don't provide a pre-built wheel. It is therefore mandatory to have a C/C++ compiler installed and available for your python environment to use.
You can download the Visual C++ build tools from [here](https://visualstudio.microsoft.com/visual-cpp-build-tools/) and install "Desktop development with C++" in it's default configuration. Unfortunately, this is a heavy download / dependency so you might want to consider WSL2 or [docker compose](docker_quickstart.md) first.
![Windows installation](assets/windows_install.png)
---
+1 -1
View File
@@ -1,6 +1,6 @@
"""Freqtrade bot"""
__version__ = "2025.11"
__version__ = "2026.2-dev"
if "dev" in __version__:
from pathlib import Path
+14 -2
View File
@@ -3,6 +3,7 @@ This module contains the argument manager class
"""
from argparse import ArgumentParser, Namespace, _ArgumentGroup
from copy import deepcopy
from functools import partial
from pathlib import Path
from typing import Any
@@ -102,7 +103,13 @@ ARGS_BACKTEST_SHOW = [
"backtest_breakdown",
]
ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all", "trading_mode", "dex_exchanges"]
ARGS_LIST_EXCHANGES = [
"print_one_column",
"list_exchanges_all",
"trading_mode",
"dex_exchanges",
"list_exchanges_futures_options",
]
ARGS_LIST_TIMEFRAMES = ["exchange", "print_one_column", "trading_mode"]
@@ -174,6 +181,7 @@ ARGS_DOWNLOAD_DATA = [
"dataformat_ohlcv",
"dataformat_trades",
"trading_mode",
"candle_types",
"prepend_data",
]
@@ -348,7 +356,11 @@ class Arguments:
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)
options = deepcopy(opt.kwargs)
help_text = options.pop("help", None)
if opt.fthelp and isinstance(opt.fthelp, dict) and hasattr(parser, "prog"):
help_text = opt.fthelp.get(parser.prog, help_text)
parser.add_argument(*opt.cli, dest=val, help=help_text, **options)
def _build_subcommands(self) -> None:
"""
+28 -3
View File
@@ -2,7 +2,7 @@
Definition of cli arguments used in arguments.py
"""
from argparse import ArgumentTypeError
from argparse import SUPPRESS, ArgumentTypeError
from freqtrade import constants
from freqtrade.constants import (
@@ -38,8 +38,14 @@ def check_int_nonzero(value: str) -> int:
class Arg:
# Optional CLI arguments
def __init__(self, *args, **kwargs):
def __init__(self, *args, fthelp: dict[str, str] | None = None, **kwargs):
"""
CLI Arguments - used to build subcommand parsers consistently.
:param fthelp: dict - fthelp per command - should be "freqtrade <command>": help_text
If not provided or not found, 'help' from kwargs is used instead.
"""
self.cli = args
self.fthelp = fthelp
self.kwargs = kwargs
@@ -174,7 +180,11 @@ AVAILABLE_CLI_OPTIONS = {
"position_stacking": Arg(
"--eps",
"--enable-position-stacking",
help="Allow buying the same pair multiple times (position stacking).",
help=(
"Allow buying the same pair multiple times (position stacking). "
"Only applicable to backtesting and hyperopt. "
"Results archived by this cannot be reproduced in dry/live trading."
),
action="store_true",
default=False,
),
@@ -378,6 +388,13 @@ AVAILABLE_CLI_OPTIONS = {
help="Print only DEX exchanges.",
action="store_true",
),
"list_exchanges_futures_options": Arg(
"--ccxt-show-futures-options-exchanges",
help=SUPPRESS,
# Show compatibility with ccxt for futures functionality
# Doesn't show in help as it's an internal/debug option.
action="store_true",
),
# List pairs / markets
"list_pairs_all": Arg(
"-a",
@@ -422,6 +439,14 @@ AVAILABLE_CLI_OPTIONS = {
),
"candle_types": Arg(
"--candle-types",
fthelp={
"freqtrade download-data": (
"Select candle type to download. "
"Defaults to the necessary candles for the selected trading mode "
"(e.g. 'spot' or ('futures', 'funding_rate' and 'mark') for futures)."
),
"_": "Select candle type to convert. Defaults to all available types.",
},
help="Select candle type to convert. Defaults to all available types.",
choices=[c.value for c in CandleType],
nargs="+",
+5 -1
View File
@@ -38,13 +38,15 @@ def start_list_exchanges(args: dict[str, Any]) -> None:
else:
available_exchanges = [e for e in available_exchanges if e["valid"] is not False]
title = f"Exchanges available for Freqtrade ({len(available_exchanges)} exchanges):"
show_fut_reasons = args.get("list_exchanges_futures_options", False)
table = Table(title=title)
table.add_column("Exchange Name")
table.add_column("Class Name")
table.add_column("Markets")
table.add_column("Reason")
if show_fut_reasons:
table.add_column("Futures Reason")
trading_mode = args.get("trading_mode", None)
dex_only = args.get("dex_exchanges", False)
@@ -78,12 +80,14 @@ def start_list_exchanges(args: dict[str, Any]) -> None:
if exchange["dex"]:
trade_modes = Text("DEX: ") + trade_modes
trade_modes.stylize("bold", 0, 3)
futcol = [] if not show_fut_reasons else [exchange["comment_futures"]]
table.add_row(
name,
classname,
trade_modes,
exchange["comment"],
*futcol,
style=None if exchange["valid"] else "red",
)
# table.add_row(*[exchange[header] for header in headers])
+1 -1
View File
@@ -30,7 +30,7 @@ def log_config_error_range(path: str, errmsg: str) -> str:
offsetlist = re.findall(r"(?<=Parse\serror\sat\soffset\s)\d+", errmsg)
if offsetlist:
offset = int(offsetlist[0])
text = Path(path).read_text()
text = Path(path).read_text(encoding="utf-8")
# Fetch an offset of 80 characters around the error line
subtext = text[offset - min(80, offset) : offset + 80]
segments = subtext.split("\n")
+2 -1
View File
@@ -38,7 +38,8 @@ def ohlcv_to_dataframe(
cols = DEFAULT_DATAFRAME_COLUMNS
df = DataFrame(ohlcv, columns=cols)
df["date"] = to_datetime(df["date"], unit="ms", utc=True)
# Floor date to seconds to account for exchange imprecisions
df["date"] = to_datetime(df["date"], unit="ms", utc=True).dt.floor("s")
# Some exchanges return int values for Volume and even for OHLC.
# Convert them since TA-LIB indicators used in the strategy assume floats
+26
View File
@@ -348,6 +348,22 @@ class DataProvider:
)
return total_candles
def __fix_funding_rate_timeframe(
self, pair: str, timeframe: str | None, candle_type: str
) -> str | None:
if (
candle_type == CandleType.FUNDING_RATE
and (ff_tf := self.get_funding_rate_timeframe()) != timeframe
):
# TODO: does this message make sense? might be pointless as funding fees don't
# have a timeframe
logger.warning(
f"{pair}, {timeframe} requested - funding rate timeframe not matching {ff_tf}."
)
return ff_tf
return timeframe
def get_pair_dataframe(
self, pair: str, timeframe: str | None = None, candle_type: str = ""
) -> DataFrame:
@@ -361,6 +377,7 @@ class DataProvider:
:return: Dataframe for this pair
:param candle_type: '', mark, index, premiumIndex, or funding_rate
"""
timeframe = self.__fix_funding_rate_timeframe(pair, timeframe, candle_type)
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
# Get live OHLCV data.
data = self.ohlcv(pair=pair, timeframe=timeframe, candle_type=candle_type)
@@ -620,3 +637,12 @@ class DataProvider:
except ExchangeError:
logger.warning(f"Could not fetch market data for {pair}. Assuming no delisting.")
return None
def get_funding_rate_timeframe(self) -> str:
"""
Get the funding rate timeframe from exchange options
:return: Timeframe string
"""
if self._exchange is None:
raise OperationalException(NO_EXCHANGE_EXCEPTION)
return self._exchange.get_option("funding_fee_timeframe")
@@ -397,6 +397,9 @@ class IDataHandler(ABC):
pairdf = self._ohlcv_load(
pair, timeframe, timerange=timerange_startup, candle_type=candle_type
)
if not pairdf.empty and candle_type == CandleType.FUNDING_RATE:
# Funding rate data is sometimes off by a couple of ms - floor to seconds
pairdf["date"] = pairdf["date"].dt.floor("s")
if self._check_empty_df(pairdf, pair, timeframe, candle_type, warn_no_data):
return pairdf
else:
@@ -508,8 +511,15 @@ class IDataHandler(ABC):
Applies to bybit and okx, where funding-fee and mark candles have different timeframes.
"""
paircombs = self.ohlcv_get_available_data(self._datadir, TradingMode.FUTURES)
ff_timeframe_s = timeframe_to_seconds(ff_timeframe)
funding_rate_combs = [
f for f in paircombs if f[2] == CandleType.FUNDING_RATE and f[1] != ff_timeframe
f
for f in paircombs
if f[2] == CandleType.FUNDING_RATE
and f[1] != ff_timeframe
# Only allow smaller timeframes to move from smaller to larger timeframes
and timeframe_to_seconds(f[1]) < ff_timeframe_s
]
if funding_rate_combs:
+48 -34
View File
@@ -308,11 +308,15 @@ 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)}.")
logger.info(
f"Downloaded data for {pair}, {timeframe}, {candle_type} with length "
f"{len(new_dataframe)}."
)
else:
new_dataframe = pair_candles
logger.info(
f"Downloaded data for {pair} with length {len(new_dataframe)}. Parallel Method."
f"Downloaded data for {pair}, {timeframe}, {candle_type} with length "
f"{len(new_dataframe)}. Parallel Method."
)
if data.empty:
@@ -349,6 +353,7 @@ def _download_pair_history(
def refresh_backtest_ohlcv_data(
exchange: Exchange,
*,
pairs: list[str],
timeframes: list[str],
datadir: Path,
@@ -359,6 +364,7 @@ def refresh_backtest_ohlcv_data(
data_format: str | None = None,
prepend: bool = False,
progress_tracker: CustomProgress | None = None,
candle_types: list[CandleType] | None = None,
no_parallel_download: bool = False,
) -> list[str]:
"""
@@ -371,10 +377,44 @@ def refresh_backtest_ohlcv_data(
pairs_not_available = []
fast_candles: dict[PairWithTimeframe, DataFrame] = {}
data_handler = get_datahandler(datadir, data_format)
candle_type = CandleType.get_default(trading_mode)
def_candletype = CandleType.SPOT if trading_mode != "futures" else CandleType.FUTURES
if trading_mode != "futures":
# Ignore user passed candle types for non-futures trading
timeframes_with_candletype = [(tf, def_candletype) for tf in timeframes]
else:
# Filter out SPOT candle type for futures trading
candle_types = (
[ct for ct in candle_types if ct != CandleType.SPOT] if candle_types else None
)
fr_candle_type = CandleType.from_string(exchange.get_option("mark_ohlcv_price"))
tf_funding_rate = exchange.get_option("funding_fee_timeframe")
tf_mark = exchange.get_option("mark_ohlcv_timeframe")
if candle_types:
for ct in candle_types:
exchange.verify_candle_type_support(ct)
timeframes_with_candletype = [
(tf, ct)
for ct in candle_types
for tf in timeframes
if ct != CandleType.FUNDING_RATE
]
else:
# Default behavior
timeframes_with_candletype = [(tf, def_candletype) for tf in timeframes]
timeframes_with_candletype.append((tf_mark, fr_candle_type))
if not candle_types or CandleType.FUNDING_RATE in candle_types:
# All exchanges need FundingRate for futures trading.
# The timeframe is aligned to the mark-price timeframe.
timeframes_with_candletype.append((tf_funding_rate, CandleType.FUNDING_RATE))
# Deduplicate list ...
timeframes_with_candletype = list(dict.fromkeys(timeframes_with_candletype))
logger.debug(
"Downloading %s.", ", ".join(f'"{tf} {ct}"' for tf, ct in timeframes_with_candletype)
)
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)
timeframe_task = progress.add_task("Timeframe", total=len(timeframes_with_candletype))
pair_task = progress.add_task("Downloading data...", total=len(pairs))
for pair in pairs:
@@ -385,7 +425,7 @@ def refresh_backtest_ohlcv_data(
pairs_not_available.append(f"{pair}: Pair not available on exchange.")
logger.info(f"Skipping pair {pair}...")
continue
for timeframe in timeframes:
for timeframe, candle_type in timeframes_with_candletype:
# Get fast candles via parallel method on first loop through per timeframe
# and candle type. Downloads all the pairs in the list and stores them.
# Also skips if only 1 pair/timeframe combination is scheduled for download.
@@ -412,7 +452,7 @@ def refresh_backtest_ohlcv_data(
# get the already downloaded pair candles if they exist
pair_candles = fast_candles.pop((pair, timeframe, candle_type), None)
progress.update(timeframe_task, description=f"Timeframe {timeframe}")
progress.update(timeframe_task, description=f"Timeframe {timeframe} {candle_type}")
logger.debug(f"Downloading pair {pair}, {candle_type}, interval {timeframe}.")
_download_pair_history(
pair=pair,
@@ -428,33 +468,6 @@ def refresh_backtest_ohlcv_data(
pair_candles=pair_candles, # optional pass of dataframe of parallel candles
)
progress.update(timeframe_task, advance=1)
if trading_mode == "futures":
# Predefined candletype (and timeframe) depending on exchange
# Downloads what is necessary to backtest based on futures data.
tf_mark = exchange.get_option("mark_ohlcv_timeframe")
tf_funding_rate = exchange.get_option("funding_fee_timeframe")
fr_candle_type = CandleType.from_string(exchange.get_option("mark_ohlcv_price"))
# All exchanges need FundingRate for futures trading.
# The timeframe is aligned to the mark-price timeframe.
combs = ((CandleType.FUNDING_RATE, tf_funding_rate), (fr_candle_type, tf_mark))
for candle_type_f, tf in combs:
logger.debug(f"Downloading pair {pair}, {candle_type_f}, interval {tf}.")
_download_pair_history(
pair=pair,
datadir=datadir,
exchange=exchange,
timerange=timerange,
data_handler=data_handler,
timeframe=str(tf),
new_pairs_days=new_pairs_days,
candle_type=candle_type_f,
erase=erase,
prepend=prepend,
)
progress.update(
timeframe_task, advance=1, description=f"Timeframe {candle_type_f}, {tf}"
)
progress.update(pair_task, advance=1)
progress.update(timeframe_task, description="Timeframe")
@@ -800,6 +813,7 @@ def download_data(
trading_mode=config.get("trading_mode", "spot"),
prepend=config.get("prepend_data", False),
progress_tracker=progress_tracker,
candle_types=config.get("candle_types"),
no_parallel_download=config.get("no_parallel_download", False),
)
finally:
+14 -4
View File
@@ -74,9 +74,10 @@ def combined_dataframes_with_rel_mean(
df_comb = combine_dataframes_by_column(data, column)
# Trim dataframes to the given timeframe
df_comb = df_comb.iloc[(df_comb.index >= fromdt) & (df_comb.index < todt)]
rel_mean = df_comb.pct_change().mean(axis=1).fillna(0).cumsum()
df_comb["count"] = df_comb.count(axis=1)
df_comb["mean"] = df_comb.mean(axis=1)
df_comb["rel_mean"] = df_comb["mean"].pct_change().fillna(0).cumsum()
df_comb["rel_mean"] = rel_mean
return df_comb[["mean", "rel_mean", "count"]]
@@ -333,7 +334,10 @@ def calculate_expectancy(trades: pd.DataFrame) -> tuple[float, float]:
def calculate_sortino(
trades: pd.DataFrame, min_date: datetime, max_date: datetime, starting_balance: float
trades: pd.DataFrame,
min_date: datetime | None,
max_date: datetime | None,
starting_balance: float,
) -> float:
"""
Calculate sortino
@@ -361,7 +365,10 @@ def calculate_sortino(
def calculate_sharpe(
trades: pd.DataFrame, min_date: datetime, max_date: datetime, starting_balance: float
trades: pd.DataFrame,
min_date: datetime | None,
max_date: datetime | None,
starting_balance: float,
) -> float:
"""
Calculate sharpe
@@ -388,7 +395,10 @@ def calculate_sharpe(
def calculate_calmar(
trades: pd.DataFrame, min_date: datetime, max_date: datetime, starting_balance: float
trades: pd.DataFrame,
min_date: datetime | None,
max_date: datetime | None,
starting_balance: float,
) -> float:
"""
Calculate calmar
+1 -1
View File
@@ -4,7 +4,7 @@ from freqtrade.exchange.common import MAP_EXCHANGE_CHILDCLASS
from freqtrade.exchange.exchange import Exchange
# isort: on
from freqtrade.exchange.binance import Binance
from freqtrade.exchange.binance import Binance, Binanceus, Binanceusdm
from freqtrade.exchange.bingx import Bingx
from freqtrade.exchange.bitget import Bitget
from freqtrade.exchange.bitmart import Bitmart
+34 -2
View File
@@ -51,6 +51,8 @@ class Binance(Exchange):
"funding_fee_candle_limit": 1000,
"stoploss_order_types": {"limit": "stop", "market": "stop_market"},
"stoploss_blocks_assets": False, # Stoploss orders do not block assets
"stoploss_query_requires_stop_flag": True,
"stoploss_algo_order_info_id": "actualOrderId",
"tickers_have_price": False,
"floor_leverage": True,
"fetch_orders_limit_minutes": 7 * 1440, # "fetch_orders" is limited to 7 days
@@ -66,6 +68,7 @@ class Binance(Exchange):
"BFUSD": "USDT",
},
}
_can_use_data_download_fast = True
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
(TradingMode.SPOT, MarginMode.NONE),
@@ -179,7 +182,8 @@ class Binance(Exchange):
return DataFrame(columns=DEFAULT_DATAFRAME_COLUMNS)
if (
self._config["exchange"].get("only_from_ccxt", False)
not self._can_use_data_download_fast
or self._config["exchange"].get("only_from_ccxt", False)
or
# only download timeframes with significant improvements,
# otherwise fall back to rest API
@@ -403,7 +407,10 @@ class Binance(Exchange):
) -> tuple[str, list[list]]:
logger.info(f"Fetching trades for {pair} from Binance, {from_id=}, {since=}, {until=}")
if not self._config["exchange"].get("only_from_ccxt", False):
if (
not self._config["exchange"].get("only_from_ccxt", False)
and self._can_use_data_download_fast
):
if from_id is None or not since:
trades = await self._api_async.fetch_trades(
pair,
@@ -544,3 +551,28 @@ class Binance(Exchange):
cache[ft_symbol] = delist_dt
return cache.get(pair, None)
class Binanceusdm(Binance):
"""Binacne USDM Exchange
Same as Binance - only futures trading is supported (via ccxt).
Not actually necessary, binance should be preferred.
"""
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
(TradingMode.FUTURES, MarginMode.CROSS),
(TradingMode.FUTURES, MarginMode.ISOLATED),
]
class Binanceus(Binance):
"""Binance US exchange class.
Minimal adjustment to disable futures trading for the US subsidiary of Binance
"""
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
(TradingMode.SPOT, MarginMode.NONE),
]
# binance vision does not have data for binanceus
_can_use_data_download_fast = False
File diff suppressed because it is too large Load Diff
+1 -4
View File
@@ -31,11 +31,11 @@ class Bitget(Exchange):
"stop_price_prop": "stopPrice",
"stoploss_blocks_assets": False, # Stoploss orders do not block assets
"stoploss_order_types": {"limit": "limit", "market": "market"},
"stoploss_query_requires_stop_flag": True,
"ohlcv_candle_limit": 200, # 200 for historical candles, 1000 for recent ones.
"order_time_in_force": ["GTC", "FOK", "IOC", "PO"],
}
_ft_has_futures: FtHas = {
"mark_ohlcv_timeframe": "4h",
"funding_fee_candle_limit": 100,
"has_delisting": True,
}
@@ -129,9 +129,6 @@ class Bitget(Exchange):
return self._fetch_stop_order_fallback(order_id, pair)
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})
@retrier
def additional_exchange_init(self) -> None:
"""
-2
View File
@@ -38,8 +38,6 @@ class Bybit(Exchange):
}
_ft_has_futures: FtHas = {
"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"},
+5 -3
View File
@@ -47,14 +47,16 @@ def check_exchange(config: Config, check_for_bad: bool = True) -> bool:
f"{', '.join(available_exchanges())}"
)
valid, reason, _ = validate_exchange(exchange)
valid, reason, _, _ = validate_exchange(exchange)
if not valid:
if check_for_bad:
raise OperationalException(
f'Exchange "{exchange}" will not work with Freqtrade. Reason: {reason}'
f'Exchange "{exchange}" will not work with Freqtrade. Reason: {reason}.'
)
else:
logger.warning(f'Exchange "{exchange}" will not work with Freqtrade. Reason: {reason}')
logger.warning(
f'Exchange "{exchange}" will not work with Freqtrade. Reason: {reason}.'
)
if MAP_EXCHANGE_CHILDCLASS.get(exchange, exchange) in SUPPORTED_EXCHANGES:
logger.info(
+35 -27
View File
@@ -36,17 +36,16 @@ API_RETRY_COUNT = 4
API_FETCH_ORDER_RETRY_COUNT = 5
BAD_EXCHANGES = {
"bitmex": "Various reasons.",
"probit": "Requires additional, regular calls to `signIn()`.",
"poloniex": "Does not provide fetch_order endpoint to fetch both open and closed orders.",
"kucoinfutures": "Unsupported futures exchange.",
"poloniexfutures": "Unsupported futures exchange.",
"binancecoinm": "Unsupported futures exchange.",
"bitmex": "Various reasons",
"probit": "Requires additional, regular calls to `signIn()`",
"poloniex": "Does not provide fetch_order endpoint to fetch both open and closed orders",
"krakenfutures": "Unsupported futures exchange",
"kucoinfutures": "Unsupported futures exchange",
"poloniexfutures": "Unsupported futures exchange",
"binancecoinm": "Unsupported futures exchange",
}
MAP_EXCHANGE_CHILDCLASS = {
"binanceus": "binance",
"binanceusdm": "binance",
"okex": "okx",
"gateio": "gate",
"huboi": "htx",
@@ -54,6 +53,8 @@ MAP_EXCHANGE_CHILDCLASS = {
SUPPORTED_EXCHANGES = [
"binance",
"binanceus",
"binanceusdm",
"bingx",
"bitmart",
"bitget",
@@ -78,28 +79,35 @@ EXCHANGE_HAS_REQUIRED: dict[str, list[str]] = {
"fetchOHLCV": [],
}
EXCHANGE_HAS_OPTIONAL = [
EXCHANGE_HAS_OPTIONAL: dict[str, list[str]] = {
# Private
"fetchMyTrades", # Trades for order - fee detection
"createLimitOrder",
"createMarketOrder", # Either OR for orders
# 'setLeverage', # Margin/Futures trading
# 'setMarginMode', # Margin/Futures trading
# 'fetchFundingHistory', # Futures trading
"fetchMyTrades": [], # Trades for order - fee detection
"createLimitOrder": [],
"createMarketOrder": [], # Either OR for orders
# Public
"fetchOrderBook",
"fetchL2OrderBook",
"fetchTicker", # OR for pricing
"fetchTickers", # For volumepairlist?
"fetchTrades", # Downloading trades data
# 'fetchFundingRateHistory', # Futures trading
# 'fetchPositions', # Futures trading
# 'fetchLeverageTiers', # Futures initialization
# 'fetchMarketLeverageTiers', # Futures initialization
# 'fetchOpenOrders', 'fetchClosedOrders', # 'fetchOrders', # Refinding balance...
"fetchOrderBook": [],
"fetchL2OrderBook": [],
"fetchTicker": [], # OR for pricing
"fetchTickers": [], # For volumepairlist?
"fetchTrades": [], # Downloading trades data
"fetchOrders": ["fetchOpenOrders", "fetchClosedOrders"], # , # Refinding balance...
# ccxt.pro
"watchOHLCV",
]
"watchOHLCV": [],
}
EXCHANGE_HAS_OPTIONAL_FUTURES: dict[str, list[str]] = {
# private
"setLeverage": [], # Margin/Futures trading
"setMarginMode": [], # Margin/Futures trading
"fetchFundingHistory": [], # Futures trading
# Public
"fetchFundingRateHistory": [], # Futures trading
"fetchPositions": [], # Futures trading
"fetchLeverageTiers": ["fetchMarketLeverageTiers"], # Futures initialization
"fetchMarkOHLCV": [],
"fetchIndexOHLCV": [], # Futures additional data
"fetchPremiumIndexOHLCV": [],
}
def calculate_backoff(retrycount, max_retries):
+212 -71
View File
@@ -73,6 +73,7 @@ from freqtrade.exchange.exchange_types import (
CcxtPosition,
FtHas,
FundingRate,
LeverageTier,
OHLCVResponse,
OrderBook,
Ticker,
@@ -104,7 +105,7 @@ from freqtrade.misc import (
deep_merge_dicts,
file_dump_json,
file_load_json,
safe_value_fallback2,
safe_value_fallback,
)
from freqtrade.util import FtTTLCache, PeriodicCache, dt_from_ts, dt_now
from freqtrade.util.datetime_helpers import dt_humanize_delta, dt_ts, format_ms_time
@@ -131,6 +132,7 @@ class Exchange:
"stop_price_prop": "stopLossPrice", # Used for stoploss_on_exchange response parsing
"stoploss_order_types": {},
"stoploss_blocks_assets": True, # By default stoploss orders block assets
"stoploss_query_requires_stop_flag": False, # Require "stop": True" to fetch stop orders
"order_time_in_force": ["GTC"],
"ohlcv_params": {},
"ohlcv_has_history": True, # Some exchanges (Kraken) don't provide history via ohlcv
@@ -152,8 +154,8 @@ class Exchange:
"l2_limit_range_required": True, # Allow Empty L2 limit (kucoin)
"l2_limit_upper": None, # Upper limit for L2 limit
"mark_ohlcv_price": "mark",
"mark_ohlcv_timeframe": "8h",
"funding_fee_timeframe": "8h",
"mark_ohlcv_timeframe": "1h",
"funding_fee_timeframe": "1h",
"ccxt_futures_name": "swap",
"needs_trading_fees": False, # use fetch_trading_fees to cache fees
"order_props_in_contracts": ["amount", "filled", "remaining"],
@@ -193,29 +195,30 @@ class Exchange:
self._exchange_ws: ExchangeWS | None = None
self._markets: dict = {}
self._trading_fees: dict[str, Any] = {}
self._leverage_tiers: dict[str, list[dict]] = {}
self._leverage_tiers: dict[str, list[LeverageTier]] = {}
# Lock event loop. This is necessary to avoid race-conditions when using force* commands
# Due to funding fee fetching.
self._loop_lock = Lock()
self.loop = self._init_async_loop()
self._config: Config = {}
self._config: Config = config
# Leverage properties
self.trading_mode: TradingMode = TradingMode(
config.get("trading_mode", self._supported_trading_mode_margin_pairs[0][0])
self._config.get("trading_mode", self._supported_trading_mode_margin_pairs[0][0])
)
self.margin_mode: MarginMode = MarginMode(
MarginMode(config.get("margin_mode"))
if config.get("margin_mode")
MarginMode(self._config.get("margin_mode"))
if self._config.get("margin_mode")
else self._supported_trading_mode_margin_pairs[0][1]
)
config["trading_mode"] = self.trading_mode
config["margin_mode"] = self.margin_mode
config["candle_type_def"] = CandleType.get_default(self.trading_mode)
self._config.update(config)
self.liquidation_buffer = config.get("liquidation_buffer", 0.05)
self._config["trading_mode"] = self.trading_mode
self._config["margin_mode"] = self.margin_mode
self._config["candle_type_def"] = CandleType.get_default(self.trading_mode)
self.liquidation_buffer = self._config.get("liquidation_buffer", 0.05)
exchange_conf: ExchangeConfig = exchange_config if exchange_config else config["exchange"]
exchange_conf: ExchangeConfig = (
exchange_config if exchange_config else self._config["exchange"]
)
# Deep merge ft_has with default ft_has options
# Must be called before ft_has is used.
@@ -246,14 +249,14 @@ class Exchange:
# Holds all open sell orders for dry_run
self._dry_run_open_orders: dict[str, Any] = {}
if config["dry_run"]:
if self._config["dry_run"]:
logger.info("Instance is running with dry_run enabled")
logger.info(f"Using CCXT {ccxt.__version__}")
# Don't remove exchange credentials for dry-run or if always_require_api_keys is set
remove_exchange_credentials(
exchange_conf,
not self._ft_has["always_require_api_keys"] and config.get("dry_run", False),
not self._ft_has["always_require_api_keys"] and self._config.get("dry_run", False),
)
self.log_responses = exchange_conf.get("log_responses", False)
@@ -294,7 +297,7 @@ class Exchange:
if validate:
# Initial markets load
self.reload_markets(True, load_leverage_tiers=False)
self.validate_config(config)
self.validate_config(self._config)
if self.trading_mode != TradingMode.SPOT and load_leverage_tiers:
self.fill_leverage_tiers()
@@ -478,7 +481,7 @@ class Exchange:
def _log_exchange_response(self, endpoint: str, response, *, add_info=None) -> None:
"""Log exchange responses"""
if self.log_responses:
add_info_str = "" if add_info is None else f" {add_info}: "
add_info_str = "" if add_info is None else f"{add_info}: "
logger.info(f"API {endpoint}: {add_info_str}{response}")
def ohlcv_candle_limit(
@@ -707,7 +710,7 @@ class Exchange:
self._markets = self._api_async.markets
self._api.set_markets_from_exchange(self._api_async)
# Assign options array, as it contains some temporary information from the exchange.
# TODO: investigate with ccxt if it's safe to remove `.options`
# ccxt does not implicitly copy options over in set_markets_from_exchange
self._api.options = self._api_async.options
if self._exchange_ws:
# Set markets to avoid reloading on websocket api
@@ -877,19 +880,20 @@ class Exchange:
# Only allow 5 calls per pair to somewhat limit the impact
raise ConfigurationError(
f"This strategy requires {startup_candles} candles to start, "
"which is more than 5x "
f"which is more than 5x ({candle_limit * 5 - 1} candles) "
f"the amount of candles {self.name} provides for {timeframe}."
)
elif required_candle_call_count > 1:
raise ConfigurationError(
f"This strategy requires {startup_candles} candles to start, which is more than "
f"This strategy requires {startup_candles} candles to start, "
f"which is more than ({candle_limit - 1} candles) "
f"the amount of candles {self.name} provides for {timeframe}."
)
if required_candle_call_count > 1:
logger.warning(
f"Using {required_candle_call_count} calls to get OHLCV. "
f"This can result in slower operations for the bot. Please check "
f"if you really need {startup_candles} candles for your strategy"
f"if you really need {startup_candles} candles for your strategy."
)
return required_candle_call_count
@@ -1119,6 +1123,7 @@ class Exchange:
leverage: float,
params: dict | None = None,
stop_loss: bool = False,
stop_price: float | None = None,
) -> CcxtOrder:
now = dt_now()
order_id = f"dry_run_{side}_{pair}_{now.timestamp()}"
@@ -1145,7 +1150,7 @@ class Exchange:
}
if stop_loss:
dry_order["info"] = {"stopPrice": dry_order["price"]}
dry_order[self._ft_has["stop_price_prop"]] = dry_order["price"]
dry_order[self._ft_has["stop_price_prop"]] = stop_price or dry_order["price"]
# Workaround to avoid filling stoploss orders immediately
dry_order["ft_order_type"] = "stoploss"
orderbook: OrderBook | None = None
@@ -1163,7 +1168,11 @@ class Exchange:
if dry_order["type"] == "market" and not dry_order.get("ft_order_type"):
# Update market order pricing
average = self.get_dry_market_fill_price(pair, side, amount, rate, orderbook)
slippage = 0.05
worst_rate = rate * ((1 + slippage) if side == "buy" else (1 - slippage))
average = self.get_dry_market_fill_price(
pair, side, amount, rate, worst_rate, orderbook
)
dry_order.update(
{
"average": average,
@@ -1203,7 +1212,13 @@ class Exchange:
return dry_order
def get_dry_market_fill_price(
self, pair: str, side: str, amount: float, rate: float, orderbook: OrderBook | None
self,
pair: str,
side: str,
amount: float,
rate: float,
worst_rate: float,
orderbook: OrderBook | None,
) -> float:
"""
Get the market order fill price based on orderbook interpolation
@@ -1212,8 +1227,6 @@ class Exchange:
if not orderbook:
orderbook = self.fetch_l2_order_book(pair, 20)
ob_type: OBLiteral = "asks" if side == "buy" else "bids"
slippage = 0.05
max_slippage_val = rate * ((1 + slippage) if side == "buy" else (1 - slippage))
remaining_amount = amount
filled_value = 0.0
@@ -1237,11 +1250,10 @@ class Exchange:
forecast_avg_filled_price = max(filled_value, 0) / amount
# Limit max. slippage to specified value
if side == "buy":
forecast_avg_filled_price = min(forecast_avg_filled_price, max_slippage_val)
forecast_avg_filled_price = min(forecast_avg_filled_price, worst_rate)
else:
forecast_avg_filled_price = max(forecast_avg_filled_price, max_slippage_val)
forecast_avg_filled_price = max(forecast_avg_filled_price, worst_rate)
return self.price_to_precision(pair, forecast_avg_filled_price)
return rate
@@ -1253,13 +1265,15 @@ class Exchange:
limit: float,
orderbook: OrderBook | None = None,
offset: float = 0.0,
is_stop: bool = False,
) -> bool:
if not self.exchange_has("fetchL2OrderBook"):
return True
# True unless checking a stoploss order
return not is_stop
if not orderbook:
orderbook = self.fetch_l2_order_book(pair, 1)
try:
if side == "buy":
if (side == "buy" and not is_stop) or (side == "sell" and is_stop):
price = orderbook["asks"][0][0]
if limit * (1 - offset) >= price:
return True
@@ -1278,6 +1292,38 @@ class Exchange:
"""
Check dry-run limit order fill and update fee (if it filled).
"""
if order["status"] != "closed" and order.get("ft_order_type") == "stoploss":
pair = order["symbol"]
if not orderbook and self.exchange_has("fetchL2OrderBook"):
orderbook = self.fetch_l2_order_book(pair, 20)
price = safe_value_fallback(order, self._ft_has["stop_price_prop"], "price")
crossed = self._dry_is_price_crossed(
pair, order["side"], price, orderbook, is_stop=True
)
if crossed:
average = self.get_dry_market_fill_price(
pair,
order["side"],
order["amount"],
price,
worst_rate=order["price"],
orderbook=orderbook,
)
order.update(
{
"status": "closed",
"filled": order["amount"],
"remaining": 0,
"average": average,
"cost": order["amount"] * average,
}
)
self.add_dry_order_fee(
pair,
order,
"taker" if immediate else "maker",
)
return order
if (
order["status"] != "closed"
and order["type"] in ["limit"]
@@ -1362,8 +1408,9 @@ class Exchange:
amount: float,
rate: float,
leverage: float,
reduceOnly: bool = False,
time_in_force: str = "GTC",
reduceOnly: bool = False,
initial_order: bool = True,
) -> CcxtOrder:
if self._config["dry_run"]:
dry_order = self.create_dry_run_order(
@@ -1380,7 +1427,7 @@ class Exchange:
rate_for_order = self.price_to_precision(pair, rate) if needs_price else None
if not reduceOnly:
self._lev_prep(pair, leverage, side)
self._lev_prep(pair, leverage, side, accept_fail=not initial_order)
order = self._api.create_order(
pair,
@@ -1517,8 +1564,9 @@ class Exchange:
ordertype,
side,
amount,
stop_price_norm,
limit_rate or stop_price_norm,
stop_loss=True,
stop_price=stop_price_norm,
leverage=leverage,
)
return dry_order
@@ -1642,7 +1690,24 @@ class Exchange:
def fetch_stoploss_order(
self, order_id: str, pair: str, params: dict | None = None
) -> CcxtOrder:
return self.fetch_order(order_id, pair, params)
if self.get_option("stoploss_query_requires_stop_flag"):
params = params or {}
params["stop"] = True
order = self.fetch_order(order_id, pair, params)
val = self.get_option("stoploss_algo_order_info_id")
if val and order.get("status", "open") == "closed":
if new_orderid := order.get("info", {}).get(val):
# Fetch real order, which was placed by the algo order.
actual_order = self.fetch_order(order_id=new_orderid, pair=pair, params=None)
actual_order["id_stop"] = actual_order["id"]
actual_order["id"] = order_id
actual_order["type"] = "stoploss"
actual_order["stopPrice"] = order.get("stopPrice")
actual_order["status_stop"] = "triggered"
return actual_order
return order
def fetch_order_or_stoploss_order(
self, order_id: str, pair: str, stoploss_order: bool = False
@@ -1696,6 +1761,9 @@ class Exchange:
raise OperationalException(e) from e
def cancel_stoploss_order(self, order_id: str, pair: str, params: dict | None = None) -> dict:
if self.get_option("stoploss_query_requires_stop_flag"):
params = params or {}
params["stop"] = True
return self.cancel_order(order_id, pair, params)
def is_cancel_order_result_suitable(self, corder) -> TypeGuard[CcxtOrder]:
@@ -1760,16 +1828,16 @@ class Exchange:
return order
@retrier
def get_balances(self) -> CcxtBalances:
def get_balances(self, params: dict | None = None) -> CcxtBalances:
try:
balances = self._api.fetch_balance()
balances = self._api.fetch_balance(params or {})
# Remove additional info from ccxt results
balances.pop("info", None)
balances.pop("free", None)
balances.pop("total", None)
balances.pop("used", None)
self._log_exchange_response("fetch_balance", balances)
self._log_exchange_response("fetch_balance", balances, add_info=params)
return balances
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
@@ -1781,7 +1849,9 @@ class Exchange:
raise OperationalException(e) from e
@retrier
def fetch_positions(self, pair: str | None = None) -> list[CcxtPosition]:
def fetch_positions(
self, pair: str | None = None, params: dict | None = None
) -> list[CcxtPosition]:
"""
Fetch positions from the exchange.
If no pair is given, all positions are returned.
@@ -1790,10 +1860,10 @@ class Exchange:
if self._config["dry_run"] or self.trading_mode != TradingMode.FUTURES:
return []
try:
symbols = []
symbols = None
if pair:
symbols.append(pair)
positions: list[CcxtPosition] = self._api.fetch_positions(symbols)
symbols = [pair]
positions: list[CcxtPosition] = self._api.fetch_positions(symbols, params=params or {})
self._log_exchange_response("fetch_positions", positions)
return positions
except ccxt.DDoSProtection as e:
@@ -1993,12 +2063,13 @@ class Exchange:
"""
return self._config["stake_currency"]
def get_conversion_rate(self, coin: str, currency: str) -> float | None:
def get_conversion_rate(self, coin: str, currency: str, *, cached=True) -> 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
:param cached: Allow cached tickers, default True
:returns: Conversion rate from coin to currency
:raises: ExchangeErrors
"""
@@ -2009,13 +2080,13 @@ class Exchange:
currency = proxy_currency
if coin == currency:
return 1.0
tickers = self.get_tickers(cached=True)
tickers = self.get_tickers(cached=cached)
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,
cached=cached,
market_type=(
TradingMode.SPOT
if self.trading_mode != TradingMode.SPOT
@@ -2024,7 +2095,7 @@ class Exchange:
)
ticker = tickers_other.get(pair, None)
if ticker:
rate: float | None = safe_value_fallback2(ticker, ticker, "last", "ask", None)
rate: float | None = safe_value_fallback(ticker, "last", "ask", None)
if rate and pair.startswith(currency) and not pair.endswith(currency):
rate = 1.0 / rate
return rate
@@ -2324,6 +2395,16 @@ class Exchange:
raise OperationalException(e) from e
def get_order_id_conditional(self, order: CcxtOrder) -> str:
"""
Return order id or id_stop (for conditional orders) based on exchange settings
:param order: ccxt order dict
:return: correct order id
"""
if self.get_option("stoploss_query_requires_stop_flag") and (
order["type"] in ("stoploss", "stop")
):
return safe_value_fallback(order, "id_stop", "id")
return order["id"]
@retrier
@@ -2483,7 +2564,13 @@ class Exchange:
)
)
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)
# funding_rates are always complete, so never need to be dropped.
drop_incomplete = (
self._ohlcv_partial_candle if candle_type != CandleType.FUNDING_RATE else False
)
return ohlcv_to_dataframe(
data, timeframe, pair, fill_missing=False, drop_incomplete=drop_incomplete
)
async def _async_get_historic_ohlcv(
self,
@@ -2646,24 +2733,25 @@ class Exchange:
input_coroutines: list[Coroutine[Any, Any, OHLCVResponse]] = []
cached_pairs = []
for pair, timeframe, candle_type in set(pair_list):
invalid_funding = (
candle_type == CandleType.FUNDING_RATE
and timeframe != self.get_option("funding_fee_timeframe")
)
if candle_type == CandleType.FUNDING_RATE and timeframe != (
ff_tf := self.get_option("funding_fee_timeframe")
):
# TODO: does this message make sense? would docs be better?
# if any, this should be cached to avoid log spam!
logger.warning(
f"Wrong funding rate timeframe {timeframe} for pair {pair}, "
f"downloading {ff_tf} instead."
)
timeframe = ff_tf
invalid_timeframe = timeframe not in self.timeframes and candle_type in (
CandleType.SPOT,
CandleType.FUTURES,
)
if invalid_timeframe or invalid_funding:
timeframes_ = (
", ".join(self.timeframes)
if candle_type != CandleType.FUNDING_RATE
else self.get_option("funding_fee_timeframe")
)
if invalid_timeframe:
logger.warning(
f"Cannot download ({pair}, {timeframe}, {candle_type}) combination as this "
f"timeframe is not available on {self.name}. Available timeframes are "
f"{timeframes_}."
f"{', '.join(self.timeframes)}."
)
continue
@@ -2700,7 +2788,11 @@ class Exchange:
has_cache = cache and (pair, timeframe, c_type) in self._klines
# in case of existing cache, fill_missing happens after concatenation
ohlcv_df = ohlcv_to_dataframe(
ticks, timeframe, pair=pair, fill_missing=not has_cache, drop_incomplete=drop_incomplete
ticks,
timeframe,
pair=pair,
fill_missing=not has_cache and c_type != CandleType.FUNDING_RATE,
drop_incomplete=drop_incomplete,
)
# keeping parsed dataframe in cache
if cache:
@@ -2711,7 +2803,7 @@ class Exchange:
concat([old, ohlcv_df], axis=0),
timeframe,
pair,
fill_missing=True,
fill_missing=c_type != CandleType.FUNDING_RATE,
drop_incomplete=False,
)
candle_limit = self.ohlcv_candle_limit(timeframe, self._config["candle_type_def"])
@@ -2804,8 +2896,11 @@ class Exchange:
}
pairs_to_download = [p for p in pairs if p not in candles]
if pairs_to_download:
candles = self.refresh_latest_ohlcv(pairs_to_download, since_ms=since_ms, cache=False)
for c, val in candles.items():
candles_new = self.refresh_latest_ohlcv(
pairs_to_download, since_ms=since_ms, cache=False
)
for c, val in candles_new.items():
candles[c] = val
self._expiring_candle_cache[(c[1], since_ms)][c] = val
return candles
@@ -2846,9 +2941,10 @@ class Exchange:
timeframe, candle_type=candle_type, since_ms=since_ms
)
if candle_type and candle_type not in (CandleType.SPOT, CandleType.FUTURES):
params.update({"price": candle_type.value})
if candle_type != CandleType.FUNDING_RATE:
if candle_type and candle_type not in (CandleType.SPOT, CandleType.FUTURES):
self.verify_candle_type_support(candle_type)
params.update({"price": str(candle_type)})
data = await self._api_async.fetch_ohlcv(
pair, timeframe=timeframe, since=since_ms, limit=candle_limit, params=params
)
@@ -2913,6 +3009,38 @@ class Exchange:
data = [[x["timestamp"], x["fundingRate"], 0, 0, 0, 0] for x in data]
return data
def check_candle_type_support(self, candle_type: CandleType) -> bool:
"""
Check that the exchange supports the given candle type.
:param candle_type: CandleType to verify
:return: True if supported, False otherwise
"""
if candle_type == CandleType.FUNDING_RATE:
if not self.exchange_has("fetchFundingRateHistory"):
return False
elif candle_type not in (CandleType.SPOT, CandleType.FUTURES):
mapping = {
CandleType.MARK: "fetchMarkOHLCV",
CandleType.INDEX: "fetchIndexOHLCV",
CandleType.PREMIUMINDEX: "fetchPremiumIndexOHLCV",
CandleType.FUNDING_RATE: "fetchFundingRateHistory",
}
_method = mapping.get(candle_type, "fetchOHLCV")
if not self.exchange_has(_method):
return False
return True
def verify_candle_type_support(self, candle_type: CandleType) -> None:
"""
Verify that the exchange supports the given candle type.
:param candle_type: CandleType to verify
:raises OperationalException: if the candle type is not supported
"""
if not self.check_candle_type_support(candle_type):
raise OperationalException(
f"Exchange {self._api.name} does not support fetching {candle_type} candles."
)
# fetch Trade data stuff
def needed_candle_for_trades_ms(self, timeframe: str, candle_type: CandleType) -> int:
@@ -3497,7 +3625,7 @@ class Exchange:
pair_tiers.append(self.parse_leverage_tier(tier))
self._leverage_tiers[pair] = pair_tiers
def parse_leverage_tier(self, tier) -> dict:
def parse_leverage_tier(self, tier) -> LeverageTier:
info = tier.get("info", {})
return {
"minNotional": tier["minNotional"],
@@ -3538,7 +3666,11 @@ class Exchange:
for tier in pair_tiers:
# Adjust notional by leverage to do a proper comparison
min_stake = tier["minNotional"] / (prior_max_lev or tier["maxLeverage"])
max_stake = tier["maxNotional"] / tier["maxLeverage"]
max_stake = (
tier["maxNotional"] / tier["maxLeverage"]
if tier["maxNotional"] is not None
else float("inf")
)
prior_max_lev = tier["maxLeverage"]
if min_stake <= stake_amount <= max_stake:
return tier["maxLeverage"]
@@ -3740,10 +3872,11 @@ class Exchange:
:param mark_rates: Dataframe containing Mark rates (Type mark_ohlcv_price)
:param futures_funding_rate: Fake funding rate to use if funding_rates are not available
"""
relevant_cols = ["date", "open_mark", "open_fund"]
if futures_funding_rate is None:
return mark_rates.merge(
funding_rates, on="date", how="inner", suffixes=["_mark", "_fund"]
)
)[relevant_cols]
else:
if len(funding_rates) == 0:
# No funding rate candles - full fillup with fallback variable
@@ -3756,15 +3889,23 @@ class Exchange:
"low": "low_mark",
"volume": "volume_mark",
}
)
)[relevant_cols]
else:
# Fill up missing funding_rate candles with fallback value
combined = mark_rates.merge(
funding_rates, on="date", how="left", suffixes=["_mark", "_fund"]
)
combined["open_fund"] = combined["open_fund"].fillna(futures_funding_rate)
return combined
# Fill only leading missing funding rates so gaps stay untouched
first_valid_idx = combined["open_fund"].first_valid_index()
if first_valid_idx is None:
combined["open_fund"] = futures_funding_rate
else:
is_leading_na = (combined.index <= first_valid_idx) & combined[
"open_fund"
].isna()
combined.loc[is_leading_na, "open_fund"] = futures_funding_rate
return combined[relevant_cols].dropna()
def calculate_funding_fees(
self,
+24
View File
@@ -19,6 +19,8 @@ class FtHas(TypedDict, total=False):
stop_price_type_value_mapping: dict
stoploss_order_types: dict[str, str]
stoploss_blocks_assets: bool
stoploss_query_requires_stop_flag: bool
stoploss_algo_order_info_id: str
# ohlcv
ohlcv_params: dict
ohlcv_candle_limit: int
@@ -113,5 +115,27 @@ class CcxtPosition(TypedDict):
CcxtOrder = dict[str, Any]
class LeverageTier(TypedDict):
"""
Represents a single leverage tier returned by the exchange.
Attributes:
minNotional: Minimum notional value (quote currency) for which this tier applies.
maxNotional: Maximum notional value (quote currency) for which this tier applies.
When ``maxNotional`` is ``None``, the tier is unbounded on the upper side,
i.e. there is no maximum notional limit for this tier
maintenanceMarginRate: Maintenance margin rate for this tier (fraction, e.g. 0.005 for 0.5%)
maxLeverage: Maximum leverage allowed for this tier
maintAmt: Optional fixed maintenance margin amount, if provided by the exchange
"""
minNotional: float
maxNotional: float | None
maintenanceMarginRate: float
maxLeverage: float
maintAmt: float | None
# pair, timeframe, candleType, OHLCV, drop last?,
OHLCVResponse = tuple[str, str, CandleType, list, bool]
+33 -14
View File
@@ -22,6 +22,7 @@ from ccxt import (
from freqtrade.exchange.common import (
BAD_EXCHANGES,
EXCHANGE_HAS_OPTIONAL,
EXCHANGE_HAS_OPTIONAL_FUTURES,
EXCHANGE_HAS_REQUIRED,
MAP_EXCHANGE_CHILDCLASS,
SUPPORTED_EXCHANGES,
@@ -53,7 +54,22 @@ def available_exchanges(ccxt_module: CcxtModuleType | None = None) -> list[str]:
return [x for x in exchanges if validate_exchange(x)[0]]
def validate_exchange(exchange: str) -> tuple[bool, str, ccxt.Exchange | None]:
def _exchange_has_helper(ex_mod: ccxt.Exchange, required: dict[str, list[str]]) -> list[str]:
"""
Checks availability of methods (or their replacement)s in ex_mod.has
:param ex_mod: ccxt Exchange module
:param required: dict of required methods, with possible replacement methods as list
:return: list of missing required methods
"""
return [
k
for k, v in required.items()
if ex_mod.has.get(k) is not True
and (len(v) == 0 or not (all(ex_mod.has.get(x) for x in v)))
]
def validate_exchange(exchange: str) -> tuple[bool, str, str, ccxt.Exchange | None]:
"""
returns: can_use, reason, exchange_object
with Reason including both missing and missing_opt
@@ -64,36 +80,38 @@ def validate_exchange(exchange: str) -> tuple[bool, str, ccxt.Exchange | None]:
ex_mod = getattr(ccxt.async_support, exchange.lower())()
if not ex_mod or not ex_mod.has:
return False, "", None
return False, "", "", None
result = True
reason = ""
missing = [
k
for k, v in EXCHANGE_HAS_REQUIRED.items()
if ex_mod.has.get(k) is not True and not (all(ex_mod.has.get(x) for x in v))
]
reasons = []
reasons_fut = ""
missing = _exchange_has_helper(ex_mod, EXCHANGE_HAS_REQUIRED)
if missing:
result = False
reason += f"missing: {', '.join(missing)}"
reasons.append(f"missing: {', '.join(missing)}")
missing_opt = [k for k in EXCHANGE_HAS_OPTIONAL if not ex_mod.has.get(k)]
missing_opt = _exchange_has_helper(ex_mod, EXCHANGE_HAS_OPTIONAL)
missing_futures = _exchange_has_helper(ex_mod, EXCHANGE_HAS_OPTIONAL_FUTURES)
if exchange.lower() in BAD_EXCHANGES:
result = False
reason = BAD_EXCHANGES.get(exchange.lower(), "")
reasons.append(BAD_EXCHANGES.get(exchange.lower(), ""))
if missing_opt:
reason += f"{'. ' if reason else ''}missing opt: {', '.join(missing_opt)}. "
reasons.append(f"missing opt: {', '.join(missing_opt)}")
return result, reason, ex_mod
if missing_futures:
reasons_fut = f"missing futures opt: {', '.join(missing_futures)}"
return result, "; ".join(reasons), reasons_fut, ex_mod
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)
valid, comment, comment_fut, 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 = {
@@ -102,6 +120,7 @@ def _build_exchange_list_entry(
"valid": valid,
"supported": mapped_exchange_name in SUPPORTED_EXCHANGES and not is_alias,
"comment": comment,
"comment_futures": comment_fut,
"dex": getattr(ex_mod, "dex", False),
"is_alias": is_alias,
"alias_for": inspect.getmro(ex_mod.__class__)[1]().id
+4 -27
View File
@@ -10,8 +10,7 @@ from freqtrade.enums import MarginMode, PriceType, TradingMode
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
from freqtrade.exchange import Exchange
from freqtrade.exchange.common import retrier
from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
from freqtrade.misc import safe_value_fallback2
from freqtrade.exchange.exchange_types import FtHas
logger = logging.getLogger(__name__)
@@ -30,6 +29,8 @@ class Gate(Exchange):
"stoploss_order_types": {"limit": "limit"},
"stop_price_param": "stopPrice",
"stop_price_prop": "stopPrice",
"stoploss_query_requires_stop_flag": True,
"stoploss_algo_order_info_id": "fired_order_id",
"l2_limit_upper": 1000,
"marketOrderRequiresPrice": True,
"trades_has_history": False, # Endpoint would support this - but ccxt doesn't.
@@ -42,6 +43,7 @@ class Gate(Exchange):
"stop_price_type_field": "price_type",
"l2_limit_upper": 300,
"stoploss_blocks_assets": False,
"stoploss_algo_order_info_id": "trade_id",
"stop_price_type_value_mapping": {
PriceType.LAST: 0,
PriceType.MARK: 1,
@@ -129,28 +131,3 @@ class Gate(Exchange):
"rate": pair_fees[takerOrMaker],
}
return trades
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: 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.
val = "trade_id" if self.trading_mode == TradingMode.FUTURES else "fired_order_id"
if new_orderid := order.get("info", {}).get(val):
order1 = self.fetch_order(order_id=new_orderid, pair=pair, params=params)
order1["id_stop"] = order1["id"]
order1["id"] = order_id
order1["type"] = "stoploss"
order1["stopPrice"] = order.get("stopPrice")
order1["status_stop"] = "triggered"
return order1
return order
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})
+103 -6
View File
@@ -7,9 +7,10 @@ from typing import Any
from freqtrade.constants import BuySell
from freqtrade.enums import MarginMode, TradingMode
from freqtrade.exceptions import ExchangeError, OperationalException
from freqtrade.enums.runmode import NON_UTIL_MODES
from freqtrade.exceptions import ConfigurationError, ExchangeError, OperationalException
from freqtrade.exchange import Exchange
from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
from freqtrade.exchange.exchange_types import CcxtBalances, CcxtOrder, CcxtPosition, FtHas
from freqtrade.util.datetime_helpers import dt_from_ts
@@ -37,9 +38,9 @@ class Hyperliquid(Exchange):
"stoploss_order_types": {"limit": "limit"},
"stoploss_blocks_assets": False,
"stop_price_prop": "stopPrice",
"funding_fee_timeframe": "1h",
"funding_fee_candle_limit": 500,
"uses_leverage_tiers": False,
"mark_ohlcv_price": "futures",
}
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
@@ -57,12 +58,108 @@ class Hyperliquid(Exchange):
config.update(super()._ccxt_config)
return config
def _get_configured_hip3_dexes(self) -> list[str]:
"""Get list of configured HIP-3 DEXes."""
return self._config.get("exchange", {}).get("hip3_dexes", [])
def validate_config(self, config: dict) -> None:
"""Validate HIP-3 configuration at bot startup."""
super().validate_config(config)
configured = self._get_configured_hip3_dexes()
if not configured or not self.markets:
return
if self.trading_mode != TradingMode.FUTURES:
if configured:
raise ConfigurationError(
"HIP-3 DEXes are only supported in FUTURES trading mode. "
"Please update your configuration!"
)
return
if configured and self.margin_mode != MarginMode.ISOLATED:
raise ConfigurationError(
"HIP-3 DEXes require 'isolated' margin mode. "
f"Current margin mode: '{self.margin_mode.value}'. "
"Please update your configuration!"
)
available = {
m.get("info", {}).get("dex")
for m in self.get_markets(
quote_currencies=[self._config["stake_currency"]],
tradable_only=True,
active_only=True,
).values()
if m.get("info", {}).get("hip3")
}
available.discard(None)
invalid = set(configured) - available
if invalid:
raise ConfigurationError(
f"Invalid HIP-3 DEXes configured: {sorted(invalid)}. "
f"Available DEXes matching your stake currency ({self._config['stake_currency']}): "
f"{sorted(available)}. "
f"Check your 'hip3_dexes' configuration!"
)
def market_is_tradable(self, market: dict[str, Any]) -> bool:
"""Check if market is tradable, including HIP-3 markets."""
parent_check = super().market_is_tradable(market)
# Exclude hip3 markets for now - which have the format XYZ:GOOGL/USDT:USDT -
# and XYZ:GOOGL as base
return parent_check and ":" not in market["base"]
market_info = market.get("info", {})
if market_info.get("hip3") and self._config["runmode"] in NON_UTIL_MODES:
configured = self._get_configured_hip3_dexes()
if not configured:
return False
market_dex = market_info.get("dex")
return parent_check and market_dex in configured
return parent_check
def get_balances(self, params: dict | None = None) -> CcxtBalances:
"""Fetch balances from default DEX and HIP-3 DEXes needed by tradable pairs.
This override is not absolutely necessary and is only there for correct used / total values
which are however not used by Freqtrade in futures mode at the moment.
"""
balances = super().get_balances()
dexes = self._get_configured_hip3_dexes()
for dex in dexes:
try:
dex_balance = super().get_balances(params={"dex": dex})
for currency, amount_info in dex_balance.items():
if currency in ["info", "free", "used", "total", "datetime", "timestamp"]:
continue
if currency not in balances:
balances[currency] = amount_info
else:
balances[currency]["free"] += amount_info["free"]
balances[currency]["used"] += amount_info["used"]
balances[currency]["total"] += amount_info["total"]
except Exception as e:
logger.error(f"Could not fetch balance for HIP-3 DEX '{dex}': {e}")
if dexes:
self._log_exchange_response("fetch_balance", balances, add_info="combined")
return balances
def fetch_positions(
self, pair: str | None = None, params: dict | None = None
) -> list[CcxtPosition]:
"""Fetch positions from default DEX and HIP-3 DEXes needed by tradable pairs."""
positions = super().fetch_positions(pair)
dexes = self._get_configured_hip3_dexes()
for dex in dexes:
try:
positions.extend(super().fetch_positions(pair, params={"dex": dex}))
except Exception as e:
logger.error(f"Could not fetch positions from HIP-3 DEX '{dex}': {e}")
if dexes:
self._log_exchange_response("fetch_positions", positions, add_info="combined")
return positions
def get_max_leverage(self, pair: str, stake_amount: float | None) -> float:
# There are no leverage tiers
+1 -2
View File
@@ -35,7 +35,6 @@ class Kraken(Exchange):
"trades_pagination_arg": "since",
"trades_pagination_overlap": False,
"trades_has_history": True,
"mark_ohlcv_timeframe": "4h",
}
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
@@ -71,7 +70,7 @@ class Kraken(Exchange):
return consolidated
@retrier
def get_balances(self) -> CcxtBalances:
def get_balances(self, params: dict | None = None) -> CcxtBalances:
if self._config["dry_run"]:
return {}
+3 -1
View File
@@ -44,8 +44,9 @@ class Kucoin(Exchange):
amount: float,
rate: float,
leverage: float,
reduceOnly: bool = False,
time_in_force: str = "GTC",
reduceOnly: bool = False,
initial_order: bool = True,
) -> CcxtOrder:
res = super().create_order(
pair=pair,
@@ -56,6 +57,7 @@ class Kucoin(Exchange):
leverage=leverage,
reduceOnly=reduceOnly,
time_in_force=time_in_force,
initial_order=initial_order,
)
# Kucoin returns only the order-id.
# ccxt returns status = 'closed' at the moment - which is information ccxt invented.
+7 -21
View File
@@ -14,7 +14,6 @@ from freqtrade.exceptions import (
from freqtrade.exchange import Exchange
from freqtrade.exchange.common import API_RETRY_COUNT, retrier
from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
from freqtrade.misc import safe_value_fallback2
from freqtrade.util import dt_now, dt_ts
@@ -29,10 +28,9 @@ class Okx(Exchange):
_ft_has: FtHas = {
"ohlcv_candle_limit": 100, # Warning, special case with data prior to X months
"mark_ohlcv_timeframe": "4h",
"funding_fee_timeframe": "8h",
"stoploss_order_types": {"limit": "limit"},
"stoploss_on_exchange": True,
"stoploss_query_requires_stop_flag": True,
"trades_has_history": False, # Endpoint doesn't have a "since" parameter
"ws_enabled": True,
}
@@ -41,8 +39,8 @@ class Okx(Exchange):
"stop_price_type_field": "slTriggerPxType",
"stop_price_type_value_mapping": {
PriceType.LAST: "last",
PriceType.MARK: "index",
PriceType.INDEX: "mark",
PriceType.MARK: "mark",
PriceType.INDEX: "index",
},
"stoploss_blocks_assets": False,
"ws_enabled": True,
@@ -184,7 +182,10 @@ class Okx(Exchange):
return float("inf")
pair_tiers = self._leverage_tiers[pair]
return pair_tiers[-1]["maxNotional"] / leverage
last_max_notional = pair_tiers[-1]["maxNotional"]
if last_max_notional is None:
return float("inf")
return last_max_notional / leverage
def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> dict:
params = super()._get_stop_params(side, ordertype, stop_price)
@@ -260,21 +261,6 @@ 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: 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: dict | None = None) -> dict:
params1 = {"stop": True}
# 'ordType': 'conditional'
#
return self.cancel_order(
order_id=order_id,
pair=pair,
params=params1,
)
def _fetch_orders_emulate(self, pair: str, since_ms: int) -> list[CcxtOrder]:
orders = []
@@ -18,7 +18,7 @@ class BaseClassifierModel(IFreqaiModel):
"""
Base class for regression type models (e.g. Catboost, LightGBM, XGboost etc.).
User *must* inherit from this class and set fit(). See example scripts
such as prediction_models/CatboostClassifier.py for guidance.
such as prediction_models/XGBoostClassifier.py for guidance.
"""
def train(self, unfiltered_df: DataFrame, pair: str, dk: FreqaiDataKitchen, **kwargs) -> Any:
@@ -18,7 +18,7 @@ class BaseRegressionModel(IFreqaiModel):
"""
Base class for regression type models (e.g. Catboost, LightGBM, XGboost etc.).
User *must* inherit from this class and set fit(). See example scripts
such as prediction_models/CatboostRegressor.py for guidance.
such as prediction_models/XGBoostRegressor.py for guidance.
"""
def train(self, unfiltered_df: DataFrame, pair: str, dk: FreqaiDataKitchen, **kwargs) -> Any:
+6 -2
View File
@@ -227,6 +227,10 @@ class IFreqaiModel(ABC):
"""
while not self._stop_event.is_set():
time.sleep(1)
if not self.train_queue:
continue
pair = self.train_queue[0]
# ensure pair is available in dp
@@ -948,7 +952,7 @@ class IFreqaiModel(ABC):
return dk
# Following methods which are overridden by user made prediction models.
# See freqai/prediction_models/CatboostPredictionModel.py for an example.
# See freqai/prediction_models/XGBoostRegressor.py for an example.
@abstractmethod
def train(self, unfiltered_df: DataFrame, pair: str, dk: FreqaiDataKitchen, **kwargs) -> Any:
@@ -964,7 +968,7 @@ class IFreqaiModel(ABC):
def fit(self, data_dictionary: dict[str, Any], dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
Most regressors use the same function names and arguments e.g. user
can drop in LGBMRegressor in place of CatBoostRegressor and all data
can drop in LGBMRegressor in place of XGBoostRegressor and all data
management will be properly handled by Freqai.
:param data_dictionary: Dict = the dictionary constructed by DataHandler to hold
all the training and test data/labels.
@@ -1,61 +0,0 @@
import logging
from pathlib import Path
from typing import Any
from catboost import CatBoostClassifier, Pool
from freqtrade.freqai.base_models.BaseClassifierModel import BaseClassifierModel
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
logger = logging.getLogger(__name__)
class CatboostClassifier(BaseClassifierModel):
"""
User created prediction model. The class inherits IFreqaiModel, which
means it has full access to all Frequency AI functionality. Typically,
users would use this to override the common `fit()`, `train()`, or
`predict()` methods to add their custom data handling tools or change
various aspects of the training that cannot be configured via the
top level config.json file.
"""
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,
labels, weights
:param dk: The datakitchen object for the current coin/model
"""
train_data = Pool(
data=data_dictionary["train_features"],
label=data_dictionary["train_labels"],
weight=data_dictionary["train_weights"],
)
if self.freqai_info.get("data_split_parameters", {}).get("test_size", 0.1) == 0:
test_data = None
else:
test_data = Pool(
data=data_dictionary["test_features"],
label=data_dictionary["test_labels"],
weight=data_dictionary["test_weights"],
)
cbr = CatBoostClassifier(
allow_writing_files=True,
loss_function="MultiClass",
train_dir=Path(dk.data_path),
**self.model_training_parameters,
)
init_model = self.get_init_model(dk.pair)
cbr.fit(
X=train_data,
eval_set=test_data,
init_model=init_model,
)
return cbr
@@ -1,79 +0,0 @@
import logging
from pathlib import Path
from typing import Any
from catboost import CatBoostClassifier, Pool
from freqtrade.freqai.base_models.BaseClassifierModel import BaseClassifierModel
from freqtrade.freqai.base_models.FreqaiMultiOutputClassifier import FreqaiMultiOutputClassifier
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
logger = logging.getLogger(__name__)
class CatboostClassifierMultiTarget(BaseClassifierModel):
"""
User created prediction model. The class inherits IFreqaiModel, which
means it has full access to all Frequency AI functionality. Typically,
users would use this to override the common `fit()`, `train()`, or
`predict()` methods to add their custom data handling tools or change
various aspects of the training that cannot be configured via the
top level config.json file.
"""
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,
labels, weights
:param dk: The datakitchen object for the current coin/model
"""
cbc = CatBoostClassifier(
allow_writing_files=True,
loss_function="MultiClass",
train_dir=Path(dk.data_path),
**self.model_training_parameters,
)
X = data_dictionary["train_features"]
y = data_dictionary["train_labels"]
sample_weight = data_dictionary["train_weights"]
eval_sets = [None] * y.shape[1]
if self.freqai_info.get("data_split_parameters", {}).get("test_size", 0.1) != 0:
eval_sets = [None] * data_dictionary["test_labels"].shape[1]
for i in range(data_dictionary["test_labels"].shape[1]):
eval_sets[i] = Pool(
data=data_dictionary["test_features"],
label=data_dictionary["test_labels"].iloc[:, i],
weight=data_dictionary["test_weights"],
)
init_model = self.get_init_model(dk.pair)
if init_model:
init_models = init_model.estimators_
else:
init_models = [None] * y.shape[1]
fit_params = []
for i in range(len(eval_sets)):
fit_params.append(
{
"eval_set": eval_sets[i],
"init_model": init_models[i],
}
)
model = FreqaiMultiOutputClassifier(estimator=cbc)
thread_training = self.freqai_info.get("multitarget_parallel_training", False)
if thread_training:
model.n_jobs = y.shape[1]
model.fit(X=X, y=y, sample_weight=sample_weight, fit_params=fit_params)
return model
@@ -1,60 +0,0 @@
import logging
from pathlib import Path
from typing import Any
from catboost import CatBoostRegressor, Pool
from freqtrade.freqai.base_models.BaseRegressionModel import BaseRegressionModel
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
logger = logging.getLogger(__name__)
class CatboostRegressor(BaseRegressionModel):
"""
User created prediction model. The class inherits IFreqaiModel, which
means it has full access to all Frequency AI functionality. Typically,
users would use this to override the common `fit()`, `train()`, or
`predict()` methods to add their custom data handling tools or change
various aspects of the training that cannot be configured via the
top level config.json file.
"""
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,
labels, weights
:param dk: The datakitchen object for the current coin/model
"""
train_data = Pool(
data=data_dictionary["train_features"],
label=data_dictionary["train_labels"],
weight=data_dictionary["train_weights"],
)
if self.freqai_info.get("data_split_parameters", {}).get("test_size", 0.1) == 0:
test_data = None
else:
test_data = Pool(
data=data_dictionary["test_features"],
label=data_dictionary["test_labels"],
weight=data_dictionary["test_weights"],
)
init_model = self.get_init_model(dk.pair)
model = CatBoostRegressor(
allow_writing_files=True,
train_dir=Path(dk.data_path),
**self.model_training_parameters,
)
model.fit(
X=train_data,
eval_set=test_data,
init_model=init_model,
)
return model
@@ -1,78 +0,0 @@
import logging
from pathlib import Path
from typing import Any
from catboost import CatBoostRegressor, Pool
from freqtrade.freqai.base_models.BaseRegressionModel import BaseRegressionModel
from freqtrade.freqai.base_models.FreqaiMultiOutputRegressor import FreqaiMultiOutputRegressor
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
logger = logging.getLogger(__name__)
class CatboostRegressorMultiTarget(BaseRegressionModel):
"""
User created prediction model. The class inherits IFreqaiModel, which
means it has full access to all Frequency AI functionality. Typically,
users would use this to override the common `fit()`, `train()`, or
`predict()` methods to add their custom data handling tools or change
various aspects of the training that cannot be configured via the
top level config.json file.
"""
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,
labels, weights
:param dk: The datakitchen object for the current coin/model
"""
cbr = CatBoostRegressor(
allow_writing_files=True,
train_dir=Path(dk.data_path),
**self.model_training_parameters,
)
X = data_dictionary["train_features"]
y = data_dictionary["train_labels"]
sample_weight = data_dictionary["train_weights"]
eval_sets = [None] * y.shape[1]
if self.freqai_info.get("data_split_parameters", {}).get("test_size", 0.1) != 0:
eval_sets = [None] * data_dictionary["test_labels"].shape[1]
for i in range(data_dictionary["test_labels"].shape[1]):
eval_sets[i] = Pool(
data=data_dictionary["test_features"],
label=data_dictionary["test_labels"].iloc[:, i],
weight=data_dictionary["test_weights"],
)
init_model = self.get_init_model(dk.pair)
if init_model:
init_models = init_model.estimators_
else:
init_models = [None] * y.shape[1]
fit_params = []
for i in range(len(eval_sets)):
fit_params.append(
{
"eval_set": eval_sets[i],
"init_model": init_models[i],
}
)
model = FreqaiMultiOutputRegressor(estimator=cbr)
thread_training = self.freqai_info.get("multitarget_parallel_training", False)
if thread_training:
model.n_jobs = y.shape[1]
model.fit(X=X, y=y, sample_weight=sample_weight, fit_params=fit_params)
return model
+3 -1
View File
@@ -97,7 +97,7 @@ def plot_feature_importance(
"""
Plot Best and worst features by importance for a single sub-train.
:param model: Any = A model which was `fit` using a common library
such as catboost or lightgbm
such as XGBoost or lightgbm
:param pair: str = pair e.g. BTC/USD
:param dk: FreqaiDataKitchen = non-persistent data container for current coin/loop
:param count_max: int = the amount of features to be loaded per column
@@ -115,6 +115,8 @@ def plot_feature_importance(
for label in models:
mdl = models[label]
if "catboost.core" in str(mdl.__class__):
# CatBoost is no longer actively supported since 2025.12
# However users can still use it in their custom models
feature_importance = mdl.get_feature_importance()
elif "lightgbm.sklearn" in str(mdl.__class__):
feature_importance = mdl.feature_importances_
+41 -23
View File
@@ -937,6 +937,7 @@ class FreqtradeBot(LoggingMixin):
reduceOnly=False,
time_in_force=time_in_force,
leverage=leverage,
initial_order=trade is None,
)
order_obj = Order.parse_from_ccxt_object(order, pair, side, amount, enter_limit_requested)
order_obj.ft_order_tag = enter_tag
@@ -1063,7 +1064,16 @@ class FreqtradeBot(LoggingMixin):
return True
def cancel_stoploss_on_exchange(self, trade: Trade) -> Trade:
def cancel_stoploss_on_exchange(self, trade: Trade, allow_nonblocking: bool = False) -> Trade:
"""
Cancels on exchange stoploss orders for the given trade.
:param trade: Trade for which to cancel stoploss order
:param allow_nonblocking: If True, will skip cancelling stoploss on exchange
if the exchange supports blocking stoploss orders.
"""
if allow_nonblocking and not self.exchange.get_option("stoploss_blocks_assets", True):
logger.info(f"Skipping cancelling stoploss on exchange for {trade}.")
return trade
# First cancelling stoploss on exchange ...
for oslo in trade.open_sl_orders:
try:
@@ -2002,14 +2012,14 @@ class FreqtradeBot(LoggingMixin):
def _safe_exit_amount(self, trade: Trade, pair: str, amount: float) -> float:
"""
Get sellable amount.
Get exitable amount.
Should be trade.amount - but will fall back to the available amount if necessary.
This should cover cases where get_real_amount() was not able to update the amount
for whatever reason.
:param trade: Trade we're working with
:param pair: Pair we're trying to sell
:param pair: Pair we're trying to exit
:param amount: amount we expect to be available
:return: amount to sell
:return: amount to exit
:raise: DependencyException: if available balance is not within 2% of the available amount.
"""
# Update wallets to ensure amounts tied up in a stoploss is now free!
@@ -2045,11 +2055,12 @@ class FreqtradeBot(LoggingMixin):
exit_tag: str | None = None,
ordertype: str | None = None,
sub_trade_amt: float | None = None,
skip_custom_exit_price: bool = False,
) -> bool:
"""
Executes a trade exit for the given trade and limit
:param trade: Trade instance
:param limit: limit rate for the sell order
:param limit: limit rate for the exit order
:param exit_check: CheckTuple with signal and reason
:return: True if it succeeds False
"""
@@ -2071,29 +2082,33 @@ class FreqtradeBot(LoggingMixin):
):
exit_type = "stoploss"
order_type = (
(ordertype or self.strategy.order_types[exit_type])
if exit_check.exit_type != ExitType.EMERGENCY_EXIT
else self.strategy.order_types.get("emergency_exit", "market")
)
# set custom_exit_price if available
proposed_limit_rate = limit
custom_exit_price = limit
current_profit = trade.calc_profit_ratio(limit)
custom_exit_price = strategy_safe_wrapper(
self.strategy.custom_exit_price, default_retval=proposed_limit_rate
)(
pair=trade.pair,
trade=trade,
current_time=datetime.now(UTC),
proposed_rate=proposed_limit_rate,
current_profit=current_profit,
exit_tag=exit_reason,
)
if order_type == "limit" and not skip_custom_exit_price:
custom_exit_price = strategy_safe_wrapper(
self.strategy.custom_exit_price, default_retval=proposed_limit_rate
)(
pair=trade.pair,
trade=trade,
current_time=datetime.now(UTC),
proposed_rate=proposed_limit_rate,
current_profit=current_profit,
exit_tag=exit_reason,
)
limit = self.get_valid_price(custom_exit_price, proposed_limit_rate)
# First cancelling stoploss on exchange ...
trade = self.cancel_stoploss_on_exchange(trade)
order_type = ordertype or self.strategy.order_types[exit_type]
if exit_check.exit_type == ExitType.EMERGENCY_EXIT:
# Emergency sells (default to market!)
order_type = self.strategy.order_types.get("emergency_exit", "market")
trade = self.cancel_stoploss_on_exchange(trade, allow_nonblocking=True)
amount = self._safe_exit_amount(trade, trade.pair, sub_trade_amt or trade.amount)
time_in_force = self.strategy.order_time_in_force["exit"]
@@ -2121,7 +2136,7 @@ class FreqtradeBot(LoggingMixin):
return False
try:
# Execute sell and update trade record
# Execute exit and update trade record
order = self.exchange.create_order(
pair=trade.pair,
ordertype=order_type,
@@ -2131,6 +2146,7 @@ class FreqtradeBot(LoggingMixin):
leverage=trade.leverage,
reduceOnly=self.trading_mode == TradingMode.FUTURES,
time_in_force=time_in_force,
initial_order=False,
)
except InsufficientFundsError as e:
logger.warning(f"Unable to place order {e}.")
@@ -2148,7 +2164,7 @@ class FreqtradeBot(LoggingMixin):
trade.exit_reason = exit_reason
self._notify_exit(trade, order_type, sub_trade=bool(sub_trade_amt), order=order_obj)
# In case of market sell orders the order can be closed immediately
# In case of market exit orders the order can be closed immediately
if order.get("status", "unknown") in ("closed", "expired"):
self.update_trade_state(trade, order_obj.order_id, order)
Trade.commit()
@@ -2378,6 +2394,8 @@ class FreqtradeBot(LoggingMixin):
self.strategy.ft_stoploss_adjust(
current_rate, trade, datetime.now(UTC), profit, 0, after_fill=True
)
if not trade.is_open:
self.cancel_stoploss_on_exchange(trade)
# Updating wallets when order is closed
self.wallets.update()
return trade
+18 -7
View File
@@ -6,25 +6,36 @@ from typing_extensions import TypedDict
class _BaseAnnotationType(TypedDict, total=False):
start: str | datetime
end: str | datetime
y_start: float
y_end: float
color: str
label: str
z_level: int
class AreaAnnotationType(_BaseAnnotationType, total=False):
class _Base2DAnnotationType(_BaseAnnotationType, total=False):
start: str | datetime
end: str | datetime
y_start: float
y_end: float
class AreaAnnotationType(_Base2DAnnotationType, total=False):
type: Required[Literal["area"]]
class LineAnnotationType(_BaseAnnotationType, total=False):
class LineAnnotationType(_Base2DAnnotationType, total=False):
type: Required[Literal["line"]]
width: int
line_style: Literal["solid", "dashed", "dotted"]
AnnotationType = AreaAnnotationType | LineAnnotationType
class PointAnnotationType(_BaseAnnotationType, total=False):
type: Required[Literal["point"]]
x: str | datetime
y: float
size: int
shape: Literal["circle", "rect", "roundRect", "triangle", "pin", "arrow", "none"]
AnnotationType = AreaAnnotationType | LineAnnotationType | PointAnnotationType
AnnotationTypeTA: TypeAdapter[AnnotationType] = TypeAdapter(AnnotationType)
@@ -14,6 +14,7 @@ class ValidExchangesType(TypedDict):
valid: bool
supported: bool
comment: str
comment_futures: str
dex: bool
is_alias: bool
alias_for: str | None
+4 -6
View File
@@ -374,6 +374,7 @@ class Backtesting:
timerange=self.timerange,
startup_candles=0,
fail_without_data=True,
fill_up_missing=False,
data_format=self.config["dataformat_ohlcv"],
candle_type=CandleType.FUNDING_RATE,
)
@@ -438,6 +439,8 @@ class Backtesting:
PairLocks.reset_locks()
Trade.reset_trades()
CustomDataWrapper.reset_custom_data()
# Ensure logging is disabled in other processes during hyperopt
LoggingMixin.show_output = False
self.rejected_trades = 0
self.timedout_entry_orders = 0
self.timedout_exit_orders = 0
@@ -602,8 +605,6 @@ class Backtesting:
trade_dur: int,
) -> float:
is_short = trade.is_short or False
leverage = trade.leverage or 1.0
side_1 = -1 if is_short else 1
roi_entry, roi = self.strategy.min_roi_reached_entry(
trade, # type: ignore[arg-type]
trade_dur,
@@ -616,10 +617,7 @@ class Backtesting:
# - we'll use open instead of close
return row[OPEN_IDX]
# - (Expected abs profit - open_rate - open_fee) / (fee_close -1)
roi_rate = trade.open_rate * roi / leverage
open_fee_rate = side_1 * trade.open_rate * (1 + side_1 * trade.fee_open)
close_rate = -(roi_rate + open_fee_rate) / ((trade.fee_close or 0.0) - side_1 * 1)
close_rate = trade.calc_close_rate_for_roi(roi)
if is_short:
is_new_roi = row[OPEN_IDX] < close_rate
else:
@@ -21,6 +21,11 @@ def logging_mp_setup(log_queue: Queue, verbosity: int):
root = logging.getLogger()
root.setLevel(verbosity)
root.addHandler(h)
# Disable freqtrade logging outside of the main process
# This only leaves logging from the strategy (unless it's prefixed with "freqtrade.")
# and eventually from other libraries.
if verbosity > logging.DEBUG:
logging.getLogger("freqtrade").setLevel(logging.WARNING)
def logging_mp_handle(q: Queue):
@@ -6,7 +6,7 @@ and will be sent to the hyperopt worker processes.
import logging
import sys
import warnings
from datetime import UTC, datetime
from datetime import datetime
from multiprocessing import Manager
from pathlib import Path
from typing import Any
@@ -42,6 +42,7 @@ from freqtrade.optimize.space import (
ft_IntDistribution,
)
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver
from freqtrade.util import dt_now
from freqtrade.util.dry_run_wallet import get_dry_run_wallet
@@ -269,7 +270,7 @@ class HyperOptimizer:
Keep this function as optimized as possible!
"""
HyperoptStateContainer.set_state(HyperoptState.OPTIMIZE)
backtest_start_time = datetime.now(UTC)
backtest_start_time = dt_now()
for attr_name, attr in self.backtesting.strategy.enumerate_parameters():
if attr.in_space and attr.optimize:
@@ -320,7 +321,7 @@ class HyperOptimizer:
bt_results = self.backtesting.backtest(
processed=processed, start_date=self.min_date, end_date=self.max_date
)
backtest_end_time = datetime.now(UTC)
backtest_end_time = dt_now()
bt_results.update(
{
"backtest_start_time": int(backtest_start_time.timestamp()),
+34 -4
View File
@@ -48,7 +48,7 @@ from freqtrade.leverage import interest
from freqtrade.misc import safe_value_fallback
from freqtrade.persistence.base import ModelBase, SessionType
from freqtrade.persistence.custom_data import CustomDataWrapper, _CustomData
from freqtrade.util import FtPrecise, dt_from_ts, dt_now, dt_ts, dt_ts_none
from freqtrade.util import FtPrecise, dt_from_ts, dt_now, dt_ts, dt_ts_none, round_value
logger = logging.getLogger(__name__)
@@ -654,9 +654,10 @@ class LocalTrade:
)
return (
f"Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, "
f"is_short={self.is_short or False}, leverage={self.leverage or 1.0}, "
f"open_rate={self.open_rate:.8f}, open_since={open_since})"
f"Trade(id={self.id}, pair={self.pair}, amount={round_value(self.amount, 8)}, "
f"is_short={self.is_short or False}, "
f"leverage={round_value(self.leverage or 1.0, 1)}, "
f"open_rate={round_value(self.open_rate, 8)}, open_since={open_since})"
)
def to_json(self, minified: bool = False) -> dict[str, Any]:
@@ -1207,6 +1208,35 @@ class LocalTrade:
return float(f"{profit_ratio:.8f}")
def calc_close_rate_for_roi(self, target_roi: float) -> float:
"""
Calculate the required close price to reach a target ROI.
Must match the logic used in `calc_profit_ratio()`.
:param target_roi: The desired return on investment (as a decimal, e.g., 0.05 for 5%)
:return: Close price (rate) required to achieve the target ROI
"""
leverage = float(self.leverage or 1.0)
deleveraged_roi = float(target_roi) / leverage
open_value = self._calc_open_trade_value(self.amount, self.open_rate)
# The ROI formula uses close_value(rate), which depends on trading mode:
# - SPOT: linear in rate, adjusted by close fee
# - MARGIN: same, but long subtracts interest, short increases amount
# - FUTURES: adds/subtracts funding to/from close value
# All cases are affine in rate:
# close_value(rate) = a * rate + b
# We extract a and b by probing close_value at rate = 0 and 1.
value_at_0 = self.calc_close_trade_value(0.0)
value_at_1 = self.calc_close_trade_value(1.0)
alpha = value_at_1 - value_at_0
beta = value_at_0
s = -1.0 if self.is_short else 1.0
adj = 1.0 + (deleveraged_roi / s)
return (adj * open_value - beta) / alpha
def recalc_trade_from_orders(self, *, is_closing: bool = False):
ZERO = FtPrecise(0.0)
current_amount = FtPrecise(0.0)
@@ -53,7 +53,7 @@ class PercentChangePairList(IPairList):
self._sort_direction: str | None = self._pairlistconfig.get("sort_direction", "desc")
self._def_candletype = self._config["candle_type_def"]
if (self._lookback_days > 0) & (self._lookback_period > 0):
if (self._lookback_days > 0) and (self._lookback_period > 0):
raise OperationalException(
"Ambiguous configuration: lookback_days and lookback_period both set in pairlist "
"config. Please set lookback_days only or lookback_period and lookback_timeframe "
@@ -70,7 +70,7 @@ class PercentChangePairList(IPairList):
_tf_in_sec = self._tf_in_min * 60
# whether to use range lookback or not
self._use_range = (self._tf_in_min > 0) & (self._lookback_period > 0)
self._use_range = (self._tf_in_min > 0) and (self._lookback_period > 0)
if self._use_range & (self._refresh_period < _tf_in_sec):
raise OperationalException(
@@ -84,9 +84,9 @@ class PercentChangePairList(IPairList):
and self._exchange.get_option("tickers_have_percentage")
):
raise OperationalException(
"Exchange does not support dynamic whitelist in this configuration. "
"Please edit your config and either remove PercentChangePairList, "
"or switch to using candles. and restart the bot."
f"Exchange {self._exchange.name} does not support dynamic whitelist in this "
"configuration. Please edit your config and either remove PercentChangePairList, "
"or switch to using candles and restart the bot."
)
candle_limit = self._exchange.ohlcv_candle_limit(
+17 -15
View File
@@ -8,7 +8,7 @@ import logging
from datetime import timedelta
from typing import Any, Literal
from freqtrade.constants import ListPairsWithTimeframes
from freqtrade.constants import DOCS_LINK, ListPairsWithTimeframes
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date
from freqtrade.exchange.exchange_types import Tickers
@@ -35,19 +35,19 @@ class VolumePairList(IPairList):
'for "pairlist.config.number_assets"'
)
self._stake_currency = self._config["stake_currency"]
self._number_pairs = self._pairlistconfig["number_assets"]
self._stake_currency: str = self._config["stake_currency"]
self._number_pairs: int = self._pairlistconfig["number_assets"]
self._sort_key: Literal["quoteVolume"] = self._pairlistconfig.get("sort_key", "quoteVolume")
self._min_value = self._pairlistconfig.get("min_value", 0)
self._max_value = self._pairlistconfig.get("max_value", None)
self._min_value: float | None = self._pairlistconfig.get("min_value", 0)
self._max_value: float | None = self._pairlistconfig.get("max_value", None)
self._refresh_period = self._pairlistconfig.get("refresh_period", 1800)
self._pair_cache: FtTTLCache = FtTTLCache(maxsize=1, ttl=self._refresh_period)
self._lookback_days = self._pairlistconfig.get("lookback_days", 0)
self._lookback_timeframe = self._pairlistconfig.get("lookback_timeframe", "1d")
self._lookback_period = self._pairlistconfig.get("lookback_period", 0)
self._lookback_days: int = self._pairlistconfig.get("lookback_days", 0)
self._lookback_timeframe: str = self._pairlistconfig.get("lookback_timeframe", "1d")
self._lookback_period: int = self._pairlistconfig.get("lookback_period", 0)
self._def_candletype = self._config["candle_type_def"]
if (self._lookback_days > 0) & (self._lookback_period > 0):
if (self._lookback_days > 0) and (self._lookback_period > 0):
raise OperationalException(
"Ambiguous configuration: lookback_days and lookback_period both set in pairlist "
"config. Please set lookback_days only or lookback_period and lookback_timeframe "
@@ -64,9 +64,9 @@ class VolumePairList(IPairList):
_tf_in_sec = self._tf_in_min * 60
# whether to use range lookback or not
self._use_range = (self._tf_in_min > 0) & (self._lookback_period > 0)
self._use_range = (self._tf_in_min > 0) and (self._lookback_period > 0)
if self._use_range & (self._refresh_period < _tf_in_sec):
if self._use_range and (self._refresh_period < _tf_in_sec):
raise OperationalException(
f"Refresh period of {self._refresh_period} seconds is smaller than one "
f"timeframe of {self._lookback_timeframe}. Please adjust refresh_period "
@@ -78,9 +78,11 @@ class VolumePairList(IPairList):
and self._exchange.get_option("tickers_have_quoteVolume")
):
raise OperationalException(
"Exchange does not support dynamic whitelist in this configuration. "
"Please edit your config and either remove Volumepairlist, "
"or switch to using candles. and restart the bot."
f"Exchange {self._exchange.name} does not support dynamic whitelist in this "
"configuration. Please edit your config and either remove Volumepairlist, "
"or switch to using candles and restart the bot. "
f"You can find more information about this in the documentation under "
f"{DOCS_LINK}/plugins/#volumepairlist-advanced-mode ."
)
if not self._validate_keys(self._sort_key):
@@ -297,7 +299,7 @@ class VolumePairList(IPairList):
# Tickers mode - filter based on incoming pairlist.
filtered_tickers = [v for k, v in tickers.items() if k in pairlist]
if self._min_value > 0:
if self._min_value and self._min_value > 0:
filtered_tickers = [v for v in filtered_tickers if v[self._sort_key] > self._min_value]
if self._max_value is not None:
filtered_tickers = [v for v in filtered_tickers if v[self._sort_key] < self._max_value]
+2 -2
View File
@@ -51,8 +51,8 @@ class PairListManager(LoggingMixin):
invalid = ". ".join([p.name for p in self._pairlist_handlers if p.needstickers])
raise OperationalException(
"Exchange does not support fetchTickers, therefore the following pairlists "
"cannot be used. Please edit your config and restart the bot.\n"
f"Exchange {self._exchange.name} does not support fetchTickers, therefore the "
"following pairlists cannot be used. Please edit your config and restart the bot.\n"
f"{invalid}."
)
+2 -2
View File
@@ -139,7 +139,7 @@ class IResolver:
:return: object class
"""
logger.debug(f"Searching for {cls.object_type.__name__} {object_name} in '{directory}'")
for entry in directory.iterdir():
for entry in sorted(directory.iterdir()):
# Only consider python files
if entry.suffix != ".py":
logger.debug("Ignoring %s", entry)
@@ -148,7 +148,7 @@ class IResolver:
logger.debug("Ignoring broken symlink %s", entry)
continue
module_path = entry.resolve()
if entry.read_text().find(f"class {object_name}(") == -1:
if entry.read_text(encoding="utf-8").find(f"class {object_name}(") == -1:
logger.debug(f"Skipping {module_path} as it does not contain class {object_name}.")
continue
@@ -13,7 +13,7 @@ logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/background", response_model=list[BackgroundTaskStatus], tags=["webserver"])
@router.get("/background", response_model=list[BackgroundTaskStatus])
def background_job_list():
return [
{
@@ -29,7 +29,7 @@ def background_job_list():
]
@router.get("/background/{jobid}", response_model=BackgroundTaskStatus, tags=["webserver"])
@router.get("/background/{jobid}", response_model=BackgroundTaskStatus)
def background_job(jobid: str):
if not (job := ApiBG.jobs.get(jobid)):
raise HTTPException(status_code=404, detail="Job not found.")
+17 -37
View File
@@ -52,29 +52,25 @@ def __run_backtest_bg(btconfig: Config):
lastconfig = ApiBG.bt["last_config"]
strat = StrategyResolver.load_strategy(btconfig)
validate_config_consistency(btconfig)
if (
not ApiBG.bt["bt"]
or lastconfig.get("timeframe") != strat.timeframe
time_settings_changed = (
lastconfig.get("timeframe") != strat.timeframe
or lastconfig.get("timeframe_detail") != btconfig.get("timeframe_detail")
or lastconfig.get("timerange") != btconfig["timerange"]
):
)
if not ApiBG.bt["bt"] or time_settings_changed:
from freqtrade.optimize.backtesting import Backtesting
ApiBG.bt["bt"] = Backtesting(btconfig)
else:
ApiBG.bt["bt"].config = deep_merge_dicts(btconfig, ApiBG.bt["bt"].config)
ApiBG.bt["bt"].init_backtest()
# Only reload data if timeframe changed.
if (
not ApiBG.bt["data"]
or not ApiBG.bt["timerange"]
or lastconfig.get("timeframe") != strat.timeframe
or lastconfig.get("timerange") != btconfig["timerange"]
):
# Only reload data if timerange is open or settings changed
if not ApiBG.bt["data"] or not ApiBG.bt["timerange"] or time_settings_changed:
ApiBG.bt["data"], ApiBG.bt["timerange"] = ApiBG.bt["bt"].load_bt_data()
lastconfig["timerange"] = btconfig["timerange"]
lastconfig["timeframe_detail"] = btconfig.get("timeframe_detail")
lastconfig["timeframe"] = strat.timeframe
lastconfig["enable_protections"] = btconfig.get("enable_protections")
lastconfig["dry_run_wallet"] = btconfig.get("dry_run_wallet")
@@ -129,7 +125,7 @@ def __run_backtest_bg(btconfig: Config):
ApiBG.bgtask_running = False
@router.post("/backtest", response_model=BacktestResponse, tags=["webserver", "backtest"])
@router.post("/backtest", response_model=BacktestResponse)
async def api_start_backtest(
bt_settings: BacktestRequest, background_tasks: BackgroundTasks, config=Depends(get_config)
):
@@ -172,7 +168,7 @@ async def api_start_backtest(
}
@router.get("/backtest", response_model=BacktestResponse, tags=["webserver", "backtest"])
@router.get("/backtest", response_model=BacktestResponse)
def api_get_backtest():
"""
Get backtesting result.
@@ -219,7 +215,7 @@ def api_get_backtest():
}
@router.delete("/backtest", response_model=BacktestResponse, tags=["webserver", "backtest"])
@router.delete("/backtest", response_model=BacktestResponse)
def api_delete_backtest():
"""Reset backtesting"""
if ApiBG.bgtask_running:
@@ -246,7 +242,7 @@ def api_delete_backtest():
}
@router.get("/backtest/abort", response_model=BacktestResponse, tags=["webserver", "backtest"])
@router.get("/backtest/abort", response_model=BacktestResponse)
def api_backtest_abort():
if not ApiBG.bgtask_running:
return {
@@ -266,17 +262,13 @@ def api_backtest_abort():
}
@router.get(
"/backtest/history", response_model=list[BacktestHistoryEntry], tags=["webserver", "backtest"]
)
@router.get("/backtest/history", response_model=list[BacktestHistoryEntry])
def api_backtest_history(config=Depends(get_config)):
# Get backtest result history, read from metadata files
return get_backtest_resultlist(config["user_data_dir"] / "backtest_results")
@router.get(
"/backtest/history/result", response_model=BacktestResponse, tags=["webserver", "backtest"]
)
@router.get("/backtest/history/result", response_model=BacktestResponse)
def api_backtest_history_result(filename: str, strategy: str, config=Depends(get_config)):
# Get backtest result history, read from metadata files
bt_results_base: Path = config["user_data_dir"] / "backtest_results"
@@ -303,11 +295,7 @@ def api_backtest_history_result(filename: str, strategy: str, config=Depends(get
}
@router.delete(
"/backtest/history/{file}",
response_model=list[BacktestHistoryEntry],
tags=["webserver", "backtest"],
)
@router.delete("/backtest/history/{file}", response_model=list[BacktestHistoryEntry])
def api_delete_backtest_history_entry(file: str, config=Depends(get_config)):
# Get backtest result history, read from metadata files
bt_results_base: Path = config["user_data_dir"] / "backtest_results"
@@ -323,11 +311,7 @@ def api_delete_backtest_history_entry(file: str, config=Depends(get_config)):
return get_backtest_resultlist(config["user_data_dir"] / "backtest_results")
@router.patch(
"/backtest/history/{file}",
response_model=list[BacktestHistoryEntry],
tags=["webserver", "backtest"],
)
@router.patch("/backtest/history/{file}", response_model=list[BacktestHistoryEntry])
def api_update_backtest_history_entry(
file: str, body: BacktestMetadataUpdate, config=Depends(get_config)
):
@@ -350,11 +334,7 @@ def api_update_backtest_history_entry(
return get_backtest_result(file_abs)
@router.get(
"/backtest/history/{file}/market_change",
response_model=BacktestMarketChange,
tags=["webserver", "backtest"],
)
@router.get("/backtest/history/{file}/market_change", response_model=BacktestMarketChange)
def api_get_backtest_market_change(file: str, config=Depends(get_config)):
bt_results_base: Path = config["user_data_dir"] / "backtest_results"
for fn in (
@@ -17,7 +17,7 @@ from freqtrade.util.progress_tracker import get_progress_tracker
logger = logging.getLogger(__name__)
# Private API, protected by authentication and webserver_mode dependency
router = APIRouter(tags=["download-data", "webserver"])
router = APIRouter()
def __run_download(job_id: str, config_loc: Config):
@@ -63,6 +63,8 @@ def pairlists_evaluate(
config_loc["timeframes"] = payload.timeframes
config_loc["erase"] = payload.erase
config_loc["download_trades"] = payload.download_trades
if payload.candle_types is not None:
config_loc["candle_types"] = payload.candle_types
handleExchangePayload(payload, config_loc)
+2 -2
View File
@@ -15,7 +15,7 @@ logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/pair_history", response_model=PairHistory, tags=["candle data"])
@router.get("/pair_history", response_model=PairHistory, tags=["Candle data"])
def pair_history(
pair: str,
timeframe: str,
@@ -43,7 +43,7 @@ def pair_history(
raise HTTPException(status_code=502, detail=str(e))
@router.post("/pair_history", response_model=PairHistory, tags=["candle data"])
@router.post("/pair_history", response_model=PairHistory, tags=["Candle data"])
def pair_history_filtered(payload: PairHistoryRequest, config=Depends(get_config)):
# The initial call to this endpoint can be slow, as it may need to initialize
# the exchange class.
+3 -9
View File
@@ -25,9 +25,7 @@ logger = logging.getLogger(__name__)
router = APIRouter()
@router.get(
"/pairlists/available", response_model=PairListsResponse, tags=["pairlists", "webserver"]
)
@router.get("/pairlists/available", response_model=PairListsResponse)
def list_pairlists(config=Depends(get_config)):
from freqtrade.resolvers import PairListResolver
@@ -72,7 +70,7 @@ def __run_pairlist(job_id: str, config_loc: Config):
ApiBG.pairlist_running = False
@router.post("/pairlists/evaluate", response_model=BgJobStarted, tags=["pairlists", "webserver"])
@router.post("/pairlists/evaluate", response_model=BgJobStarted)
def pairlists_evaluate(
payload: PairListsPayload, background_tasks: BackgroundTasks, config=Depends(get_config)
):
@@ -126,11 +124,7 @@ def handleExchangePayload(payload: ExchangeModePayloadMixin, config_loc: Config)
config_loc["margin_mode"] = payload.margin_mode
@router.get(
"/pairlists/evaluate/{jobid}",
response_model=WhitelistEvaluateResponse,
tags=["pairlists", "webserver"],
)
@router.get("/pairlists/evaluate/{jobid}", response_model=WhitelistEvaluateResponse)
def pairlists_evaluate_get(jobid: str):
if not (job := ApiBG.jobs.get(jobid)):
raise HTTPException(status_code=404, detail="Job not found.")
+7
View File
@@ -157,6 +157,11 @@ class Profit(BaseModel):
winrate: float
expectancy: float
expectancy_ratio: float
sharpe: float
sortino: float
sqn: float
calmar: float
cagr: float
max_drawdown: float
max_drawdown_abs: float
max_drawdown_start: str
@@ -426,6 +431,7 @@ class ForceExitPayload(BaseModel):
tradeid: str | int
ordertype: OrderTypeValues | None = None
amount: float | None = None
price: float | None = None
class BlacklistPayload(BaseModel):
@@ -506,6 +512,7 @@ class DownloadDataPayload(ExchangeModePayloadMixin, BaseModel):
timerange: str | None = None
erase: bool = False
download_trades: bool = False
candle_types: list[str] | None = None
@model_validator(mode="before")
def check_mutually_exclusive(cls, values):
+337
View File
@@ -0,0 +1,337 @@
import logging
from fastapi import APIRouter, Depends, Query
from fastapi.exceptions import HTTPException
from freqtrade.enums import TradingMode
from freqtrade.rpc import RPC
from freqtrade.rpc.api_server.api_schemas import (
Balances,
BlacklistPayload,
BlacklistResponse,
Count,
DailyWeeklyMonthly,
DeleteLockRequest,
DeleteTrade,
Entry,
Exit,
ForceEnterPayload,
ForceEnterResponse,
ForceExitPayload,
ListCustomData,
Locks,
LocksPayload,
MixTag,
OpenTradeSchema,
PairCandlesRequest,
PairHistory,
PerformanceEntry,
Profit,
ProfitAll,
ResultMsg,
Stats,
StatusMsg,
WhitelistResponse,
)
from freqtrade.rpc.api_server.deps import get_config, get_rpc
from freqtrade.rpc.rpc import RPCException
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/balance", response_model=Balances, tags=["Trading-info"])
def balance(rpc: RPC = Depends(get_rpc), config=Depends(get_config)):
"""Account Balances"""
return rpc._rpc_balance(
config["stake_currency"],
config.get("fiat_display_currency", ""),
)
@router.get("/count", response_model=Count, tags=["Trading-info"])
def count(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_count()
@router.get("/entries", response_model=list[Entry], tags=["Trading-info"])
def entries(pair: str | None = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_enter_tag_performance(pair)
@router.get("/exits", response_model=list[Exit], tags=["Trading-info"])
def exits(pair: str | None = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_exit_reason_performance(pair)
@router.get("/mix_tags", response_model=list[MixTag], tags=["Trading-info"])
def mix_tags(pair: str | None = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_mix_tag_performance(pair)
@router.get("/performance", response_model=list[PerformanceEntry], tags=["Trading-info"])
def performance(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_performance()
@router.get("/profit", response_model=Profit, tags=["Trading-info"])
def profit(rpc: RPC = Depends(get_rpc), config=Depends(get_config)):
return rpc._rpc_trade_statistics(config["stake_currency"], config.get("fiat_display_currency"))
@router.get("/profit_all", response_model=ProfitAll, tags=["Trading-info"])
def profit_all(rpc: RPC = Depends(get_rpc), config=Depends(get_config)):
response = {
"all": rpc._rpc_trade_statistics(
config["stake_currency"], config.get("fiat_display_currency")
),
}
if config.get("trading_mode", TradingMode.SPOT) != TradingMode.SPOT:
response["long"] = rpc._rpc_trade_statistics(
config["stake_currency"], config.get("fiat_display_currency"), direction="long"
)
response["short"] = rpc._rpc_trade_statistics(
config["stake_currency"], config.get("fiat_display_currency"), direction="short"
)
return response
@router.get("/stats", response_model=Stats, tags=["Trading-info"])
def stats(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_stats()
@router.get("/daily", response_model=DailyWeeklyMonthly, tags=["Trading-info"])
def daily(
timescale: int = Query(7, ge=1, description="Number of days to fetch data for"),
rpc: RPC = Depends(get_rpc),
config=Depends(get_config),
):
return rpc._rpc_timeunit_profit(
timescale, config["stake_currency"], config.get("fiat_display_currency", "")
)
@router.get("/weekly", response_model=DailyWeeklyMonthly, tags=["Trading-info"])
def weekly(
timescale: int = Query(4, ge=1, description="Number of weeks to fetch data for"),
rpc: RPC = Depends(get_rpc),
config=Depends(get_config),
):
return rpc._rpc_timeunit_profit(
timescale, config["stake_currency"], config.get("fiat_display_currency", ""), "weeks"
)
@router.get("/monthly", response_model=DailyWeeklyMonthly, tags=["Trading-info"])
def monthly(
timescale: int = Query(3, ge=1, description="Number of months to fetch data for"),
rpc: RPC = Depends(get_rpc),
config=Depends(get_config),
):
return rpc._rpc_timeunit_profit(
timescale, config["stake_currency"], config.get("fiat_display_currency", ""), "months"
)
@router.get("/status", response_model=list[OpenTradeSchema], tags=["Trading-info"])
def status(rpc: RPC = Depends(get_rpc)):
try:
return rpc._rpc_trade_status()
except RPCException:
return []
# Using the responsemodel here will cause a ~100% increase in response time (from 1s to 2s)
# on big databases. Correct response model: response_model=TradeResponse,
@router.get("/trades", tags=["Trading-info", "Trades"])
def trades(
limit: int = Query(500, ge=1, description="Maximum number of different trades to return data"),
offset: int = Query(0, ge=0, description="Number of trades to skip for pagination"),
order_by_id: bool = Query(
True, description="Sort trades by id (default: True). If False, sorts by latest timestamp"
),
rpc: RPC = Depends(get_rpc),
):
return rpc._rpc_trade_history(limit, offset=offset, order_by_id=order_by_id)
@router.get("/trade/{tradeid}", response_model=OpenTradeSchema, tags=["Trades"])
def trade(tradeid: int = 0, rpc: RPC = Depends(get_rpc)):
try:
return rpc._rpc_trade_status([tradeid])[0]
except (RPCException, KeyError):
raise HTTPException(status_code=404, detail="Trade not found.")
@router.delete("/trades/{tradeid}", response_model=DeleteTrade, tags=["Trades"])
def trades_delete(tradeid: int, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_delete(tradeid)
@router.delete("/trades/{tradeid}/open-order", response_model=OpenTradeSchema, tags=["Trades"])
def trade_cancel_open_order(tradeid: int, rpc: RPC = Depends(get_rpc)):
rpc._rpc_cancel_open_order(tradeid)
return rpc._rpc_trade_status([tradeid])[0]
@router.post("/trades/{tradeid}/reload", response_model=OpenTradeSchema, tags=["Trades"])
def trade_reload(tradeid: int, rpc: RPC = Depends(get_rpc)):
rpc._rpc_reload_trade_from_exchange(tradeid)
return rpc._rpc_trade_status([tradeid])[0]
@router.get("/trades/open/custom-data", response_model=list[ListCustomData], tags=["Trades"])
def list_open_trades_custom_data(
key: str | None = Query(None, description="Optional key to filter data"),
limit: int = Query(100, ge=1, description="Maximum number of different trades to return data"),
offset: int = Query(0, ge=0, description="Number of trades to skip for pagination"),
rpc: RPC = Depends(get_rpc),
):
"""
Fetch custom data for all open trades.
If a key is provided, it will be used to filter data accordingly.
Pagination is implemented via the `limit` and `offset` parameters.
"""
try:
return rpc._rpc_list_custom_data(key=key, limit=limit, offset=offset)
except RPCException as e:
raise HTTPException(status_code=404, detail=str(e))
@router.get("/trades/{trade_id}/custom-data", response_model=list[ListCustomData], tags=["Trades"])
def list_custom_data(trade_id: int, key: str | None = Query(None), rpc: RPC = Depends(get_rpc)):
"""
Fetch custom data for a specific trade.
If a key is provided, it will be used to filter data accordingly.
"""
try:
return rpc._rpc_list_custom_data(trade_id, key=key)
except RPCException as e:
raise HTTPException(status_code=404, detail=str(e))
# /forcebuy is deprecated with short addition. use /forceentry instead
@router.post("/forceenter", response_model=ForceEnterResponse, tags=["Trades"])
@router.post(
"/forcebuy",
response_model=ForceEnterResponse,
tags=["Trades"],
summary="(deprecated) Please use /forceenter instead",
)
def force_entry(payload: ForceEnterPayload, rpc: RPC = Depends(get_rpc)):
ordertype = payload.ordertype.value if payload.ordertype else None
trade = rpc._rpc_force_entry(
payload.pair,
payload.price,
order_side=payload.side,
order_type=ordertype,
stake_amount=payload.stakeamount,
enter_tag=payload.entry_tag or "force_entry",
leverage=payload.leverage,
)
if trade:
return ForceEnterResponse.model_validate(trade.to_json())
else:
return ForceEnterResponse.model_validate(
{"status": f"Error entering {payload.side} trade for pair {payload.pair}."}
)
# /forcesell is deprecated with short addition. use /forceexit instead
@router.post("/forceexit", response_model=ResultMsg, tags=["Trades"])
@router.post(
"/forcesell",
response_model=ResultMsg,
tags=["Trades"],
summary="(deprecated) Please use /forceexit instead",
)
def forceexit(payload: ForceExitPayload, rpc: RPC = Depends(get_rpc)):
ordertype = payload.ordertype.value if payload.ordertype else None
return rpc._rpc_force_exit(
str(payload.tradeid), ordertype, amount=payload.amount, price=payload.price
)
@router.get("/blacklist", response_model=BlacklistResponse, tags=["Trading-info", "Pairlist"])
def blacklist(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_blacklist()
@router.post("/blacklist", response_model=BlacklistResponse, tags=["Pairlist"])
def blacklist_post(payload: BlacklistPayload, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_blacklist(payload.blacklist)
@router.delete("/blacklist", response_model=BlacklistResponse, tags=["Pairlist"])
def blacklist_delete(pairs_to_delete: list[str] = Query([]), rpc: RPC = Depends(get_rpc)):
"""Provide a list of pairs to delete from the blacklist"""
return rpc._rpc_blacklist_delete(pairs_to_delete)
@router.get("/whitelist", response_model=WhitelistResponse, tags=["Trading-info", "Pairlist"])
def whitelist(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_whitelist()
@router.get("/locks", response_model=Locks, tags=["Trading-info", "Locks"])
def locks(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_locks()
@router.delete("/locks/{lockid}", response_model=Locks, tags=["Locks"])
def delete_lock(lockid: int, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_delete_lock(lockid=lockid)
@router.post("/locks/delete", response_model=Locks, tags=["Locks"])
def delete_lock_pair(payload: DeleteLockRequest, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_delete_lock(lockid=payload.lockid, pair=payload.pair)
@router.post("/locks", response_model=Locks, tags=["Locks"])
def add_locks(payload: list[LocksPayload], rpc: RPC = Depends(get_rpc)):
for lock in payload:
rpc._rpc_add_lock(lock.pair, lock.until, lock.reason, lock.side)
return rpc._rpc_locks()
@router.post("/start", response_model=StatusMsg, tags=["Bot-control"])
def start(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_start()
@router.post("/stop", response_model=StatusMsg, tags=["Bot-control"])
def stop(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_stop()
@router.post("/pause", response_model=StatusMsg, tags=["Bot-control"])
@router.post("/stopentry", response_model=StatusMsg, tags=["Bot-control"])
@router.post("/stopbuy", response_model=StatusMsg, tags=["Bot-control"])
def pause(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_pause()
@router.post("/reload_config", response_model=StatusMsg, tags=["Bot-control"])
def reload_config(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_reload_config()
@router.get("/pair_candles", response_model=PairHistory, tags=["Candle data"])
def pair_candles(pair: str, timeframe: str, limit: int | None = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_analysed_dataframe(pair, timeframe, limit, None)
@router.post("/pair_candles", response_model=PairHistory, tags=["Candle data"])
def pair_candles_filtered(payload: PairCandlesRequest, rpc: RPC = Depends(get_rpc)):
# Advanced pair_candles endpoint with column filtering
return rpc._rpc_analysed_dataframe(
payload.pair, payload.timeframe, payload.limit, payload.columns
)
+12 -439
View File
@@ -6,53 +6,19 @@ from fastapi import APIRouter, Depends, Query
from fastapi.exceptions import HTTPException
from freqtrade import __version__
from freqtrade.data.history import get_datahandler
from freqtrade.enums import CandleType, RunMode, State, TradingMode
from freqtrade.exceptions import OperationalException
from freqtrade.enums import RunMode, State
from freqtrade.rpc import RPC
from freqtrade.rpc.api_server.api_pairlists import handleExchangePayload
from freqtrade.rpc.api_server.api_schemas import (
AvailablePairs,
Balances,
BlacklistPayload,
BlacklistResponse,
Count,
DailyWeeklyMonthly,
DeleteLockRequest,
DeleteTrade,
Entry,
ExchangeListResponse,
Exit,
ForceEnterPayload,
ForceEnterResponse,
ForceExitPayload,
FreqAIModelListResponse,
Health,
HyperoptLossListResponse,
ListCustomData,
Locks,
LocksPayload,
Logs,
MarketRequest,
MarketResponse,
MixTag,
OpenTradeSchema,
PairCandlesRequest,
PairHistory,
PerformanceEntry,
Ping,
PlotConfig,
Profit,
ProfitAll,
ResultMsg,
ShowConfig,
Stats,
StatusMsg,
StrategyListResponse,
StrategyResponse,
SysInfo,
Version,
WhitelistResponse,
)
from freqtrade.rpc.api_server.deps import get_config, get_exchange, get_rpc, get_rpc_optional
from freqtrade.rpc.rpc import RPCException
@@ -91,7 +57,9 @@ logger = logging.getLogger(__name__)
# 2.41: Add download-data endpoint
# 2.42: Add /pair_history endpoint with live data
# 2.43: Add /profit_all endpoint
API_VERSION = 2.43
# 2.44: Add candle_types parameter to download-data endpoint
# 2.45: Add price to forceexit endpoint
API_VERSION = 2.45
# Public API, requires no auth.
router_public = APIRouter()
@@ -99,191 +67,19 @@ router_public = APIRouter()
router = APIRouter()
@router_public.get("/ping", response_model=Ping)
@router_public.get("/ping", response_model=Ping, tags=["Info"])
def ping():
"""simple ping"""
return {"status": "pong"}
@router.get("/version", response_model=Version, tags=["info"])
@router.get("/version", response_model=Version, tags=["Info"])
def version():
"""Bot Version info"""
return {"version": __version__}
@router.get("/balance", response_model=Balances, tags=["info"])
def balance(rpc: RPC = Depends(get_rpc), config=Depends(get_config)):
"""Account Balances"""
return rpc._rpc_balance(
config["stake_currency"],
config.get("fiat_display_currency", ""),
)
@router.get("/count", response_model=Count, tags=["info"])
def count(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_count()
@router.get("/entries", response_model=list[Entry], tags=["info"])
def entries(pair: str | None = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_enter_tag_performance(pair)
@router.get("/exits", response_model=list[Exit], tags=["info"])
def exits(pair: str | None = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_exit_reason_performance(pair)
@router.get("/mix_tags", response_model=list[MixTag], tags=["info"])
def mix_tags(pair: str | None = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_mix_tag_performance(pair)
@router.get("/performance", response_model=list[PerformanceEntry], tags=["info"])
def performance(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_performance()
@router.get("/profit", response_model=Profit, tags=["info"])
def profit(rpc: RPC = Depends(get_rpc), config=Depends(get_config)):
return rpc._rpc_trade_statistics(config["stake_currency"], config.get("fiat_display_currency"))
@router.get("/profit_all", response_model=ProfitAll, tags=["info"])
def profit_all(rpc: RPC = Depends(get_rpc), config=Depends(get_config)):
response = {
"all": rpc._rpc_trade_statistics(
config["stake_currency"], config.get("fiat_display_currency")
),
}
if config.get("trading_mode", TradingMode.SPOT) != TradingMode.SPOT:
response["long"] = rpc._rpc_trade_statistics(
config["stake_currency"], config.get("fiat_display_currency"), direction="long"
)
response["short"] = rpc._rpc_trade_statistics(
config["stake_currency"], config.get("fiat_display_currency"), direction="short"
)
return response
@router.get("/stats", response_model=Stats, tags=["info"])
def stats(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_stats()
@router.get("/daily", response_model=DailyWeeklyMonthly, tags=["info"])
def daily(
timescale: int = Query(7, ge=1, description="Number of days to fetch data for"),
rpc: RPC = Depends(get_rpc),
config=Depends(get_config),
):
return rpc._rpc_timeunit_profit(
timescale, config["stake_currency"], config.get("fiat_display_currency", "")
)
@router.get("/weekly", response_model=DailyWeeklyMonthly, tags=["info"])
def weekly(
timescale: int = Query(4, ge=1, description="Number of weeks to fetch data for"),
rpc: RPC = Depends(get_rpc),
config=Depends(get_config),
):
return rpc._rpc_timeunit_profit(
timescale, config["stake_currency"], config.get("fiat_display_currency", ""), "weeks"
)
@router.get("/monthly", response_model=DailyWeeklyMonthly, tags=["info"])
def monthly(
timescale: int = Query(3, ge=1, description="Number of months to fetch data for"),
rpc: RPC = Depends(get_rpc),
config=Depends(get_config),
):
return rpc._rpc_timeunit_profit(
timescale, config["stake_currency"], config.get("fiat_display_currency", ""), "months"
)
@router.get("/status", response_model=list[OpenTradeSchema], tags=["info"])
def status(rpc: RPC = Depends(get_rpc)):
try:
return rpc._rpc_trade_status()
except RPCException:
return []
# Using the responsemodel here will cause a ~100% increase in response time (from 1s to 2s)
# on big databases. Correct response model: response_model=TradeResponse,
@router.get("/trades", tags=["info", "trading"])
def trades(
limit: int = Query(500, ge=1, description="Maximum number of different trades to return data"),
offset: int = Query(0, ge=0, description="Number of trades to skip for pagination"),
order_by_id: bool = Query(
True, description="Sort trades by id (default: True). If False, sorts by latest timestamp"
),
rpc: RPC = Depends(get_rpc),
):
return rpc._rpc_trade_history(limit, offset=offset, order_by_id=order_by_id)
@router.get("/trade/{tradeid}", response_model=OpenTradeSchema, tags=["info", "trading"])
def trade(tradeid: int = 0, rpc: RPC = Depends(get_rpc)):
try:
return rpc._rpc_trade_status([tradeid])[0]
except (RPCException, KeyError):
raise HTTPException(status_code=404, detail="Trade not found.")
@router.delete("/trades/{tradeid}", response_model=DeleteTrade, tags=["info", "trading"])
def trades_delete(tradeid: int, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_delete(tradeid)
@router.delete("/trades/{tradeid}/open-order", response_model=OpenTradeSchema, tags=["trading"])
def trade_cancel_open_order(tradeid: int, rpc: RPC = Depends(get_rpc)):
rpc._rpc_cancel_open_order(tradeid)
return rpc._rpc_trade_status([tradeid])[0]
@router.post("/trades/{tradeid}/reload", response_model=OpenTradeSchema, tags=["trading"])
def trade_reload(tradeid: int, rpc: RPC = Depends(get_rpc)):
rpc._rpc_reload_trade_from_exchange(tradeid)
return rpc._rpc_trade_status([tradeid])[0]
@router.get("/trades/open/custom-data", response_model=list[ListCustomData], tags=["trading"])
def list_open_trades_custom_data(
key: str | None = Query(None, description="Optional key to filter data"),
limit: int = Query(100, ge=1, description="Maximum number of different trades to return data"),
offset: int = Query(0, ge=0, description="Number of trades to skip for pagination"),
rpc: RPC = Depends(get_rpc),
):
"""
Fetch custom data for all open trades.
If a key is provided, it will be used to filter data accordingly.
Pagination is implemented via the `limit` and `offset` parameters.
"""
try:
return rpc._rpc_list_custom_data(key=key, limit=limit, offset=offset)
except RPCException as e:
raise HTTPException(status_code=404, detail=str(e))
@router.get("/trades/{trade_id}/custom-data", response_model=list[ListCustomData], tags=["trading"])
def list_custom_data(trade_id: int, key: str | None = Query(None), rpc: RPC = Depends(get_rpc)):
"""
Fetch custom data for a specific trade.
If a key is provided, it will be used to filter data accordingly.
"""
try:
return rpc._rpc_list_custom_data(trade_id, key=key)
except RPCException as e:
raise HTTPException(status_code=404, detail=str(e))
@router.get("/show_config", response_model=ShowConfig, tags=["info"])
@router.get("/show_config", response_model=ShowConfig, tags=["Info"])
def show_config(rpc: RPC | None = Depends(get_rpc_optional), config=Depends(get_config)):
state: State | str = ""
strategy_version = None
@@ -295,123 +91,12 @@ def show_config(rpc: RPC | None = Depends(get_rpc_optional), config=Depends(get_
return resp
# /forcebuy is deprecated with short addition. use /forceentry instead
@router.post("/forceenter", response_model=ForceEnterResponse, tags=["trading"])
@router.post("/forcebuy", response_model=ForceEnterResponse, tags=["trading"])
def force_entry(payload: ForceEnterPayload, rpc: RPC = Depends(get_rpc)):
ordertype = payload.ordertype.value if payload.ordertype else None
trade = rpc._rpc_force_entry(
payload.pair,
payload.price,
order_side=payload.side,
order_type=ordertype,
stake_amount=payload.stakeamount,
enter_tag=payload.entry_tag or "force_entry",
leverage=payload.leverage,
)
if trade:
return ForceEnterResponse.model_validate(trade.to_json())
else:
return ForceEnterResponse.model_validate(
{"status": f"Error entering {payload.side} trade for pair {payload.pair}."}
)
# /forcesell is deprecated with short addition. use /forceexit instead
@router.post("/forceexit", response_model=ResultMsg, tags=["trading"])
@router.post("/forcesell", response_model=ResultMsg, tags=["trading"])
def forceexit(payload: ForceExitPayload, rpc: RPC = Depends(get_rpc)):
ordertype = payload.ordertype.value if payload.ordertype else None
return rpc._rpc_force_exit(str(payload.tradeid), ordertype, amount=payload.amount)
@router.get("/blacklist", response_model=BlacklistResponse, tags=["info", "pairlist"])
def blacklist(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_blacklist()
@router.post("/blacklist", response_model=BlacklistResponse, tags=["info", "pairlist"])
def blacklist_post(payload: BlacklistPayload, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_blacklist(payload.blacklist)
@router.delete("/blacklist", response_model=BlacklistResponse, tags=["info", "pairlist"])
def blacklist_delete(pairs_to_delete: list[str] = Query([]), rpc: RPC = Depends(get_rpc)):
"""Provide a list of pairs to delete from the blacklist"""
return rpc._rpc_blacklist_delete(pairs_to_delete)
@router.get("/whitelist", response_model=WhitelistResponse, tags=["info", "pairlist"])
def whitelist(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_whitelist()
@router.get("/locks", response_model=Locks, tags=["info", "locks"])
def locks(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_locks()
@router.delete("/locks/{lockid}", response_model=Locks, tags=["info", "locks"])
def delete_lock(lockid: int, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_delete_lock(lockid=lockid)
@router.post("/locks/delete", response_model=Locks, tags=["info", "locks"])
def delete_lock_pair(payload: DeleteLockRequest, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_delete_lock(lockid=payload.lockid, pair=payload.pair)
@router.post("/locks", response_model=Locks, tags=["info", "locks"])
def add_locks(payload: list[LocksPayload], rpc: RPC = Depends(get_rpc)):
for lock in payload:
rpc._rpc_add_lock(lock.pair, lock.until, lock.reason, lock.side)
return rpc._rpc_locks()
@router.get("/logs", response_model=Logs, tags=["info"])
@router.get("/logs", response_model=Logs, tags=["Info"])
def logs(limit: int | None = None):
return RPC._rpc_get_logs(limit)
@router.post("/start", response_model=StatusMsg, tags=["botcontrol"])
def start(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_start()
@router.post("/stop", response_model=StatusMsg, tags=["botcontrol"])
def stop(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_stop()
@router.post("/pause", response_model=StatusMsg, tags=["botcontrol"])
@router.post("/stopentry", response_model=StatusMsg, tags=["botcontrol"])
@router.post("/stopbuy", response_model=StatusMsg, tags=["botcontrol"])
def pause(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_pause()
@router.post("/reload_config", response_model=StatusMsg, tags=["botcontrol"])
def reload_config(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_reload_config()
@router.get("/pair_candles", response_model=PairHistory, tags=["candle data"])
def pair_candles(pair: str, timeframe: str, limit: int | None = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_analysed_dataframe(pair, timeframe, limit, None)
@router.post("/pair_candles", response_model=PairHistory, tags=["candle data"])
def pair_candles_filtered(payload: PairCandlesRequest, rpc: RPC = Depends(get_rpc)):
# Advanced pair_candles endpoint with column filtering
return rpc._rpc_analysed_dataframe(
payload.pair, payload.timeframe, payload.limit, payload.columns
)
@router.get("/plot_config", response_model=PlotConfig, tags=["candle data"])
@router.get("/plot_config", response_model=PlotConfig, tags=["Candle data"])
def plot_config(
strategy: str | None = None,
config=Depends(get_config),
@@ -430,119 +115,7 @@ def plot_config(
raise HTTPException(status_code=502, detail=str(e))
@router.get("/strategies", response_model=StrategyListResponse, tags=["strategy"])
def list_strategies(config=Depends(get_config)):
from freqtrade.resolvers.strategy_resolver import StrategyResolver
strategies = StrategyResolver.search_all_objects(
config, False, config.get("recursive_strategy_search", False)
)
strategies = sorted(strategies, key=lambda x: x["name"])
return {"strategies": [x["name"] for x in strategies]}
@router.get("/strategy/{strategy}", response_model=StrategyResponse, tags=["strategy"])
def get_strategy(strategy: str, config=Depends(get_config)):
if ":" in strategy:
raise HTTPException(status_code=500, detail="base64 encoded strategies are not allowed.")
config_ = deepcopy(config)
from freqtrade.resolvers.strategy_resolver import StrategyResolver
try:
strategy_obj = StrategyResolver._load_strategy(
strategy, config_, extra_dir=config_.get("strategy_path")
)
except OperationalException:
raise HTTPException(status_code=404, detail="Strategy not found")
except Exception as e:
raise HTTPException(status_code=502, detail=str(e))
return {
"strategy": strategy_obj.get_strategy_name(),
"code": strategy_obj.__source__,
"timeframe": getattr(strategy_obj, "timeframe", None),
}
@router.get("/exchanges", response_model=ExchangeListResponse, tags=[])
def list_exchanges(config=Depends(get_config)):
from freqtrade.exchange import list_available_exchanges
exchanges = list_available_exchanges(config)
return {
"exchanges": exchanges,
}
@router.get(
"/hyperoptloss", response_model=HyperoptLossListResponse, tags=["hyperopt", "webserver"]
)
def list_hyperoptloss(
config=Depends(get_config),
):
import textwrap
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver
loss_functions = HyperOptLossResolver.search_all_objects(config, False)
loss_functions = sorted(loss_functions, key=lambda x: x["name"])
return {
"loss_functions": [
{
"name": x["name"],
"description": textwrap.dedent((x["class"].__doc__ or "").strip()),
}
for x in loss_functions
]
}
@router.get("/freqaimodels", response_model=FreqAIModelListResponse, tags=["freqai"])
def list_freqaimodels(config=Depends(get_config)):
from freqtrade.resolvers.freqaimodel_resolver import FreqaiModelResolver
models = FreqaiModelResolver.search_all_objects(config, False)
models = sorted(models, key=lambda x: x["name"])
return {"freqaimodels": [x["name"] for x in models]}
@router.get("/available_pairs", response_model=AvailablePairs, tags=["candle data"])
def list_available_pairs(
timeframe: str | None = None,
stake_currency: str | None = None,
candletype: CandleType | None = None,
config=Depends(get_config),
):
dh = get_datahandler(config["datadir"], config.get("dataformat_ohlcv"))
trading_mode: TradingMode = config.get("trading_mode", TradingMode.SPOT)
pair_interval = dh.ohlcv_get_available_data(config["datadir"], trading_mode)
if timeframe:
pair_interval = [pair for pair in pair_interval if pair[1] == timeframe]
if stake_currency:
pair_interval = [pair for pair in pair_interval if pair[0].endswith(stake_currency)]
if candletype:
pair_interval = [pair for pair in pair_interval if pair[2] == candletype]
else:
candle_type = CandleType.get_default(trading_mode)
pair_interval = [pair for pair in pair_interval if pair[2] == candle_type]
pair_interval = sorted(pair_interval, key=lambda x: x[0])
pairs = list({x[0] for x in pair_interval})
pairs.sort()
result = {
"length": len(pairs),
"pairs": pairs,
"pair_interval": pair_interval,
}
return result
@router.get("/markets", response_model=MarketResponse, tags=["candle data", "webserver"])
@router.get("/markets", response_model=MarketResponse, tags=["Candle data"])
def markets(
query: Annotated[MarketRequest, Query()],
config=Depends(get_config),
@@ -565,11 +138,11 @@ def markets(
}
@router.get("/sysinfo", response_model=SysInfo, tags=["info"])
@router.get("/sysinfo", response_model=SysInfo, tags=["Info"])
def sysinfo():
return RPC._rpc_sysinfo()
@router.get("/health", response_model=Health, tags=["info"])
@router.get("/health", response_model=Health, tags=["Info"])
def health(rpc: RPC = Depends(get_rpc)):
return rpc.health()
+136
View File
@@ -0,0 +1,136 @@
import logging
from copy import deepcopy
from fastapi import APIRouter, Depends
from fastapi.exceptions import HTTPException
from freqtrade.data.history.datahandlers import get_datahandler
from freqtrade.enums import CandleType, TradingMode
from freqtrade.exceptions import OperationalException
from freqtrade.rpc.api_server.api_schemas import (
AvailablePairs,
ExchangeListResponse,
FreqAIModelListResponse,
HyperoptLossListResponse,
StrategyListResponse,
StrategyResponse,
)
from freqtrade.rpc.api_server.deps import get_config
logger = logging.getLogger(__name__)
# Private API, protected by authentication and webserver_mode dependency
router = APIRouter()
@router.get("/strategies", response_model=StrategyListResponse, tags=["Strategy"])
def list_strategies(config=Depends(get_config)):
from freqtrade.resolvers.strategy_resolver import StrategyResolver
strategies = StrategyResolver.search_all_objects(
config, False, config.get("recursive_strategy_search", False)
)
strategies = sorted(strategies, key=lambda x: x["name"])
return {"strategies": [x["name"] for x in strategies]}
@router.get("/strategy/{strategy}", response_model=StrategyResponse, tags=["Strategy"])
def get_strategy(strategy: str, config=Depends(get_config)):
if ":" in strategy:
raise HTTPException(status_code=500, detail="base64 encoded strategies are not allowed.")
config_ = deepcopy(config)
from freqtrade.resolvers.strategy_resolver import StrategyResolver
try:
strategy_obj = StrategyResolver._load_strategy(
strategy, config_, extra_dir=config_.get("strategy_path")
)
except OperationalException:
raise HTTPException(status_code=404, detail="Strategy not found")
except Exception as e:
raise HTTPException(status_code=502, detail=str(e))
return {
"strategy": strategy_obj.get_strategy_name(),
"code": strategy_obj.__source__,
"timeframe": getattr(strategy_obj, "timeframe", None),
}
@router.get("/exchanges", response_model=ExchangeListResponse, tags=[])
def list_exchanges(config=Depends(get_config)):
from freqtrade.exchange import list_available_exchanges
exchanges = list_available_exchanges(config)
return {
"exchanges": exchanges,
}
@router.get("/hyperoptloss", response_model=HyperoptLossListResponse, tags=["Hyperopt"])
def list_hyperoptloss(
config=Depends(get_config),
):
import textwrap
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver
loss_functions = HyperOptLossResolver.search_all_objects(config, False)
loss_functions = sorted(loss_functions, key=lambda x: x["name"])
return {
"loss_functions": [
{
"name": x["name"],
"description": textwrap.dedent((x["class"].__doc__ or "").strip()),
}
for x in loss_functions
]
}
@router.get("/freqaimodels", response_model=FreqAIModelListResponse, tags=["FreqAI"])
def list_freqaimodels(config=Depends(get_config)):
from freqtrade.resolvers.freqaimodel_resolver import FreqaiModelResolver
models = FreqaiModelResolver.search_all_objects(config, False)
models = sorted(models, key=lambda x: x["name"])
return {"freqaimodels": [x["name"] for x in models]}
@router.get(
"/available_pairs", response_model=AvailablePairs, tags=["Candle data", "Download-data"]
)
def list_available_pairs(
timeframe: str | None = None,
stake_currency: str | None = None,
candletype: CandleType | None = None,
config=Depends(get_config),
):
dh = get_datahandler(config["datadir"], config.get("dataformat_ohlcv"))
trading_mode: TradingMode = config.get("trading_mode", TradingMode.SPOT)
pair_interval = dh.ohlcv_get_available_data(config["datadir"], trading_mode)
if timeframe:
pair_interval = [pair for pair in pair_interval if pair[1] == timeframe]
if stake_currency:
pair_interval = [pair for pair in pair_interval if pair[0].endswith(stake_currency)]
if candletype:
pair_interval = [pair for pair in pair_interval if pair[2] == candletype]
else:
candle_type = CandleType.get_default(trading_mode)
pair_interval = [pair for pair in pair_interval if pair[2] == candle_type]
pair_interval = sorted(pair_interval, key=lambda x: x[0])
pairs = list({x[0] for x in pair_interval})
pairs.sort()
result = {
"length": len(pairs),
"pairs": pairs,
"pair_interval": pair_interval,
}
return result
+7 -1
View File
@@ -5,7 +5,7 @@ from uuid import uuid4
from fastapi import Depends, HTTPException
from freqtrade.constants import Config
from freqtrade.enums import RunMode
from freqtrade.enums import TRADE_MODES, RunMode
from freqtrade.persistence import Trade
from freqtrade.persistence.models import _request_id_ctx_var
from freqtrade.rpc.api_server.webserver_bgwork import ApiBG
@@ -69,3 +69,9 @@ def is_webserver_mode(config=Depends(get_config)):
if config["runmode"] != RunMode.WEBSERVER:
raise HTTPException(status_code=503, detail="Bot is not in the correct state.")
return None
def is_trading_mode(config=Depends(get_config)):
if config["runmode"] not in TRADE_MODES:
raise HTTPException(status_code=503, detail="Bot is not in the correct state.")
return None
+89 -2
View File
@@ -21,6 +21,74 @@ from freqtrade.rpc.rpc_types import RPCSendMsg
logger = logging.getLogger(__name__)
_TRADE_MODE_ONLY = "*only available in trading mode*"
_WEBSERVER_MODE_ONLY = "*only available in webserver mode*"
_OPENAPI_TAGS = [
{"name": "Auth", "description": "Authentication endpoints."},
{
"name": "Info",
"description": ("Information endpoints providing general information about the bot."),
},
{
"name": "Bot-control",
"description": (f"Bot control endpoints to start/stop trading - {_TRADE_MODE_ONLY}."),
},
{
"name": "Pairlist",
"description": f"Pairlist management - {_TRADE_MODE_ONLY}.",
},
{
"name": "Locks",
"description": f"Pair lock management - {_TRADE_MODE_ONLY}.",
},
{
"name": "Candle data",
"description": "Candle / OHLCV data.",
},
{
"name": "Trading-info",
"description": f"Trading related information - {_TRADE_MODE_ONLY}.",
},
{
"name": "Trades",
"description": f"Trade management - {_TRADE_MODE_ONLY}.",
},
{
"name": "Strategy",
"description": f"List and retrieve strategies - {_WEBSERVER_MODE_ONLY}.",
},
{
"name": "Hyperopt",
"description": f"Retrieve hyperopt loss functions - {_WEBSERVER_MODE_ONLY}.",
},
{
"name": "FreqAI",
"description": f"FreqAI related endpoints - {_WEBSERVER_MODE_ONLY}.",
},
{
"name": "Download-data",
"description": f"Download data endpoints - {_WEBSERVER_MODE_ONLY}.",
},
{
"name": "Backtest",
"description": f"Backtest endpoints - {_WEBSERVER_MODE_ONLY}.",
},
{
"name": "Pairlists",
"description": f"Pairlist endpoints - {_WEBSERVER_MODE_ONLY}.",
},
{
"name": "Trading",
"description": f"Trading related endpoints - {_TRADE_MODE_ONLY}.",
},
{
"name": "Webserver",
"description": (f"Webserver related endpoints - {_WEBSERVER_MODE_ONLY}."),
},
]
class FTJSONResponse(JSONResponse):
media_type = "application/json"
@@ -68,6 +136,7 @@ class ApiServer(RPCHandler):
docs_url="/docs" if api_config.get("enable_openapi", False) else None,
redoc_url=None,
default_response_class=FTJSONResponse,
openapi_tags=_OPENAPI_TAGS,
)
self.configure_app(self.app, self._config)
self.start_api()
@@ -122,28 +191,44 @@ class ApiServer(RPCHandler):
from freqtrade.rpc.api_server.api_download_data import router as api_download_data
from freqtrade.rpc.api_server.api_pair_history import router as api_pair_history
from freqtrade.rpc.api_server.api_pairlists import router as api_pairlists
from freqtrade.rpc.api_server.api_trading import router as api_trading
from freqtrade.rpc.api_server.api_v1 import router as api_v1
from freqtrade.rpc.api_server.api_v1 import router_public as api_v1_public
from freqtrade.rpc.api_server.api_webserver import router as api_webserver
from freqtrade.rpc.api_server.api_ws import router as ws_router
from freqtrade.rpc.api_server.deps import is_webserver_mode
from freqtrade.rpc.api_server.deps import is_trading_mode, is_webserver_mode
from freqtrade.rpc.api_server.web_ui import router_ui
app.include_router(api_v1_public, prefix="/api/v1")
app.include_router(router_login, prefix="/api/v1", tags=["auth"])
app.include_router(router_login, prefix="/api/v1", tags=["Auth"])
app.include_router(
api_v1,
prefix="/api/v1",
dependencies=[Depends(http_basic_or_jwt_token)],
)
app.include_router(
api_trading,
prefix="/api/v1",
tags=["Trading"],
dependencies=[Depends(http_basic_or_jwt_token), Depends(is_trading_mode)],
)
app.include_router(
api_webserver,
prefix="/api/v1",
tags=["Webserver"],
dependencies=[Depends(http_basic_or_jwt_token), Depends(is_webserver_mode)],
)
app.include_router(
api_backtest,
prefix="/api/v1",
tags=["Backtest"],
dependencies=[Depends(http_basic_or_jwt_token), Depends(is_webserver_mode)],
)
app.include_router(
api_bg_tasks,
prefix="/api/v1",
tags=["Webserver"],
dependencies=[Depends(http_basic_or_jwt_token), Depends(is_webserver_mode)],
)
app.include_router(
@@ -154,11 +239,13 @@ class ApiServer(RPCHandler):
app.include_router(
api_pairlists,
prefix="/api/v1",
tags=["Webserver", "Pairlists"],
dependencies=[Depends(http_basic_or_jwt_token), Depends(is_webserver_mode)],
)
app.include_router(
api_download_data,
prefix="/api/v1",
tags=["Download-data", "Webserver"],
dependencies=[Depends(http_basic_or_jwt_token), Depends(is_webserver_mode)],
)
app.include_router(ws_router, prefix="/api/v1")
+2 -12
View File
@@ -5,7 +5,6 @@ e.g BTC to USD
import logging
from datetime import datetime
from typing import Any
from requests.exceptions import RequestException
@@ -13,6 +12,7 @@ from freqtrade.constants import SUPPORTED_FIAT, Config
from freqtrade.mixins.logging_mixin import LoggingMixin
from freqtrade.util import FtTTLCache
from freqtrade.util.coin_gecko import FtCoinGeckoApi
from freqtrade.util.singleton import SingletonMeta
logger = logging.getLogger(__name__)
@@ -32,26 +32,16 @@ coingecko_mapping = {
}
class CryptoToFiatConverter(LoggingMixin):
class CryptoToFiatConverter(LoggingMixin, metaclass=SingletonMeta):
"""
Main class to initiate Crypto to FIAT.
This object contains a list of pair Crypto, FIAT
This object is also a Singleton
"""
__instance = None
_coinlistings: list[dict] = []
_backoff: float = 0.0
def __new__(cls, *args: Any, **kwargs: Any) -> Any:
"""
Singleton pattern to ensure only one instance is created.
"""
if not cls.__instance:
cls.__instance = super().__new__(cls)
return cls.__instance
def __init__(self, config: Config) -> None:
# Timeout: 6h
self._pair_price: FtTTLCache = FtTTLCache(maxsize=500, ttl=6 * 60 * 60)
+104 -23
View File
@@ -19,7 +19,16 @@ from freqtrade import __version__
from freqtrade.configuration.timerange import TimeRange
from freqtrade.constants import CANCEL_REASON, DEFAULT_DATAFRAME_COLUMNS, Config
from freqtrade.data.history import load_data
from freqtrade.data.metrics import DrawDownResult, calculate_expectancy, calculate_max_drawdown
from freqtrade.data.metrics import (
DrawDownResult,
calculate_cagr,
calculate_calmar,
calculate_expectancy,
calculate_max_drawdown,
calculate_sharpe,
calculate_sortino,
calculate_sqn,
)
from freqtrade.enums import (
CandleType,
ExitCheckTuple,
@@ -689,6 +698,34 @@ class RPC:
last_date = trades[-1].open_date_utc if trades else None
num = float(len(durations) or 1)
bot_start = KeyValueStore.get_datetime_value("bot_start_time")
sharpe = calculate_sharpe(
trades=trades_df,
min_date=first_date,
max_date=last_date,
starting_balance=starting_balance,
)
sortino = calculate_sortino(
trades=trades_df,
min_date=first_date,
max_date=last_date,
starting_balance=starting_balance,
)
sqn = calculate_sqn(trades=trades_df, starting_balance=starting_balance)
calmar = calculate_calmar(
trades=trades_df,
min_date=first_date,
max_date=last_date,
starting_balance=starting_balance,
)
current_balance = self._freqtrade.wallets.get_total_stake_amount()
days_passed = max(1, (last_date - first_date).days) if first_date and last_date else 1
cagr = calculate_cagr(
starting_balance=starting_balance,
final_balance=current_balance,
days_passed=days_passed,
)
return {
"profit_closed_coin": profit_closed_coin_sum,
"profit_closed_percent_mean": round(profit_closed_ratio_mean * 100, 2),
@@ -725,6 +762,11 @@ class RPC:
"winrate": winrate,
"expectancy": expectancy,
"expectancy_ratio": expectancy_ratio,
"sharpe": sharpe,
"sortino": sortino,
"sqn": sqn,
"calmar": calmar,
"cagr": cagr,
"max_drawdown": drawdown.relative_account_drawdown,
"max_drawdown_abs": drawdown.drawdown_abs,
"max_drawdown_start": format_date(drawdown.high_date),
@@ -804,12 +846,9 @@ class RPC:
if is_stake_currency:
trade_amount = self._freqtrade.wallets.get_available_stake_amount()
try:
est_stake, est_stake_bot = self.__balance_get_est_stake(
coin, stake_currency, trade_amount, balance
)
except ValueError:
continue
est_stake, est_stake_bot = self.__balance_get_est_stake(
coin, stake_currency, trade_amount, balance
)
total += est_stake
@@ -832,10 +871,33 @@ class RPC:
}
)
symbol: str
position: PositionWallet
for symbol, position in self._freqtrade.wallets.get_all_positions().items():
total += position.collateral
total_bot += position.collateral
pos: PositionWallet
for symbol, pos in self._freqtrade.wallets.get_all_positions().items():
est_stake = pos.collateral
pos_base = self._freqtrade.exchange.get_pair_base_currency(symbol)
if pos.leverage:
try:
rate = self._freqtrade.exchange.get_conversion_rate(pos_base, stake_currency)
if rate:
# For a leveraged position, equity (what we want as est_stake) is:
# equity = collateral + unlevered PnL
# For longs: unlevered PnL = current_value - open_value
# est_stake = rate * pos.position - pos.collateral * (pos.leverage - 1)
# For shorts: unlevered PnL = open_value - current_value
# est_stake = collateral + (open_value - current_value)
# = collateral + (pos.collateral * pos.leverage)
# - rate * pos.position
if pos.side == "long":
est_stake = rate * pos.position - pos.collateral * (pos.leverage - 1)
else:
est_stake = pos.collateral * (1 + pos.leverage) - rate * pos.position
except (ExchangeError, PricingError) as e:
logger.warning(f"Error {e} getting rate for futures {symbol} / {pos_base}")
pass
# Add the estimated stake (collateral + unlevered PnL) to totals
total += est_stake
total_bot += est_stake
currencies.append(
{
@@ -843,12 +905,12 @@ class RPC:
"free": 0,
"balance": 0,
"used": 0,
"position": position.position,
"est_stake": position.collateral,
"est_stake_bot": position.collateral,
"position": pos.position,
"est_stake": est_stake,
"est_stake_bot": est_stake,
"stake": stake_currency,
"side": position.side,
"is_bot_managed": True,
"side": pos.side,
"is_bot_managed": pos_base in open_assets,
"is_position": True,
}
)
@@ -940,7 +1002,11 @@ class RPC:
return {"status": "Reloaded from orders from exchange"}
def __exec_force_exit(
self, trade: Trade, ordertype: str | None, amount: float | None = None
self,
trade: Trade,
ordertype: str | None,
amount: float | None = None,
price: float | None = None,
) -> bool:
# Check if there is there are open orders
trade_entry_cancelation_registry = []
@@ -964,8 +1030,13 @@ class RPC:
# Order cancellation failed, so we can't exit.
return False
# Get current rate and execute sell
current_rate = self._freqtrade.exchange.get_rate(
trade.pair, side="exit", is_short=trade.is_short, refresh=True
current_rate = (
self._freqtrade.exchange.get_rate(
trade.pair, side="exit", is_short=trade.is_short, refresh=True
)
if ordertype == "market" or price is None
else price
)
exit_check = ExitCheckTuple(exit_type=ExitType.FORCE_EXIT)
order_type = ordertype or self._freqtrade.strategy.order_types.get(
@@ -983,18 +1054,28 @@ class RPC:
sub_amount = amount
self._freqtrade.execute_trade_exit(
trade, current_rate, exit_check, ordertype=order_type, sub_trade_amt=sub_amount
trade,
current_rate,
exit_check,
ordertype=order_type,
sub_trade_amt=sub_amount,
skip_custom_exit_price=price is not None and ordertype == "limit",
)
return True
return False
def _rpc_force_exit(
self, trade_id: str, ordertype: str | None = None, *, amount: float | None = None
self,
trade_id: str,
ordertype: str | None = None,
*,
amount: float | None = None,
price: float | None = None,
) -> dict[str, str]:
"""
Handler for forceexit <id>.
Sells the given trade at current price
exits the given trade. Uses current price if price is None.
"""
if self._freqtrade.state == State.STOPPED:
@@ -1024,7 +1105,7 @@ class RPC:
logger.warning("force_exit: Invalid argument received")
raise RPCException("invalid argument")
result = self.__exec_force_exit(trade, ordertype, amount)
result = self.__exec_force_exit(trade, ordertype, amount, price)
Trade.commit()
self._freqtrade.wallets.update()
if not result:
+5 -5
View File
@@ -1285,7 +1285,7 @@ class Telegram(RPCHandler):
if curr["is_position"]:
curr_output = (
f"*{curr['currency']}:*\n"
f"\t`{curr['side']}: {curr['position']:.8f}`\n"
f"\t`{curr['side']}: {round_value(curr['position'], 8)}`\n"
f"\t`Est. {curr['stake']}: "
f"{fmt_coin(curr['est_stake'], curr['stake'], False)}`\n"
)
@@ -1296,10 +1296,10 @@ class Telegram(RPCHandler):
curr_output = (
f"*{curr['currency']}:*\n"
f"\t`Available: {curr['free']:.8f}`\n"
f"\t`Balance: {curr['balance']:.8f}`\n"
f"\t`Pending: {curr['used']:.8f}`\n"
f"\t`Bot Owned: {curr['bot_owned']:.8f}`\n"
f"\t`Available: {fmt_coin(curr['free'], curr['currency'], False)}`\n"
f"\t`Balance: {fmt_coin(curr['balance'], curr['currency'], False)}`\n"
f"\t`Pending: {fmt_coin(curr['used'], curr['currency'], False)}`\n"
f"\t`Bot Owned: {fmt_coin(curr['bot_owned'], curr['currency'], False)}`\n"
f"\t`Est. {curr['stake']}: {est_stake}`\n"
)
+6 -3
View File
@@ -104,8 +104,11 @@ def _create_and_merge_informative_pair(
):
asset = inf_data.asset or ""
timeframe = inf_data.timeframe
timeframe1 = inf_data.timeframe
fmt = inf_data.fmt
candle_type = inf_data.candle_type
if candle_type == CandleType.FUNDING_RATE:
timeframe1 = strategy.dp.get_funding_rate_timeframe()
config = strategy.config
@@ -132,10 +135,10 @@ def _create_and_merge_informative_pair(
fmt = "{base}_{quote}_" + fmt # Informatives of other pairs
inf_metadata = {"pair": asset, "timeframe": timeframe}
inf_dataframe = strategy.dp.get_pair_dataframe(asset, timeframe, candle_type)
inf_dataframe = strategy.dp.get_pair_dataframe(asset, timeframe1, candle_type)
if inf_dataframe.empty:
raise ValueError(
f"Informative dataframe for ({asset}, {timeframe}, {candle_type}) is empty. "
f"Informative dataframe for ({asset}, {timeframe1}, {candle_type}) is empty. "
"Can't populate informative indicators."
)
inf_dataframe = populate_indicators_fn(strategy, inf_dataframe, inf_metadata)
@@ -163,7 +166,7 @@ def _create_and_merge_informative_pair(
dataframe,
inf_dataframe,
strategy.timeframe,
timeframe,
timeframe1,
ffill=inf_data.ffill,
append_timeframe=False,
date_column=date_column,
+1 -1
View File
@@ -1718,7 +1718,7 @@ class IStrategy(ABC, HyperStrategyMixin):
timeout_unit = self.config.get("unfilledtimeout", {}).get("unit", "minutes")
timeout_kwargs = {timeout_unit: -timeout}
timeout_threshold = current_time + timedelta(**timeout_kwargs)
timedout = order.status == "open" and order.order_date_utc < timeout_threshold
timedout = order.status == "open" and order.order_date_utc <= timeout_threshold
if timedout:
return True
time_method = (

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