Compare commits

...

251 Commits

Author SHA1 Message Date
Matthias cc807d1cf9 Merge pull request #11945 from freqtrade/new_release
New release 2025.6
2025-07-02 21:08:03 +02:00
Matthias 5f907a4b1a chore: bump version to 2025.6 2025-07-02 20:03:29 +02:00
Matthias a464a2fdbc Merge branch 'stable' into new_release 2025-07-02 20:03:16 +02:00
Matthias fa3d95fd89 Merge pull request #11919 from freqtrade/stash86-patch-1
Fix example config
2025-07-02 18:16:51 +02:00
Matthias ba8778b181 chore: revert max_open_trades in sample config for now 2025-07-02 17:55:28 +02:00
Matthias 6421f767c6 chore: remove github buttons js code 2025-07-02 16:49:36 +02:00
Matthias f18c7f0cad docs: make sure github buttons render 2025-07-02 16:49:36 +02:00
Matthias e63017a2b9 docs: enable "edit" functionality for docs 2025-07-02 16:49:36 +02:00
Matthias 049cc10169 docs: improved github icon for docs 2025-07-02 16:49:36 +02:00
Matthias 595db54ac7 Merge pull request #11917 from froggleston/frog-contrib-exch-1
Add short explanation of where exchange files are
2025-07-02 09:16:54 +02:00
Matthias bfa9c818e6 chore: update margin/trading mode exception to be a configuration error 2025-07-02 07:47:38 +02:00
Matthias 30afedd4f8 chore: use value instead of string to avoid "none" output in "marginmode" 2025-07-02 07:47:19 +02:00
Matthias 6049ac1561 docs: update wording of developer docs 2025-07-02 07:35:02 +02:00
Matthias 25c24ddc9c chore: improve error message
part of #11923
2025-07-01 20:48:32 +02:00
Matthias 03df35b49b Merge pull request #11930 from freqtrade/dependabot/pip/develop/ast-comments-1.2.3
chore(deps): bump ast-comments from 1.2.2 to 1.2.3
2025-07-01 07:26:15 +02:00
Matthias 3b441cd08f Merge pull request #11942 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-07-01 07:22:09 +02:00
Freqtrade Bot 0f67d32b75 chore: update pre-commit hooks 2025-07-01 03:31:30 +00:00
Matthias 820a83c96f Merge pull request #11934 from freqtrade/dependabot/pip/develop/ccxt-4.4.91
chore(deps): bump ccxt from 4.4.90 to 4.4.91
2025-06-30 10:46:44 +02:00
dependabot[bot] fae30c6ff9 chore(deps): bump ccxt from 4.4.90 to 4.4.91
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.90 to 4.4.91.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/v4.4.90...v4.4.91)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-30 07:54:13 +00:00
Matthias 3e4ffb9c1d Merge pull request #11939 from freqtrade/dependabot/pip/develop/uvicorn-0.35.0
chore(deps): bump uvicorn from 0.34.3 to 0.35.0
2025-06-30 09:53:10 +02:00
Matthias f6d10dad6a Merge pull request #11931 from freqtrade/dependabot/pip/develop/plotly-6.2.0
chore(deps): bump plotly from 6.1.2 to 6.2.0
2025-06-30 09:49:27 +02:00
Matthias 79b530d98e Merge pull request #11933 from freqtrade/dependabot/pip/develop/python-telegram-bot-22.2
chore(deps): bump python-telegram-bot from 22.1 to 22.2
2025-06-30 09:48:49 +02:00
Matthias 3b8e521b81 Merge pull request #11938 from freqtrade/dependabot/github_actions/develop/astral-sh/setup-uv-6.3.1
chore(deps): bump astral-sh/setup-uv from 6.3.0 to 6.3.1
2025-06-30 09:48:17 +02:00
Matthias 7e228ba7ab Merge pull request #11937 from freqtrade/dependabot/pip/develop/ruff-0.12.1
chore(deps-dev): bump ruff from 0.12.0 to 0.12.1
2025-06-30 08:23:56 +02:00
Matthias 6050d09ed8 Merge pull request #11936 from freqtrade/dependabot/github_actions/develop/rjstone/discord-webhook-notify-2.2.1
chore(deps): bump rjstone/discord-webhook-notify from 2.1.1 to 2.2.1
2025-06-30 08:23:18 +02:00
dependabot[bot] b7f158a79e chore(deps): bump uvicorn from 0.34.3 to 0.35.0
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.34.3 to 0.35.0.
- [Release notes](https://github.com/encode/uvicorn/releases)
- [Changelog](https://github.com/encode/uvicorn/blob/master/docs/release-notes.md)
- [Commits](https://github.com/encode/uvicorn/compare/0.34.3...0.35.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-30 06:13:21 +00:00
Matthias bc13d1636c Merge pull request #11935 from freqtrade/dependabot/pip/develop/fastapi-0.115.14
chore(deps): bump fastapi from 0.115.13 to 0.115.14
2025-06-30 08:11:30 +02:00
dependabot[bot] 021fdcab43 chore(deps): bump astral-sh/setup-uv from 6.3.0 to 6.3.1
Bumps [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) from 6.3.0 to 6.3.1.
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/445689ea25e0de0a23313031f5fe577c74ae45a1...bd01e18f51369d5a26f1651c3cb451d3417e3bba)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-30 04:40:04 +00:00
dependabot[bot] d206b601b3 chore(deps-dev): bump ruff from 0.12.0 to 0.12.1
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.12.0 to 0.12.1.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.12.0...0.12.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-30 04:39:33 +00:00
dependabot[bot] 949c7318f4 chore(deps): bump rjstone/discord-webhook-notify from 2.1.1 to 2.2.1
Bumps [rjstone/discord-webhook-notify](https://github.com/rjstone/discord-webhook-notify) from 2.1.1 to 2.2.1.
- [Release notes](https://github.com/rjstone/discord-webhook-notify/releases)
- [Commits](https://github.com/rjstone/discord-webhook-notify/compare/a975c85e53c8ea07b0b10f8461b0a90059816dcf...c2597273488aeda841dd1e891321952b51f7996f)

---
updated-dependencies:
- dependency-name: rjstone/discord-webhook-notify
  dependency-version: 2.2.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-30 04:39:19 +00:00
dependabot[bot] 3666362479 chore(deps): bump fastapi from 0.115.13 to 0.115.14
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.115.13 to 0.115.14.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.115.13...0.115.14)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-30 04:39:08 +00:00
dependabot[bot] f27540d221 chore(deps): bump python-telegram-bot from 22.1 to 22.2
Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 22.1 to 22.2.
- [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases)
- [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v22.1...v22.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-30 04:39:00 +00:00
dependabot[bot] 31dc0fe327 chore(deps): bump plotly from 6.1.2 to 6.2.0
Bumps [plotly](https://github.com/plotly/plotly.py) from 6.1.2 to 6.2.0.
- [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.1.2...v6.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-30 04:37:03 +00:00
dependabot[bot] 1125c60f07 chore(deps): bump ast-comments from 1.2.2 to 1.2.3
Bumps [ast-comments](https://github.com/t3rn0/ast-comments) from 1.2.2 to 1.2.3.
- [Release notes](https://github.com/t3rn0/ast-comments/releases)
- [Commits](https://github.com/t3rn0/ast-comments/compare/1.2.2...1.2.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-30 04:36:19 +00:00
Matthias 9f5fd574b4 Merge pull request #11922 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2025-06-26 10:47:19 +02:00
Freqtrade Bot 74eb0d9f88 chore: update pre-commit hooks 2025-06-26 03:24:41 +00:00
Stefano 52c78966ef Fix example config
- Fix the low stake anount
- Set max open trades to unlimited, as a good practice
2025-06-25 19:21:00 +09:00
froggleston 1641f56956 Add short explanation of where exchange files are 2025-06-24 15:23:45 +01:00
Matthias d27981f8cf Merge pull request #11915 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-06-24 09:06:24 +02:00
Freqtrade Bot 6031ccabac chore: update pre-commit hooks 2025-06-24 03:25:22 +00:00
Matthias c64b6b0d69 Merge pull request #11901 from freqtrade/dependabot/pip/develop/ruff-0.12.0
chore(deps-dev): bump ruff from 0.11.13 to 0.12.0
2025-06-23 15:16:57 +02:00
Matthias 4203693428 chore: Ruf ignore extension 2025-06-23 15:01:59 +02:00
Matthias c76c26cc34 Merge pull request #11910 from freqtrade/dependabot/github_actions/develop/astral-sh/setup-uv-6.3.0
chore(deps): bump astral-sh/setup-uv from 6.1.0 to 6.3.0
2025-06-23 09:14:13 +02:00
Matthias 1c87748b99 Merge pull request #11906 from freqtrade/dependabot/pip/develop/optuna-4.4.0
chore(deps): bump optuna from 4.3.0 to 4.4.0
2025-06-23 08:58:31 +02:00
Matthias 4ae80423e0 Merge pull request #11903 from freqtrade/dependabot/pip/develop/markdown-3.8.2
chore(deps): bump markdown from 3.8 to 3.8.2
2025-06-23 08:09:38 +02:00
Matthias 4e6ae88557 Merge pull request #11911 from freqtrade/dependabot/pip/develop/fastapi-0.115.13
chore(deps): bump fastapi from 0.115.12 to 0.115.13
2025-06-23 07:56:52 +02:00
dependabot[bot] 102c33d68b chore(deps-dev): bump ruff from 0.11.13 to 0.12.0
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.13 to 0.12.0.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.11.13...0.12.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-23 05:41:30 +00:00
Matthias 12a72fa161 Merge pull request #11909 from freqtrade/dependabot/pip/develop/mypy-1.16.1
chore(deps-dev): bump mypy from 1.16.0 to 1.16.1
2025-06-23 07:41:01 +02:00
dependabot[bot] 3a5b555f1e chore(deps): bump markdown from 3.8 to 3.8.2
---
updated-dependencies:
- dependency-name: markdown
  dependency-version: 3.8.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-23 05:40:59 +00:00
Matthias 8f2ee28395 Merge pull request #11908 from freqtrade/dependabot/pip/develop/pytest-4784b1c68e
chore(deps-dev): bump the pytest group with 2 updates
2025-06-23 07:40:07 +02:00
Matthias e57d0e3bc4 Merge pull request #11905 from freqtrade/dependabot/pip/develop/pymdown-extensions-10.16
chore(deps): bump pymdown-extensions from 10.15 to 10.16
2025-06-23 07:38:53 +02:00
Matthias f5205da459 Merge pull request #11902 from freqtrade/dependabot/pip/develop/cachetools-6.1.0
chore(deps): bump cachetools from 6.0.0 to 6.1.0
2025-06-23 07:27:09 +02:00
Matthias bd773b9baf Merge pull request #11907 from freqtrade/dependabot/pip/develop/ccxt-4.4.90
chore(deps): bump ccxt from 4.4.89 to 4.4.90
2025-06-23 07:20:53 +02:00
Matthias 7b4a6a48ee Merge pull request #11904 from freqtrade/dependabot/github_actions/develop/docker/setup-buildx-action-3.11.1
chore(deps): bump docker/setup-buildx-action from 3.10.0 to 3.11.1
2025-06-23 07:16:40 +02:00
dependabot[bot] 24a3a29360 chore(deps): bump fastapi from 0.115.12 to 0.115.13
---
updated-dependencies:
- dependency-name: fastapi
  dependency-version: 0.115.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-23 03:46:28 +00:00
dependabot[bot] 6a279043fa chore(deps): bump astral-sh/setup-uv from 6.1.0 to 6.3.0
---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
  dependency-version: 6.3.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-23 03:45:59 +00:00
dependabot[bot] 60764500db chore(deps-dev): bump mypy from 1.16.0 to 1.16.1
Bumps [mypy](https://github.com/python/mypy) from 1.16.0 to 1.16.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.16.0...v1.16.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-23 03:45:07 +00:00
dependabot[bot] ba5a64aa34 chore(deps-dev): bump the pytest group with 2 updates
Bumps the pytest group with 2 updates: [pytest](https://github.com/pytest-dev/pytest) and [pytest-random-order](https://github.com/jbasko/pytest-random-order).


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

Updates `pytest-random-order` from 1.1.1 to 1.2.0
- [Release notes](https://github.com/jbasko/pytest-random-order/releases)
- [Changelog](https://github.com/pytest-dev/pytest-random-order/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/jbasko/pytest-random-order/compare/v1.1.1...v1.2.0)

---
updated-dependencies:
- dependency-name: pytest
  dependency-version: 8.4.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: pytest
- dependency-name: pytest-random-order
  dependency-version: 1.2.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: pytest
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-23 03:44:37 +00:00
dependabot[bot] f40576091a chore(deps): bump ccxt from 4.4.89 to 4.4.90
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.89 to 4.4.90.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/v4.4.89...v4.4.90)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-23 03:43:55 +00:00
dependabot[bot] de3eedb39e chore(deps): bump optuna from 4.3.0 to 4.4.0
---
updated-dependencies:
- dependency-name: optuna
  dependency-version: 4.4.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-23 03:43:46 +00:00
dependabot[bot] fded8bf43e chore(deps): bump pymdown-extensions from 10.15 to 10.16
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.15 to 10.16.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.15...10.16)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-23 03:42:57 +00:00
dependabot[bot] 5a45b4a3e1 chore(deps): bump docker/setup-buildx-action from 3.10.0 to 3.11.1
---
updated-dependencies:
- dependency-name: docker/setup-buildx-action
  dependency-version: 3.11.1
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-23 03:39:03 +00:00
dependabot[bot] 0b3ea4d0ed chore(deps): bump cachetools from 6.0.0 to 6.1.0
Bumps [cachetools](https://github.com/tkem/cachetools) from 6.0.0 to 6.1.0.
- [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/tkem/cachetools/compare/v6.0.0...v6.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-23 03:38:14 +00:00
Matthias 822dce09b7 tests: keys are no longer added to the dict 2025-06-22 11:29:45 +00:00
Matthias 3465f35d75 feat: reuse exchange secrets for exchange credential removal 2025-06-20 20:23:22 +02:00
Matthias 2165c3e542 tests: update tests to ues new module 2025-06-20 20:19:38 +02:00
Matthias 5e059660d4 chore: move remove_exchange_credentials to config module 2025-06-20 20:19:20 +02:00
Matthias 5ca69687dc chore: minor refactor of config cleanup 2025-06-20 20:12:50 +02:00
Matthias d042085b24 chore: remove macos-13 from CI
(it's slow and going to be EOL in November 2025 anyway)
2025-06-19 20:16:18 +02:00
Matthias effd989796 Merge pull request #11895 from freqtrade/dependabot/pip/urllib3-2.5.0
chore(deps): bump urllib3 from 2.4.0 to 2.5.0
2025-06-19 06:26:04 +02:00
Matthias ca85deafb8 Merge pull request #11896 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2025-06-19 06:25:34 +02:00
Freqtrade Bot d46cc552be chore: update pre-commit hooks 2025-06-19 03:23:34 +00:00
dependabot[bot] 1dc2bcb47e chore(deps): bump urllib3 from 2.4.0 to 2.5.0
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.4.0 to 2.5.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.4.0...2.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-18 18:33:07 +00:00
Matthias d6bf20b7b4 Merge pull request #11889 from racequite/develop
docs: Fix comment
2025-06-18 07:07:20 +02:00
Matthias 2d842ea129 Merge pull request #11891 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-06-17 06:30:06 +02:00
Freqtrade Bot 76d5423eee chore: update pre-commit hooks 2025-06-17 03:24:27 +00:00
racequite 489b7e6d18 docs: Fix comment
Signed-off-by: racequite <quiterace@gmail.com>
2025-06-16 22:16:14 +08:00
Matthias 18dfb0fe39 Merge pull request #11886 from freqtrade/dependabot/pip/develop/numexpr-2.11.0
chore(deps): bump numexpr from 2.10.2 to 2.11.0
2025-06-16 09:47:45 +02:00
Matthias 63efed736c Merge pull request #11885 from freqtrade/dependabot/pip/develop/aiohttp-3.12.13
chore(deps): bump aiohttp from 3.12.11 to 3.12.13
2025-06-16 09:44:14 +02:00
Matthias 4c568e40c2 Merge pull request #11879 from freqtrade/dependabot/pip/develop/types-8fad52cb21
chore(deps-dev): bump types-requests from 2.32.0.20250602 to 2.32.4.20250611 in the types group
2025-06-16 09:20:14 +02:00
dependabot[bot] b54d231b79 chore(deps): bump numexpr from 2.10.2 to 2.11.0
Bumps [numexpr](https://github.com/pydata/numexpr) from 2.10.2 to 2.11.0.
- [Release notes](https://github.com/pydata/numexpr/releases)
- [Changelog](https://github.com/pydata/numexpr/blob/master/RELEASE_NOTES.rst)
- [Commits](https://github.com/pydata/numexpr/compare/v2.10.2...v2.11.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 07:09:21 +00:00
Matthias 58f361f8b3 Merge pull request #11888 from freqtrade/dependabot/pip/develop/bottleneck-1.5.0
chore(deps): bump bottleneck from 1.4.2 to 1.5.0
2025-06-16 09:08:13 +02:00
Matthias f78d383448 Merge pull request #11887 from freqtrade/dependabot/pip/develop/scikit-learn-1.7.0
chore(deps): bump scikit-learn from 1.6.1 to 1.7.0
2025-06-16 09:04:43 +02:00
Matthias 1be9a79e96 Merge pull request #11884 from freqtrade/dependabot/pip/develop/stable-baselines3-2.6.0
chore(deps): bump stable-baselines3 from 2.5.0 to 2.6.0
2025-06-16 09:00:54 +02:00
Matthias d9e98bb687 Merge pull request #11882 from freqtrade/dependabot/pip/develop/certifi-2025.6.15
chore(deps): bump certifi from 2025.4.26 to 2025.6.15
2025-06-16 08:37:07 +02:00
Matthias 7fd7061304 Merge pull request #11880 from freqtrade/dependabot/pip/develop/pytest-ed2313a973
chore(deps-dev): bump the pytest group with 2 updates
2025-06-16 08:36:20 +02:00
dependabot[bot] db50d2b85d chore(deps): bump aiohttp from 3.12.11 to 3.12.13
---
updated-dependencies:
- dependency-name: aiohttp
  dependency-version: 3.12.13
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 06:29:44 +00:00
Matthias d2a1e52d79 Merge pull request #11883 from freqtrade/dependabot/pip/develop/cryptography-45.0.4
chore(deps): bump cryptography from 45.0.3 to 45.0.4
2025-06-16 08:28:28 +02:00
Matthias 70e78e6521 Merge pull request #11881 from freqtrade/dependabot/pip/develop/pydantic-2.11.7
chore(deps): bump pydantic from 2.11.5 to 2.11.7
2025-06-16 07:59:13 +02:00
Matthias 913148bf1d Merge pull request #11877 from freqtrade/dependabot/github_actions/develop/rjstone/discord-webhook-notify-2.1.1
chore(deps): bump rjstone/discord-webhook-notify from 1.1.1 to 2.1.1
2025-06-16 06:40:30 +02:00
Matthias 0ecb3e45f6 chore: bump types-requests in pre-commit config 2025-06-16 06:39:55 +02:00
dependabot[bot] 74f870c435 chore(deps): bump bottleneck from 1.4.2 to 1.5.0
Bumps [bottleneck](https://github.com/pydata/bottleneck) from 1.4.2 to 1.5.0.
- [Release notes](https://github.com/pydata/bottleneck/releases)
- [Changelog](https://github.com/pydata/bottleneck/blob/master/RELEASE.rst)
- [Commits](https://github.com/pydata/bottleneck/compare/v1.4.2...v1.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 03:35:14 +00:00
dependabot[bot] 8f62462e15 chore(deps): bump scikit-learn from 1.6.1 to 1.7.0
Bumps [scikit-learn](https://github.com/scikit-learn/scikit-learn) from 1.6.1 to 1.7.0.
- [Release notes](https://github.com/scikit-learn/scikit-learn/releases)
- [Commits](https://github.com/scikit-learn/scikit-learn/compare/1.6.1...1.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 03:35:06 +00:00
dependabot[bot] 38183fe952 chore(deps): bump stable-baselines3 from 2.5.0 to 2.6.0
Bumps [stable-baselines3](https://github.com/DLR-RM/stable-baselines3) from 2.5.0 to 2.6.0.
- [Release notes](https://github.com/DLR-RM/stable-baselines3/releases)
- [Commits](https://github.com/DLR-RM/stable-baselines3/compare/v2.5.0...v2.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 03:34:39 +00:00
dependabot[bot] 215b111cff chore(deps): bump cryptography from 45.0.3 to 45.0.4
Bumps [cryptography](https://github.com/pyca/cryptography) from 45.0.3 to 45.0.4.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/45.0.3...45.0.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 03:34:35 +00:00
dependabot[bot] 4ea9d70743 chore(deps): bump certifi from 2025.4.26 to 2025.6.15
Bumps [certifi](https://github.com/certifi/python-certifi) from 2025.4.26 to 2025.6.15.
- [Commits](https://github.com/certifi/python-certifi/compare/2025.04.26...2025.06.15)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 03:34:29 +00:00
dependabot[bot] 0893350285 chore(deps): bump pydantic from 2.11.5 to 2.11.7
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.11.5 to 2.11.7.
- [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.11.5...v2.11.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 03:34:20 +00:00
dependabot[bot] 27bd34c3be chore(deps-dev): bump the pytest group with 2 updates
Bumps the pytest group with 2 updates: [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) and [pytest-cov](https://github.com/pytest-dev/pytest-cov).


Updates `pytest-asyncio` from 0.26.0 to 1.0.0
- [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases)
- [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.26.0...v1.0.0)

Updates `pytest-cov` from 6.1.1 to 6.2.1
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v6.1.1...v6.2.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 03:34:05 +00:00
dependabot[bot] ebf6ca1a5f 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.0.20250602 to 2.32.4.20250611
- [Commits](https://github.com/typeshed-internal/stub_uploader/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 03:33:36 +00:00
dependabot[bot] a91ed0b4d9 chore(deps): bump rjstone/discord-webhook-notify from 1.1.1 to 2.1.1
Bumps [rjstone/discord-webhook-notify](https://github.com/rjstone/discord-webhook-notify) from 1.1.1 to 2.1.1.
- [Release notes](https://github.com/rjstone/discord-webhook-notify/releases)
- [Commits](https://github.com/rjstone/discord-webhook-notify/compare/1399c1b2d57cc05894d506d2cfdc33c5f012b993...a975c85e53c8ea07b0b10f8461b0a90059816dcf)

---
updated-dependencies:
- dependency-name: rjstone/discord-webhook-notify
  dependency-version: 2.1.1
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-16 03:07:41 +00:00
Matthias 097b27208d chore: remove pointless TODO 2025-06-15 09:36:53 +02:00
Matthias 136623b3f5 Merge pull request #11874 from freqtrade/deps/ccxt_update
chore: bump ccxt to 4.4.89
2025-06-13 08:54:41 +02:00
Matthias 02648e29c4 chore: bump ccxt to 4.4.89 2025-06-13 06:43:10 +02:00
Matthias beb6d41360 chore: remove py_find_1st from requirements
this was only required by edge
2025-06-13 06:38:21 +02:00
Matthias a1207eca56 Merge pull request #11872 from freqtrade/dependabot/docker/python-3.13.5-slim-bookworm
chore(deps): bump python from 3.13.4-slim-bookworm to 3.13.5-slim-bookworm
2025-06-13 06:12:21 +02:00
dependabot[bot] 1a247a1035 chore(deps): bump python
Bumps python from 3.13.4-slim-bookworm to 3.13.5-slim-bookworm.

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-13 03:37:34 +00:00
Matthias 4e0203d4af Merge pull request #11870 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2025-06-12 06:24:48 +02:00
Freqtrade Bot 332518c1a0 chore: update pre-commit hooks 2025-06-12 03:22:52 +00:00
Matthias 15353f44f0 fix: improve "missing fee" recovery
closes #11813
2025-06-11 06:43:51 +02:00
Matthias 093cdae39c chore: add explaining comments to some columns 2025-06-11 06:41:46 +02:00
Matthias 5dd5b95d6a Merge pull request #11867 from freqtrade/maint/remove_edge
Remove Edge from Freqtrade
2025-06-10 20:19:53 +02:00
Matthias 20e0b542e3 docs: update command partials 2025-06-10 19:36:02 +02:00
Matthias 1d24f9047c Merge pull request #11869 from mrpabloyeah/fix-telegram-balance-command-when-fiat_display_currency-is-not-set
Fix Telegram balance command when fiat_display_currency is not set
2025-06-10 19:30:16 +02:00
Matthias f2c37e2575 test: remove missed test 2025-06-10 19:27:23 +02:00
Matthias 95fbe04225 test: add tests for edge startup errors 2025-06-10 18:24:47 +02:00
Matthias 43c5b8d0ed test: remove final edge test occurance 2025-06-10 18:21:45 +02:00
Matthias 301d85ef4f test: remove edge tests 2025-06-10 18:20:02 +02:00
mrpabloyeah 48f3d612e9 Fix Telegram balance command when fiat_display_currency is not set 2025-06-10 13:35:49 +02:00
Matthias c53c70d597 tests: further test cleanup 2025-06-10 07:27:03 +02:00
Matthias aa89ce80ab test: remove edge from tests (initial round) 2025-06-10 07:15:02 +02:00
Matthias 971dfef0ee chore: remove edge from RPC modules 2025-06-10 07:09:30 +02:00
Matthias 5d8a3cdb31 feat: fail if edge is in config 2025-06-10 07:09:20 +02:00
Matthias 5246eecaf6 chore: remove edge runmode 2025-06-10 07:09:10 +02:00
Matthias 18b6f374d6 chore: remove edge_cli module 2025-06-10 07:06:31 +02:00
Matthias 95c6997209 chore: remove edge as RunMode 2025-06-10 07:05:36 +02:00
Matthias ca79b84e4a docs: reword section around edge in utility subcommands 2025-06-10 07:05:36 +02:00
Matthias 3670df8771 chore: remove edge from ft_rest_client 2025-06-10 07:05:24 +02:00
Matthias ff06d58ace chore: remove edge module and config options 2025-06-10 07:01:33 +02:00
Matthias 1b4592a2f3 chore: remove edge from full_config 2025-06-10 07:00:11 +02:00
Matthias 5138e97b70 docs: remove edge from FAQ 2025-06-10 06:53:44 +02:00
Matthias 340cad3707 chore: remove edge from freqtradebot 2025-06-10 06:53:07 +02:00
Matthias ca32cb9e61 docs: remove further edge usages 2025-06-10 06:50:55 +02:00
Matthias c911b7fa70 chore: remove edge from config schema 2025-06-10 06:49:42 +02:00
Matthias 9323b6b946 chore: fail to start edge command. 2025-06-10 06:48:31 +02:00
Matthias 92ef1ef761 docs: deprecated: removal of Edge module 2025-06-10 06:46:39 +02:00
Matthias e38504b9db chore: remove edge from docs 2025-06-10 06:44:14 +02:00
Matthias e437847916 Merge pull request #11865 from freqtrade/dependabot/pip/requests-2.32.4
chore(deps): bump requests from 2.32.3 to 2.32.4
2025-06-10 06:28:18 +02:00
Matthias a3a30f9046 Merge pull request #11866 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-06-10 06:28:09 +02:00
Freqtrade Bot df507e2335 chore: update pre-commit hooks 2025-06-10 03:24:54 +00:00
dependabot[bot] 2f1f60a8fc chore(deps): bump requests from 2.32.3 to 2.32.4
Bumps [requests](https://github.com/psf/requests) from 2.32.3 to 2.32.4.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/main/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.32.3...v2.32.4)

---
updated-dependencies:
- dependency-name: requests
  dependency-version: 2.32.4
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 21:15:23 +00:00
Matthias 1c1a165ba4 chore(ci): improve docker-build action 2025-06-09 19:20:48 +02:00
Matthias 00fe02c854 feat(ci): extract docker container building 2025-06-09 19:20:48 +02:00
Matthias 6ff26b075e fix: error during logging shutdown 2025-06-09 19:12:57 +02:00
Matthias 03a46ec784 chore: reorder requirements logically 2025-06-09 18:06:09 +02:00
Matthias ec31764aed chore: update technical to 1.5.1 2025-06-09 18:05:07 +02:00
Matthias 1ba411cccf tests: Update empty error match 2025-06-09 18:04:21 +02:00
Matthias 7957231ce4 Merge pull request #11864 from freqtrade/deps/pip/talib
Update ta-lib to version 0.5.5
2025-06-09 12:10:19 +02:00
Matthias 6b88c49e0a chore: revert ta-lib install workarounds 2025-06-09 09:37:36 +02:00
Matthias 4fe99e0333 chore: update binary files for ta-lib 2025-06-09 09:37:36 +02:00
Matthias 9a38dfcc26 Merge pull request #11859 from freqtrade/dependabot/pip/develop/pytest-5ee6585723
chore(deps-dev): bump pytest from 8.3.5 to 8.4.0 in the pytest group
2025-06-09 09:27:04 +02:00
Matthias 3ecbfeb1d5 chore: bump ta-lib to 0.5.5 2025-06-09 09:20:37 +02:00
Matthias 02229c959f Merge pull request #11863 from freqtrade/dependabot/pip/develop/aiohttp-3.12.11
chore(deps): bump aiohttp from 3.12.6 to 3.12.11
2025-06-09 09:15:28 +02:00
Matthias 2a2f205fbe Merge pull request #11858 from freqtrade/dependabot/pip/develop/pandas-2.3.0
chore(deps): bump pandas from 2.2.3 to 2.3.0
2025-06-09 09:09:17 +02:00
dependabot[bot] c415dab846 chore(deps-dev): bump pytest from 8.3.5 to 8.4.0 in the pytest group
Bumps the pytest group with 1 update: [pytest](https://github.com/pytest-dev/pytest).


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

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 06:11:09 +00:00
dependabot[bot] 5b47960567 chore(deps): bump aiohttp from 3.12.6 to 3.12.11
---
updated-dependencies:
- dependency-name: aiohttp
  dependency-version: 3.12.11
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 06:09:32 +00:00
dependabot[bot] 3f9749b03b chore(deps): bump pandas from 2.2.3 to 2.3.0
Bumps [pandas](https://github.com/pandas-dev/pandas) from 2.2.3 to 2.3.0.
- [Release notes](https://github.com/pandas-dev/pandas/releases)
- [Commits](https://github.com/pandas-dev/pandas/compare/v2.2.3...v2.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 06:09:32 +00:00
Matthias 528b11af07 Merge pull request #11861 from freqtrade/dependabot/pip/develop/ruff-0.11.13
chore(deps-dev): bump ruff from 0.11.12 to 0.11.13
2025-06-09 08:08:29 +02:00
Matthias 0a31ff3747 Merge pull request #11860 from freqtrade/dependabot/pip/develop/ccxt-4.4.88
chore(deps): bump ccxt from 4.4.87 to 4.4.88
2025-06-09 08:08:17 +02:00
dependabot[bot] e1ee566634 chore(deps-dev): bump ruff from 0.11.12 to 0.11.13
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.12 to 0.11.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.11.12...0.11.13)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 03:41:24 +00:00
dependabot[bot] 1cff4d13de chore(deps): bump ccxt from 4.4.87 to 4.4.88
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.87 to 4.4.88.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/v4.4.87...v4.4.88)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-09 03:41:22 +00:00
Matthias 22b1bc7210 Merge pull request #11855 from hippocritical/develop
fix parsing trade-data for kraken_csv
2025-06-08 19:57:05 +02:00
hippocritical c741c168e8 Merge remote-tracking branch 'origin/develop' into develop 2025-06-08 19:33:50 +02:00
hippocritical c997f02085 removed %1 = 0 since it was not truly necessary 2025-06-08 19:31:17 +02:00
hippocritical ead6d89e35 Merge branch 'freqtrade:develop' into develop 2025-06-08 14:07:49 +02:00
hippocritical 985c15bdd9 checking kraken data for any rows not having a timestamp in the timestamp column, purging those
(there was a text in the 1st row of USDGEUR.csv, making the conversion fail previously)
2025-06-08 14:07:31 +02:00
Matthias 7fb7c43e45 chore: pip_constraint fix for dockerfile 2025-06-08 12:52:06 +02:00
Matthias 0e68de0c6d feat: document --notes usage 2025-06-08 12:51:38 +02:00
Matthias ba5f771e65 chore: update documentation command 2025-06-08 12:46:25 +02:00
Matthias e0f147e076 feat: add "--notes" to backtest command
closes #11847
2025-06-08 12:42:26 +02:00
Matthias b874ceaf2f chore: improved "pins" for numpy 2025-06-08 11:11:15 +02:00
Matthias 11f339f802 chore(ci): UV workaround for build-online 2025-06-08 10:25:41 +02:00
Matthias 9d46fd21dc chore(ci): clean up after pip install 2025-06-08 10:17:14 +02:00
Matthias 286f385f99 chore(ci): use proper directory 2025-06-08 10:07:01 +02:00
Matthias 69cca13d15 chore: use UV_BUILD_CONSTRAINT 2025-06-08 10:05:56 +02:00
Matthias 37d03a5b44 chore: proper naming for environment var 2025-06-08 10:04:04 +02:00
Matthias 0e1d3e279c chore: attempt CI workaround for ta-lib build failure 2025-06-08 10:02:52 +02:00
Matthias ae671c736e feat: don't lowercase ccxt_config keys
closes #11852
2025-06-08 09:52:58 +02:00
Matthias 28064d013c feat: add ccxt_sync_config to json schema 2025-06-08 09:36:38 +02:00
Matthias 5b9c03e7e2 fix: don't filter min duration on > 0
closes #11838
2025-06-08 09:15:52 +02:00
Matthias f2569e36e8 chore: develop doesn't need to depend on coveralls
closes #11848
2025-06-07 20:58:13 +02:00
Matthias bf2092977c chore: add docstring 2025-06-07 16:53:14 +02:00
Matthias 613e31a9a5 chore: simplify handle_order_fee code 2025-06-07 08:43:26 +02:00
Matthias 326a4f17d2 chore: update missleading comment 2025-06-07 08:39:08 +02:00
Matthias a8d617d747 chore: load ft_fee_base when loading from_json 2025-06-07 08:35:19 +02:00
Matthias ba9f795df5 fix: Order is not bound to a Session
closes #11831
2025-06-06 20:54:34 +02:00
Matthias dbccc69f05 Merge pull request #11841 from freqtrade/dependabot/docker/python-3.13.4-slim-bookworm
chore(deps): bump python from 3.12.10-slim-bookworm to 3.13.4-slim-bookworm
2025-06-06 06:35:38 +02:00
Matthias db80318946 Merge pull request #11843 from freqtrade/dependabot/pip/torch-2.7.1
chore(deps): bump torch from 2.7.0 to 2.7.1
2025-06-05 16:00:20 +02:00
dependabot[bot] 5698793dcc chore(deps): bump torch from 2.7.0 to 2.7.1
Bumps [torch](https://github.com/pytorch/pytorch) from 2.7.0 to 2.7.1.
- [Release notes](https://github.com/pytorch/pytorch/releases)
- [Changelog](https://github.com/pytorch/pytorch/blob/main/RELEASE.md)
- [Commits](https://github.com/pytorch/pytorch/compare/v2.7.0...v2.7.1)

---
updated-dependencies:
- dependency-name: torch
  dependency-version: 2.7.1
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-05 13:32:25 +00:00
Matthias bf96be2b71 chore: bump ccxt to 4.4.87 2025-06-05 06:46:17 +02:00
dependabot[bot] 2af454333b chore(deps): bump python
Bumps python from 3.12.10-slim-bookworm to 3.13.4-slim-bookworm.

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-05 04:32:28 +00:00
Matthias cb869a2e4d chore: bump armhf dockerfile to 3.11.13 2025-06-05 06:31:29 +02:00
Matthias afc5205bea Merge pull request #11840 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2025-06-05 06:26:34 +02:00
Freqtrade Bot 842f3a2a36 chore: update pre-commit hooks 2025-06-05 03:23:11 +00:00
Matthias 582871b0d5 Merge pull request #11837 from freqtrade/feat/python_13
Add support for python 3.13
2025-06-04 18:24:35 +02:00
Matthias c6c2934741 chore(ci): fix 3.13 tests and incompatibilities 2025-06-04 08:18:50 +02:00
Matthias d3b5be6cb6 chore: update docs and ft_client taxonomy 2025-06-04 07:21:13 +02:00
Matthias 473a15c4ef chore: scripts should support 3.13 2025-06-04 07:20:58 +02:00
Matthias 2c4452453a chore: update pyproject for python 3.13 2025-06-04 07:19:40 +02:00
Matthias da6affcd6a chore: run CI against 3.13 2025-06-04 07:18:24 +02:00
Matthias 3ebbe2b56c chore: pin ta-lib<0.6 to avoid false installs 2025-06-04 07:16:29 +02:00
Matthias 8f13a5db29 Merge pull request #11834 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-06-03 06:41:42 +02:00
Matthias 3155dfc14c Merge pull request #11789 from freqtrade/fix/customdata_sessions
fix: improved session handling for custom_data sessions
2025-06-03 06:30:39 +02:00
Freqtrade Bot cc30e71ab2 chore: update pre-commit hooks 2025-06-03 03:23:16 +00:00
Matthias f5cb486f5e Merge pull request #11828 from freqtrade/dependabot/pip/develop/mypy-1.16.0
chore(deps-dev): bump mypy from 1.15.0 to 1.16.0
2025-06-02 11:03:05 +02:00
Matthias 42c8795141 Merge pull request #11827 from freqtrade/dependabot/pip/develop/types-59cf8d3746
chore(deps-dev): bump types-requests from 2.32.0.20250515 to 2.32.0.20250602 in the types group
2025-06-02 09:40:14 +02:00
Matthias f878d051df Merge pull request #11823 from freqtrade/dependabot/pip/develop/ccxt-4.4.86
chore(deps): bump ccxt from 4.4.85 to 4.4.86
2025-06-02 09:13:28 +02:00
Matthias aefa7e401c chore: bump mypy in pre-commit config 2025-06-02 08:54:19 +02:00
Matthias dc06510d4a chore: adjust type-ignores for new mypy version 2025-06-02 08:54:19 +02:00
Matthias 7740b61f02 chore: remove unused #type-ignore settings 2025-06-02 08:54:19 +02:00
dependabot[bot] 950f5a722e chore(deps-dev): bump mypy from 1.15.0 to 1.16.0
Bumps [mypy](https://github.com/python/mypy) from 1.15.0 to 1.16.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.15.0...v1.16.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 06:49:06 +00:00
Matthias 9b56245b1d Merge pull request #11830 from freqtrade/dependabot/pip/develop/ruff-0.11.12
chore(deps-dev): bump ruff from 0.11.11 to 0.11.12
2025-06-02 08:47:43 +02:00
Matthias b05fb57fb7 chore: huobijp rename to bittrade
aligned with https://github.com/ccxt/ccxt/pull/26036
2025-06-02 08:40:05 +02:00
Matthias 1a4db77131 chore: types-requests update 2025-06-02 08:34:52 +02:00
dependabot[bot] 71920cefb1 chore(deps): bump ccxt from 4.4.85 to 4.4.86
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.85 to 4.4.86.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/v4.4.85...v4.4.86)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 06:34:10 +00:00
Matthias b5cbbd7c30 Merge pull request #11822 from freqtrade/dependabot/pip/develop/pytest-6fa8ff6387
chore(deps-dev): bump the pytest group with 2 updates
2025-06-02 08:33:40 +02:00
Matthias f75a0817a6 Merge pull request #11826 from freqtrade/dependabot/pip/develop/plotly-6.1.2
chore(deps): bump plotly from 6.1.1 to 6.1.2
2025-06-02 08:33:17 +02:00
Matthias d12132ae7a Merge pull request #11829 from freqtrade/dependabot/pip/develop/aiohttp-3.12.6
chore(deps): bump aiohttp from 3.11.18 to 3.12.6
2025-06-02 08:32:17 +02:00
Matthias a81bec0da2 Merge pull request #11825 from freqtrade/dependabot/pip/develop/jsonschema-4.24.0
chore(deps): bump jsonschema from 4.23.0 to 4.24.0
2025-06-02 08:31:50 +02:00
Matthias cc41cb831f Merge pull request #11824 from freqtrade/dependabot/pip/develop/uvicorn-0.34.3
chore(deps): bump uvicorn from 0.34.2 to 0.34.3
2025-06-02 08:31:35 +02:00
dependabot[bot] 2b70ec896d chore(deps-dev): bump ruff from 0.11.11 to 0.11.12
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.11 to 0.11.12.
- [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.11.11...0.11.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 03:40:08 +00:00
dependabot[bot] 470dbb6060 chore(deps): bump aiohttp from 3.11.18 to 3.12.6
---
updated-dependencies:
- dependency-name: aiohttp
  dependency-version: 3.12.6
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 03:39:55 +00:00
dependabot[bot] afc61ca788 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.0.20250515 to 2.32.0.20250602
- [Commits](https://github.com/typeshed-internal/stub_uploader/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 03:37:48 +00:00
dependabot[bot] ba62e5e007 chore(deps): bump plotly from 6.1.1 to 6.1.2
Bumps [plotly](https://github.com/plotly/plotly.py) from 6.1.1 to 6.1.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.1.1...v6.1.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 03:37:38 +00:00
dependabot[bot] 9af401b091 chore(deps): bump jsonschema from 4.23.0 to 4.24.0
Bumps [jsonschema](https://github.com/python-jsonschema/jsonschema) from 4.23.0 to 4.24.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.23.0...v4.24.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 03:37:22 +00:00
dependabot[bot] 1fdbace02d chore(deps): bump uvicorn from 0.34.2 to 0.34.3
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.34.2 to 0.34.3.
- [Release notes](https://github.com/encode/uvicorn/releases)
- [Changelog](https://github.com/encode/uvicorn/blob/master/docs/release-notes.md)
- [Commits](https://github.com/encode/uvicorn/compare/0.34.2...0.34.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 03:37:05 +00:00
dependabot[bot] cfeda4493b chore(deps-dev): bump the pytest group with 2 updates
Bumps the pytest group with 2 updates: [pytest-mock](https://github.com/pytest-dev/pytest-mock) and [pytest-xdist](https://github.com/pytest-dev/pytest-xdist).


Updates `pytest-mock` from 3.14.0 to 3.14.1
- [Release notes](https://github.com/pytest-dev/pytest-mock/releases)
- [Changelog](https://github.com/pytest-dev/pytest-mock/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.14.0...v3.14.1)

Updates `pytest-xdist` from 3.6.1 to 3.7.0
- [Release notes](https://github.com/pytest-dev/pytest-xdist/releases)
- [Changelog](https://github.com/pytest-dev/pytest-xdist/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-xdist/compare/v3.6.1...v3.7.0)

---
updated-dependencies:
- dependency-name: pytest-mock
  dependency-version: 3.14.1
  dependency-type: direct:development
  update-type: version-update:semver-patch
  dependency-group: pytest
- dependency-name: pytest-xdist
  dependency-version: 3.7.0
  dependency-type: direct:development
  update-type: version-update:semver-minor
  dependency-group: pytest
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-06-02 03:35:48 +00:00
Matthias 0de48d90f8 Merge pull request #11820 from mrpabloyeah/fix-telegram-profit-stats-when-fiat_display_currency-is-not-set
Fix telegram profit stats when fiat_display_currency is not set
2025-06-01 14:53:24 +02:00
mrpabloyeah c4eb26be9d Fix telegram profit stats when fiat_display_currency is not set 2025-06-01 13:53:17 +02:00
Matthias 04a8539f10 Merge pull request #11819 from arenstar/patch-3
fix: order_by_id always true
2025-06-01 13:28:05 +02:00
David Arena d600461a84 fix: order_by_id always true 2025-06-01 12:03:44 +02:00
Matthias ce46cd7343 Merge pull request #11646 from freqtrade/deps/numpy2
Bump numpy to 2.x
2025-06-01 09:13:58 +02:00
Matthias d1cca4ac51 Merge pull request #11796 from freqtrade/dependabot/pip/develop/ccxt-4.4.85
chore(deps): bump ccxt from 4.4.82 to 4.4.85
2025-05-31 17:57:26 +02:00
Matthias eb5d5e0d3f chore: bump dev version to 2025.6-dev 2025-05-31 16:39:46 +02:00
Matthias 77dcc5354c test: update hyperliquid test for new load_markets behavior 2025-05-30 07:08:58 +02:00
Matthias 3ac4a872b1 Merge remote-tracking branch 'origin/develop' into dependabot/pip/develop/ccxt-4.4.85 2025-05-29 20:14:05 +02:00
Matthias b8b94d58c4 test: update tests for new reload_markets behavior 2025-05-29 20:12:59 +02:00
Matthias 33b5482065 fix: memory leak on binance in combination with aiohttp>3.10
apparently, returning big data through
run_until_complete can cause this (or

closes #11317
2025-05-29 07:06:57 +02:00
Matthias 43fea43363 chore: bump aiohttp to 3.11.18
Aligns dependency with ccxt requirements.
2025-05-26 07:15:38 +02:00
dependabot[bot] 168593d7af chore(deps): bump ccxt from 4.4.82 to 4.4.85
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.82 to 4.4.85.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/v4.4.82...v4.4.85)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-05-26 03:13:34 +00:00
Matthias 29ae71333a fix: improved session handling for custom_data sessions 2025-05-25 08:37:01 +02:00
Matthias 7a84d1ecba chore: bump numpy to 2.2.6 2025-05-20 07:14:55 +02:00
Matthias bc78e2e1b9 feat: update to ft-pandas-ta
Freqtrade's compatibility fork of pandas-ta
available at https://github.com/freqtrade/pandas-ta/
2025-05-13 07:03:52 +02:00
Matthias 3fb6bc888b chore: Mypy-exclusions to account for ta-lib import problem 2025-05-12 18:22:01 +02:00
Matthias 0228ec796b chore: bump datasieve to 0.1.9 2025-05-11 16:28:15 +02:00
Matthias 69de7b4045 chore: bump datasieve to 0.1.8 2025-05-10 13:51:37 +02:00
Matthias 825c059c2e chore: update ta-lib binaries 2025-04-16 07:12:56 +02:00
Matthias 8e8f4dbdd3 chore: drop stable-baselines install from mac x64
in line with #11541
2025-04-15 06:38:08 +02:00
Matthias 865c253922 chore: bump dockerfile and windows install numpy 2025-04-15 06:31:16 +02:00
Matthias cd6e06c86e chore: bump ta-lib to 0.5.4
This version supports numpy2 - while still remaining on ta-lib C of 0.4
This will avoid huge update problems, as the underlying library doesn't need to be updated
2025-04-15 06:30:34 +02:00
Matthias 422f225e8e chore: update numpy pyproject lock range 2025-04-15 06:29:26 +02:00
Matthias 9921c54882 chore: bump nunmpy to 2.2.4 2025-04-15 06:28:40 +02:00
110 changed files with 15190 additions and 12697 deletions
+24 -106
View File
@@ -25,7 +25,7 @@ jobs:
strategy:
matrix:
os: [ "ubuntu-22.04", "ubuntu-24.04" ]
python-version: ["3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
@@ -38,7 +38,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
with:
activate-environment: true
enable-cache: true
@@ -90,6 +90,7 @@ jobs:
COVERALLS_REPO_TOKEN: 6D1m0xupS3FgutfuGao8keFf9Hc0FpIXu
run: |
# Allow failure for coveralls
uv pip install coveralls
coveralls || true
- name: Run json schema extract
@@ -103,6 +104,8 @@ jobs:
python build_helpers/create_command_partials.py
- name: Check for repository changes
# TODO: python 3.13 slightly changed the output of argparse.
if: (matrix.python-version != '3.13')
run: |
if [ -n "$(git status --porcelain)" ]; then
echo "Repository is dirty, changes detected:"
@@ -145,7 +148,7 @@ jobs:
mypy freqtrade scripts tests
- name: Discord notification
uses: rjstone/discord-webhook-notify@1399c1b2d57cc05894d506d2cfdc33c5f012b993 #v1.1.1
uses: rjstone/discord-webhook-notify@c2597273488aeda841dd1e891321952b51f7996f #v2.2.1
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
severity: error
@@ -156,8 +159,8 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ "macos-13", "macos-14", "macos-15" ]
python-version: ["3.10", "3.11", "3.12"]
os: [ "macos-14", "macos-15" ]
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
@@ -171,7 +174,7 @@ jobs:
check-latest: true
- name: Install uv
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
with:
activate-environment: true
enable-cache: true
@@ -272,7 +275,7 @@ jobs:
mypy freqtrade scripts
- name: Discord notification
uses: rjstone/discord-webhook-notify@1399c1b2d57cc05894d506d2cfdc33c5f012b993 #v1.1.1
uses: rjstone/discord-webhook-notify@c2597273488aeda841dd1e891321952b51f7996f #v2.2.1
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
severity: info
@@ -285,7 +288,7 @@ jobs:
strategy:
matrix:
os: [ windows-latest ]
python-version: ["3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v4
@@ -298,7 +301,7 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Install uv
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
with:
activate-environment: true
enable-cache: true
@@ -366,7 +369,7 @@ jobs:
shell: powershell
- name: Discord notification
uses: rjstone/discord-webhook-notify@1399c1b2d57cc05894d506d2cfdc33c5f012b993 #v1.1.1
uses: rjstone/discord-webhook-notify@c2597273488aeda841dd1e891321952b51f7996f #v2.2.1
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
severity: error
@@ -424,7 +427,7 @@ jobs:
mkdocs build
- name: Discord notification
uses: rjstone/discord-webhook-notify@1399c1b2d57cc05894d506d2cfdc33c5f012b993 #v1.1.1
uses: rjstone/discord-webhook-notify@c2597273488aeda841dd1e891321952b51f7996f #v2.2.1
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
severity: error
@@ -446,7 +449,7 @@ jobs:
python-version: "3.12"
- name: Install uv
uses: astral-sh/setup-uv@f0ec1fc3b38f5e7cd731bb6ce540c5af426746bb # v6.1.0
uses: astral-sh/setup-uv@bd01e18f51369d5a26f1651c3cb451d3417e3bba # v6.3.1
with:
activate-environment: true
enable-cache: true
@@ -512,7 +515,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Discord notification
uses: rjstone/discord-webhook-notify@1399c1b2d57cc05894d506d2cfdc33c5f012b993 #v1.1.1
uses: rjstone/discord-webhook-notify@c2597273488aeda841dd1e891321952b51f7996f #v2.2.1
if: always() && steps.check.outputs.has-permission && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
severity: info
@@ -616,100 +619,15 @@ jobs:
uses: pypa/gh-action-pypi-publish@76f52bc884231f62b9a034ebfe128415bbaabdfc # v1.12.4
deploy-docker:
docker-build:
name: "Docker Build and Deploy"
needs: [ build-linux, build-macos, build-windows, docs-check, mypy-version-check, pre-commit ]
runs-on: ubuntu-22.04
if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade'
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Extract branch name
id: extract-branch
run: |
echo "GITHUB_REF='${GITHUB_REF}'"
echo "branch=${GITHUB_REF##*/}" >> "$GITHUB_OUTPUT"
- name: Dockerhub login
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: |
echo "${DOCKER_PASSWORD}" | docker login --username ${DOCKER_USERNAME} --password-stdin
# We need docker experimental to pull the ARM image.
- name: Switch docker to experimental
run: |
docker version -f '{{.Server.Experimental}}'
echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker
docker version -f '{{.Server.Experimental}}'
- name: Set up QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@b5ca514318bd6ebac0fb2aedd5d36ec1b5c232a2 #v3.10.0
- name: Available platforms
run: echo ${PLATFORMS}
env:
PLATFORMS: ${{ steps.buildx.outputs.platforms }}
- name: Build and test and push docker images
env:
BRANCH_NAME: ${{ steps.extract-branch.outputs.branch }}
run: |
build_helpers/publish_docker_multi.sh
deploy-arm:
name: "Deploy Docker"
uses: ./.github/workflows/docker-build.yml
permissions:
packages: write
needs: [ deploy-docker ]
# Only run on 64bit machines
runs-on: [self-hosted, linux, ARM64]
if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade'
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Extract branch name
id: extract-branch
run: |
echo "GITHUB_REF='${GITHUB_REF}'"
echo "branch=${GITHUB_REF##*/}" >> "$GITHUB_OUTPUT"
- name: Dockerhub login
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: |
echo "${DOCKER_PASSWORD}" | docker login --username ${DOCKER_USERNAME} --password-stdin
- name: Build and test and push docker images
env:
BRANCH_NAME: ${{ steps.extract-branch.outputs.branch }}
GHCR_USERNAME: ${{ github.actor }}
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
build_helpers/publish_docker_arm64.sh
- name: Discord notification
uses: rjstone/discord-webhook-notify@1399c1b2d57cc05894d506d2cfdc33c5f012b993 #v1.1.1
if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) && (github.event_name != 'schedule')
with:
severity: info
details: Deploy Succeeded!
webhookUrl: ${{ secrets.DISCORD_WEBHOOK }}
contents: read
secrets:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
DISCORD_WEBHOOK: ${{ secrets.DISCORD_WEBHOOK }}
+132
View File
@@ -0,0 +1,132 @@
name: Docker Build and Deploy
on:
workflow_call:
secrets:
DOCKER_PASSWORD:
required: true
DOCKER_USERNAME:
required: true
DISCORD_WEBHOOK:
required: false
workflow_dispatch:
inputs:
branch_name:
description: 'Branch name to build Docker images for'
required: false
default: 'develop'
type: string
permissions:
contents: read
jobs:
deploy-docker:
runs-on: ubuntu-22.04
if: github.repository == 'freqtrade/freqtrade'
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Extract branch name
id: extract-branch
env:
BRANCH_NAME_INPUT: ${{ github.event.inputs.branch_name }}
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
BRANCH_NAME="${BRANCH_NAME_INPUT}"
else
BRANCH_NAME="${GITHUB_REF##*/}"
fi
echo "GITHUB_REF='${GITHUB_REF}'"
echo "branch=${BRANCH_NAME}" >> "$GITHUB_OUTPUT"
- name: Dockerhub login
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: |
echo "${DOCKER_PASSWORD}" | docker login --username ${DOCKER_USERNAME} --password-stdin
# We need docker experimental to pull the ARM image.
- name: Switch docker to experimental
run: |
docker version -f '{{.Server.Experimental}}'
echo $'{\n "experimental": true\n}' | sudo tee /etc/docker/daemon.json
sudo systemctl restart docker
docker version -f '{{.Server.Experimental}}'
- name: Set up QEMU
uses: docker/setup-qemu-action@29109295f81e9208d7d86ff1c6c12d2833863392 # v3.6.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 #v3.11.1
- name: Available platforms
run: echo ${PLATFORMS}
env:
PLATFORMS: ${{ steps.buildx.outputs.platforms }}
- name: Build and test and push docker images
env:
BRANCH_NAME: ${{ steps.extract-branch.outputs.branch }}
run: |
build_helpers/publish_docker_multi.sh
deploy-arm:
name: "Deploy Docker"
permissions:
packages: write
needs: [ deploy-docker ]
# Only run on 64bit machines
runs-on: [self-hosted, linux, ARM64]
if: github.repository == 'freqtrade/freqtrade'
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Extract branch name
id: extract-branch
env:
BRANCH_NAME_INPUT: ${{ github.event.inputs.branch_name }}
run: |
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
BRANCH_NAME="${BRANCH_NAME_INPUT}"
else
BRANCH_NAME="${GITHUB_REF##*/}"
fi
echo "GITHUB_REF='${GITHUB_REF}'"
echo "branch=${BRANCH_NAME}" >> "$GITHUB_OUTPUT"
- name: Dockerhub login
env:
DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
run: |
echo "${DOCKER_PASSWORD}" | docker login --username ${DOCKER_USERNAME} --password-stdin
- name: Build and test and push docker images
env:
BRANCH_NAME: ${{ steps.extract-branch.outputs.branch }}
GHCR_USERNAME: ${{ github.actor }}
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
build_helpers/publish_docker_arm64.sh
- name: Discord notification
uses: rjstone/discord-webhook-notify@c2597273488aeda841dd1e891321952b51f7996f #v2.2.1
if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) && (github.event_name != 'schedule')
with:
severity: info
details: Deploy Succeeded!
webhookUrl: ${{ secrets.DISCORD_WEBHOOK }}
+6 -6
View File
@@ -14,21 +14,21 @@ repos:
additional_dependencies: ["python-rapidjson", "jsonschema"]
- repo: https://github.com/pycqa/flake8
rev: "7.2.0"
rev: "7.3.0"
hooks:
- id: flake8
additional_dependencies: [Flake8-pyproject]
# stages: [push]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.15.0"
rev: "v1.16.1"
hooks:
- id: mypy
exclude: build_helpers
additional_dependencies:
- types-cachetools==6.0.0.20250525
- types-filelock==3.2.7
- types-requests==2.32.0.20250515
- types-requests==2.32.4.20250611
- types-tabulate==0.9.0.20241207
- types-python-dateutil==2.9.0.20250516
- SQLAlchemy==2.0.41
@@ -43,7 +43,7 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.11.11'
rev: 'v0.12.1'
hooks:
- id: ruff
- id: ruff-format
@@ -69,7 +69,7 @@ repos:
)$
- repo: https://github.com/stefmolin/exif-stripper
rev: 0.6.2
rev: 1.0.0
hooks:
- id: strip-exif
@@ -82,6 +82,6 @@ repos:
# Ensure github actions remain safe
- repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v1.8.0
rev: v1.11.0
hooks:
- id: zizmor
+2 -2
View File
@@ -1,4 +1,4 @@
FROM python:3.12.10-slim-bookworm as base
FROM python:3.13.5-slim-bookworm as base
# Setup env
ENV LANG C.UTF-8
@@ -35,7 +35,7 @@ ENV LD_LIBRARY_PATH /usr/local/lib
# Install dependencies
COPY --chown=ftuser:ftuser requirements.txt requirements-hyperopt.txt /freqtrade/
USER ftuser
RUN pip install --user --no-cache-dir "numpy<2.0" \
RUN pip install --user --no-cache-dir "numpy<3.0" \
&& pip install --user --no-cache-dir -r requirements-hyperopt.txt
# Copy dependencies to runtime-image
-2
View File
@@ -70,7 +70,6 @@ Please find the complete documentation on the [freqtrade website](https://www.fr
- [x] **Backtesting**: Run a simulation of your buy/sell strategy.
- [x] **Strategy Optimization by machine learning**: Use machine learning to optimize your buy/sell strategy parameters with real exchange data.
- [X] **Adaptive prediction modeling**: Build a smart strategy with FreqAI that self-trains to the market via adaptive machine learning methods. [Learn more](https://www.freqtrade.io/en/stable/freqai/)
- [x] **Edge position sizing** Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. [Learn more](https://www.freqtrade.io/en/stable/edge/).
- [x] **Whitelist crypto-currencies**: Select which crypto-currency you want to trade or use dynamic whitelists.
- [x] **Blacklist crypto-currencies**: Select which crypto-currency you want to avoid.
- [x] **Builtin WebUI**: Builtin web UI to manage your bot.
@@ -112,7 +111,6 @@ positional arguments:
backtesting-show Show past Backtest results
backtesting-analysis
Backtest Analysis module.
edge Edge module.
hyperopt Hyperopt module.
hyperopt-list List Hyperopt results
hyperopt-show Show details of Hyperopt results
+1 -1
View File
@@ -3,7 +3,7 @@
python -m pip install --upgrade pip
python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
pip install -U wheel "numpy<2"
pip install -U wheel "numpy<3.0"
pip install --only-binary ta-lib --find-links=build_helpers\ ta-lib
pip install -r requirements-dev.txt
+5 -51
View File
@@ -538,10 +538,6 @@
"description": "Exchange configuration.",
"$ref": "#/definitions/exchange"
},
"edge": {
"description": "Edge configuration.",
"$ref": "#/definitions/edge"
},
"log_config": {
"description": "Logging configuration.",
"$ref": "#/definitions/logging"
@@ -1247,7 +1243,11 @@
"type": "object"
},
"ccxt_async_config": {
"description": "CCXT asynchronous configuration settings.",
"description": "CCXT asynchronous configuration settings.Usually ccxt_config should be used instead.",
"type": "object"
},
"ccxt_sync_config": {
"description": "CCXT synchronous configuration settings. Usually ccxt_config should be used instead.",
"type": "object"
}
},
@@ -1255,52 +1255,6 @@
"name"
]
},
"edge": {
"type": "object",
"properties": {
"enabled": {
"type": "boolean"
},
"process_throttle_secs": {
"type": "integer",
"minimum": 600
},
"calculate_since_number_of_days": {
"type": "integer"
},
"allowed_risk": {
"type": "number"
},
"stoploss_range_min": {
"type": "number"
},
"stoploss_range_max": {
"type": "number"
},
"stoploss_range_step": {
"type": "number"
},
"minimum_winrate": {
"type": "number"
},
"minimum_expectancy": {
"type": "number"
},
"min_trade_number": {
"type": "number"
},
"max_trade_duration_minute": {
"type": "integer"
},
"remove_pumps": {
"type": "boolean"
}
},
"required": [
"process_throttle_secs",
"allowed_risk"
]
},
"logging": {
"type": "object",
"properties": {
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -2,7 +2,7 @@
"$schema": "https://schema.freqtrade.io/schema.json",
"max_open_trades": 3,
"stake_currency": "USDT",
"stake_amount": 0.05,
"stake_amount": 30,
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"timeframe": "5m",
-14
View File
@@ -121,20 +121,6 @@
"outdated_offset": 5,
"markets_refresh_interval": 60
},
"edge": {
"enabled": false,
"process_throttle_secs": 3600,
"calculate_since_number_of_days": 7,
"allowed_risk": 0.01,
"stoploss_range_min": -0.01,
"stoploss_range_max": -0.1,
"stoploss_range_step": -0.01,
"minimum_winrate": 0.60,
"minimum_expectancy": 0.20,
"min_trade_number": 10,
"max_trade_duration_minute": 1440,
"remove_pumps": false
},
"telegram": {
"enabled": false,
"token": "your_telegram_token",
+2 -2
View File
@@ -1,4 +1,4 @@
FROM python:3.11.12-slim-bookworm as base
FROM python:3.11.13-slim-bookworm as base
# Setup env
ENV LANG C.UTF-8
@@ -34,7 +34,7 @@ COPY build_helpers/* /tmp/
# Install dependencies
COPY --chown=ftuser:ftuser requirements.txt /freqtrade/
USER ftuser
RUN pip install --user --no-cache-dir "numpy<2" \
RUN pip install --user --no-cache-dir "numpy<3.0" \
&& pip install --user --no-index --find-links /tmp/ pyarrow TA-Lib \
&& pip install --user --no-cache-dir -r requirements.txt
+6
View File
@@ -5,6 +5,8 @@ This page explains how to validate your strategy performance by using Backtestin
Backtesting requires historic data to be available.
To learn how to get data for the pairs and exchange you're interested in, head over to the [Data Downloading](data-download.md) section of the documentation.
Backtesting is also available in [webserver mode](freq-ui.md#backtesting), which allows you to run backtests via the web interface.
## Backtesting command reference
--8<-- "commands/backtesting.md"
@@ -435,6 +437,10 @@ To save time, by default backtest will reuse a cached result from within the las
To further analyze your backtest results, freqtrade will export the trades to file by default.
You can then load the trades to perform further analysis as shown in the [data analysis](strategy_analysis_example.md#load-backtest-results-to-pandas-dataframe) backtesting section.
Also, you can use freqtrade in [webserver mode](freq-ui.md#backtesting) to visualize the backtest results in a web interface.
This mode also allows you to load existing backtest results, so you can analyze them without running the backtest again.
For this mode - `--notes "<notes>"` can be used to add notes to the backtest results, which will be shown in the web interface.
### Backtest output file
The output file freqtrade produces is a zip file containing the following files:
+2 -1
View File
@@ -17,7 +17,7 @@ usage: freqtrade backtesting [-h] [-v] [--no-color] [--logfile FILE] [-V]
[--export-filename PATH]
[--breakdown {day,week,month,year} [{day,week,month,year} ...]]
[--cache {none,day,week,month}]
[--freqai-backtest-live-models]
[--freqai-backtest-live-models] [--notes TEXT]
options:
-h, --help show this help message and exit
@@ -73,6 +73,7 @@ options:
age (default: day).
--freqai-backtest-live-models
Run backtest with ready models.
--notes TEXT Add notes to the backtest results.
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
-6
View File
@@ -7,7 +7,6 @@ usage: freqtrade edge [-h] [-v] [--no-color] [--logfile FILE] [-V] [-c PATH]
[--data-format-ohlcv {json,jsongz,feather,parquet}]
[--max-open-trades INT] [--stake-amount STAKE_AMOUNT]
[--fee FLOAT] [-p PAIRS [PAIRS ...]]
[--stoplosses STOPLOSS_RANGE]
options:
-h, --help show this help message and exit
@@ -29,11 +28,6 @@ options:
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Limit command to these pairs. Pairs are space-
separated.
--stoplosses STOPLOSS_RANGE
Defines a range of stoploss values against which edge
will assess the strategy. The format is "min,max,step"
(without any space). Example:
`--stoplosses=-0.01,-0.1,-0.001`
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
+1 -1
View File
@@ -22,7 +22,7 @@ positional arguments:
backtesting-show Show past Backtest results
backtesting-analysis
Backtest Analysis module.
edge Edge module.
edge Edge module. No longer part of Freqtrade
hyperopt Hyperopt module.
hyperopt-list List Hyperopt results
hyperopt-show Show details of Hyperopt results
-1
View File
@@ -234,7 +234,6 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `exchange.only_from_ccxt` | Prevent data-download from data.binance.vision. Leaving this as false can greatly speed up downloads, but may be problematic if the site is not available.<br>*Defaults to `false`*<br> **Datatype:** Boolean
| `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
| | **Plugins**
| `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation of all possible configuration options.
| `pairlists` | Define one or more pairlists to be used. [More information](plugins.md#pairlists-and-pairlist-handlers). <br>*Defaults to `StaticPairList`.* <br> **Datatype:** List of Dicts
| | **Telegram**
| `telegram.enabled` | Enable the usage of Telegram. <br> **Datatype:** Boolean
+5
View File
@@ -93,3 +93,8 @@ Please use the [`convert-data` subcommand](data-download.md#sub-command-convert-
Configuring syslog and journald via `--logfile systemd` and `--logfile journald` respectively has been deprecated in 2025.3.
Please use configuration based [log setup](advanced-setup.md#advanced-logging) instead.
## Removal of the edge module
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.
+7
View File
@@ -304,6 +304,13 @@ The `IProtection` parent class provides a helper method for this in `calculate_l
Most exchanges supported by CCXT should work out of the box.
If you need to implement a specific exchange class, these are found in the `freqtrade/exchange` source folder. You'll also need to add the import to `freqtrade/exchange/__init__.py` to make the loading logic aware of the new exchange.
We recommend looking at existing exchange implementations to get an idea of what might be required.
!!! Warning
Implementing and testing an exchange can be a lot of trial and error, so please bear this in mind.
You should also have some development experience, as this is not a beginner task.
To quickly test the public endpoints of an exchange, add a configuration for your exchange to `tests/exchange_online/conftest.py` and run these tests with `pytest --longrun tests/exchange_online/test_ccxt_compat.py`.
Completing these tests successfully a good basis point (it's a requirement, actually), however these won't guarantee correct exchange functioning, as this only tests public endpoints, but no private endpoint (like generate order or similar).
-300
View File
@@ -1,300 +0,0 @@
# Edge positioning
The `Edge Positioning` module uses probability to calculate your win rate and risk reward ratio. It will use these statistics to control your strategy trade entry points, position size and, stoploss.
!!! Danger "Deprecated functionality"
`Edge positioning` (or short Edge) is currently in maintenance mode only (we keep existing functionality alive) and should be considered as deprecated.
It will currently not receive new features until either someone stepped forward to take up ownership of that module - or we'll decide to remove edge from freqtrade.
!!! Warning
When using `Edge positioning` with a dynamic whitelist (VolumePairList), make sure to also use `AgeFilter` and set it to at least `calculate_since_number_of_days` to avoid problems with missing data.
!!! Note
`Edge Positioning` only considers *its own* buy/sell/stoploss signals. It ignores the stoploss, trailing stoploss, and ROI settings in the strategy configuration file.
`Edge Positioning` improves the performance of some trading strategies and *decreases* the performance of others.
## Introduction
Trading strategies are not perfect. They are frameworks that are susceptible to the market and its indicators. Because the market is not at all predictable, sometimes a strategy will win and sometimes the same strategy will lose.
To obtain an edge in the market, a strategy has to make more money than it loses. Making money in trading is not only about *how often* the strategy makes or loses money.
!!! tip "It doesn't matter how often, but how much!"
A bad strategy might make 1 penny in *ten* transactions but lose 1 dollar in *one* transaction. If one only checks the number of winning trades, it would be misleading to think that the strategy is actually making a profit.
The Edge Positioning module seeks to improve a strategy's winning probability and the money that the strategy will make *on the long run*.
We raise the following question[^1]:
!!! Question "Which trade is a better option?"
a) A trade with 80% of chance of losing 100\$ and 20% chance of winning 200\$<br/>
b) A trade with 100% of chance of losing 30\$
???+ Info "Answer"
The expected value of *a)* is smaller than the expected value of *b)*.<br/>
Hence, *b*) represents a smaller loss in the long run.<br/>
However, the answer is: *it depends*
Another way to look at it is to ask a similar question:
!!! Question "Which trade is a better option?"
a) A trade with 80% of chance of winning 100\$ and 20% chance of losing 200\$<br/>
b) A trade with 100% of chance of winning 30\$
Edge positioning tries to answer the hard questions about risk/reward and position size automatically, seeking to minimizes the chances of losing of a given strategy.
### Trading, winning and losing
Let's call $o$ the return of a single transaction $o$ where $o \in \mathbb{R}$. The collection $O = \{o_1, o_2, ..., o_N\}$ is the set of all returns of transactions made during a trading session. We say that $N$ is the cardinality of $O$, or, in lay terms, it is the number of transactions made in a trading session.
!!! Example
In a session where a strategy made three transactions we can say that $O = \{3.5, -1, 15\}$. That means that $N = 3$ and $o_1 = 3.5$, $o_2 = -1$, $o_3 = 15$.
A winning trade is a trade where a strategy *made* money. Making money means that the strategy closed the position in a value that returned a profit, after all deducted fees. Formally, a winning trade will have a return $o_i > 0$. Similarly, a losing trade will have a return $o_j \leq 0$. With that, we can discover the set of all winning trades, $T_{win}$, as follows:
$$ T_{win} = \{ o \in O | o > 0 \} $$
Similarly, we can discover the set of losing trades $T_{lose}$ as follows:
$$ T_{lose} = \{o \in O | o \leq 0\} $$
!!! Example
In a section where a strategy made four transactions $O = \{3.5, -1, 15, 0\}$:<br>
$T_{win} = \{3.5, 15\}$<br>
$T_{lose} = \{-1, 0\}$<br>
### Win Rate and Lose Rate
The win rate $W$ is the proportion of winning trades with respect to all the trades made by a strategy. We use the following function to compute the win rate:
$$W = \frac{|T_{win}|}{N}$$
Where $W$ is the win rate, $N$ is the number of trades and, $T_{win}$ is the set of all trades where the strategy made money.
Similarly, we can compute the rate of losing trades:
$$
L = \frac{|T_{lose}|}{N}
$$
Where $L$ is the lose rate, $N$ is the amount of trades made and, $T_{lose}$ is the set of all trades where the strategy lost money. Note that the above formula is the same as calculating $L = 1 W$ or $W = 1 L$
### Risk Reward Ratio
Risk Reward Ratio ($R$) is a formula used to measure the expected gains of a given investment against the risk of loss. It is basically what you potentially win divided by what you potentially lose. Formally:
$$ R = \frac{\text{potential_profit}}{\text{potential_loss}} $$
???+ Example "Worked example of $R$ calculation"
Let's say that you think that the price of *stonecoin* today is 10.0\$. You believe that, because they will start mining stonecoin, it will go up to 15.0\$ tomorrow. There is the risk that the stone is too hard, and the GPUs can't mine it, so the price might go to 0\$ tomorrow. You are planning to invest 100\$, which will give you 10 shares (100 / 10).
Your potential profit is calculated as:
$\begin{aligned}
\text{potential_profit} &= (\text{potential_price} - \text{entry_price}) * \frac{\text{investment}}{\text{entry_price}} \\
&= (15 - 10) * (100 / 10) \\
&= 50
\end{aligned}$
Since the price might go to 0\$, the 100\$ dollars invested could turn into 0.
We do however use a stoploss of 15% - so in the worst case, we'll sell 15% below entry price (or at 8.5$\).
$\begin{aligned}
\text{potential_loss} &= (\text{entry_price} - \text{stoploss}) * \frac{\text{investment}}{\text{entry_price}} \\
&= (10 - 8.5) * (100 / 10)\\
&= 15
\end{aligned}$
We can compute the Risk Reward Ratio as follows:
$\begin{aligned}
R &= \frac{\text{potential_profit}}{\text{potential_loss}}\\
&= \frac{50}{15}\\
&= 3.33
\end{aligned}$<br>
What it effectively means is that the strategy have the potential to make 3.33\$ for each 1\$ invested.
On a long horizon, that is, on many trades, we can calculate the risk reward by dividing the strategy' average profit on winning trades by the strategy' average loss on losing trades. We can calculate the average profit, $\mu_{win}$, as follows:
$$ \text{average_profit} = \mu_{win} = \frac{\text{sum_of_profits}}{\text{count_winning_trades}} = \frac{\sum^{o \in T_{win}} o}{|T_{win}|} $$
Similarly, we can calculate the average loss, $\mu_{lose}$, as follows:
$$ \text{average_loss} = \mu_{lose} = \frac{\text{sum_of_losses}}{\text{count_losing_trades}} = \frac{\sum^{o \in T_{lose}} o}{|T_{lose}|} $$
Finally, we can calculate the Risk Reward ratio, $R$, as follows:
$$ R = \frac{\text{average_profit}}{\text{average_loss}} = \frac{\mu_{win}}{\mu_{lose}}\\ $$
???+ Example "Worked example of $R$ calculation using mean profit/loss"
Let's say the strategy that we are using makes an average win $\mu_{win} = 2.06$ and an average loss $\mu_{loss} = 4.11$.<br>
We calculate the risk reward ratio as follows:<br>
$R = \frac{\mu_{win}}{\mu_{loss}} = \frac{2.06}{4.11} = 0.5012...$
### Expectancy
By combining the Win Rate $W$ and the Risk Reward ratio $R$ to create an expectancy ratio $E$. A expectance ratio is the expected return of the investment made in a trade. We can compute the value of $E$ as follows:
$$E = R * W - L$$
!!! Example "Calculating $E$"
Let's say that a strategy has a win rate $W = 0.28$ and a risk reward ratio $R = 5$. What this means is that the strategy is expected to make 5 times the investment around on 28% of the trades it makes. Working out the example:<br>
$E = R * W - L = 5 * 0.28 - 0.72 = 0.68$
<br>
The expectancy worked out in the example above means that, on average, this strategy' trades will return 1.68 times the size of its losses. Said another way, the strategy makes 1.68\$ for every 1\$ it loses, on average.
This is important for two reasons: First, it may seem obvious, but you know right away that you have a positive return. Second, you now have a number you can compare to other candidate systems to make decisions about which ones you employ.
It is important to remember that any system with an expectancy greater than 0 is profitable using past data. The key is finding one that will be profitable in the future.
You can also use this value to evaluate the effectiveness of modifications to this system.
!!! Note
It's important to keep in mind that Edge is testing your expectancy using historical data, there's no guarantee that you will have a similar edge in the future. It's still vital to do this testing in order to build confidence in your methodology but be wary of "curve-fitting" your approach to the historical data as things are unlikely to play out the exact same way for future trades.
## How does it work?
Edge combines dynamic stoploss, dynamic positions, and whitelist generation into one isolated module which is then applied to the trading strategy. If enabled in config, Edge will go through historical data with a range of stoplosses in order to find buy and sell/stoploss signals. It then calculates win rate and expectancy over *N* trades for each stoploss. Here is an example:
| Pair | Stoploss | Win Rate | Risk Reward Ratio | Expectancy |
|----------|:-------------:|-------------:|------------------:|-----------:|
| XZC/ETH | -0.01 | 0.50 |1.176384 | 0.088 |
| XZC/ETH | -0.02 | 0.51 |1.115941 | 0.079 |
| XZC/ETH | -0.03 | 0.52 |1.359670 | 0.228 |
| XZC/ETH | -0.04 | 0.51 |1.234539 | 0.117 |
The goal here is to find the best stoploss for the strategy in order to have the maximum expectancy. In the above example stoploss at $3%$ leads to the maximum expectancy according to historical data.
Edge module then forces stoploss value it evaluated to your strategy dynamically.
### Position size
Edge dictates the amount at stake for each trade to the bot according to the following factors:
- Allowed capital at risk
- Stoploss
Allowed capital at risk is calculated as follows:
```
Allowed capital at risk = (Capital available_percentage) X (Allowed risk per trade)
```
Stoploss is calculated as described above with respect to historical data.
The position size is calculated as follows:
```
Position size = (Allowed capital at risk) / Stoploss
```
Example:
Let's say the stake currency is **ETH** and there is $10$ **ETH** on the wallet. The capital available percentage is $50%$ and the allowed risk per trade is $1\%$. Thus, the available capital for trading is $10 * 0.5 = 5$ **ETH** and the allowed capital at risk would be $5 * 0.01 = 0.05$ **ETH**.
- **Trade 1:** The strategy detects a new buy signal in the **XLM/ETH** market. `Edge Positioning` calculates a stoploss of $2\%$ and a position of $0.05 / 0.02 = 2.5$ **ETH**. The bot takes a position of $2.5$ **ETH** in the **XLM/ETH** market.
- **Trade 2:** The strategy detects a buy signal on the **BTC/ETH** market while **Trade 1** is still open. `Edge Positioning` calculates the stoploss of $4\%$ on this market. Thus, **Trade 2** position size is $0.05 / 0.04 = 1.25$ **ETH**.
!!! Tip "Available Capital $\neq$ Available in wallet"
The available capital for trading didn't change in **Trade 2** even with **Trade 1** still open. The available capital **is not** the free amount in the wallet.
- **Trade 3:** The strategy detects a buy signal in the **ADA/ETH** market. `Edge Positioning` calculates a stoploss of $1\%$ and a position of $0.05 / 0.01 = 5$ **ETH**. Since **Trade 1** has $2.5$ **ETH** blocked and **Trade 2** has $1.25$ **ETH** blocked, there is only $5 - 1.25 - 2.5 = 1.25$ **ETH** available. Hence, the position size of **Trade 3** is $1.25$ **ETH**.
!!! Tip "Available Capital Updates"
The available capital does not change before a position is sold. After a trade is closed the Available Capital goes up if the trade was profitable or goes down if the trade was a loss.
- The strategy detects a sell signal in the **XLM/ETH** market. The bot exits **Trade 1** for a profit of $1$ **ETH**. The total capital in the wallet becomes $11$ **ETH** and the available capital for trading becomes $5.5$ **ETH**.
- **Trade 4** The strategy detects a new buy signal int the **XLM/ETH** market. `Edge Positioning` calculates the stoploss of $2\%$, and the position size of $0.055 / 0.02 = 2.75$ **ETH**.
## Edge command reference
--8<-- "commands/edge.md"
## Configurations
Edge module has following configuration options:
| Parameter | Description |
|------------|-------------|
| `enabled` | If true, then Edge will run periodically. <br>*Defaults to `false`.* <br> **Datatype:** Boolean
| `process_throttle_secs` | How often should Edge run in seconds. <br>*Defaults to `3600` (once per hour).* <br> **Datatype:** Integer
| `calculate_since_number_of_days` | Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy. <br> **Note** that it downloads historical data so increasing this number would lead to slowing down the bot. <br>*Defaults to `7`.* <br> **Datatype:** Integer
| `allowed_risk` | Ratio of allowed risk per trade. <br>*Defaults to `0.01` (1%)).* <br> **Datatype:** Float
| `stoploss_range_min` | Minimum stoploss. <br>*Defaults to `-0.01`.* <br> **Datatype:** Float
| `stoploss_range_max` | Maximum stoploss. <br>*Defaults to `-0.10`.* <br> **Datatype:** Float
| `stoploss_range_step` | As an example if this is set to -0.01 then Edge will test the strategy for `[-0.01, -0,02, -0,03 ..., -0.09, -0.10]` ranges. <br> **Note** than having a smaller step means having a bigger range which could lead to slow calculation. <br> If you set this parameter to -0.001, you then slow down the Edge calculation by a factor of 10. <br>*Defaults to `-0.001`.* <br> **Datatype:** Float
| `minimum_winrate` | It filters out pairs which don't have at least minimum_winrate. <br>This comes handy if you want to be conservative and don't comprise win rate in favour of risk reward ratio. <br>*Defaults to `0.60`.* <br> **Datatype:** Float
| `minimum_expectancy` | It filters out pairs which have the expectancy lower than this number. <br>Having an expectancy of 0.20 means if you put 10\$ on a trade you expect a 12\$ return. <br>*Defaults to `0.20`.* <br> **Datatype:** Float
| `min_trade_number` | When calculating *W*, *R* and *E* (expectancy) against historical data, you always want to have a minimum number of trades. The more this number is the more Edge is reliable. <br>Having a win rate of 100% on a single trade doesn't mean anything at all. But having a win rate of 70% over past 100 trades means clearly something. <br>*Defaults to `10` (it is highly recommended not to decrease this number).* <br> **Datatype:** Integer
| `max_trade_duration_minute` | Edge will filter out trades with long duration. If a trade is profitable after 1 month, it is hard to evaluate the strategy based on it. But if most of trades are profitable and they have maximum duration of 30 minutes, then it is clearly a good sign.<br>**NOTICE:** While configuring this value, you should take into consideration your timeframe. As an example filtering out trades having duration less than one day for a strategy which has 4h interval does not make sense. Default value is set assuming your strategy interval is relatively small (1m or 5m, etc.).<br>*Defaults to `1440` (one day).* <br> **Datatype:** Integer
| `remove_pumps` | Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off.<br>*Defaults to `false`.* <br> **Datatype:** Boolean
## Running Edge independently
You can run Edge independently in order to see in details the result. Here is an example:
``` bash
freqtrade edge
```
An example of its output:
| **pair** | **stoploss** | **win rate** | **risk reward ratio** | **required risk reward** | **expectancy** | **total number of trades** | **average duration (min)** |
|:----------|-----------:|-----------:|--------------------:|-----------------------:|-------------:|-----------------:|---------------:|
| **AGI/BTC** | -0.02 | 0.64 | 5.86 | 0.56 | 3.41 | 14 | 54 |
| **NXS/BTC** | -0.03 | 0.64 | 2.99 | 0.57 | 1.54 | 11 | 26 |
| **LEND/BTC** | -0.02 | 0.82 | 2.05 | 0.22 | 1.50 | 11 | 36 |
| **VIA/BTC** | -0.01 | 0.55 | 3.01 | 0.83 | 1.19 | 11 | 48 |
| **MTH/BTC** | -0.09 | 0.56 | 2.82 | 0.80 | 1.12 | 18 | 52 |
| **ARDR/BTC** | -0.04 | 0.42 | 3.14 | 1.40 | 0.73 | 12 | 42 |
| **BCPT/BTC** | -0.01 | 0.71 | 1.34 | 0.40 | 0.67 | 14 | 30 |
| **WINGS/BTC** | -0.02 | 0.56 | 1.97 | 0.80 | 0.65 | 27 | 42 |
| **VIBE/BTC** | -0.02 | 0.83 | 0.91 | 0.20 | 0.59 | 12 | 35 |
| **MCO/BTC** | -0.02 | 0.79 | 0.97 | 0.27 | 0.55 | 14 | 31 |
| **GNT/BTC** | -0.02 | 0.50 | 2.06 | 1.00 | 0.53 | 18 | 24 |
| **HOT/BTC** | -0.01 | 0.17 | 7.72 | 4.81 | 0.50 | 209 | 7 |
| **SNM/BTC** | -0.03 | 0.71 | 1.06 | 0.42 | 0.45 | 17 | 38 |
| **APPC/BTC** | -0.02 | 0.44 | 2.28 | 1.27 | 0.44 | 25 | 43 |
| **NEBL/BTC** | -0.03 | 0.63 | 1.29 | 0.58 | 0.44 | 19 | 59 |
Edge produced the above table by comparing `calculate_since_number_of_days` to `minimum_expectancy` to find `min_trade_number` historical information based on the config file. The timerange Edge uses for its comparisons can be further limited by using the `--timerange` switch.
In live and dry-run modes, after the `process_throttle_secs` has passed, Edge will again process `calculate_since_number_of_days` against `minimum_expectancy` to find `min_trade_number`. If no `min_trade_number` is found, the bot will return "whitelist empty". Depending on the trade strategy being deployed, "whitelist empty" may be return much of the time - or *all* of the time. The use of Edge may also cause trading to occur in bursts, though this is rare.
If you encounter "whitelist empty" a lot, condsider tuning `calculate_since_number_of_days`, `minimum_expectancy` and `min_trade_number` to align to the trading frequency of your strategy.
### Update cached pairs with the latest data
Edge requires historic data the same way as backtesting does.
Please refer to the [Data Downloading](data-download.md) section of the documentation for details.
### Precising stoploss range
```bash
freqtrade edge --stoplosses=-0.01,-0.1,-0.001 #min,max,step
```
### Advanced use of timerange
```bash
freqtrade edge --timerange=20181110-20181113
```
Doing `--timerange=-20190901` will get all available data until September 1st (excluding September 1st 2019).
The full timerange specification:
* Use tickframes till 2018/01/31: `--timerange=-20180131`
* Use tickframes since 2018/01/31: `--timerange=20180131-`
* Use tickframes since 2018/01/31 till 2018/03/01 : `--timerange=20180131-20180301`
* Use tickframes between POSIX timestamps 1527595200 1527618600: `--timerange=1527595200-1527618600`
[^1]: Question extracted from MIT Opencourseware S096 - Mathematics with applications in Finance: https://ocw.mit.edu/courses/mathematics/18-s096-topics-in-mathematics-with-applications-in-finance-fall-2013/
-14
View File
@@ -276,20 +276,6 @@ Example: 4% profit 650 times vs 0,3% profit a trade 10000 times in a year. If we
Example:
`freqtrade --config config.json --strategy SampleStrategy --hyperopt SampleHyperopt -e 1000 --timerange 20190601-20200601`
## Edge module
### Edge implements interesting approach for controlling position size, is there any theory behind it?
The Edge module is mostly a result of brainstorming of [@mishaker](https://github.com/mishaker) and [@creslinux](https://github.com/creslinux) freqtrade team members.
You can find further info on expectancy, win rate, risk management and position size in the following sources:
- https://www.tradeciety.com/ultimate-math-guide-for-traders/
- https://samuraitradingacademy.com/trading-expectancy/
- https://www.learningmarkets.com/determining-expectancy-in-your-trading/
- https://www.lonestocktrader.com/make-money-trading-positive-expectancy/
- https://www.babypips.com/trading/trade-expectancy-matter
## Official channels
Freqtrade is using exclusively the following official channels:
+4 -5
View File
@@ -5,10 +5,10 @@
[![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
[![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](https://codeclimate.com/github/freqtrade/freqtrade/maintainability)
<!-- Place this tag where you want the button to render. -->
<a class="github-button" href="https://github.com/freqtrade/freqtrade" data-icon="octicon-star" data-size="large" aria-label="Star freqtrade/freqtrade on GitHub">Star</a>
<a class="github-button" href="https://github.com/freqtrade/freqtrade/fork" data-icon="octicon-repo-forked" data-size="large" aria-label="Fork freqtrade/freqtrade on GitHub">Fork</a>
<a class="github-button" href="https://github.com/freqtrade/freqtrade/archive/stable.zip" data-icon="octicon-cloud-download" data-size="large" aria-label="Download freqtrade/freqtrade on GitHub">Download</a>
<!-- GitHub action buttons -->
[:octicons-star-16: Star](https://github.com/freqtrade/freqtrade){ .md-button .md-button--sm }
[:octicons-repo-forked-16: Fork](https://github.com/freqtrade/freqtrade/fork){ .md-button .md-button--sm }
[:octicons-download-16: Download](https://github.com/freqtrade/freqtrade/archive/stable.zip){ .md-button .md-button--sm }
## Introduction
@@ -31,7 +31,6 @@ Freqtrade is a free and open source crypto trading bot written in Python. It is
- Optimize: Find the best parameters for your strategy using hyperoptimization which employs machine learning methods. You can optimize buy, sell, take profit (ROI), stop-loss and trailing stop-loss parameters for your strategy.
- Select markets: Create your static list or use an automatic one based on top traded volumes and/or prices (not available during backtesting). You can also explicitly blacklist markets you don't want to trade.
- Run: Test your strategy with simulated money (Dry-Run mode) or deploy it with real money (Live-Trade mode).
- Run using Edge (optional module): The concept is to find the best historical [trade expectancy](edge.md#expectancy) by markets based on variation of the stop-loss and then allow/reject markets to trade. The sizing of the trade is based on a risk of a percentage of your capital.
- Control/Monitor: Use Telegram or a WebUI (start/stop the bot, show profit/loss, daily summary, current open trades results, etc.).
- Analyze: Further analysis can be performed on either Backtesting data or Freqtrade trading history (SQL database), including automated standard plots, and methods to load the data into [interactive environments](data-analysis.md).
-1
View File
@@ -37,7 +37,6 @@
{{ super() }}
<!-- Place this tag in your head or just before your close body tag. -->
<script async defer src="https://buttons.github.io/buttons.js"></script>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
+2 -2
View File
@@ -1,7 +1,7 @@
markdown==3.8
markdown==3.8.2
mkdocs==1.6.1
mkdocs-material==9.6.14
mdx_truly_sane_lists==1.3
pymdown-extensions==10.15
pymdown-extensions==10.16
jinja2==3.1.6
mike==2.1.3
-4
View File
@@ -190,9 +190,6 @@ delete_trade
:param trade_id: Deletes the trade with this ID from the database.
edge
Return information about edge.
forcebuy
Buy an asset.
@@ -368,7 +365,6 @@ All endpoints in the below table need to be prefixed with the base URL of the AP
| `/blacklist` | GET | Show the current blacklist.
| `/blacklist` | POST | Adds the specified pair to the blacklist.<br/>*Params:*<br/>- `pair` (`str`)
| `/blacklist` | DELETE | Deletes the specified list of pairs from the blacklist.<br/>*Params:*<br/>- `[pair,pair]` (`list[str]`)
| `/edge` | GET | Show validated pairs by Edge if it is enabled.
| `/pair_candles` | GET | Returns dataframe for a pair / timeframe combination while the bot is running. **Alpha**
| `/pair_candles` | POST | Returns dataframe for a pair / timeframe combination while the bot is running, filtered by a provided list of columns to return. **Alpha**<br/>*Params:*<br/>- `<column_list>` (`list[str]`)
| `/pair_history` | GET | Returns an analyzed dataframe for a given timerange, analyzed by a given strategy. **Alpha**
+1 -1
View File
@@ -256,4 +256,4 @@ The new stoploss value will be applied to open trades (and corresponding log-mes
### Limitations
Stoploss values cannot be changed if `trailing_stop` is enabled and the stoploss has already been adjusted, or if [Edge](edge.md) is enabled (since Edge would recalculate stoploss based on the current market situation).
Stoploss values cannot be changed if `trailing_stop` is enabled and the stoploss has already been adjusted.
+1 -1
View File
@@ -1068,7 +1068,7 @@ To verify if a pair is currently locked, use `self.is_pair_locked(pair)`.
``` python
from freqtrade.persistence import Trade
from datetime import timedelta, datetime, timezone
# Put the above lines a the top of the strategy file, next to all the other imports
# Put the above lines at the top of the strategy file, next to all the other imports
# --------
# Within populate indicators (or populate_entry_trend):
+28
View File
@@ -19,3 +19,31 @@
#available-endpoints ~ .md-typeset__scrollwrap .md-typeset__table th:first-of-type {
width: 35% !important;
}
.md-typeset .md-button--sm {
padding: 0.2em 1em;
font-size: 12px;
font-weight: 600;
background-color: #f6f8fa;
color: #24292f;
border: 1px solid #d0d7de;
border-radius: 0.25em;
text-decoration: none;
display: inline-block;
transition: all 0.2s ease;
cursor: pointer;
}
.md-typeset .md-button--sm:hover {
background-color: #e5eaee;
border-color: #d1d9e0;
text-decoration: none;
color: #24292f;
}
.md-typeset .md-button--sm:active {
background-color: #ebecf0;
border-color: #afb8c1;
box-shadow: inset 0 1px 0 rgba(175, 184, 193, 0.2);
}
+1 -17
View File
@@ -188,7 +188,7 @@ You can create your own keyboard in `config.json`:
!!! Note "Supported Commands"
Only the following commands are allowed. Command arguments are not supported!
`/start`, `/pause`, `/stop`, `/status`, `/status table`, `/trades`, `/profit`, `/performance`, `/daily`, `/stats`, `/count`, `/locks`, `/balance`, `/stopentry`, `/reload_config`, `/show_config`, `/logs`, `/whitelist`, `/blacklist`, `/edge`, `/help`, `/version`, `/marketdir`
`/start`, `/pause`, `/stop`, `/status`, `/status table`, `/trades`, `/profit`, `/performance`, `/daily`, `/stats`, `/count`, `/locks`, `/balance`, `/stopentry`, `/reload_config`, `/show_config`, `/logs`, `/whitelist`, `/blacklist`, `/help`, `/version`, `/marketdir`
## Telegram commands
@@ -240,7 +240,6 @@ official commands. You can ask at any moment for help with `/help`.
| `/entries` | Shows Wins / losses by Exit reason as well as Avg. holding durations for buys and sells
| `/whitelist [sorted] [baseonly]` | Show the current whitelist. Optionally display in alphabetical order and/or with just the base currency of each pairing.
| `/blacklist [pair]` | Show the current blacklist, or adds a pair to the blacklist.
| `/edge` | Show validated pairs by Edge if it is enabled.
## Telegram commands in action
@@ -451,21 +450,6 @@ Use `/reload_config` to reset the blacklist.
> Using blacklist `StaticPairList` with 2 pairs
>`DODGE/BTC`, `HOT/BTC`.
### /edge
Shows pairs validated by Edge along with their corresponding win-rate, expectancy and stoploss values.
> **Edge only validated following pairs:**
```
Pair Winrate Expectancy Stoploss
-------- --------- ------------ ----------
DOCK/ETH 0.522727 0.881821 -0.03
PHX/ETH 0.677419 0.560488 -0.03
HOT/ETH 0.733333 0.490492 -0.03
HC/ETH 0.588235 0.280988 -0.02
ARDR/ETH 0.366667 0.143059 -0.01
```
### /version
> **Version:** `0.14.3`
+1 -1
View File
@@ -1,6 +1,6 @@
# Utility Subcommands
Besides the Live-Trade and Dry-Run run modes, the `backtesting`, `edge` and `hyperopt` optimization subcommands, and the `download-data` subcommand which prepares historical data, the bot contains a number of utility subcommands. They are described in this section.
Besides the Live-Trade and Dry-Run run modes, the `backtesting` and `hyperopt` optimization subcommands, and the `download-data` subcommand which prepares historical data, the bot contains a number of utility subcommands. They are described in this section.
## Create userdir
+1 -1
View File
@@ -42,7 +42,7 @@ cd freqtrade
Install ta-lib according to the [ta-lib documentation](https://github.com/TA-Lib/ta-lib-python#windows).
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), Freqtrade provides these dependencies (in the binary wheel format) for the latest 3 Python versions (3.10, 3.11 and 3.12) and for 64bit Windows.
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), Freqtrade provides these dependencies (in the binary wheel format) for the latest 3 Python versions (3.10, 3.11, 3.12 and 3.13) and for 64bit Windows.
These Wheels are also used by CI running on windows, and are therefore tested together with freqtrade.
Other versions must be downloaded from the above link.
+1 -1
View File
@@ -1,6 +1,6 @@
"""Freqtrade bot"""
__version__ = "2025.5"
__version__ = "2025.6"
if "dev" in __version__:
from pathlib import Path
+6 -3
View File
@@ -57,6 +57,7 @@ ARGS_BACKTEST = [
"backtest_breakdown",
"backtest_cache",
"freqai_backtest_live_models",
"backtest_notes",
]
ARGS_HYPEROPT = [
@@ -81,7 +82,7 @@ ARGS_HYPEROPT = [
"early_stop",
]
ARGS_EDGE = [*ARGS_COMMON_OPTIMIZE, "stoploss_range"]
ARGS_EDGE = [*ARGS_COMMON_OPTIMIZE]
ARGS_LIST_STRATEGIES = [
"strategy_path",
@@ -250,7 +251,7 @@ ARGS_STRATEGY_UPDATER = ["strategy_list", "strategy_path", "recursive_strategy_s
ARGS_LOOKAHEAD_ANALYSIS = [
a
for a in ARGS_BACKTEST
if a not in ("position_stacking", "backtest_cache", "backtest_breakdown")
if a not in ("position_stacking", "backtest_cache", "backtest_breakdown", "backtest_notes")
] + ["minimum_trade_amount", "targeted_trade_amount", "lookahead_analysis_exportfilename"]
ARGS_RECURSIVE_ANALYSIS = ["timeframe", "timerange", "dataformat_ohlcv", "pairs", "startup_candle"]
@@ -505,7 +506,9 @@ class Arguments:
# Add edge subcommand
edge_cmd = subparsers.add_parser(
"edge", help="Edge module.", parents=[_common_parser, _strategy_parser]
"edge",
help="Edge module. No longer part of Freqtrade",
parents=[_common_parser, _strategy_parser],
)
edge_cmd.set_defaults(func=start_edge)
self._build_args(optionlist=ARGS_EDGE, parser=edge_cmd)
+5 -7
View File
@@ -204,6 +204,11 @@ AVAILABLE_CLI_OPTIONS = {
help="Export backtest results (default: trades).",
choices=constants.EXPORT_OPTIONS,
),
"backtest_notes": Arg(
"--notes",
help="Add notes to the backtest results.",
metavar="TEXT",
),
"exportfilename": Arg(
"--export-filename",
"--backtest-filename",
@@ -235,13 +240,6 @@ AVAILABLE_CLI_OPTIONS = {
default=constants.BACKTEST_CACHE_DEFAULT,
choices=constants.BACKTEST_CACHE_AGE,
),
# Edge
"stoploss_range": Arg(
"--stoplosses",
help="Defines a range of stoploss values against which edge will assess the strategy. "
'The format is "min,max,step" (without any space). '
"Example: `--stoplosses=-0.01,-0.1,-0.001`",
),
# Hyperopt
"hyperopt": Arg(
"--hyperopt",
+4 -9
View File
@@ -129,15 +129,10 @@ def start_edge(args: dict[str, Any]) -> None:
:param args: Cli args from Arguments()
:return: None
"""
from freqtrade.optimize.edge_cli import EdgeCli
# Initialize configuration
config = setup_optimize_configuration(args, RunMode.EDGE)
logger.info("Starting freqtrade in Edge mode")
# Initialize Edge object
edge_cli = EdgeCli(config)
edge_cli.start()
raise ConfigurationError(
"The Edge module has been deprecated in 2023.9 and removed in 2025.6. "
"All functionalities of edge have been removed."
)
def start_lookahead_analysis(args: dict[str, Any]) -> None:
+11 -23
View File
@@ -423,10 +423,6 @@ CONF_SCHEMA = {
"description": "Exchange configuration.",
"$ref": "#/definitions/exchange",
},
"edge": {
"description": "Edge configuration.",
"$ref": "#/definitions/edge",
},
"log_config": {
"description": "Logging configuration.",
"$ref": "#/definitions/logging",
@@ -913,30 +909,22 @@ CONF_SCHEMA = {
},
"ccxt_config": {"description": "CCXT configuration settings.", "type": "object"},
"ccxt_async_config": {
"description": "CCXT asynchronous configuration settings.",
"description": (
"CCXT asynchronous configuration settings."
"Usually ccxt_config should be used instead."
),
"type": "object",
},
"ccxt_sync_config": {
"description": (
"CCXT synchronous configuration settings. "
"Usually ccxt_config should be used instead."
),
"type": "object",
},
},
"required": ["name"],
},
"edge": {
"type": "object",
"properties": {
"enabled": {"type": "boolean"},
"process_throttle_secs": {"type": "integer", "minimum": 600},
"calculate_since_number_of_days": {"type": "integer"},
"allowed_risk": {"type": "number"},
"stoploss_range_min": {"type": "number"},
"stoploss_range_max": {"type": "number"},
"stoploss_range_step": {"type": "number"},
"minimum_winrate": {"type": "number"},
"minimum_expectancy": {"type": "number"},
"min_trade_number": {"type": "number"},
"max_trade_duration_minute": {"type": "integer"},
"remove_pumps": {"type": "boolean"},
},
"required": ["process_throttle_secs", "allowed_risk"],
},
"logging": {
"type": "object",
"properties": {
+1 -1
View File
@@ -1,6 +1,6 @@
# flake8: noqa: F401
from freqtrade.configuration.config_secrets import sanitize_config
from freqtrade.configuration.config_secrets import remove_exchange_credentials, sanitize_config
from freqtrade.configuration.config_setup import setup_utils_configuration
from freqtrade.configuration.config_validation import validate_config_consistency
from freqtrade.configuration.configuration import Configuration
+41 -21
View File
@@ -1,6 +1,27 @@
from copy import deepcopy
from freqtrade.constants import Config
from freqtrade.constants import Config, ExchangeConfig
_SENSITIVE_KEYS = [
"exchange.key",
"exchange.api_key",
"exchange.apiKey",
"exchange.secret",
"exchange.password",
"exchange.uid",
"exchange.account_id",
"exchange.accountId",
"exchange.wallet_address",
"exchange.walletAddress",
"exchange.private_key",
"exchange.privateKey",
"telegram.token",
"telegram.chat_id",
"discord.webhook_url",
"api_server.password",
"webhook.url",
]
def sanitize_config(config: Config, *, show_sensitive: bool = False) -> Config:
@@ -12,27 +33,8 @@ def sanitize_config(config: Config, *, show_sensitive: bool = False) -> Config:
"""
if show_sensitive:
return config
keys_to_remove = [
"exchange.key",
"exchange.api_key",
"exchange.apiKey",
"exchange.secret",
"exchange.password",
"exchange.uid",
"exchange.account_id",
"exchange.accountId",
"exchange.wallet_address",
"exchange.walletAddress",
"exchange.private_key",
"exchange.privateKey",
"telegram.token",
"telegram.chat_id",
"discord.webhook_url",
"api_server.password",
"webhook.url",
]
config = deepcopy(config)
for key in keys_to_remove:
for key in _SENSITIVE_KEYS:
if "." in key:
nested_keys = key.split(".")
nested_config = config
@@ -45,3 +47,21 @@ def sanitize_config(config: Config, *, show_sensitive: bool = False) -> Config:
config[key] = "REDACTED"
return config
def remove_exchange_credentials(exchange_config: ExchangeConfig, dry_run: bool) -> None:
"""
Removes exchange keys from the configuration and specifies dry-run
Used for backtesting / hyperopt and utils.
Modifies the input dict!
:param exchange_config: Exchange configuration
:param dry_run: If True, remove sensitive keys from the exchange configuration
"""
if not dry_run:
return
for key in [k for k in _SENSITIVE_KEYS if k.startswith("exchange.")]:
if "." in key:
key1 = key.removeprefix("exchange.")
if key1 in exchange_config:
exchange_config[key1] = ""
+5 -10
View File
@@ -99,14 +99,12 @@ def validate_config_consistency(conf: dict[str, Any], *, preliminary: bool = Fal
def _validate_unlimited_amount(conf: dict[str, Any]) -> None:
"""
If edge is disabled, either max_open_trades or stake_amount need to be set.
Either max_open_trades or stake_amount need to be set.
:raise: ConfigurationError if config validation failed
"""
if (
not conf.get("edge", {}).get("enabled")
and (conf.get("max_open_trades") == float("inf") or conf.get("max_open_trades") == -1)
and conf.get("stake_amount") == UNLIMITED_STAKE_AMOUNT
):
conf.get("max_open_trades") == float("inf") or conf.get("max_open_trades") == -1
) and conf.get("stake_amount") == UNLIMITED_STAKE_AMOUNT:
raise ConfigurationError("`max_open_trades` and `stake_amount` cannot both be unlimited.")
@@ -164,12 +162,9 @@ def _validate_edge(conf: dict[str, Any]) -> None:
Edge and Dynamic whitelist should not both be enabled, since edge overrides dynamic whitelists.
"""
if not conf.get("edge", {}).get("enabled"):
return
if not conf.get("use_exit_signal", True):
if conf.get("edge", {}).get("enabled"):
raise ConfigurationError(
"Edge requires `use_exit_signal` to be True, otherwise no sells will happen."
"Edge is no longer supported and has been removed from Freqtrade with 2025.6."
)
+1 -9
View File
@@ -2,7 +2,6 @@
This module contains the configuration class
"""
import ast
import logging
import warnings
from collections.abc import Callable
@@ -310,17 +309,10 @@ class Configuration:
("backtest_cache", "Parameter --cache={} detected ..."),
("disableparamexport", "Parameter --disableparamexport detected: {} ..."),
("freqai_backtest_live_models", "Parameter --freqai-backtest-live-models detected ..."),
("backtest_notes", "Parameter --notes detected: {} ..."),
]
self._args_to_config_loop(config, configurations)
# Edge section:
if self.args.get("stoploss_range"):
txt_range = ast.literal_eval(self.args["stoploss_range"])
config["edge"].update({"stoploss_range_min": txt_range[0]})
config["edge"].update({"stoploss_range_max": txt_range[1]})
config["edge"].update({"stoploss_range_step": txt_range[2]})
logger.info("Parameter --stoplosses detected: %s ...", self.args["stoploss_range"])
# Hyperopt section
configurations = [
@@ -159,16 +159,6 @@ def process_temporary_deprecated_settings(config: Config) -> None:
process_removed_setting(
config, "ask_strategy", "ignore_roi_if_buy_signal", None, "ignore_roi_if_entry_signal"
)
if config.get("edge", {}).get(
"enabled", False
) and "capital_available_percentage" in config.get("edge", {}):
raise ConfigurationError(
"DEPRECATED: "
"Using 'edge.capital_available_percentage' has been deprecated in favor of "
"'tradable_balance_ratio'. Please migrate your configuration to "
"'tradable_balance_ratio' and remove 'capital_available_percentage' "
"from the edge configuration."
)
if "ticker_interval" in config:
raise ConfigurationError(
"DEPRECATED: 'ticker_interval' detected. "
+14 -2
View File
@@ -43,15 +43,27 @@ def _flat_vars_to_nested_dict(env_dict: dict[str, Any], prefix: str) -> dict[str
:return: Nested dict based on available and relevant variables.
"""
no_convert = ["CHAT_ID", "PASSWORD"]
ccxt_config_keys = ["ccxt_config", "ccxt_sync_config", "ccxt_async_config"]
relevant_vars: dict[str, Any] = {}
for env_var, val in sorted(env_dict.items()):
if env_var.startswith(prefix):
logger.info(f"Loading variable '{env_var}'")
key = env_var.replace(prefix, "")
for k in reversed(key.split("__")):
key_parts = key.split("__")
logger.info("Key parts: %s", key_parts)
# Check if any ccxt config key is in the key parts
preserve_case = key_parts[0].lower() == "exchange" and any(
ccxt_key in [part.lower() for part in key_parts] for ccxt_key in ccxt_config_keys
)
for i, k in enumerate(reversed(key_parts)):
# Preserve case for the final key if ccxt config is involved
key_name = k if preserve_case and i == 0 else k.lower()
val = {
k.lower(): (
key_name: (
_get_var_typed(val)
if not isinstance(val, dict) and k not in no_convert
else val
@@ -69,6 +69,10 @@ def import_kraken_trades_from_csv(config: Config, convert_to: str):
trades = pd.concat(dfs, ignore_index=True)
del dfs
# drop any row not having a number in the column timestamp
timestamp_numeric = pd.to_numeric(trades["timestamp"], errors="coerce")
trades = trades[timestamp_numeric.notna()]
trades.loc[:, "timestamp"] = trades["timestamp"] * 1e3
trades.loc[:, "cost"] = trades["price"] * trades["amount"]
for col in DEFAULT_TRADES_COLUMNS:
+1 -1
View File
@@ -405,7 +405,7 @@ class DataProvider:
def runmode(self) -> RunMode:
"""
Get runmode of the bot
can be "live", "dry-run", "backtest", "edgecli", "hyperopt" or "other".
can be "live", "dry-run", "backtest", "hyperopt" or "other".
"""
return RunMode(self._config.get("runmode", RunMode.OTHER))
+3 -1
View File
@@ -331,7 +331,9 @@ def process_entry_exit_reasons(config: Config):
exit_only = config.get("exit_only", False)
do_rejected = config.get("analysis_rejected", False)
to_csv = config.get("analysis_to_csv", False)
csv_path = Path(config.get("analysis_csv_path", config["exportfilename"]))
csv_path = Path(
config.get("analysis_csv_path", config["exportfilename"]), # type: ignore[arg-type]
)
if entry_only is True and exit_only is True:
raise OperationalException(
-1
View File
@@ -1 +0,0 @@
from .edge_positioning import Edge, PairInfo # noqa: F401
-524
View File
@@ -1,524 +0,0 @@
# pragma pylint: disable=W0603
"""Edge positioning package"""
import logging
from collections import defaultdict
from copy import deepcopy
from datetime import timedelta
from typing import Any, NamedTuple
import numpy as np
import utils_find_1st as utf1st
from pandas import DataFrame
from freqtrade.configuration import TimeRange
from freqtrade.constants import DATETIME_PRINT_FORMAT, UNLIMITED_STAKE_AMOUNT, Config
from freqtrade.data.history import get_timerange, load_data, refresh_data
from freqtrade.enums import CandleType, ExitType, RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_seconds
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.strategy.interface import IStrategy
from freqtrade.util import dt_now
logger = logging.getLogger(__name__)
class PairInfo(NamedTuple):
stoploss: float
winrate: float
risk_reward_ratio: float
required_risk_reward: float
expectancy: float
nb_trades: int
avg_trade_duration: float
class Edge:
"""
Calculates Win Rate, Risk Reward Ratio, Expectancy
against historical data for a give set of markets and a strategy
it then adjusts stoploss and position size accordingly
and force it into the strategy
Author: https://github.com/mishaker
"""
_cached_pairs: dict[str, Any] = {} # Keeps a list of pairs
def __init__(self, config: Config, exchange, strategy) -> None:
self.config = config
self.exchange = exchange
self.strategy: IStrategy = strategy
self.edge_config = self.config.get("edge", {})
self._cached_pairs: dict[str, Any] = {} # Keeps a list of pairs
self._final_pairs: list = []
# checking max_open_trades. it should be -1 as with Edge
# the number of trades is determined by position size
if self.config["max_open_trades"] != float("inf"):
logger.critical("max_open_trades should be -1 in config !")
if self.config["stake_amount"] != UNLIMITED_STAKE_AMOUNT:
raise OperationalException("Edge works only with unlimited stake amount")
self._capital_ratio: float = self.config["tradable_balance_ratio"]
self._allowed_risk: float = self.edge_config.get("allowed_risk")
self._since_number_of_days: int = self.edge_config.get("calculate_since_number_of_days", 14)
self._last_updated: int = 0 # Timestamp of pairs last updated time
self._refresh_pairs = True
self._stoploss_range_min = float(self.edge_config.get("stoploss_range_min", -0.01))
self._stoploss_range_max = float(self.edge_config.get("stoploss_range_max", -0.05))
self._stoploss_range_step = float(self.edge_config.get("stoploss_range_step", -0.001))
# calculating stoploss range
self._stoploss_range = np.arange(
self._stoploss_range_min, self._stoploss_range_max, self._stoploss_range_step
)
self._timerange: TimeRange = TimeRange.parse_timerange(
f"{(dt_now() - timedelta(days=self._since_number_of_days)).strftime('%Y%m%d')}-"
)
if config.get("fee"):
self.fee = config["fee"]
else:
try:
self.fee = self.exchange.get_fee(
symbol=expand_pairlist(
self.config["exchange"]["pair_whitelist"], list(self.exchange.markets)
)[0]
)
except IndexError:
self.fee = None
def calculate(self, pairs: list[str]) -> bool:
if self.fee is None and pairs:
self.fee = self.exchange.get_fee(pairs[0])
heartbeat = self.edge_config.get("process_throttle_secs")
if (self._last_updated > 0) and (
self._last_updated + heartbeat > int(dt_now().timestamp())
):
return False
data: dict[str, Any] = {}
logger.info("Using stake_currency: %s ...", self.config["stake_currency"])
logger.info("Using local backtesting data (using whitelist in given config) ...")
if self._refresh_pairs:
timerange_startup = deepcopy(self._timerange)
timerange_startup.subtract_start(
timeframe_to_seconds(self.strategy.timeframe) * self.strategy.startup_candle_count
)
refresh_data(
datadir=self.config["datadir"],
pairs=pairs,
exchange=self.exchange,
timeframe=self.strategy.timeframe,
timerange=timerange_startup,
data_format=self.config["dataformat_ohlcv"],
candle_type=self.config.get("candle_type_def", CandleType.SPOT),
)
# Download informative pairs too
res = defaultdict(list)
for pair, timeframe, _ in self.strategy.gather_informative_pairs():
res[timeframe].append(pair)
for timeframe, inf_pairs in res.items():
timerange_startup = deepcopy(self._timerange)
timerange_startup.subtract_start(
timeframe_to_seconds(timeframe) * self.strategy.startup_candle_count
)
refresh_data(
datadir=self.config["datadir"],
pairs=inf_pairs,
exchange=self.exchange,
timeframe=timeframe,
timerange=timerange_startup,
data_format=self.config["dataformat_ohlcv"],
candle_type=self.config.get("candle_type_def", CandleType.SPOT),
)
data = load_data(
datadir=self.config["datadir"],
pairs=pairs,
timeframe=self.strategy.timeframe,
timerange=self._timerange,
startup_candles=self.strategy.startup_candle_count,
data_format=self.config["dataformat_ohlcv"],
candle_type=self.config.get("candle_type_def", CandleType.SPOT),
)
if not data:
# Reinitializing cached pairs
self._cached_pairs = {}
logger.critical("No data found. Edge is stopped ...")
return False
# Fake run-mode to Edge
prior_rm = self.config["runmode"]
self.config["runmode"] = RunMode.EDGE
preprocessed = self.strategy.advise_all_indicators(data)
self.config["runmode"] = prior_rm
# Print timeframe
min_date, max_date = get_timerange(preprocessed)
logger.info(
f"Measuring data from {min_date.strftime(DATETIME_PRINT_FORMAT)} "
f"up to {max_date.strftime(DATETIME_PRINT_FORMAT)} "
f"({(max_date - min_date).days} days).."
)
# TODO: Should edge support shorts? needs to be investigated further
# * (add enter_short exit_short)
headers = ["date", "open", "high", "low", "close", "enter_long", "exit_long"]
trades: list = []
for pair, pair_data in preprocessed.items():
# Sorting dataframe by date and reset index
pair_data = pair_data.sort_values(by=["date"])
pair_data = pair_data.reset_index(drop=True)
df_analyzed = self.strategy.ft_advise_signals(pair_data, {"pair": pair})[headers].copy()
trades += self._find_trades_for_stoploss_range(df_analyzed, pair, self._stoploss_range)
# If no trade found then exit
if len(trades) == 0:
logger.info("No trades found.")
return False
# Fill missing, calculable columns, profit, duration , abs etc.
trades_df = self._fill_calculable_fields(DataFrame(trades))
self._cached_pairs = self._process_expectancy(trades_df)
self._last_updated = int(dt_now().timestamp())
return True
def stake_amount(
self, pair: str, free_capital: float, total_capital: float, capital_in_trade: float
) -> float:
stoploss = self.get_stoploss(pair)
available_capital = (total_capital + capital_in_trade) * self._capital_ratio
allowed_capital_at_risk = available_capital * self._allowed_risk
max_position_size = abs(allowed_capital_at_risk / stoploss)
# Position size must be below available capital.
position_size = min(min(max_position_size, free_capital), available_capital)
if pair in self._cached_pairs:
logger.info(
"winrate: %s, expectancy: %s, position size: %s, pair: %s,"
" capital in trade: %s, free capital: %s, total capital: %s,"
" stoploss: %s, available capital: %s.",
self._cached_pairs[pair].winrate,
self._cached_pairs[pair].expectancy,
position_size,
pair,
capital_in_trade,
free_capital,
total_capital,
stoploss,
available_capital,
)
return round(position_size, 15)
def get_stoploss(self, pair: str) -> float:
if pair in self._cached_pairs:
return self._cached_pairs[pair].stoploss
else:
logger.warning(
f"Tried to access stoploss of non-existing pair {pair}, "
"strategy stoploss is returned instead."
)
return self.strategy.stoploss
def adjust(self, pairs: list[str]) -> list:
"""
Filters out and sorts "pairs" according to Edge calculated pairs
"""
final = []
for pair, info in self._cached_pairs.items():
if (
info.expectancy > float(self.edge_config.get("minimum_expectancy", 0.2))
and info.winrate > float(self.edge_config.get("minimum_winrate", 0.60))
and pair in pairs
):
final.append(pair)
if self._final_pairs != final:
self._final_pairs = final
if self._final_pairs:
logger.info(
"Minimum expectancy and minimum winrate are met only for %s,"
" so other pairs are filtered out.",
self._final_pairs,
)
else:
logger.info(
"Edge removed all pairs as no pair with minimum expectancy "
"and minimum winrate was found !"
)
return self._final_pairs
def accepted_pairs(self) -> list[dict[str, Any]]:
"""
return a list of accepted pairs along with their winrate, expectancy and stoploss
"""
final = []
for pair, info in self._cached_pairs.items():
if info.expectancy > float(
self.edge_config.get("minimum_expectancy", 0.2)
) and info.winrate > float(self.edge_config.get("minimum_winrate", 0.60)):
final.append(
{
"Pair": pair,
"Winrate": info.winrate,
"Expectancy": info.expectancy,
"Stoploss": info.stoploss,
}
)
return final
def _fill_calculable_fields(self, result: DataFrame) -> DataFrame:
"""
The result frame contains a number of columns that are calculable
from other columns. These are left blank till all rows are added,
to be populated in single vector calls.
Columns to be populated are:
- Profit
- trade duration
- profit abs
:param result Dataframe
:return: result Dataframe
"""
# We set stake amount to an arbitrary amount, as it doesn't change the calculation.
# All returned values are relative, they are defined as ratios.
stake = 0.015
result["trade_duration"] = result["close_date"] - result["open_date"]
result["trade_duration"] = result["trade_duration"].map(
lambda x: int(x.total_seconds() / 60)
)
# Spends, Takes, Profit, Absolute Profit
# Buy Price
result["buy_vol"] = stake / result["open_rate"] # How many target are we buying
result["buy_fee"] = stake * self.fee
result["buy_spend"] = stake + result["buy_fee"] # How much we're spending
# Sell price
result["sell_sum"] = result["buy_vol"] * result["close_rate"]
result["sell_fee"] = result["sell_sum"] * self.fee
result["sell_take"] = result["sell_sum"] - result["sell_fee"]
# profit_ratio
result["profit_ratio"] = (result["sell_take"] - result["buy_spend"]) / result["buy_spend"]
# Absolute profit
result["profit_abs"] = result["sell_take"] - result["buy_spend"]
return result
def _process_expectancy(self, results: DataFrame) -> dict[str, Any]:
"""
This calculates WinRate, Required Risk Reward, Risk Reward and Expectancy of all pairs
The calculation will be done per pair and per strategy.
"""
# Removing pairs having less than min_trades_number
min_trades_number = self.edge_config.get("min_trade_number", 10)
results = results.groupby(["pair", "stoploss"]).filter(lambda x: len(x) > min_trades_number)
###################################
# Removing outliers (Only Pumps) from the dataset
# The method to detect outliers is to calculate standard deviation
# Then every value more than (standard deviation + 2*average) is out (pump)
#
# Removing Pumps
if self.edge_config.get("remove_pumps", False):
results = results[
results["profit_abs"]
< 2 * results["profit_abs"].std() + results["profit_abs"].mean()
]
##########################################################################
# Removing trades having a duration more than X minutes (set in config)
max_trade_duration = self.edge_config.get("max_trade_duration_minute", 1440)
results = results[results.trade_duration < max_trade_duration]
#######################################################################
if results.empty:
return {}
groupby_aggregator = {
"profit_abs": [
("nb_trades", "count"), # number of all trades
("profit_sum", lambda x: x[x > 0].sum()), # cumulative profit of all winning trades
("loss_sum", lambda x: abs(x[x < 0].sum())), # cumulative loss of all losing trades
("nb_win_trades", lambda x: x[x > 0].count()), # number of winning trades
],
"trade_duration": [("avg_trade_duration", "mean")],
}
# Group by (pair and stoploss) by applying above aggregator
df = (
results.groupby(["pair", "stoploss"])[["profit_abs", "trade_duration"]]
.agg(groupby_aggregator)
.reset_index(col_level=1)
)
# Dropping level 0 as we don't need it
df.columns = df.columns.droplevel(0)
# Calculating number of losing trades, average win and average loss
df["nb_loss_trades"] = df["nb_trades"] - df["nb_win_trades"]
df["average_win"] = np.where(
df["nb_win_trades"] == 0, 0.0, df["profit_sum"] / df["nb_win_trades"]
)
df["average_loss"] = np.where(
df["nb_loss_trades"] == 0, 0.0, df["loss_sum"] / df["nb_loss_trades"]
)
# Win rate = number of profitable trades / number of trades
df["winrate"] = df["nb_win_trades"] / df["nb_trades"]
# risk_reward_ratio = average win / average loss
df["risk_reward_ratio"] = df["average_win"] / df["average_loss"]
# required_risk_reward = (1 / winrate) - 1
df["required_risk_reward"] = (1 / df["winrate"]) - 1
# expectancy = (risk_reward_ratio * winrate) - (lossrate)
df["expectancy"] = (df["risk_reward_ratio"] * df["winrate"]) - (1 - df["winrate"])
# sort by expectancy and stoploss
df = (
df.sort_values(by=["expectancy", "stoploss"], ascending=False)
.groupby("pair")
.first()
.sort_values(by=["expectancy"], ascending=False)
.reset_index()
)
final = {}
for x in df.itertuples():
final[x.pair] = PairInfo(
x.stoploss,
x.winrate,
x.risk_reward_ratio,
x.required_risk_reward,
x.expectancy,
x.nb_trades,
x.avg_trade_duration,
)
# Returning a list of pairs in order of "expectancy"
return final
def _find_trades_for_stoploss_range(self, df, pair: str, stoploss_range) -> list:
buy_column = df["enter_long"].values
sell_column = df["exit_long"].values
date_column = df["date"].values
ohlc_columns = df[["open", "high", "low", "close"]].values
result: list = []
for stoploss in stoploss_range:
result += self._detect_next_stop_or_sell_point(
buy_column, sell_column, date_column, ohlc_columns, round(stoploss, 6), pair
)
return result
def _detect_next_stop_or_sell_point(
self, buy_column, sell_column, date_column, ohlc_columns, stoploss, pair: str
):
"""
Iterate through ohlc_columns in order to find the next trade
Next trade opens from the first buy signal noticed to
The sell or stoploss signal after it.
It then cuts OHLC, buy_column, sell_column and date_column.
Cut from (the exit trade index) + 1.
Author: https://github.com/mishaker
"""
result: list = []
start_point = 0
while True:
open_trade_index = utf1st.find_1st(buy_column, 1, utf1st.cmp_equal)
# Return empty if we don't find trade entry (i.e. buy==1) or
# we find a buy but at the end of array
if open_trade_index == -1 or open_trade_index == len(buy_column) - 1:
break
else:
# When a buy signal is seen,
# trade opens in reality on the next candle
open_trade_index += 1
open_price = ohlc_columns[open_trade_index, 0]
stop_price = open_price * (stoploss + 1)
# Searching for the index where stoploss is hit
stop_index = utf1st.find_1st(
ohlc_columns[open_trade_index:, 2], stop_price, utf1st.cmp_smaller
)
# If we don't find it then we assume stop_index will be far in future (infinite number)
if stop_index == -1:
stop_index = float("inf")
# Searching for the index where sell is hit
sell_index = utf1st.find_1st(sell_column[open_trade_index:], 1, utf1st.cmp_equal)
# If we don't find it then we assume sell_index will be far in future (infinite number)
if sell_index == -1:
sell_index = float("inf")
# Check if we don't find any stop or sell point (in that case trade remains open)
# It is not interesting for Edge to consider it so we simply ignore the trade
# And stop iterating there is no more entry
if stop_index == sell_index == float("inf"):
break
if stop_index <= sell_index:
exit_index = open_trade_index + stop_index
exit_type = ExitType.STOP_LOSS
exit_price = stop_price
elif stop_index > sell_index:
# If exit is SELL then we exit at the next candle
exit_index = open_trade_index + sell_index + 1
# Check if we have the next candle
if len(ohlc_columns) - 1 < exit_index:
break
exit_type = ExitType.EXIT_SIGNAL
exit_price = ohlc_columns[exit_index, 0]
trade = {
"pair": pair,
"stoploss": stoploss,
"profit_ratio": "",
"profit_abs": "",
"open_date": date_column[open_trade_index],
"close_date": date_column[exit_index],
"trade_duration": "",
"open_rate": round(open_price, 15),
"close_rate": round(exit_price, 15),
"exit_type": exit_type,
}
result.append(trade)
# Giving a view of exit_index till the end of array
buy_column = buy_column[exit_index:]
sell_column = sell_column[exit_index:]
date_column = date_column[exit_index:]
ohlc_columns = ohlc_columns[exit_index:]
start_point += exit_index
return result
+1 -1
View File
@@ -13,4 +13,4 @@ class MarginMode(str, Enum):
NONE = ""
def __str__(self):
return f"{self.name.lower()}"
return f"{self.value.lower()}"
+2 -3
View File
@@ -4,13 +4,12 @@ from enum import Enum
class RunMode(str, Enum):
"""
Bot running mode (backtest, hyperopt, ...)
can be "live", "dry-run", "backtest", "edge", "hyperopt".
can be "live", "dry-run", "backtest", "hyperopt".
"""
LIVE = "live"
DRY_RUN = "dry_run"
BACKTEST = "backtest"
EDGE = "edge"
HYPEROPT = "hyperopt"
UTIL_EXCHANGE = "util_exchange"
UTIL_NO_EXCHANGE = "util_no_exchange"
@@ -20,5 +19,5 @@ class RunMode(str, Enum):
TRADE_MODES = [RunMode.LIVE, RunMode.DRY_RUN]
OPTIMIZE_MODES = [RunMode.BACKTEST, RunMode.EDGE, RunMode.HYPEROPT]
OPTIMIZE_MODES = [RunMode.BACKTEST, RunMode.HYPEROPT]
NON_UTIL_MODES = TRADE_MODES + OPTIMIZE_MODES
+1 -1
View File
@@ -1,6 +1,6 @@
# flake8: noqa: F401
# isort: off
from freqtrade.exchange.common import remove_exchange_credentials, MAP_EXCHANGE_CHILDCLASS
from freqtrade.exchange.common import MAP_EXCHANGE_CHILDCLASS
from freqtrade.exchange.exchange import Exchange
# isort: on
+4 -1
View File
@@ -76,7 +76,10 @@ class Binance(Exchange):
:return: Proxy coin or stake currency
"""
if self.margin_mode == MarginMode.CROSS:
return self._config.get("proxy_coin", self._config["stake_currency"])
return self._config.get(
"proxy_coin",
self._config["stake_currency"],
) # type: ignore[return-value]
return self._config["stake_currency"]
def get_tickers(
File diff suppressed because it is too large Load Diff
-15
View File
@@ -5,7 +5,6 @@ from collections.abc import Callable
from functools import wraps
from typing import Any, TypeVar, cast, overload
from freqtrade.constants import ExchangeConfig
from freqtrade.exceptions import DDosProtection, RetryableOrderError, TemporaryError
from freqtrade.mixins import LoggingMixin
@@ -104,20 +103,6 @@ EXCHANGE_HAS_OPTIONAL = [
]
def remove_exchange_credentials(exchange_config: ExchangeConfig, dry_run: bool) -> None:
"""
Removes exchange keys from the configuration and specifies dry-run
Used for backtesting / hyperopt / edge and utils.
Modifies the input dict!
"""
if dry_run:
exchange_config["key"] = ""
exchange_config["apiKey"] = ""
exchange_config["secret"] = ""
exchange_config["password"] = ""
exchange_config["uid"] = ""
def calculate_backoff(retrycount, max_retries):
"""
Calculate backoff
+11 -9
View File
@@ -21,6 +21,7 @@ from ccxt import TICK_SIZE
from dateutil import parser
from pandas import DataFrame, concat
from freqtrade.configuration import remove_exchange_credentials
from freqtrade.constants import (
DEFAULT_AMOUNT_RESERVE_PERCENT,
DEFAULT_TRADES_COLUMNS,
@@ -64,7 +65,6 @@ from freqtrade.exceptions import (
)
from freqtrade.exchange.common import (
API_FETCH_ORDER_RETRY_COUNT,
remove_exchange_credentials,
retrier,
retrier_async,
)
@@ -637,9 +637,9 @@ class Exchange:
if self._exchange_ws:
self._exchange_ws.reset_connections()
async def _api_reload_markets(self, reload: bool = False) -> dict[str, Any]:
async def _api_reload_markets(self, reload: bool = False) -> None:
try:
return await self._api_async.load_markets(reload=reload, params={})
await self._api_async.load_markets(reload=reload, params={})
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
@@ -649,14 +649,14 @@ class Exchange:
except ccxt.BaseError as e:
raise TemporaryError(e) from e
def _load_async_markets(self, reload: bool = False) -> dict[str, Any]:
def _load_async_markets(self, reload: bool = False) -> None:
try:
with self._loop_lock:
markets = self.loop.run_until_complete(self._api_reload_markets(reload=reload))
if isinstance(markets, Exception):
raise markets
return markets
return None
except asyncio.TimeoutError as e:
logger.warning("Could not load markets. Reason: %s", e)
raise TemporaryError from e
@@ -679,7 +679,8 @@ class Exchange:
# on initial load, we retry 3 times to ensure we get the markets
retries: int = 3 if force else 0
# Reload async markets, then assign them to sync api
self._markets = retrier(self._load_async_markets, retries=retries)(reload=True)
retrier(self._load_async_markets, retries=retries)(reload=True)
self._markets = self._api_async.markets
self._api.set_markets(self._api_async.markets, self._api_async.currencies)
# Assign options array, as it contains some temporary information from the exchange.
self._api.options = self._api_async.options
@@ -876,8 +877,8 @@ class Exchange:
(trading_mode, margin_mode) not in self._supported_trading_mode_margin_pairs
):
mm_value = margin_mode and margin_mode.value
raise OperationalException(
f"Freqtrade does not support {mm_value} {trading_mode} on {self.name}"
raise ConfigurationError(
f"Freqtrade does not support '{mm_value}' '{trading_mode}' on {self.name}."
)
def get_option(self, param: str, default: Any | None = None) -> Any:
@@ -3428,7 +3429,8 @@ class Exchange:
raise InvalidOrderException(f"Amount {stake_amount} too high for {pair}")
raise OperationalException(
"Looped through all tiers without finding a max leverage. Should never be reached"
f"Looped through all tiers without finding a max leverage for {pair}. "
"Should never be reached."
)
elif self.trading_mode == TradingMode.MARGIN: # Search markets.limits for max lev
-1
View File
@@ -70,7 +70,6 @@ class Gate(Exchange):
"""
try:
if not self._config["dry_run"]:
# TODO: This should work with 4.4.34 and later.
self._api.load_unified_status()
is_unified = self._api.options.get("unifiedAccount")
@@ -101,7 +101,7 @@ class ReinforcementLearner(BaseReinforcementLearningModel):
return model
MyRLEnv: type[BaseEnvironment]
MyRLEnv: type[BaseEnvironment] # type: ignore[assignment, unused-ignore]
class MyRLEnv(Base5ActionRLEnv): # type: ignore[no-redef]
"""
+18 -44
View File
@@ -14,11 +14,10 @@ from typing import Any
from schedule import Scheduler
from freqtrade import constants
from freqtrade.configuration import validate_config_consistency
from freqtrade.configuration import remove_exchange_credentials, validate_config_consistency
from freqtrade.constants import BuySell, Config, EntryExecuteMode, ExchangeConfig, LongShort
from freqtrade.data.converter import order_book_to_dataframe
from freqtrade.data.dataprovider import DataProvider
from freqtrade.edge import Edge
from freqtrade.enums import (
ExitCheckTuple,
ExitType,
@@ -38,7 +37,6 @@ from freqtrade.exceptions import (
from freqtrade.exchange import (
ROUND_DOWN,
ROUND_UP,
remove_exchange_credentials,
timeframe_to_minutes,
timeframe_to_next_date,
timeframe_to_seconds,
@@ -131,13 +129,6 @@ class FreqtradeBot(LoggingMixin):
# Attach Wallets to strategy instance
self.strategy.wallets = self.wallets
# Initializing Edge only if enabled
self.edge = (
Edge(self.config, self.exchange, self.strategy)
if self.config.get("edge", {}).get("enabled", False)
else None
)
# Init ExternalMessageConsumer if enabled
self.emc = (
ExternalMessageConsumer(self.config, self.dataprovider)
@@ -242,9 +233,8 @@ class FreqtradeBot(LoggingMixin):
self.rpc.startup_messages(self.config, self.pairlists, self.protections)
# Update older trades with precision and precision mode
self.startup_backpopulate_precision()
if not self.edge:
# Adjust stoploss if it was changed
Trade.stoploss_reinitialization(self.strategy.stoploss)
# Adjust stoploss if it was changed
Trade.stoploss_reinitialization(self.strategy.stoploss)
# Only update open orders on startup
# This will update the database after the initial migration
@@ -335,7 +325,7 @@ class FreqtradeBot(LoggingMixin):
def _refresh_active_whitelist(self, trades: list[Trade] | None = None) -> list[str]:
"""
Refresh active whitelist from pairlist or edge and extend it with
Refresh active whitelist from pairlist and extend it with
pairs that have open trades.
"""
# Refresh whitelist
@@ -343,11 +333,6 @@ class FreqtradeBot(LoggingMixin):
self.pairlists.refresh_pairlist()
_whitelist = self.pairlists.whitelist
# Calculating Edge positioning
if self.edge:
self.edge.calculate(_whitelist)
_whitelist = self.edge.adjust(_whitelist)
if trades:
# Extend active-pair whitelist with pairs of open trades
# It ensures that candle (OHLCV) data are downloaded for open trades as well
@@ -701,9 +686,7 @@ class FreqtradeBot(LoggingMixin):
else:
self.log_once(f"Pair {pair} is currently locked.", logger.info)
return False
stake_amount = self.wallets.get_trade_stake_amount(
pair, self.config["max_open_trades"], self.edge
)
stake_amount = self.wallets.get_trade_stake_amount(pair, self.config["max_open_trades"])
bid_check_dom = self.config.get("entry_pricing", {}).get("check_depth_of_market", {})
if (bid_check_dom.get("enabled", False)) and (
@@ -1042,13 +1025,11 @@ class FreqtradeBot(LoggingMixin):
precision_mode_price=self.exchange.precision_mode_price,
contract_size=self.exchange.get_contract_size(pair),
)
stoploss = self.strategy.stoploss if not self.edge else self.edge.get_stoploss(pair)
stoploss = self.strategy.stoploss
trade.adjust_stop_loss(trade.open_rate, stoploss, initial=True)
else:
# This is additional entry, we reset fee_open_currency so timeout checking can work
trade.is_open = True
trade.fee_open_currency = None
trade.set_funding_fees(funding_fees)
trade.orders.append(order_obj)
@@ -1170,7 +1151,7 @@ class FreqtradeBot(LoggingMixin):
pair, enter_limit_requested, leverage
)
if not self.edge and trade is None:
if trade is None:
stake_available = self.wallets.get_available_stake_amount()
stake_amount = strategy_safe_wrapper(
self.strategy.custom_stake_amount, default_retval=stake_amount
@@ -1299,6 +1280,7 @@ class FreqtradeBot(LoggingMixin):
if (
not trade.has_open_orders
and not trade.has_open_sl_orders
and trade.fee_open_currency is not None
and not self.wallets.check_exit_amount(trade)
):
logger.warning(
@@ -1382,7 +1364,7 @@ class FreqtradeBot(LoggingMixin):
datetime.now(timezone.utc),
enter=enter,
exit_=exit_,
force_stoploss=self.edge.get_stoploss(trade.pair) if self.edge else 0,
force_stoploss=0,
)
for should_exit in exits:
if should_exit.exit_flag:
@@ -1487,13 +1469,6 @@ class FreqtradeBot(LoggingMixin):
# If enter order is fulfilled but there is no stoploss, we add a stoploss on exchange
if len(stoploss_orders) == 0:
stop_price = trade.stoploss_or_liquidation
if self.edge:
stoploss = self.edge.get_stoploss(pair=trade.pair)
stop_price = (
trade.open_rate * (1 - stoploss)
if trade.is_short
else trade.open_rate * (1 + stoploss)
)
if self.create_stoploss_order(trade=trade, stop_price=stop_price):
# The above will return False if the placement failed and the trade was force-sold.
@@ -2368,12 +2343,13 @@ class FreqtradeBot(LoggingMixin):
# If a entry order was closed, force update on stoploss on exchange
if order.ft_order_side == trade.entry_side:
if send_msg:
if trade.nr_of_successful_entries > 1:
# Reset fee_open_currency so fee checking can work
# Only necessary for additional entries
trade.fee_open_currency = None
# Don't cancel stoploss in recovery modes immediately
trade = self.cancel_stoploss_on_exchange(trade)
if not self.edge:
# TODO: should shorting/leverage be supported by Edge,
# then this will need to be fixed.
trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss, initial=True)
trade.adjust_stop_loss(trade.open_rate, self.strategy.stoploss, initial=True)
if (
order.ft_order_side == trade.entry_side
or (trade.amount > 0 and trade.is_open)
@@ -2476,10 +2452,9 @@ class FreqtradeBot(LoggingMixin):
return None
def handle_order_fee(self, trade: Trade, order_obj: Order, order: CcxtOrder) -> None:
# Try update amount (binance-fix)
# Try update amount (binance-fix - but also applies to different exchanges)
try:
fee_abs = self.get_real_amount(trade, order, order_obj)
if fee_abs is not None:
if (fee_abs := self.get_real_amount(trade, order, order_obj)) is not None:
order_obj.ft_fee_base = fee_abs
except DependencyException as exception:
logger.warning("Could not update trade amount: %s", exception)
@@ -2496,9 +2471,8 @@ class FreqtradeBot(LoggingMixin):
order_amount = safe_value_fallback(order, "filled", "amount")
# Only run for closed orders
if (
trade.fee_updated(order.get("side", ""))
or order["status"] == "open"
or order_obj.ft_fee_base
trade.fee_updated(order.get("side", "")) or order["status"] == "open"
# or order_obj.ft_fee_base
):
return None
+6 -1
View File
@@ -21,7 +21,9 @@ class FtRichHandler(Handler):
msg = self.format(record)
# Format log message
log_time = Text(
datetime.fromtimestamp(record.created).strftime("%Y-%m-%d %H:%M:%S,%f")[:-3],
datetime.fromtimestamp(record.created).strftime("%Y-%m-%d %H:%M:%S,%f")[:-3]
if record.created
else "N/A",
)
name = Text(record.name, style="violet")
log_level = Text(record.levelname, style=f"logging.level.{record.levelname.lower()}")
@@ -40,5 +42,8 @@ class FtRichHandler(Handler):
except RecursionError:
raise
except ImportError:
# Error when shutting down the console...
pass
except Exception:
self.handleError(record)
+5 -1
View File
@@ -1822,7 +1822,11 @@ class Backtesting:
# Update old results with new ones.
if len(self.all_bt_content) > 0:
results = generate_backtest_stats(
data, self.all_bt_content, min_date=min_date, max_date=max_date
data,
self.all_bt_content,
min_date=min_date,
max_date=max_date,
notes=self.config.get("backtest_notes"),
)
if self.results:
self.results["metadata"].update(results["metadata"])
-55
View File
@@ -1,55 +0,0 @@
# pragma pylint: disable=missing-docstring, W0212, too-many-arguments
"""
This module contains the edge backtesting interface
"""
import logging
from freqtrade import constants
from freqtrade.configuration import TimeRange, validate_config_consistency
from freqtrade.constants import Config
from freqtrade.data.dataprovider import DataProvider
from freqtrade.edge import Edge
from freqtrade.optimize.optimize_reports import generate_edge_table
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
logger = logging.getLogger(__name__)
class EdgeCli:
"""
EdgeCli class, this class contains all the logic to run edge backtesting
To run a edge backtest:
edge = EdgeCli(config)
edge.start()
"""
def __init__(self, config: Config) -> None:
self.config = config
# Ensure using dry-run
self.config["dry_run"] = True
self.config["stake_amount"] = constants.UNLIMITED_STAKE_AMOUNT
self.exchange = ExchangeResolver.load_exchange(self.config)
self.strategy = StrategyResolver.load_strategy(self.config)
self.strategy.dp = DataProvider(config, self.exchange)
validate_config_consistency(self.config)
self.edge = Edge(config, self.exchange, self.strategy)
# Set refresh_pairs to false for edge-cli (it must be true for edge)
self.edge._refresh_pairs = False
self.edge._timerange = TimeRange.parse_timerange(
None if self.config.get("timerange") is None else str(self.config.get("timerange"))
)
self.strategy.ft_bot_start()
def start(self) -> None:
result = self.edge.calculate(self.config["exchange"]["pair_whitelist"])
if result:
print("") # blank line for readability
generate_edge_table(self.edge._cached_pairs)
@@ -1,6 +1,5 @@
# flake8: noqa: F401
from freqtrade.optimize.optimize_reports.bt_output import (
generate_edge_table,
generate_wins_draws_losses,
show_backtest_result,
show_backtest_results,
@@ -499,33 +499,3 @@ def show_sorted_pairlist(config: Config, backtest_stats: BacktestResultType):
if result["key"] != "TOTAL":
print(f'"{result["key"]}", // {result["profit_mean"]:.2%}')
print("]")
def generate_edge_table(results: dict) -> None:
tabular_data = []
headers = [
"Pair",
"Stoploss",
"Win Rate",
"Risk Reward Ratio",
"Required Risk Reward",
"Expectancy",
"Total Number of Trades",
"Average Duration (min)",
]
for result in results.items():
if result[1].nb_trades > 0:
tabular_data.append(
[
result[0],
f"{result[1].stoploss:.10g}",
f"{result[1].winrate:.2f}",
f"{result[1].risk_reward_ratio:.2f}",
f"{result[1].required_risk_reward:.2f}",
f"{result[1].expectancy:.2f}",
result[1].nb_trades,
round(result[1].avg_trade_duration),
]
)
print_rich_table(tabular_data, headers, summary="EDGE TABLE")
@@ -347,7 +347,7 @@ def generate_trading_stats(results: DataFrame) -> dict[str, Any]:
else timedelta()
)
winner_holding_min = (
timedelta(minutes=round(winning_duration[winning_duration > 0].min()))
timedelta(minutes=round(winning_duration.min()))
if not winning_duration.empty
else timedelta()
)
@@ -362,7 +362,7 @@ def generate_trading_stats(results: DataFrame) -> dict[str, Any]:
else timedelta()
)
loser_holding_min = (
timedelta(minutes=round(losing_duration[losing_duration > 0].min()))
timedelta(minutes=round(losing_duration.min()))
if not losing_duration.empty
else timedelta()
)
@@ -669,6 +669,7 @@ def generate_backtest_stats(
all_results: dict[str, BacktestContentType],
min_date: datetime,
max_date: datetime,
notes: str | None = None,
) -> BacktestResultType:
"""
:param btdata: Backtest data
@@ -694,6 +695,8 @@ def generate_backtest_stats(
"backtest_start_ts": int(min_date.timestamp()),
"backtest_end_ts": int(max_date.timestamp()),
}
if notes:
metadata[strategy]["notes"] = notes
result["strategy"][strategy] = strat_stats
strategy_results = generate_strategy_comparison(bt_stats=result["strategy"])
+20
View File
@@ -2,6 +2,7 @@
This module contains the class to persist trades into SQLite
"""
import functools
import logging
import threading
from contextvars import ContextVar
@@ -94,3 +95,22 @@ def init_db(db_url: str) -> None:
previous_tables = inspect(engine).get_table_names()
ModelBase.metadata.create_all(engine)
check_migrate(engine, decl_base=ModelBase, previous_tables=previous_tables)
def custom_data_rpc_wrapper(func):
"""
Wrapper for RPC methods when using custom_data
Similar behavior to deps.get_rpc() - but limited to custom_data.
"""
@functools.wraps(func)
def wrapper(*args, **kwargs):
try:
_CustomData.session.rollback()
return func(*args, **kwargs)
finally:
_CustomData.session.rollback()
# Ensure the session is removed after use
_CustomData.session.remove()
return wrapper
+67 -93
View File
@@ -114,6 +114,7 @@ class Order(ModelBase):
order_update_date: Mapped[datetime | None] = mapped_column(nullable=True)
funding_fee: Mapped[float | None] = mapped_column(Float(), nullable=True)
# Fee if paid in base currency
ft_fee_base: Mapped[float | None] = mapped_column(Float(), nullable=True)
ft_order_tag: Mapped[str | None] = mapped_column(String(CUSTOM_TAG_MAX_LENGTH), nullable=True)
@@ -957,6 +958,10 @@ class LocalTrade:
) -> None:
"""
Update Fee parameters. Only acts once per side
:param fee_cost: Cost of the fee in stake currency
:param fee_currency: Currency the fee was paid in
:param fee_rate: Rate of the fee (e.g. 0.001 for 0.1%)
:param side: Side of the fee (buy / sell)
"""
if self.entry_side == side and self.fee_open_currency is None:
self.fee_open_cost = fee_cost
@@ -1627,6 +1632,7 @@ class LocalTrade:
remaining=order.get("remaining", 0.0),
funding_fee=order.get("funding_fee", None),
ft_order_tag=order.get("ft_order_tag", None),
ft_fee_base=order.get("ft_fee_base", None),
)
trade.orders.append(order_obj)
@@ -1646,120 +1652,88 @@ class Trade(ModelBase, LocalTrade):
use_db: bool = True
id: Mapped[int] = mapped_column(Integer, primary_key=True) # type: ignore
id: Mapped[int] = mapped_column(Integer, primary_key=True)
orders: Mapped[list[Order]] = relationship(
"Order", order_by="Order.id", cascade="all, delete-orphan", lazy="selectin", innerjoin=True
) # type: ignore
"Order",
order_by="Order.id",
cascade="all, delete-orphan",
lazy="selectin",
innerjoin=True,
back_populates="_trade_live",
)
custom_data: Mapped[list[_CustomData]] = relationship(
"_CustomData", cascade="all, delete-orphan", lazy="raise"
)
exchange: Mapped[str] = mapped_column(String(25), nullable=False) # type: ignore
pair: Mapped[str] = mapped_column(String(25), nullable=False, index=True) # type: ignore
base_currency: Mapped[str | None] = mapped_column(String(25), nullable=True) # type: ignore
stake_currency: Mapped[str | None] = mapped_column(String(25), nullable=True) # type: ignore
is_open: Mapped[bool] = mapped_column(nullable=False, default=True, index=True) # type: ignore
fee_open: Mapped[float] = mapped_column(Float(), nullable=False, default=0.0) # type: ignore
fee_open_cost: Mapped[float | None] = mapped_column(Float(), nullable=True) # type: ignore
fee_open_currency: Mapped[str | None] = mapped_column( # type: ignore
String(25), nullable=True
)
fee_close: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=False, default=0.0
)
fee_close_cost: Mapped[float | None] = mapped_column(Float(), nullable=True) # type: ignore
fee_close_currency: Mapped[str | None] = mapped_column( # type: ignore
String(25), nullable=True
)
open_rate: Mapped[float] = mapped_column(Float()) # type: ignore
open_rate_requested: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=True
)
exchange: Mapped[str] = mapped_column(String(25), nullable=False)
pair: Mapped[str] = mapped_column(String(25), nullable=False, index=True)
base_currency: Mapped[str | None] = mapped_column(String(25), nullable=True)
stake_currency: Mapped[str | None] = mapped_column(String(25), nullable=True)
is_open: Mapped[bool] = mapped_column(nullable=False, default=True, index=True)
fee_open: Mapped[float] = mapped_column(Float(), nullable=False, default=0.0)
# Fee cost in quote currency for entry the trade
fee_open_cost: Mapped[float | None] = mapped_column(Float(), nullable=True)
# Currency the fee was paid in. Has no relation to fee_open_cost.
fee_open_currency: Mapped[str | None] = mapped_column(String(25), nullable=True)
fee_close: Mapped[float | None] = mapped_column(Float(), nullable=False, default=0.0)
# Fee cost in quote currency for exit orders
fee_close_cost: Mapped[float | None] = mapped_column(Float(), nullable=True)
fee_close_currency: Mapped[str | None] = mapped_column(String(25), nullable=True)
open_rate: Mapped[float] = mapped_column(Float())
open_rate_requested: Mapped[float | None] = mapped_column(Float(), nullable=True)
# open_trade_value - calculated via _calc_open_trade_value
open_trade_value: Mapped[float] = mapped_column(Float(), nullable=True) # type: ignore
close_rate: Mapped[float | None] = mapped_column(Float()) # type: ignore
close_rate_requested: Mapped[float | None] = mapped_column(Float()) # type: ignore
realized_profit: Mapped[float] = mapped_column( # type: ignore
Float(), default=0.0, nullable=True
)
close_profit: Mapped[float | None] = mapped_column(Float()) # type: ignore
close_profit_abs: Mapped[float | None] = mapped_column(Float()) # type: ignore
stake_amount: Mapped[float] = mapped_column(Float(), nullable=False) # type: ignore
max_stake_amount: Mapped[float | None] = mapped_column(Float()) # type: ignore
amount: Mapped[float] = mapped_column(Float()) # type: ignore
amount_requested: Mapped[float | None] = mapped_column(Float()) # type: ignore
open_date: Mapped[datetime] = mapped_column( # type: ignore
nullable=False, default=datetime.now
)
close_date: Mapped[datetime | None] = mapped_column() # type: ignore
open_trade_value: Mapped[float] = mapped_column(Float(), nullable=True)
close_rate: Mapped[float | None] = mapped_column(Float())
close_rate_requested: Mapped[float | None] = mapped_column(Float())
realized_profit: Mapped[float] = mapped_column(Float(), default=0.0, nullable=True)
close_profit: Mapped[float | None] = mapped_column(Float())
close_profit_abs: Mapped[float | None] = mapped_column(Float())
stake_amount: Mapped[float] = mapped_column(Float(), nullable=False)
max_stake_amount: Mapped[float | None] = mapped_column(Float())
amount: Mapped[float] = mapped_column(Float())
amount_requested: Mapped[float | None] = mapped_column(Float())
open_date: Mapped[datetime] = mapped_column(nullable=False, default=datetime.now)
close_date: Mapped[datetime | None] = mapped_column()
# absolute value of the stop loss
stop_loss: Mapped[float] = mapped_column(Float(), nullable=True, default=0.0) # type: ignore
stop_loss: Mapped[float] = mapped_column(Float(), nullable=True, default=0.0)
# percentage value of the stop loss
stop_loss_pct: Mapped[float | None] = mapped_column(Float(), nullable=True) # type: ignore
stop_loss_pct: Mapped[float | None] = mapped_column(Float(), nullable=True)
# absolute value of the initial stop loss
initial_stop_loss: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=True, default=0.0
)
initial_stop_loss: Mapped[float | None] = mapped_column(Float(), nullable=True, default=0.0)
# percentage value of the initial stop loss
initial_stop_loss_pct: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=True
)
is_stop_loss_trailing: Mapped[bool] = mapped_column( # type: ignore
nullable=False, default=False
)
initial_stop_loss_pct: Mapped[float | None] = mapped_column(Float(), nullable=True)
is_stop_loss_trailing: Mapped[bool] = mapped_column(nullable=False, default=False)
# absolute value of the highest reached price
max_rate: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=True, default=0.0
)
max_rate: Mapped[float | None] = mapped_column(Float(), nullable=True, default=0.0)
# Lowest price reached
min_rate: Mapped[float | None] = mapped_column(Float(), nullable=True) # type: ignore
exit_reason: Mapped[str | None] = mapped_column( # type: ignore
String(CUSTOM_TAG_MAX_LENGTH), nullable=True
)
exit_order_status: Mapped[str | None] = mapped_column( # type: ignore
String(100), nullable=True
)
strategy: Mapped[str | None] = mapped_column(String(100), nullable=True) # type: ignore
enter_tag: Mapped[str | None] = mapped_column( # type: ignore
String(CUSTOM_TAG_MAX_LENGTH), nullable=True
)
timeframe: Mapped[int | None] = mapped_column(Integer, nullable=True) # type: ignore
min_rate: Mapped[float | None] = mapped_column(Float(), nullable=True)
exit_reason: Mapped[str | None] = mapped_column(String(CUSTOM_TAG_MAX_LENGTH), nullable=True)
exit_order_status: Mapped[str | None] = mapped_column(String(100), nullable=True)
strategy: Mapped[str | None] = mapped_column(String(100), nullable=True)
enter_tag: Mapped[str | None] = mapped_column(String(CUSTOM_TAG_MAX_LENGTH), nullable=True)
timeframe: Mapped[int | None] = mapped_column(Integer, nullable=True)
trading_mode: Mapped[TradingMode] = mapped_column( # type: ignore
Enum(TradingMode), nullable=True
)
amount_precision: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=True
)
price_precision: Mapped[float | None] = mapped_column(Float(), nullable=True) # type: ignore
precision_mode: Mapped[int | None] = mapped_column(Integer, nullable=True) # type: ignore
precision_mode_price: Mapped[int | None] = mapped_column( # type: ignore
Integer, nullable=True
)
contract_size: Mapped[float | None] = mapped_column(Float(), nullable=True) # type: ignore
trading_mode: Mapped[TradingMode] = mapped_column(Enum(TradingMode), nullable=True)
amount_precision: Mapped[float | None] = mapped_column(Float(), nullable=True)
price_precision: Mapped[float | None] = mapped_column(Float(), nullable=True)
precision_mode: Mapped[int | None] = mapped_column(Integer, nullable=True)
precision_mode_price: Mapped[int | None] = mapped_column(Integer, nullable=True)
contract_size: Mapped[float | None] = mapped_column(Float(), nullable=True)
# Leverage trading properties
leverage: Mapped[float] = mapped_column(Float(), nullable=True, default=1.0) # type: ignore
is_short: Mapped[bool] = mapped_column(nullable=False, default=False) # type: ignore
liquidation_price: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=True
)
leverage: Mapped[float] = mapped_column(Float(), nullable=True, default=1.0)
is_short: Mapped[bool] = mapped_column(nullable=False, default=False)
liquidation_price: Mapped[float | None] = mapped_column(Float(), nullable=True)
# Margin Trading Properties
interest_rate: Mapped[float] = mapped_column( # type: ignore
Float(), nullable=False, default=0.0
)
interest_rate: Mapped[float] = mapped_column(Float(), nullable=False, default=0.0)
# Futures properties
funding_fees: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=True, default=None
)
funding_fee_running: Mapped[float | None] = mapped_column( # type: ignore
Float(), nullable=True, default=None
)
funding_fees: Mapped[float | None] = mapped_column(Float(), nullable=True, default=None)
funding_fee_running: Mapped[float | None] = mapped_column(Float(), nullable=True, default=None)
record_version: Mapped[int] = mapped_column(Integer, nullable=False, default=2) # type: ignore
record_version: Mapped[int] = mapped_column(Integer, nullable=False, default=2)
def __init__(self, **kwargs):
from_json = kwargs.pop("__FROM_JSON", None)
+1 -1
View File
@@ -61,7 +61,7 @@ class PairListManager(LoggingMixin):
LoggingMixin.__init__(self, logger, refresh_period)
def _check_backtest(self) -> None:
if self._config["runmode"] not in (RunMode.BACKTEST, RunMode.EDGE, RunMode.HYPEROPT):
if self._config["runmode"] not in (RunMode.BACKTEST, RunMode.HYPEROPT):
return
pairlist_errors: list[str] = []
+1 -1
View File
@@ -8,6 +8,7 @@ from typing import Any
from fastapi import APIRouter, BackgroundTasks, Depends
from fastapi.exceptions import HTTPException
from freqtrade.configuration import remove_exchange_credentials
from freqtrade.configuration.config_validation import validate_config_consistency
from freqtrade.constants import Config
from freqtrade.data.btanalysis import (
@@ -20,7 +21,6 @@ from freqtrade.data.btanalysis import (
)
from freqtrade.enums import BacktestState
from freqtrade.exceptions import ConfigurationError, DependencyException, OperationalException
from freqtrade.exchange.common import remove_exchange_credentials
from freqtrade.ft_types import get_BacktestResultType_default
from freqtrade.misc import deep_merge_dicts, is_file_in_dir
from freqtrade.rpc.api_server.api_schemas import (
-6
View File
@@ -263,12 +263,6 @@ def list_custom_data(trade_id: int, key: str | None = Query(None), rpc: RPC = De
raise HTTPException(status_code=404, detail=str(e))
# TODO: Missing response model
@router.get("/edge", tags=["info"])
def edge(rpc: RPC = Depends(get_rpc)):
return rpc._rpc_edge()
@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 = ""
+3 -7
View File
@@ -35,7 +35,7 @@ from freqtrade.exchange.exchange_utils import price_to_precision
from freqtrade.ft_types import AnnotationType
from freqtrade.loggers import bufferHandler
from freqtrade.persistence import CustomDataWrapper, KeyValueStore, PairLocks, Trade
from freqtrade.persistence.models import PairLock
from freqtrade.persistence.models import PairLock, custom_data_rpc_wrapper
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
from freqtrade.rpc.rpc_types import RPCSendMsg
@@ -1125,6 +1125,7 @@ class RPC:
"cancel_order_count": c_count,
}
@custom_data_rpc_wrapper
def _rpc_list_custom_data(
self, trade_id: int | None = None, key: str | None = None, limit: int = 100, offset: int = 0
) -> list[dict[str, Any]]:
@@ -1137,6 +1138,7 @@ class RPC:
- "custom_data": a list of custom data dicts, each with the fields:
"id", "key", "type", "value", "created_at", "updated_at"
"""
trades: Sequence[Trade]
if trade_id is None:
# Get all open trades
@@ -1343,12 +1345,6 @@ class RPC:
return {"log_count": len(records), "logs": records}
def _rpc_edge(self) -> list[dict[str, Any]]:
"""Returns information related to Edge"""
if not self._freqtrade.edge:
raise RPCException("Edge is not enabled.")
return self._freqtrade.edge.accepted_pairs()
@staticmethod
def _convert_dataframe_to_dict(
strategy: str,
+8 -22
View File
@@ -215,7 +215,6 @@ class Telegram(RPCHandler):
r"/forceshort$",
r"/forcesell$",
r"/forceexit$",
r"/edge$",
r"/health$",
r"/help$",
r"/version$",
@@ -299,7 +298,6 @@ class Telegram(RPCHandler):
CommandHandler("blacklist", self._blacklist),
CommandHandler(["blacklist_delete", "bl_delete"], self._blacklist_delete),
CommandHandler("logs", self._logs),
CommandHandler("edge", self._edge),
CommandHandler("health", self._health),
CommandHandler("help", self._help),
CommandHandler("version", self._version),
@@ -1044,12 +1042,15 @@ class Telegram(RPCHandler):
else:
# Message to display
if stats["closed_trade_count"] > 0:
fiat_closed_trades = (
f"∙ `{fmt_coin(profit_closed_fiat, fiat_disp_cur)}`\n" if fiat_disp_cur else ""
)
markdown_msg = (
"*ROI:* Closed trades\n"
f"∙ `{fmt_coin(profit_closed_coin, stake_cur)} "
f"({profit_closed_ratio_mean:.2%}) "
f"({profit_closed_percent} \N{GREEK CAPITAL LETTER SIGMA}%)`\n"
f"∙ `{fmt_coin(profit_closed_fiat, fiat_disp_cur)}`\n"
f"{fiat_closed_trades}"
)
else:
markdown_msg = "`No closed trade` \n"
@@ -1224,10 +1225,13 @@ class Telegram(RPCHandler):
total_stake = fmt_coin(
result["total" if full_result else "total_bot"], result["stake"], False
)
fiat_estimated_value = (
f"\t`{result['symbol']}: {value}`{fiat_val}\n" if result["symbol"] else ""
)
output += (
f"\n*Estimated Value{' (Bot managed assets only)' if not full_result else ''}*:\n"
f"\t`{result['stake']}: {total_stake}`{stake_improve}\n"
f"\t`{result['symbol']}: {value}`{fiat_val}\n"
f"{fiat_estimated_value}"
)
await self._send_msg(
output, reload_able=True, callback_path="update_balance", query=update.callback_query
@@ -1789,23 +1793,6 @@ class Telegram(RPCHandler):
if msgs:
await self._send_msg(msgs, parse_mode=ParseMode.MARKDOWN_V2)
@authorized_only
async def _edge(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /edge
Shows information related to Edge
"""
edge_pairs = self._rpc._rpc_edge()
if not edge_pairs:
message = "<b>Edge only validated following pairs:</b>"
await self._send_msg(message, parse_mode=ParseMode.HTML)
for chunk in chunks(edge_pairs, 25):
edge_pairs_tab = tabulate(chunk, headers="keys", tablefmt="simple")
message = f"<b>Edge only validated following pairs:</b>\n<pre>{edge_pairs_tab}</pre>"
await self._send_msg(message, parse_mode=ParseMode.HTML)
@authorized_only
async def _help(self, update: Update, context: CallbackContext) -> None:
"""
@@ -1858,7 +1845,6 @@ class Telegram(RPCHandler):
"*/balance total:* `Show account balance per currency`\n"
"*/logs [limit]:* `Show latest logs - defaults to 10` \n"
"*/count:* `Show number of active trades compared to allowed number of trades`\n"
"*/edge:* `Shows validated pairs by Edge if it is enabled` \n"
"*/health* `Show latest process timestamp - defaults to 1970-01-01 00:00:00` \n"
"*/marketdir [long | short | even | none]:* `Updates the user managed variable "
"that represents the current market direction. If no direction is provided `"
+5 -13
View File
@@ -352,7 +352,7 @@ class Wallets:
return max(stake_amount, 0)
def get_trade_stake_amount(
self, pair: str, max_open_trades: IntOrInf, edge=None, update: bool = True
self, pair: str, max_open_trades: IntOrInf, update: bool = True
) -> float:
"""
Calculate stake amount for the trade
@@ -366,19 +366,11 @@ class Wallets:
val_tied_up = Trade.total_open_trades_stakes()
available_amount = self.get_available_stake_amount()
if edge:
stake_amount = edge.stake_amount(
pair,
self.get_free(self._stake_currency),
self.get_total(self._stake_currency),
val_tied_up,
stake_amount = self._config["stake_amount"]
if stake_amount == UNLIMITED_STAKE_AMOUNT:
stake_amount = self._calculate_unlimited_stake_amount(
available_amount, val_tied_up, max_open_trades
)
else:
stake_amount = self._config["stake_amount"]
if stake_amount == UNLIMITED_STAKE_AMOUNT:
stake_amount = self._calculate_unlimited_stake_amount(
available_amount, val_tied_up, max_open_trades
)
return self._check_available_stake_amount(stake_amount, available_amount)
+1 -1
View File
@@ -1,7 +1,7 @@
from freqtrade_client.ft_rest_client import FtRestClient
__version__ = "2025.5"
__version__ = "2025.6"
if "dev" in __version__:
from pathlib import Path
+2 -9
View File
@@ -189,13 +189,6 @@ class FtRestClient:
"""
return self._get("monthly", params={"timescale": months} if months else None)
def edge(self):
"""Return information about edge.
:return: json object
"""
return self._get("edge")
def profit(self):
"""Return the profit summary.
@@ -268,8 +261,8 @@ class FtRestClient:
params["limit"] = limit
if offset:
params["offset"] = offset
if order_by_id:
params["order_by_id"] = True
if not order_by_id:
params["order_by_id"] = False
return self._get("trades", params)
def list_open_trades_custom_data(self, key=None, limit=100, offset=0):
+1
View File
@@ -23,6 +23,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Operating System :: MacOS",
"Operating System :: Unix",
"Topic :: Office/Business :: Financial :: Investment",
+1 -1
View File
@@ -1,3 +1,3 @@
# Requirements for freqtrade client library
requests==2.32.3
requests==2.32.4
python-rapidjson==1.20
@@ -72,7 +72,6 @@ def test_FtRestClient_call_invalid(caplog):
("weekly", [15], {}),
("monthly", [], {}),
("monthly", [12], {}),
("edge", [], {}),
("profit", [], {}),
("stats", [], {}),
("performance", [], {}),
+6 -1
View File
@@ -52,7 +52,6 @@ nav:
- Orderflow: advanced-orderflow.md
- Producer/Consumer mode: producer-consumer.md
- SQL Cheat-sheet: sql_cheatsheet.md
- Edge Positioning: edge.md
- FAQ: faq.md
- Strategy migration: strategy_migration.md
- Updating Freqtrade: updating.md
@@ -62,11 +61,14 @@ theme:
name: material
logo: "images/logo.png"
favicon: "images/logo.png"
icon:
repo: fontawesome/brands/github
custom_dir: "docs/overrides"
features:
- content.code.annotate
- search.share
- content.code.copy
- content.action.edit
- navigation.top
- navigation.footer
palette:
@@ -115,6 +117,9 @@ markdown_extensions:
custom_checkbox: true
- pymdownx.tilde
- mdx_truly_sane_lists
- pymdownx.emoji:
emoji_index: !!python/name:material.extensions.emoji.twemoji
emoji_generator: !!python/name:material.extensions.emoji.to_svg
extra:
version:
provider: mike
+14 -6
View File
@@ -22,6 +22,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Operating System :: MacOS",
"Operating System :: Unix",
"Topic :: Office/Business :: Financial :: Investment",
@@ -29,7 +30,7 @@ classifiers = [
dependencies = [
# from requirements.txt
"ccxt>=4.4.60",
"ccxt>=4.4.87",
"SQLAlchemy>=2.0.6",
"python-telegram-bot>=20.1",
"humanize>=4.0.0",
@@ -38,14 +39,13 @@ dependencies = [
"httpx>=0.24.1",
"urllib3",
"jsonschema",
"numpy<2.0",
"numpy>2.0,<3.0",
"pandas>=2.2.0,<3.0",
"TA-Lib",
"pandas-ta",
"TA-Lib<0.6",
"ft-pandas-ta",
"technical",
"tabulate",
"pycoingecko>=3.2.0",
"py_find_1st",
"python-rapidjson",
"orjson",
"jinja2",
@@ -100,7 +100,6 @@ freqai_rl = [
"tqdm",
]
develop = [
"coveralls",
"isort",
"mypy",
"pre-commit",
@@ -207,6 +206,14 @@ plugins = [
module = "tests.*"
ignore_errors = true
[[tool.mypy.overrides]]
module = [
"freqtrade.templates.*",
"tests.strategy.strats"
]
# Disable attr-defined check due to ta-lib not having type stubs
disable_error_code = "attr-defined"
[tool.pyright]
include = ["freqtrade", "ft_client"]
exclude = [
@@ -283,6 +290,7 @@ extend-ignore = [
"RUF010", # Use explicit conversion flag
"RUF012", # mutable-class-default
"RUF022", # unsorted-dunder-all
"RUF005", # list concatenation
]
[tool.ruff.lint.mccabe]
+9 -10
View File
@@ -6,17 +6,16 @@
-r requirements-freqai-rl.txt
-r docs/requirements-docs.txt
coveralls==4.0.1
ruff==0.11.11
mypy==1.15.0
ruff==0.12.1
mypy==1.16.1
pre-commit==4.2.0
pytest==8.3.5
pytest-asyncio==0.26.0
pytest-cov==6.1.1
pytest-mock==3.14.0
pytest-random-order==1.1.1
pytest==8.4.1
pytest-asyncio==1.0.0
pytest-cov==6.2.1
pytest-mock==3.14.1
pytest-random-order==1.2.0
pytest-timeout==2.4.0
pytest-xdist==3.6.1
pytest-xdist==3.7.0
isort==6.0.1
# For datetime mocking
time-machine==2.16.0
@@ -27,6 +26,6 @@ nbconvert==7.16.6
# mypy types
types-cachetools==6.0.0.20250525
types-filelock==3.2.7
types-requests==2.32.0.20250515
types-requests==2.32.4.20250611
types-tabulate==0.9.0.20241207
types-python-dateutil==2.9.0.20250516
+2 -3
View File
@@ -2,11 +2,10 @@
-r requirements-freqai.txt
# Required for freqai-rl
torch==2.7.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
torch==2.7.1; sys_platform != 'darwin' or platform_machine != 'x86_64'
gymnasium==0.29.1
# SB3 >=2.5.0 depends on torch 2.3.0 - which implies it dropped support x86 macos
stable_baselines3==2.4.1; sys_platform == 'darwin' and platform_machine == 'x86_64'
stable_baselines3==2.5.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
stable_baselines3==2.6.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
sb3_contrib>=2.2.1
# Progress bar for stable-baselines3 and sb3-contrib
tqdm==4.67.1
+1 -1
View File
@@ -3,7 +3,7 @@
-r requirements-plot.txt
# Required for freqai
scikit-learn==1.6.1
scikit-learn==1.7.0
joblib==1.5.1
catboost==1.2.8; 'arm' not in platform_machine
lightgbm==4.6.0
+2 -2
View File
@@ -3,7 +3,7 @@
# Required for hyperopt
scipy==1.15.3
scikit-learn==1.6.1
scikit-learn==1.7.0
filelock==3.18.0
optuna==4.3.0
optuna==4.4.0
cmaes==0.11.1
+1 -1
View File
@@ -1,4 +1,4 @@
# Include all requirements to run the bot.
-r requirements.txt
plotly==6.1.1
plotly==6.2.0
+21 -23
View File
@@ -1,24 +1,25 @@
numpy==1.26.4
pandas==2.2.3
bottleneck==1.4.2
numexpr==2.10.2
pandas-ta==0.3.14b
numpy==2.2.6
pandas==2.3.0
bottleneck==1.5.0
numexpr==2.11.0
# Indicator libraries
ft-pandas-ta==0.3.15
ta-lib==0.5.5
technical==1.5.1
ccxt==4.4.82
cryptography==45.0.3
aiohttp==3.9.5
ccxt==4.4.91
cryptography==45.0.4
aiohttp==3.12.13
SQLAlchemy==2.0.41
python-telegram-bot==22.1
python-telegram-bot==22.2
# can't be hard-pinned due to telegram-bot pinning httpx with ~
httpx>=0.24.1
humanize==4.12.3
cachetools==6.0.0
requests==2.32.3
urllib3==2.4.0
certifi==2025.4.26
jsonschema==4.23.0
TA-Lib==0.4.38
technical==1.5.0
cachetools==6.1.0
requests==2.32.4
urllib3==2.5.0
certifi==2025.6.15
jsonschema==4.24.0
tabulate==0.9.0
pycoingecko==3.2.0
jinja2==3.1.6
@@ -26,9 +27,6 @@ joblib==1.5.1
rich==14.0.0
pyarrow==20.0.0; platform_machine != 'armv7l'
# find first, C search in arrays
py_find_1st==1.1.7
# Load ticker files 30% faster
python-rapidjson==1.20
# Properly format api responses
@@ -38,9 +36,9 @@ orjson==3.10.18
sdnotify==0.3.2
# API Server
fastapi==0.115.12
pydantic==2.11.5
uvicorn==0.34.2
fastapi==0.115.14
pydantic==2.11.7
uvicorn==0.35.0
pyjwt==2.10.1
aiofiles==24.1.0
psutil==7.0.0
@@ -59,5 +57,5 @@ schedule==1.2.2
websockets==15.0.1
janus==2.0.0
ast-comments==1.2.2
ast-comments==1.2.3
packaging==25.0
+3
View File
@@ -150,13 +150,16 @@ function Test-PythonExecutable {
function Find-PythonExecutable {
$PythonExecutables = @(
"python",
"python3.13",
"python3.12",
"python3.11",
"python3.10",
"python3",
"C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python313\python.exe",
"C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python312\python.exe",
"C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python311\python.exe",
"C:\Users\$env:USERNAME\AppData\Local\Programs\Python\Python310\python.exe",
"C:\Python313\python.exe",
"C:\Python312\python.exe",
"C:\Python311\python.exe",
"C:\Python310\python.exe"
+2 -2
View File
@@ -25,7 +25,7 @@ function check_installed_python() {
exit 2
fi
for v in 12 11 10
for v in 13 12 11 10
do
PYTHON="python3.${v}"
which $PYTHON
@@ -257,7 +257,7 @@ function install() {
install_redhat
else
echo "This script does not support your OS."
echo "If you have Python version 3.10 - 3.12, pip, virtualenv, ta-lib you can continue."
echo "If you have Python version 3.10 - 3.13, pip, virtualenv, ta-lib you can continue."
echo "Wait 10 seconds to continue the next install steps or use ctrl+c to interrupt this shell."
sleep 10
fi
+13
View File
@@ -16,6 +16,7 @@ from freqtrade.commands import (
start_convert_trades,
start_create_userdir,
start_download_data,
start_edge,
start_hyperopt_list,
start_hyperopt_show,
start_install_ui,
@@ -1937,3 +1938,15 @@ def test_start_show_config(capsys, caplog):
assert '"max_open_trades":' in captured.out
assert '"secret": "REDACTED"' not in captured.out
assert log_has_re(r"Sensitive information will be shown in the upcoming output.*", caplog)
def test_start_edge():
args = [
"edge",
"--config",
"tests/testdata/testconfigs/main_test_config.json",
]
pargs = get_args(args)
with pytest.raises(OperationalException, match="The Edge module has been deprecated in 2023.9"):
start_edge(pargs)
+1 -45
View File
@@ -16,8 +16,7 @@ from xdist.scheduler.loadscope import LoadScopeScheduling
from freqtrade import constants
from freqtrade.commands import Arguments
from freqtrade.data.converter import ohlcv_to_dataframe, trades_list_to_df
from freqtrade.edge import PairInfo
from freqtrade.enums import CandleType, MarginMode, RunMode, SignalDirection, TradingMode
from freqtrade.enums import CandleType, MarginMode, SignalDirection, TradingMode
from freqtrade.exchange import Exchange, timeframe_to_minutes, timeframe_to_seconds
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import LocalTrade, Order, Trade, init_db
@@ -298,24 +297,6 @@ def patch_whitelist(mocker, conf) -> None:
)
def patch_edge(mocker) -> None:
# "ETH/BTC",
# "LTC/BTC",
# "XRP/BTC",
# "NEO/BTC"
mocker.patch(
"freqtrade.edge.Edge._cached_pairs",
mocker.PropertyMock(
return_value={
"NEO/BTC": PairInfo(-0.20, 0.66, 3.71, 0.50, 1.71, 10, 25),
"LTC/BTC": PairInfo(-0.21, 0.66, 3.71, 0.50, 1.71, 11, 20),
}
),
)
mocker.patch("freqtrade.edge.Edge.calculate", MagicMock(return_value=True))
# Functions for recurrent object patching
@@ -2603,31 +2584,6 @@ def buy_order_fee():
}
@pytest.fixture(scope="function")
def edge_conf(default_conf):
conf = deepcopy(default_conf)
conf["runmode"] = RunMode.DRY_RUN
conf["max_open_trades"] = -1
conf["tradable_balance_ratio"] = 0.5
conf["stake_amount"] = constants.UNLIMITED_STAKE_AMOUNT
conf["edge"] = {
"enabled": True,
"process_throttle_secs": 1800,
"calculate_since_number_of_days": 14,
"allowed_risk": 0.01,
"stoploss_range_min": -0.01,
"stoploss_range_max": -0.1,
"stoploss_range_step": -0.01,
"maximum_winrate": 0.80,
"minimum_expectancy": 0.20,
"min_trade_number": 15,
"max_trade_duration_minute": 1440,
"remove_pumps": False,
}
return conf
@pytest.fixture
def rpc_balance():
return {
-606
View File
@@ -1,606 +0,0 @@
# pragma pylint: disable=missing-docstring, C0103, C0330
# pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments
import logging
import math
from datetime import timedelta
from unittest.mock import MagicMock
import numpy as np
import pytest
from pandas import DataFrame
from freqtrade.data.converter import ohlcv_to_dataframe
from freqtrade.edge import Edge, PairInfo
from freqtrade.enums import ExitType
from freqtrade.exceptions import OperationalException
from freqtrade.util.datetime_helpers import dt_ts, dt_utc
from tests.conftest import EXMS, get_patched_freqtradebot, log_has
from tests.optimize import (
BTContainer,
BTrade,
_build_backtest_dataframe,
_get_frame_time_from_offset,
)
# Cases to be tested:
# 1) Open trade should be removed from the end
# 2) Two complete trades within dataframe (with sell hit for all)
# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
# 4) Entered, sl 3%, candle drops 4%, recovers to 1% => Trade closed, 3% loss
# 5) Stoploss and sell are hit. should sell on stoploss
####################################################################
tests_start_time = dt_utc(2018, 10, 3)
timeframe_in_minute = 60
# End helper functions
# Open trade should be removed from the end
tc0 = BTContainer(
data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 1],
], # enter trade (signal on last candle)
stop_loss=-0.99,
roi={"0": float("inf")},
profit_perc=0.00,
trades=[],
)
# Two complete trades within dataframe(with sell hit for all)
tc1 = BTContainer(
data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 1], # enter trade (signal on last candle)
[2, 5000, 5025, 4975, 4987, 6172, 0, 0], # exit at open
[3, 5000, 5025, 4975, 4987, 6172, 1, 0], # no action
[4, 5000, 5025, 4975, 4987, 6172, 0, 0], # should enter the trade
[5, 5000, 5025, 4975, 4987, 6172, 0, 1], # no action
[6, 5000, 5025, 4975, 4987, 6172, 0, 0], # should sell
],
stop_loss=-0.99,
roi={"0": float("inf")},
profit_perc=0.00,
trades=[
BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=1, close_tick=2),
BTrade(exit_reason=ExitType.EXIT_SIGNAL, open_tick=4, close_tick=6),
],
)
# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
tc2 = BTContainer(
data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4600, 4987, 6172, 0, 0], # enter trade, stoploss hit
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
],
stop_loss=-0.01,
roi={"0": float("inf")},
profit_perc=-0.01,
trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)],
)
# 4) Entered, sl 3 %, candle drops 4%, recovers to 1 % = > Trade closed, 3 % loss
tc3 = BTContainer(
data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4800, 4987, 6172, 0, 0], # enter trade, stoploss hit
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
],
stop_loss=-0.03,
roi={"0": float("inf")},
profit_perc=-0.03,
trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)],
)
# 5) Stoploss and sell are hit. should sell on stoploss
tc4 = BTContainer(
data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4800, 4987, 6172, 0, 1], # enter trade, stoploss hit, sell signal
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
],
stop_loss=-0.03,
roi={"0": float("inf")},
profit_perc=-0.03,
trades=[BTrade(exit_reason=ExitType.STOP_LOSS, open_tick=1, close_tick=1)],
)
TESTS = [tc0, tc1, tc2, tc3, tc4]
@pytest.mark.parametrize("data", TESTS)
def test_edge_results(edge_conf, mocker, caplog, data) -> None:
"""
run functional tests
"""
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
frame = _build_backtest_dataframe(data.data)
caplog.set_level(logging.DEBUG)
edge.fee = 0
trades = edge._find_trades_for_stoploss_range(frame, "TEST/BTC", [data.stop_loss])
results = edge._fill_calculable_fields(DataFrame(trades)) if trades else DataFrame()
assert len(trades) == len(data.trades)
if not results.empty:
assert round(results["profit_ratio"].sum(), 3) == round(data.profit_perc, 3)
for c, trade in enumerate(data.trades):
res = results.iloc[c]
assert res.exit_type == trade.exit_reason
assert res.open_date == _get_frame_time_from_offset(trade.open_tick).replace(tzinfo=None)
assert res.close_date == _get_frame_time_from_offset(trade.close_tick).replace(tzinfo=None)
def test_adjust(mocker, edge_conf):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
mocker.patch(
"freqtrade.edge.Edge._cached_pairs",
mocker.PropertyMock(
return_value={
"E/F": PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
"C/D": PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
"N/O": PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
}
),
)
pairs = ["A/B", "C/D", "E/F", "G/H"]
assert edge.adjust(pairs) == ["E/F", "C/D"]
def test_edge_get_stoploss(mocker, edge_conf):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
mocker.patch(
"freqtrade.edge.Edge._cached_pairs",
mocker.PropertyMock(
return_value={
"E/F": PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
"C/D": PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
"N/O": PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
}
),
)
assert edge.get_stoploss("E/F") == -0.01
def test_nonexisting_get_stoploss(mocker, edge_conf):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
mocker.patch(
"freqtrade.edge.Edge._cached_pairs",
mocker.PropertyMock(
return_value={
"E/F": PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
}
),
)
assert edge.get_stoploss("N/O") == -0.1
def test_edge_stake_amount(mocker, edge_conf):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
mocker.patch(
"freqtrade.edge.Edge._cached_pairs",
mocker.PropertyMock(
return_value={
"E/F": PairInfo(-0.02, 0.66, 3.71, 0.50, 1.71, 10, 60),
}
),
)
assert edge._capital_ratio == 0.5
assert (
edge.stake_amount("E/F", free_capital=100, total_capital=100, capital_in_trade=25) == 31.25
)
assert edge.stake_amount("E/F", free_capital=20, total_capital=100, capital_in_trade=25) == 20
assert edge.stake_amount("E/F", free_capital=0, total_capital=100, capital_in_trade=25) == 0
# Test with increased allowed_risk
# Result should be no more than allowed capital
edge._allowed_risk = 0.4
edge._capital_ratio = 0.5
assert (
edge.stake_amount("E/F", free_capital=100, total_capital=100, capital_in_trade=25) == 62.5
)
assert edge.stake_amount("E/F", free_capital=100, total_capital=100, capital_in_trade=0) == 50
edge._capital_ratio = 1
# Full capital is available
assert edge.stake_amount("E/F", free_capital=100, total_capital=100, capital_in_trade=0) == 100
# Full capital is available
assert edge.stake_amount("E/F", free_capital=0, total_capital=100, capital_in_trade=0) == 0
def test_nonexisting_stake_amount(mocker, edge_conf):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
mocker.patch(
"freqtrade.edge.Edge._cached_pairs",
mocker.PropertyMock(
return_value={
"E/F": PairInfo(-0.11, 0.66, 3.71, 0.50, 1.71, 10, 60),
}
),
)
# should use strategy stoploss
assert edge.stake_amount("N/O", 1, 2, 1) == 0.15
def test_edge_heartbeat_calculate(mocker, edge_conf):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
heartbeat = edge_conf["edge"]["process_throttle_secs"]
# should not recalculate if heartbeat not reached
edge._last_updated = dt_ts() - heartbeat + 1
assert edge.calculate(edge_conf["exchange"]["pair_whitelist"]) is False
def mocked_load_data(datadir, pairs=None, timeframe="0m", timerange=None, *args, **kwargs):
if pairs is None:
pairs = []
hz = 0.1
base = 0.001
NEOBTC = [
[
dt_ts(tests_start_time + timedelta(minutes=(x * timeframe_in_minute))),
math.sin(x * hz) / 1000 + base,
math.sin(x * hz) / 1000 + base + 0.0001,
math.sin(x * hz) / 1000 + base - 0.0001,
math.sin(x * hz) / 1000 + base,
123.45,
]
for x in range(0, 500)
]
hz = 0.2
base = 0.002
LTCBTC = [
[
dt_ts(tests_start_time + timedelta(minutes=(x * timeframe_in_minute))),
math.sin(x * hz) / 1000 + base,
math.sin(x * hz) / 1000 + base + 0.0001,
math.sin(x * hz) / 1000 + base - 0.0001,
math.sin(x * hz) / 1000 + base,
123.45,
]
for x in range(0, 500)
]
pairdata = {
"NEO/BTC": ohlcv_to_dataframe(NEOBTC, "1h", pair="NEO/BTC", fill_missing=True),
"LTC/BTC": ohlcv_to_dataframe(LTCBTC, "1h", pair="LTC/BTC", fill_missing=True),
}
return pairdata
def test_edge_process_downloaded_data(mocker, edge_conf):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
mocker.patch(f"{EXMS}.get_fee", MagicMock(return_value=0.001))
mocker.patch("freqtrade.edge.edge_positioning.refresh_data", MagicMock())
mocker.patch("freqtrade.edge.edge_positioning.load_data", mocked_load_data)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
assert edge.calculate(edge_conf["exchange"]["pair_whitelist"])
assert len(edge._cached_pairs) == 2
assert edge._last_updated <= dt_ts() + 2
def test_edge_process_no_data(mocker, edge_conf, caplog):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
mocker.patch(f"{EXMS}.get_fee", MagicMock(return_value=0.001))
mocker.patch("freqtrade.edge.edge_positioning.refresh_data", MagicMock())
mocker.patch("freqtrade.edge.edge_positioning.load_data", MagicMock(return_value={}))
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
assert not edge.calculate(edge_conf["exchange"]["pair_whitelist"])
assert len(edge._cached_pairs) == 0
assert log_has("No data found. Edge is stopped ...", caplog)
assert edge._last_updated == 0
def test_edge_process_no_trades(mocker, edge_conf, caplog):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
mocker.patch(f"{EXMS}.get_fee", return_value=0.001)
mocker.patch(
"freqtrade.edge.edge_positioning.refresh_data",
)
mocker.patch("freqtrade.edge.edge_positioning.load_data", mocked_load_data)
# Return empty
mocker.patch("freqtrade.edge.Edge._find_trades_for_stoploss_range", return_value=[])
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
assert not edge.calculate(edge_conf["exchange"]["pair_whitelist"])
assert len(edge._cached_pairs) == 0
assert log_has("No trades found.", caplog)
def test_edge_process_no_pairs(mocker, edge_conf, caplog):
edge_conf["exchange"]["pair_whitelist"] = []
mocker.patch("freqtrade.freqtradebot.validate_config_consistency")
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
fee_mock = mocker.patch(f"{EXMS}.get_fee", return_value=0.001)
mocker.patch("freqtrade.edge.edge_positioning.refresh_data")
mocker.patch("freqtrade.edge.edge_positioning.load_data", mocked_load_data)
# Return empty
mocker.patch("freqtrade.edge.Edge._find_trades_for_stoploss_range", return_value=[])
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
assert fee_mock.call_count == 0
assert edge.fee is None
assert not edge.calculate(["XRP/USDT"])
assert fee_mock.call_count == 1
assert edge.fee == 0.001
def test_edge_init_error(mocker, edge_conf):
edge_conf["stake_amount"] = 0.5
mocker.patch(f"{EXMS}.get_fee", MagicMock(return_value=0.001))
with pytest.raises(OperationalException, match="Edge works only with unlimited stake amount"):
get_patched_freqtradebot(mocker, edge_conf)
@pytest.mark.parametrize(
"fee,risk_reward_ratio,expectancy",
[
(0.0005, 306.5384615384, 101.5128205128),
(0.001, 152.6923076923, 50.2307692308),
],
)
def test_process_expectancy(mocker, edge_conf, fee, risk_reward_ratio, expectancy):
edge_conf["edge"]["min_trade_number"] = 2
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
def get_fee(*args, **kwargs):
return fee
freqtrade.exchange.get_fee = get_fee
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
trades = [
{
"pair": "TEST/BTC",
"stoploss": -0.9,
"profit_percent": "",
"profit_abs": "",
"open_date": np.datetime64("2018-10-03T00:05:00.000000000"),
"close_date": np.datetime64("2018-10-03T00:10:00.000000000"),
"trade_duration": "",
"open_rate": 17,
"close_rate": 17,
"exit_type": "exit_signal",
},
{
"pair": "TEST/BTC",
"stoploss": -0.9,
"profit_percent": "",
"profit_abs": "",
"open_date": np.datetime64("2018-10-03T00:20:00.000000000"),
"close_date": np.datetime64("2018-10-03T00:25:00.000000000"),
"trade_duration": "",
"open_rate": 20,
"close_rate": 20,
"exit_type": "exit_signal",
},
{
"pair": "TEST/BTC",
"stoploss": -0.9,
"profit_percent": "",
"profit_abs": "",
"open_date": np.datetime64("2018-10-03T00:30:00.000000000"),
"close_date": np.datetime64("2018-10-03T00:40:00.000000000"),
"trade_duration": "",
"open_rate": 26,
"close_rate": 34,
"exit_type": "exit_signal",
},
]
trades_df = DataFrame(trades)
trades_df = edge._fill_calculable_fields(trades_df)
final = edge._process_expectancy(trades_df)
assert len(final) == 1
assert "TEST/BTC" in final
assert final["TEST/BTC"].stoploss == -0.9
assert round(final["TEST/BTC"].winrate, 10) == 0.3333333333
assert round(final["TEST/BTC"].risk_reward_ratio, 10) == risk_reward_ratio
assert round(final["TEST/BTC"].required_risk_reward, 10) == 2.0
assert round(final["TEST/BTC"].expectancy, 10) == expectancy
# Pop last item so no trade is profitable
trades.pop()
trades_df = DataFrame(trades)
trades_df = edge._fill_calculable_fields(trades_df)
final = edge._process_expectancy(trades_df)
assert len(final) == 0
assert isinstance(final, dict)
def test_process_expectancy_remove_pumps(mocker, edge_conf, fee):
edge_conf["edge"]["min_trade_number"] = 2
edge_conf["edge"]["remove_pumps"] = True
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
freqtrade.exchange.get_fee = fee
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
trades = [
{
"pair": "TEST/BTC",
"stoploss": -0.9,
"profit_percent": "",
"profit_abs": "",
"open_date": np.datetime64("2018-10-03T00:05:00.000000000"),
"close_date": np.datetime64("2018-10-03T00:10:00.000000000"),
"open_index": 1,
"close_index": 1,
"trade_duration": "",
"open_rate": 17,
"close_rate": 15,
"exit_type": "sell_signal",
},
{
"pair": "TEST/BTC",
"stoploss": -0.9,
"profit_percent": "",
"profit_abs": "",
"open_date": np.datetime64("2018-10-03T00:20:00.000000000"),
"close_date": np.datetime64("2018-10-03T00:25:00.000000000"),
"open_index": 4,
"close_index": 4,
"trade_duration": "",
"open_rate": 20,
"close_rate": 10,
"exit_type": "sell_signal",
},
{
"pair": "TEST/BTC",
"stoploss": -0.9,
"profit_percent": "",
"profit_abs": "",
"open_date": np.datetime64("2018-10-03T00:20:00.000000000"),
"close_date": np.datetime64("2018-10-03T00:25:00.000000000"),
"open_index": 4,
"close_index": 4,
"trade_duration": "",
"open_rate": 20,
"close_rate": 10,
"exit_type": "sell_signal",
},
{
"pair": "TEST/BTC",
"stoploss": -0.9,
"profit_percent": "",
"profit_abs": "",
"open_date": np.datetime64("2018-10-03T00:20:00.000000000"),
"close_date": np.datetime64("2018-10-03T00:25:00.000000000"),
"open_index": 4,
"close_index": 4,
"trade_duration": "",
"open_rate": 20,
"close_rate": 10,
"exit_type": "sell_signal",
},
{
"pair": "TEST/BTC",
"stoploss": -0.9,
"profit_percent": "",
"profit_abs": "",
"open_date": np.datetime64("2018-10-03T00:20:00.000000000"),
"close_date": np.datetime64("2018-10-03T00:25:00.000000000"),
"open_index": 4,
"close_index": 4,
"trade_duration": "",
"open_rate": 20,
"close_rate": 10,
"exit_type": "sell_signal",
},
{
"pair": "TEST/BTC",
"stoploss": -0.9,
"profit_percent": "",
"profit_abs": "",
"open_date": np.datetime64("2018-10-03T00:30:00.000000000"),
"close_date": np.datetime64("2018-10-03T00:40:00.000000000"),
"open_index": 6,
"close_index": 7,
"trade_duration": "",
"open_rate": 26,
"close_rate": 134,
"exit_type": "sell_signal",
},
]
trades_df = DataFrame(trades)
trades_df = edge._fill_calculable_fields(trades_df)
final = edge._process_expectancy(trades_df)
assert "TEST/BTC" in final
assert final["TEST/BTC"].stoploss == -0.9
assert final["TEST/BTC"].nb_trades == len(trades_df) - 1
assert round(final["TEST/BTC"].winrate, 10) == 0.0
def test_process_expectancy_only_wins(mocker, edge_conf, fee):
edge_conf["edge"]["min_trade_number"] = 2
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
freqtrade.exchange.get_fee = fee
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
trades = [
{
"pair": "TEST/BTC",
"stoploss": -0.9,
"profit_percent": "",
"profit_abs": "",
"open_date": np.datetime64("2018-10-03T00:05:00.000000000"),
"close_date": np.datetime64("2018-10-03T00:10:00.000000000"),
"open_index": 1,
"close_index": 1,
"trade_duration": "",
"open_rate": 15,
"close_rate": 17,
"exit_type": "sell_signal",
},
{
"pair": "TEST/BTC",
"stoploss": -0.9,
"profit_percent": "",
"profit_abs": "",
"open_date": np.datetime64("2018-10-03T00:20:00.000000000"),
"close_date": np.datetime64("2018-10-03T00:25:00.000000000"),
"open_index": 4,
"close_index": 4,
"trade_duration": "",
"open_rate": 10,
"close_rate": 20,
"exit_type": "sell_signal",
},
{
"pair": "TEST/BTC",
"stoploss": -0.9,
"profit_percent": "",
"profit_abs": "",
"open_date": np.datetime64("2018-10-03T00:30:00.000000000"),
"close_date": np.datetime64("2018-10-03T00:40:00.000000000"),
"open_index": 6,
"close_index": 7,
"trade_duration": "",
"open_rate": 26,
"close_rate": 134,
"exit_type": "sell_signal",
},
]
trades_df = DataFrame(trades)
trades_df = edge._fill_calculable_fields(trades_df)
final = edge._process_expectancy(trades_df)
assert "TEST/BTC" in final
assert final["TEST/BTC"].stoploss == -0.9
assert final["TEST/BTC"].nb_trades == len(trades_df)
assert round(final["TEST/BTC"].winrate, 10) == 1.0
assert round(final["TEST/BTC"].risk_reward_ratio, 10) == float("inf")
assert round(final["TEST/BTC"].expectancy, 10) == float("inf")
+22 -35
View File
@@ -35,7 +35,6 @@ from freqtrade.exchange.common import (
API_FETCH_ORDER_RETRY_COUNT,
API_RETRY_COUNT,
calculate_backoff,
remove_exchange_credentials,
)
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
from freqtrade.util import dt_now, dt_ts
@@ -167,20 +166,6 @@ def test_init(default_conf, mocker, caplog):
assert log_has("Instance is running with dry_run enabled", caplog)
def test_remove_exchange_credentials(default_conf) -> None:
conf = deepcopy(default_conf)
remove_exchange_credentials(conf["exchange"], False)
assert conf["exchange"]["key"] != ""
assert conf["exchange"]["secret"] != ""
remove_exchange_credentials(conf["exchange"], True)
assert conf["exchange"]["key"] == ""
assert conf["exchange"]["secret"] == ""
assert conf["exchange"]["password"] == ""
assert conf["exchange"]["uid"] == ""
def test_init_ccxt_kwargs(default_conf, mocker, caplog):
mocker.patch(f"{EXMS}.reload_markets")
mocker.patch(f"{EXMS}.validate_stakecurrency")
@@ -590,7 +575,8 @@ def test__load_markets(default_conf, mocker, caplog):
expected_return = {"ETH/BTC": "available"}
api_mock = MagicMock()
api_mock.load_markets = get_mock_coro(return_value=expected_return)
api_mock.load_markets = get_mock_coro()
api_mock.markets = expected_return
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
default_conf["exchange"]["pair_whitelist"] = ["ETH/BTC"]
ex = Exchange(default_conf)
@@ -606,6 +592,7 @@ def test_reload_markets(default_conf, mocker, caplog, time_machine):
time_machine.move_to(start_dt, tick=False)
api_mock = MagicMock()
api_mock.load_markets = get_mock_coro(return_value=initial_markets)
api_mock.markets = initial_markets
default_conf["exchange"]["markets_refresh_interval"] = 10
exchange = get_patched_exchange(
mocker, default_conf, api_mock, exchange="binance", mock_markets=False
@@ -624,6 +611,7 @@ def test_reload_markets(default_conf, mocker, caplog, time_machine):
api_mock.load_markets = get_mock_coro(return_value=updated_markets)
# more than 10 minutes have passed, reload is executed
time_machine.move_to(start_dt + timedelta(minutes=11), tick=False)
api_mock.markets = updated_markets
exchange.reload_markets()
assert exchange.markets == updated_markets
assert lam_spy.call_count == 1
@@ -669,34 +657,33 @@ def test_reload_markets_exception(default_conf, mocker, caplog):
@pytest.mark.parametrize("stake_currency", ["ETH", "BTC", "USDT"])
def test_validate_stakecurrency(default_conf, stake_currency, mocker, caplog):
def test_validate_stakecurrency(default_conf, stake_currency, mocker):
default_conf["stake_currency"] = stake_currency
api_mock = MagicMock()
type(api_mock).load_markets = get_mock_coro(
return_value={
"ETH/BTC": {"quote": "BTC"},
"LTC/BTC": {"quote": "BTC"},
"XRP/ETH": {"quote": "ETH"},
"NEO/USDT": {"quote": "USDT"},
}
)
api_mock.load_markets = get_mock_coro()
api_mock.markets = {
"ETH/BTC": {"quote": "BTC"},
"LTC/BTC": {"quote": "BTC"},
"XRP/ETH": {"quote": "ETH"},
"NEO/USDT": {"quote": "USDT"},
}
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
mocker.patch(f"{EXMS}.validate_timeframes")
mocker.patch(f"{EXMS}.validate_pricing")
Exchange(default_conf)
def test_validate_stakecurrency_error(default_conf, mocker, caplog):
def test_validate_stakecurrency_error(default_conf, mocker):
default_conf["stake_currency"] = "XRP"
api_mock = MagicMock()
type(api_mock).load_markets = get_mock_coro(
return_value={
"ETH/BTC": {"quote": "BTC"},
"LTC/BTC": {"quote": "BTC"},
"XRP/ETH": {"quote": "ETH"},
"NEO/USDT": {"quote": "USDT"},
}
)
api_mock.load_markets = get_mock_coro()
api_mock.markets = {
"ETH/BTC": {"quote": "BTC"},
"LTC/BTC": {"quote": "BTC"},
"XRP/ETH": {"quote": "ETH"},
"NEO/USDT": {"quote": "USDT"},
}
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
mocker.patch(f"{EXMS}.validate_timeframes")
with pytest.raises(
@@ -705,7 +692,7 @@ def test_validate_stakecurrency_error(default_conf, mocker, caplog):
):
Exchange(default_conf)
type(api_mock).load_markets = get_mock_coro(side_effect=ccxt.NetworkError("No connection."))
api_mock.load_markets = get_mock_coro(side_effect=ccxt.NetworkError("No connection."))
mocker.patch(f"{EXMS}._init_ccxt", MagicMock(return_value=api_mock))
with pytest.raises(
+1 -1
View File
@@ -66,7 +66,7 @@ def test_check_exchange(default_conf, caplog) -> None:
)
caplog.clear()
# Test an available exchange, supported by ccxt
default_conf.get("exchange").update({"name": "huobijp"})
default_conf.get("exchange").update({"name": "bittrade"})
assert check_exchange(default_conf)
assert log_has_re(
r"Exchange .* is known to the ccxt library, available for the bot, "
+2 -1
View File
@@ -283,7 +283,8 @@ def test_hyperliquid_dry_run_liquidation_price(default_conf, mocker):
default_conf["trading_mode"] = "futures"
default_conf["margin_mode"] = "isolated"
default_conf["stake_currency"] = "USDC"
api_mock.load_markets = get_mock_coro(return_value=markets)
api_mock.load_markets = get_mock_coro()
api_mock.markets = markets
exchange = get_patched_exchange(
mocker, default_conf, api_mock, exchange="hyperliquid", mock_markets=False
)

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