Compare commits

...

491 Commits

Author SHA1 Message Date
Matthias 6ee500db4f Merge pull request #11566 from freqtrade/new_release
New release 2025.3
2025-03-27 17:56:46 +01:00
Matthias b3296a06d9 chore: bump version to 2025.3 2025-03-27 07:04:07 +01:00
Matthias 09051fb641 Merge branch 'stable' into new_release 2025-03-27 07:03:41 +01:00
Matthias 3c6500c2bc Merge pull request #11565 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2025-03-27 06:50:51 +01:00
Freqtrade Bot f242110957 chore: update pre-commit hooks 2025-03-27 03:13:26 +00:00
Matthias 9480331b45 Merge pull request #11548 from freqtrade/feat/config_to_btresults
Save config and Strategy to backtest result file
2025-03-26 06:36:23 +01:00
Matthias 7c5b2fdffb Merge pull request #11554 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-03-25 06:31:27 +01:00
Freqtrade Bot 8cf3f3826f chore: update pre-commit hooks 2025-03-25 03:14:17 +00:00
Matthias 799ce4e1f2 test: Enhance test to also cover params file saving 2025-03-24 07:10:43 +01:00
Matthias 1fdce89806 chore: Implement safeguard against non-existing strategy files 2025-03-24 06:41:58 +01:00
Matthias e1f2f0cb64 Merge pull request #11542 from freqtrade/dependabot/pip/develop/mkdocs-05e23a3903
chore(deps): bump mkdocs-material from 9.6.8 to 9.6.9 in the mkdocs group
2025-03-24 06:35:42 +01:00
Matthias c3b6258dfe Merge pull request #11545 from freqtrade/dependabot/pip/develop/ccxt-4.4.69
chore(deps): bump ccxt from 4.4.68 to 4.4.69
2025-03-24 06:35:15 +01:00
Matthias 4154a7058d Merge pull request #11547 from freqtrade/dependabot/pip/develop/pre-commit-4.2.0
chore(deps-dev): bump pre-commit from 4.1.0 to 4.2.0
2025-03-24 06:34:58 +01:00
Matthias bdfa9c146e Merge pull request #11543 from freqtrade/dependabot/pip/develop/fastapi-0.115.12
chore(deps): bump fastapi from 0.115.11 to 0.115.12
2025-03-24 06:34:37 +01:00
Matthias c6d6bc62eb Merge pull request #11546 from freqtrade/dependabot/pip/develop/plotly-6.0.1
chore(deps): bump plotly from 6.0.0 to 6.0.1
2025-03-24 06:34:26 +01:00
Matthias 2347195b0b Merge pull request #11544 from freqtrade/dependabot/pip/develop/ruff-0.11.2
chore(deps-dev): bump ruff from 0.11.0 to 0.11.2
2025-03-24 06:34:09 +01:00
dependabot[bot] f3101e8a75 chore(deps-dev): bump pre-commit from 4.1.0 to 4.2.0
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 4.1.0 to 4.2.0.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v4.1.0...v4.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 03:21:46 +00:00
dependabot[bot] 43dd90f6d4 chore(deps): bump plotly from 6.0.0 to 6.0.1
Bumps [plotly](https://github.com/plotly/plotly.py) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/plotly/plotly.py/releases)
- [Changelog](https://github.com/plotly/plotly.py/blob/main/CHANGELOG.md)
- [Commits](https://github.com/plotly/plotly.py/compare/v6.0.0...v6.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 03:21:41 +00:00
dependabot[bot] 7ef52365f7 chore(deps): bump ccxt from 4.4.68 to 4.4.69
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.68 to 4.4.69.
- [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.68...v4.4.69)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 03:21:33 +00:00
dependabot[bot] 00bdf13fae chore(deps-dev): bump ruff from 0.11.0 to 0.11.2
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.11.0 to 0.11.2.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.11.0...0.11.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 03:21:22 +00:00
dependabot[bot] 5f8608d3b3 chore(deps): bump fastapi from 0.115.11 to 0.115.12
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.115.11 to 0.115.12.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.115.11...0.115.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 03:21:15 +00:00
dependabot[bot] d19498667a chore(deps): bump mkdocs-material in the mkdocs group
Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `mkdocs-material` from 9.6.8 to 9.6.9
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.8...9.6.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-24 03:21:05 +00:00
Matthias 286371c1e4 docs: add documentation for output zip file format 2025-03-23 19:35:02 +01:00
Matthias 3ffc69ad02 chore: fix typing errors 2025-03-23 19:09:11 +01:00
Matthias 7b44dd17ae feat: strategy-file saving for api backtest 2025-03-23 17:24:00 +01:00
Matthias 5b0b44069a test: Ensure strategy file is added to the test file. 2025-03-23 17:23:05 +01:00
Matthias 85fc936431 feat: add Strategy and parameter file to backtest zip file 2025-03-23 17:22:50 +01:00
Matthias 04a28b2550 fix: backtestResulttype - simplify 2025-03-23 15:51:48 +01:00
Matthias 9fa3a15e24 feat: store (sanitized) configuration 2025-03-23 15:44:55 +01:00
Matthias 23b10161d5 test: improve test resiliance 2025-03-23 15:42:38 +01:00
Matthias 31e4501765 docs: improve static pairlist documentation 2025-03-22 14:37:52 +01:00
Matthias c6e64bad49 chore: backtesting will always be "true" for dry-run. 2025-03-21 19:09:30 +01:00
Matthias 8e2de9ef7e docs: improve adjust_trade_position formatting 2025-03-21 07:11:08 +01:00
Matthias 5ea7ba6b9a docs: improve adjust_trade_position docs further
part of #11461
2025-03-21 07:05:21 +01:00
Matthias e3e924d888 docs: minor update to realign summary metric docs 2025-03-21 06:50:52 +01:00
Matthias 02b0f0abd6 refactor: extract replace_order handling 2025-03-20 20:35:17 +01:00
Matthias 7735ea91bb fix: adjust_order_price return type 2025-03-20 20:32:34 +01:00
Matthias 7178ce8559 chore: Add descriptions to api parameters 2025-03-20 18:05:25 +01:00
Matthias 26ba076143 Merge pull request #11093 from arenstar/api-server-list-custom-data
feat: api_server and client supporting list_custom_data
2025-03-20 07:05:48 +01:00
Matthias 425701ddcf test: simplify assert message 2025-03-20 06:44:34 +01:00
Matthias 42e45a0a65 chore: simplify import and docstring 2025-03-20 06:43:50 +01:00
Matthias b97f3ca818 Merge pull request #11531 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2025-03-20 06:38:29 +01:00
Freqtrade Bot dfc77945ce chore: update pre-commit hooks 2025-03-20 03:10:55 +00:00
Axel-CH 6d8011e075 chore: use Trade id for _rpc_list_custom_data output ordering 2025-03-19 15:48:04 -04:00
Axel-CH 1b4f8dfa54 chore: use open_date for _rpc_list_custom_data output ordering 2025-03-19 15:30:02 -04:00
Axel-CH 48b7a85c90 chore: update schema after removal of cd_value field from list custom-data response 2025-03-19 15:24:55 -04:00
Axel-CH 0d7854ff1b test: update tests after removal of cd_value field from list custom-data response 2025-03-19 15:24:19 -04:00
Axel-CH d3464ac2dc chore: remove cd_value from rpc custom data output 2025-03-19 15:18:33 -04:00
Axel-CH 95f5db9dcd fix: implement ordering for rpc_list_custom_data 2025-03-19 03:11:43 -04:00
Axel-CH 61b29962c4 test: update test after trade id removal from telegram response for list_custom_data 2025-03-19 03:10:23 -04:00
Axel-CH b83754c5a4 chore: remove trade id from telegram response for list_custom_data 2025-03-19 02:55:51 -04:00
Axel-CH 9a1f2d42a7 fix: list_custom_data trade id variable value 2025-03-19 02:52:38 -04:00
Axel-CH bd511c2158 fix: rest client custom-data path 2025-03-18 14:56:33 -04:00
Matthias 6b6d7fc310 Merge pull request #11505 from freqtrade/feat/log_from_config
allow loading logging from config
2025-03-18 18:08:07 +01:00
Matthias c6959c92ac Merge pull request #11499 from mrpabloyeah/add-relevant-metrics-to-backtest-breakdowns
Add relevant metrics to backtest breakdowns
2025-03-18 07:27:05 +01:00
Matthias 7bc6a2279c chore: only round to 2 for output, not for the saved file 2025-03-18 07:02:30 +01:00
Matthias b431c5b343 Merge pull request #11525 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-03-18 06:37:15 +01:00
Axel-CH 176f5518ea Merge branch 'develop' into api-server-list-custom-data 2025-03-18 00:53:10 -04:00
Axel-CH 06406b7103 test: update test_telegram_list_custom_data 2025-03-18 00:52:21 -04:00
Axel-CH 17e4f5ed1f chore: update telegram _list_custom_data 2025-03-18 00:51:18 -04:00
Axel-CH 578ba9ea4a test: update custom-data api related tests according rpc_list_custom_data output 2025-03-18 00:50:03 -04:00
Axel-CH 83a8651d41 chore: update api schema according rpc_list_custom_data output 2025-03-18 00:48:18 -04:00
Axel-CH ba0c22b6f0 chore: enhance update rpc_list_custom_data output 2025-03-18 00:46:46 -04:00
Axel-CH 90219f040b chore: enhance list custom-data output format 2025-03-17 23:18:44 -04:00
Freqtrade Bot fef234d80f chore: update pre-commit hooks 2025-03-18 03:12:53 +00:00
Axel-CH 68ad688665 test: update test_telegram_list_custom_data wording 2025-03-17 18:04:36 -04:00
Axel-CH 0c7a2747d3 chore: revert unnecessary get_custom_data changes 2025-03-17 18:02:04 -04:00
Axel-CH 23187f0c41 chore: simplify error handling in _rpc_list_custom_data 2025-03-17 17:52:29 -04:00
Axel-CH f66d81c4b8 chore: wording update in _rpc_list_custom_data 2025-03-17 17:45:29 -04:00
Axel-CH 87a64cbe68 chore: small refactor in _rpc_list_custom_data 2025-03-17 17:43:08 -04:00
Axel-CH ef58aaf9e9 chore: update rest client custom-data related functions inline comment and help content 2025-03-17 17:33:40 -04:00
Axel-CH 4a432760ed feat: add list_open_trades_custom_data to ft rest client 2025-03-17 17:14:37 -04:00
Axel-CH 484943a640 feat: set trade_id as required param in list_custom_data, add key as optional 2025-03-17 15:13:47 -04:00
Axel-CH eec16cfc8a chore: move list_custom_data closer to trades related functions 2025-03-17 14:58:16 -04:00
Matthias fddceb2572 chore: pin ccxt version to more recent number 2025-03-17 19:13:26 +01:00
Matthias d665a01fe6 Merge pull request #11516 from freqtrade/dependabot/pip/develop/sqlalchemy-2.0.39
chore(deps): bump sqlalchemy from 2.0.38 to 2.0.39
2025-03-17 07:23:50 +01:00
Matthias 8f93ac5770 Merge pull request #11514 from freqtrade/dependabot/pip/develop/ccxt-4.4.68
chore(deps): bump ccxt from 4.4.65 to 4.4.68
2025-03-17 07:12:19 +01:00
Matthias 33164ec041 chore: bump sqlalchemy in pre-commit config 2025-03-17 06:35:29 +01:00
dependabot[bot] 0e6cd72edd chore(deps): bump ccxt from 4.4.65 to 4.4.68
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.65 to 4.4.68.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/v4.4.65...v4.4.68)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 05:34:06 +00:00
dependabot[bot] 4f218e4fb2 chore(deps): bump sqlalchemy from 2.0.38 to 2.0.39
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.38 to 2.0.39.
- [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases)
- [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst)
- [Commits](https://github.com/sqlalchemy/sqlalchemy/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 05:33:48 +00:00
Matthias 0bf411ae19 Merge pull request #11518 from freqtrade/dependabot/pip/develop/python-telegram-bot-22.0
chore(deps): bump python-telegram-bot from 21.11.1 to 22.0
2025-03-17 06:32:43 +01:00
Matthias 283199b6ed Merge pull request #11515 from freqtrade/dependabot/pip/develop/filelock-3.18.0
chore(deps): bump filelock from 3.17.0 to 3.18.0
2025-03-17 06:31:48 +01:00
Matthias 0270a61cba Merge pull request #11519 from freqtrade/dependabot/pip/develop/ruff-0.11.0
chore(deps-dev): bump ruff from 0.9.10 to 0.11.0
2025-03-17 06:31:32 +01:00
Matthias 1aeed3951b Merge pull request #11513 from freqtrade/dependabot/pip/develop/mkdocs-7637a67f6b
chore(deps): bump mkdocs-material from 9.6.7 to 9.6.8 in the mkdocs group
2025-03-17 06:29:17 +01:00
dependabot[bot] ba38f8120c chore(deps-dev): bump ruff from 0.9.10 to 0.11.0
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.9.10 to 0.11.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.9.10...0.11.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 03:56:11 +00:00
dependabot[bot] 7de9e2c4b6 chore(deps): bump python-telegram-bot from 21.11.1 to 22.0
Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 21.11.1 to 22.0.
- [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases)
- [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v21.11.1...v22.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 03:56:01 +00:00
dependabot[bot] 331c6beafc chore(deps): bump filelock from 3.17.0 to 3.18.0
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.17.0 to 3.18.0.
- [Release notes](https://github.com/tox-dev/py-filelock/releases)
- [Changelog](https://github.com/tox-dev/filelock/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/py-filelock/compare/3.17.0...3.18.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 03:55:37 +00:00
dependabot[bot] 93cbbbbe31 chore(deps): bump mkdocs-material in the mkdocs group
Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `mkdocs-material` from 9.6.7 to 9.6.8
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.7...9.6.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-17 03:55:22 +00:00
mrpabloyeah ddd2e2cc3b Add relevant metrics to backtest breakdowns (fix 5) 2025-03-16 18:20:38 +01:00
mrpabloyeah 55301b8d0c Add relevant metrics to backtest breakdowns (fix 4) 2025-03-16 14:25:32 +01:00
mrpabloyeah a47ed1dbdb Add relevant metrics to backtest breakdowns (fix 3) 2025-03-16 14:17:40 +01:00
Matthias 17b161cb03 chore: improve types in telegram 2025-03-16 14:00:25 +01:00
Matthias 5d3c764e27 test for authorized_users telegram functionality
closes #11503
2025-03-16 14:00:17 +01:00
Matthias f3aac3d66d feat: add authorized-users key to config schema 2025-03-16 13:48:50 +01:00
Matthias 39903d521e docs: add authorized_users to documentation
closes #11504
2025-03-16 13:48:22 +01:00
Matthias 7255beddea chore: improved null check 2025-03-16 13:47:59 +01:00
Matthias a95c3ace92 feat: add authorized_users support for telegram 2025-03-16 13:44:23 +01:00
Matthias d543feb032 tests: simplify telegram test setup 2025-03-16 13:42:15 +01:00
Matthias 623f27341f chore: simplify telegram safety wrapper 2025-03-16 13:21:10 +01:00
Matthias 55dc2d4bf4 chore: update watch reuse log message 2025-03-16 13:07:52 +01:00
Matthias 24e94cfaa7 feat: Improved default fmt_dict for json formatter 2025-03-16 09:54:21 +01:00
Matthias 7db62689c4 test: add test for json formatter 2025-03-16 09:50:58 +01:00
Matthias da53b5d115 docs: document logging JSON format 2025-03-16 09:43:23 +01:00
Matthias 03dfe4ec45 feat: add jsonFormatter 2025-03-16 09:37:57 +01:00
Matthias 39288d2e53 test: Add fixture to prevent having disabled loggers 2025-03-15 08:30:32 +01:00
Matthias 2a6dc3e45d Merge pull request #11508 from kowhi/develop
Fix the type of the fit_live_predictions_candles field in the config …
2025-03-15 08:05:25 +01:00
Matthias 5964845265 chore: Export json schema 2025-03-15 07:50:43 +01:00
kowhi 9911d2aa32 fix the type of the fit_live_predictions_candles field in the config schema 2025-03-15 12:23:42 +08:00
Matthias bfd2c3f942 chore: pin freqtrade CI badge to develop branch 2025-03-13 20:43:53 +01:00
Matthias 74c837519a chore: don't use deprecated pandas timeperiod 2025-03-13 20:40:38 +01:00
Matthias 415e361c66 docs: fix note syntax 2025-03-13 20:19:12 +01:00
Matthias 7e154c6fb6 chore: fix too long comment 2025-03-13 20:17:07 +01:00
Matthias 385b39ff66 docs: enhance logging documentation with links 2025-03-13 20:16:11 +01:00
Matthias 9d232c4949 docs: add log_config to supported log options 2025-03-13 19:58:34 +01:00
Matthias 85ccc31a63 docs: document basic log_config setup 2025-03-13 19:57:24 +01:00
Matthias 65d19c38b6 docs: document deprecation of --logfile syslog 2025-03-13 19:46:18 +01:00
Matthias 5b8752e649 chore: deprecate syslog from configuration 2025-03-13 19:44:44 +01:00
Matthias 8832bae371 docs: document syslog logging via log_config 2025-03-13 19:44:16 +01:00
Matthias a85e044e99 docs: improve log formatting 2025-03-13 19:33:49 +01:00
Matthias 7b1ee84b34 chore: deprecate --logfile=journald 2025-03-13 19:33:20 +01:00
Matthias 79ebc943a2 docs: Document journald logging via configuration file 2025-03-13 19:33:09 +01:00
Matthias eabcaa38e2 feat: try improved config schema 2025-03-13 19:25:18 +01:00
Matthias e930221b78 test: update logging tests 2025-03-13 19:18:00 +01:00
Matthias 08c4f24bdf test: add log setup workaround for tests 2025-03-13 19:18:00 +01:00
Matthias 56c23f9dd8 tests: remove unnecessary log-config 2025-03-13 19:18:00 +01:00
Matthias 6edee269d8 chore: deepcopy log config 2025-03-13 19:18:00 +01:00
Matthias f9d978f16a chore: migrate set_log_levels to log_config 2025-03-13 19:18:00 +01:00
Matthias 1eac77fe0b chore: don't shadow built-in functions 2025-03-13 19:17:32 +01:00
Matthias a98121ea26 chore: update json schema 2025-03-13 19:17:32 +01:00
Matthias cc9c373a76 feat: update config_schema for logging 2025-03-13 19:17:32 +01:00
Matthias 390b113776 feat: Ensure freqtrade-logger is always configured 2025-03-13 19:17:32 +01:00
Matthias bf33f8b632 chore: clean up log-levels 2025-03-13 19:17:32 +01:00
Matthias 030dcfdd83 chore: extract log formatter addition 2025-03-13 19:17:32 +01:00
Matthias cd77758852 chore: remove some unnecessary comments 2025-03-13 19:17:32 +01:00
Matthias 1aa6c2ad55 feat: Improve filehandler config 2025-03-13 19:17:32 +01:00
Matthias 55d71cecdd refactor: move root-handler adding to separate function 2025-03-13 19:17:32 +01:00
Matthias 70a81c86ba feat: dynamically assign error console 2025-03-13 19:17:32 +01:00
Matthias 1c6c710696 chore: rename log_config naming 2025-03-13 19:17:32 +01:00
Matthias 4ca2a043b5 refactor: extract log_config creation 2025-03-13 19:17:32 +01:00
Matthias 41418784e3 feat: add log_config generator 2025-03-13 19:17:32 +01:00
Matthias 9afd572948 chore: add "log init from config" ... 2025-03-13 19:17:32 +01:00
mrpabloyeah 51a99f2a66 Add relevant metrics to backtest breakdowns (fix 2) 2025-03-13 14:49:24 +01:00
mrpabloyeah 969b0570c2 Add relevant metrics to backtest breakdowns (fix) 2025-03-13 14:29:10 +01:00
mrpabloyeah e94ccece93 Add relevant metrics to backtest breakdowns 2025-03-13 13:51:35 +01:00
Matthias cd435098bd Merge pull request #11497 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2025-03-13 06:29:31 +01:00
Freqtrade Bot 33659ee273 chore: update pre-commit hooks 2025-03-13 03:10:53 +00:00
Axel-CH c30db9d0d6 Merge branch 'develop' into api-server-list-custom-data 2025-03-12 10:39:43 -04:00
Axel-CH 429505b134 test: add test_api_custom_data_multiple_open_trades to validate api route behaviour 2025-03-12 10:37:39 -04:00
Axel-CH 2c2cc086c3 test: add test_api_custom_data_single_trade to validate api route behaviour 2025-03-12 00:24:17 -04:00
Axel-CH fe16654733 test: slight change on expected string in test_telegram_list_custom_data 2025-03-12 00:21:24 -04:00
Axel-CH f85891941f chore: update telegram _list_custom_data according to _rpc_list_custom_data output format change 2025-03-12 00:17:12 -04:00
Axel-CH 493b6f6592 chore: update api custom-data related routes with better not found error handling 2025-03-12 00:12:43 -04:00
Axel-CH 97faa7fc5a feat: update api schema custom data related classes 2025-03-11 23:43:30 -04:00
Axel-CH 5402b14336 chore: enhance _rpc_list_custom_data error handling, output format and docstring 2025-03-11 23:38:50 -04:00
Axel-CH 4c7ff7ab0c feat: add retrieval_mode in get_custom_data function to chose between value or full custom_data object 2025-03-11 23:36:24 -04:00
Matthias 8288a5cf91 Merge pull request #11481 from mrpabloyeah/add-year-to-backtest-breakdowns
Add year to backtest breakdowns
2025-03-11 20:45:15 +01:00
Matthias 442b29e0ea chore: run schema export 2025-03-11 20:01:21 +01:00
Axel-CH 743422ccf3 feat: implement pagination for open trades custom-data rpc endpoint 2025-03-11 11:19:36 -04:00
Matthias d69b1566b1 chore: auto-build schema.json 2025-03-11 07:10:37 +01:00
Matthias 5c01023d55 feat: add missing freqAI parameters to config schema 2025-03-11 07:10:04 +01:00
Matthias 97d303579a feat: add continual_learning to config schema 2025-03-11 07:09:56 +01:00
Matthias 265a798f78 docs: auto-generate command snippets 2025-03-11 06:37:44 +01:00
Matthias db19446e27 Merge pull request #11488 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-03-11 06:30:36 +01:00
Freqtrade Bot e62a0f76ed chore: update pre-commit hooks 2025-03-11 03:11:15 +00:00
Axel-CH 673447794e chore: implement pagination for _rpc_list_custom_data 2025-03-10 22:17:34 -04:00
Axel-CH 76aefccd03 fix: on custom-data endpoints key is now an optional parameter 2025-03-10 16:18:42 -04:00
Matthias b0b9e398e1 fix: assume 200char terminal if no terminal size is available
closes #11477
2025-03-10 21:13:32 +01:00
Axel-CH fac0491658 fix: _rpc_list_custom_data send custom-data for all trades if a key is provided 2025-03-10 16:01:16 -04:00
Axel-CH 7770f082c8 chore: relocate custom-data endpoints of api server near trade related endpoint, replace info tag by trading 2025-03-10 15:51:34 -04:00
mrpabloyeah 3281049264 Add year also in cli_options 2025-03-10 18:44:01 +01:00
Matthias fe48f6769f Merge pull request #11482 from freqtrade/dependabot/pip/develop/types-dfbe43a2fe
chore(deps-dev): bump types-requests from 2.32.0.20250301 to 2.32.0.20250306 in the types group
2025-03-10 07:12:15 +01:00
Matthias 6a5be68367 Merge pull request #11485 from freqtrade/dependabot/pip/develop/ccxt-4.4.65
chore(deps): bump ccxt from 4.4.64 to 4.4.65
2025-03-10 06:40:07 +01:00
Matthias 6613366343 chore: bump types-requests for pre-commit config 2025-03-10 06:28:39 +01:00
Matthias 48c1aaaa38 Merge pull request #11483 from freqtrade/dependabot/pip/develop/websockets-15.0.1
chore(deps): bump websockets from 15.0 to 15.0.1
2025-03-10 06:25:50 +01:00
Matthias 23928e55a3 Merge pull request #11484 from freqtrade/dependabot/pip/develop/ruff-0.9.10
chore(deps-dev): bump ruff from 0.9.9 to 0.9.10
2025-03-10 06:25:32 +01:00
dependabot[bot] 6d84b93715 chore(deps): bump ccxt from 4.4.64 to 4.4.65
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.64 to 4.4.65.
- [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.64...v4.4.65)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 03:54:03 +00:00
dependabot[bot] 5b1011668a chore(deps-dev): bump ruff from 0.9.9 to 0.9.10
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.9.9 to 0.9.10.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.9.9...0.9.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 03:53:55 +00:00
dependabot[bot] 30d2298d54 chore(deps): bump websockets from 15.0 to 15.0.1
Bumps [websockets](https://github.com/python-websockets/websockets) from 15.0 to 15.0.1.
- [Release notes](https://github.com/python-websockets/websockets/releases)
- [Commits](https://github.com/python-websockets/websockets/compare/15.0...15.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 03:53:40 +00:00
dependabot[bot] 98b6f58eb2 chore(deps-dev): bump types-requests in the types group
Bumps the types group with 1 update: [types-requests](https://github.com/python/typeshed).


Updates `types-requests` from 2.32.0.20250301 to 2.32.0.20250306
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-10 03:52:54 +00:00
mrpabloyeah a0cbdc9135 Add year to backtest breakdowns 2025-03-09 18:21:33 +01:00
Matthias 805e03b83b test: update test for new bt cache behavior 2025-03-09 15:00:20 +01:00
Matthias 35cc7da9b3 fix: keep pair index per pair
closes #11479
2025-03-09 15:00:13 +01:00
Matthias 6efe6bbfc2 fix: restore previous startup time 2025-03-08 19:53:11 +01:00
Matthias ac29ef67fd chore: fall back to 0.0 if min-pair returns None 2025-03-08 19:39:02 +01:00
Matthias 4a7140c05d chore: Update bybit dry-liquidation calculation
the result remains the same - but the calculation now matches
the bybit documentation better.
2025-03-08 17:29:55 +01:00
Matthias cd971cff4f test: improve liquidation-calculation test
Move maintenance margin to parameter, add tests from bybit page
2025-03-08 17:27:25 +01:00
Matthias 538139ef31 chore: fix incorrect docstrings 2025-03-08 17:10:23 +01:00
Matthias 8bd4926c29 feat: add Min Stake to list-pairs command 2025-03-08 17:00:55 +01:00
Matthias a9195c8ff9 chore: remove pointless else 2025-03-08 16:49:07 +01:00
Axel-CH 93c8a11824 fix: update _rpc_list_custom_data to add all custom data only if key is not provided 2025-03-07 14:08:21 -04:00
Axel-CH aec496a73b fix: update _rpc_list_custom_data with proper typing and custom_data collection loop 2025-03-07 09:40:15 -04:00
Axel-CH 7bc1398574 Merge branch 'develop' into api-server-list-custom-data 2025-03-07 09:37:56 -04:00
Matthias 3371bad504 test: initialize exception for mock 2025-03-06 20:07:24 +01:00
Matthias 64e27637dc test: adjust test for raise-exception behavior 2025-03-06 07:17:39 +01:00
Matthias ee4f05a5b8 fix: have download-data hard-fail if it times out
this will avoid holes in data if individual requests fail.
The tradeoff is that the download will stop for this pair/timeframe combination.
2025-03-06 07:17:39 +01:00
Matthias 6636a2fda8 docs: improved adjust_trade_position sample 2025-03-06 07:17:39 +01:00
Matthias 754936d253 chore: improved backtest method wording 2025-03-06 07:17:39 +01:00
Matthias 3762f40538 Merge pull request #11468 from freqtrade/dependabot/pip/docs/jinja2-3.1.6
chore(deps): bump jinja2 from 3.1.5 to 3.1.6 in /docs
2025-03-06 06:59:35 +01:00
Matthias 4a3c45af2d chore(deps): fix dependabot missing the bump of 2nd jinja2 reference 2025-03-06 06:33:40 +01:00
dependabot[bot] 29fdbc8f7f chore(deps): bump jinja2 from 3.1.5 to 3.1.6 in /docs
Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.5 to 3.1.6.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/3.1.5...3.1.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-06 05:31:04 +00:00
Matthias 6116c6abb9 Merge pull request #11467 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2025-03-06 06:29:27 +01:00
Freqtrade Bot 466c21e9fd chore: update pre-commit hooks 2025-03-06 03:09:52 +00:00
Matthias d461d6f8e8 fix: spot data is in microseconds
closes #11465
As announced in the documentation:
https://github.com/binance/binance-public-data

> Note: The timestamp for SPOT Data from January 1st 2025 onwards will be in microseconds.
2025-03-05 20:18:49 +01:00
Matthias 53f68a3834 chore: let BaseException pass in binance 2025-03-05 20:18:49 +01:00
Matthias 76d43782db chore: fix typo in exception 2025-03-05 20:18:49 +01:00
Robert Caulk e7a6db8916 Merge pull request #10173 from freqtrade/fix/mutable_defaults
Fix mutable defaults, enable bugbear ruff rule also for freqAI code
2025-03-04 10:43:48 +01:00
Matthias cdb7fa83e4 Merge pull request #11462 from freqtrade/enhance-pricing-logs
Enhance pricing logs
2025-03-04 07:12:27 +01:00
Matthias f785f9ff00 chore: use log_has_re for test log assertion 2025-03-04 06:46:13 +01:00
Matthias 4d87d13165 Merge pull request #11463 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-03-04 06:39:49 +01:00
Freqtrade Bot 9128011208 chore: update pre-commit hooks 2025-03-04 03:10:52 +00:00
Matthias 3dd7f7f17b chore: improved log messages for partial filled entries 2025-03-03 20:48:45 +01:00
Matthias 7665d3d1f2 docs: add has_open_orders to trade object documentation 2025-03-03 20:48:25 +01:00
Axel-CH e055feb1d3 chore: update test_process_trade_creation test 2025-03-03 15:32:41 -04:00
Axel-CH dfe6982099 chore: change log level of get_valid_price log from warning to info 2025-03-03 15:18:04 -04:00
Axel-CH ad2aef09c4 chore: fix format 2025-03-03 14:55:15 -04:00
Axel-CH facfdfd85c chore: add price in execute_entry logs 2025-03-03 14:42:16 -04:00
Axel-CH 1bba2a5f21 chore: add a warning log if custom_price_max_distance_ratio change the price 2025-03-03 14:32:39 -04:00
Matthias ef239b8d0c fix: update datadir when exchange is changed for an endpoint 2025-03-03 19:28:02 +01:00
Matthias bccbac9f23 Merge pull request #11453 from freqtrade/dependabot/pip/develop/ccxt-4.4.64
chore(deps): bump ccxt from 4.4.62 to 4.4.64
2025-03-03 10:20:39 +01:00
Matthias 113efe129d Merge pull request #11448 from freqtrade/dependabot/pip/develop/types-5ea53ede25
chore(deps-dev): bump types-requests from 2.32.0.20241016 to 2.32.0.20250301 in the types group
2025-03-03 08:57:00 +01:00
Matthias 8d73cccde5 Merge pull request #11457 from freqtrade/dependabot/pip/develop/ruff-0.9.9
chore(deps-dev): bump ruff from 0.9.7 to 0.9.9
2025-03-03 07:56:01 +01:00
dependabot[bot] 055c56edfc chore(deps): bump ccxt from 4.4.62 to 4.4.64
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.62 to 4.4.64.
- [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.62...v4.4.64)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 06:37:01 +00:00
Matthias 942c1c1f5c Merge pull request #11456 from freqtrade/dependabot/pip/develop/cryptography-44.0.2
chore(deps): bump cryptography from 44.0.1 to 44.0.2
2025-03-03 07:35:44 +01:00
Matthias 243a91a141 chore: bump types-requests in pre-commit 2025-03-03 06:46:58 +01:00
dependabot[bot] 281bd69fa5 chore(deps): bump cryptography from 44.0.1 to 44.0.2
Bumps [cryptography](https://github.com/pyca/cryptography) from 44.0.1 to 44.0.2.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/44.0.1...44.0.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 05:46:06 +00:00
Matthias 6630259833 Merge pull request #11452 from freqtrade/dependabot/pip/develop/fastapi-0.115.11
chore(deps): bump fastapi from 0.115.8 to 0.115.11
2025-03-03 06:45:32 +01:00
Matthias 674d28622b Merge pull request #11450 from freqtrade/dependabot/pip/develop/mkdocs-8a51c936ee
chore(deps): bump mkdocs-material from 9.6.5 to 9.6.7 in the mkdocs group
2025-03-03 06:45:08 +01:00
dependabot[bot] e21aedfb73 chore(deps-dev): bump ruff from 0.9.7 to 0.9.9
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.9.7 to 0.9.9.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.9.7...0.9.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 05:44:23 +00:00
Matthias 8f22c58875 Merge pull request #11451 from freqtrade/dependabot/pip/develop/python-telegram-bot-21.11.1
chore(deps): bump python-telegram-bot from 21.10 to 21.11.1
2025-03-03 06:44:03 +01:00
Matthias e3d1c4c9c7 Merge pull request #11455 from freqtrade/dependabot/pip/develop/isort-6.0.1
chore(deps-dev): bump isort from 6.0.0 to 6.0.1
2025-03-03 06:43:41 +01:00
Matthias 40f87c9ab5 Merge pull request #11449 from freqtrade/dependabot/pip/develop/pytest-6c7deac02c
chore(deps-dev): bump pytest from 8.3.4 to 8.3.5 in the pytest group
2025-03-03 06:43:05 +01:00
dependabot[bot] bd5f02a8df chore(deps-dev): bump isort from 6.0.0 to 6.0.1
Bumps [isort](https://github.com/PyCQA/isort) from 6.0.0 to 6.0.1.
- [Release notes](https://github.com/PyCQA/isort/releases)
- [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md)
- [Commits](https://github.com/PyCQA/isort/compare/6.0.0...6.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 03:30:33 +00:00
dependabot[bot] 91438affc8 chore(deps): bump fastapi from 0.115.8 to 0.115.11
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.115.8 to 0.115.11.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.115.8...0.115.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 03:29:54 +00:00
dependabot[bot] 1548fdfca6 chore(deps): bump python-telegram-bot from 21.10 to 21.11.1
Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 21.10 to 21.11.1.
- [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases)
- [Changelog](https://github.com/python-telegram-bot/python-telegram-bot/blob/v21.11.1/CHANGES.rst)
- [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v21.10...v21.11.1)

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

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


Updates `mkdocs-material` from 9.6.5 to 9.6.7
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.5...9.6.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 03:29:41 +00:00
dependabot[bot] 3fb4524c5a chore(deps-dev): bump pytest from 8.3.4 to 8.3.5 in the pytest group
Bumps the pytest group with 1 update: [pytest](https://github.com/pytest-dev/pytest).


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

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 03:29:30 +00:00
dependabot[bot] cdd1273ed0 chore(deps-dev): bump types-requests in the types group
Bumps the types group with 1 update: [types-requests](https://github.com/python/typeshed).


Updates `types-requests` from 2.32.0.20241016 to 2.32.0.20250301
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-03-03 03:29:01 +00:00
Matthias 717f17a9b5 chore: fix mutable defaults bug 2025-03-02 19:56:59 +01:00
Matthias 11763e8cd1 chore: improve clarity 2025-03-02 16:09:55 +01:00
Matthias 5e50348150 Merge branch 'develop' into fix/mutable_defaults 2025-03-02 16:04:30 +01:00
Matthias ccfc690281 feat: add SQN to backtest result
closes #11397
2025-03-02 15:47:30 +01:00
Matthias e1f6702932 feat: add SQN calculation as backtest metric 2025-03-02 15:41:43 +01:00
Matthias 13e9f8a98e fix: use initialMargin over collateral for position wallet
closes #11415
2025-03-02 14:43:58 +01:00
Matthias cc0337585e Merge pull request #11289 from freqtrade/feat/binance_trades_fast
Binance: Download trades "fast" from binance.vision
2025-03-02 14:43:47 +01:00
Matthias 3070e37335 docs: add plain merge to known common mistakes
closes #11438
2025-03-01 08:01:35 +01:00
Matthias c50edafac6 feat: add record_version to trades table 2025-02-28 19:14:38 +01:00
Matthias 7ccac492bb fix: max_stake_amount should not include leverage 2025-02-28 16:12:20 +01:00
Matthias a23409b72f Merge pull request #11436 from freqtrade/new_release
New release 2025.2
2025-02-28 10:33:14 +01:00
Matthias 5b9102f9a8 tests: Use higher limit on x86 macos to avoid random fails 2025-02-28 07:01:50 +01:00
Matthias f447de4840 chore: bump version to 2025.3-dev 2025-02-28 06:36:02 +01:00
Matthias 186bc4d272 chore: bump version to 2025.2 2025-02-28 06:30:43 +01:00
Matthias 06fd176286 Merge branch 'stable' into develop 2025-02-28 06:30:27 +01:00
Matthias c5fd6f4519 Merge pull request #11432 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-02-27 07:25:48 +01:00
Freqtrade Bot efe0275ae1 chore: update pre-commit hooks 2025-02-27 05:50:03 +00:00
Matthias ba98f1a9be chore: update pre-commit author to freqtradeBot 2025-02-27 06:49:19 +01:00
Matthias 5380b22729 chore: update leverage tiers author to use the bot email 2025-02-27 06:48:54 +01:00
Matthias 338bc7c677 chore: Bump docker image to 3.12.9 2025-02-27 06:33:05 +01:00
Matthias 449a5e85b4 Merge pull request #11431 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2025-02-27 06:20:15 +01:00
xmatthias 0f5cb87c65 chore: update pre-commit hooks 2025-02-27 03:08:16 +00:00
Matthias 822e7bad1f Merge pull request #11426 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-02-25 06:23:10 +01:00
xmatthias 86a5240174 chore: update pre-commit hooks 2025-02-25 03:08:51 +00:00
Matthias 7b7c8d9374 chore(deps): bump ccxt from 4.4.61 to 4.4.62
closes #11405
2025-02-24 19:51:54 +01:00
Matthias fde3547dbb docs: add warning about duplicate symbols in coingecko 2025-02-24 18:06:52 +01:00
Matthias 9bd7fac86f Merge pull request #11424 from freqtrade/dependabot/pip/develop/pyarrow-19.0.1
chore(deps): bump pyarrow from 19.0.0 to 19.0.1
2025-02-24 09:57:58 +01:00
Matthias 8085d8cd00 chore: update prebuilt pyarrow binary 2025-02-24 09:40:06 +01:00
Matthias 3f656a9758 Merge pull request #11421 from freqtrade/dependabot/pip/develop/humanize-4.12.1
chore(deps): bump humanize from 4.12.0 to 4.12.1
2025-02-24 08:12:28 +01:00
Matthias 38f1981d6f Merge pull request #11412 from freqtrade/feat/adjust_order_price
Add adjust_order_price callback
2025-02-24 07:02:27 +01:00
Matthias d55b3e0228 Merge pull request #11420 from freqtrade/dependabot/pip/develop/ccxt-4.4.61
chore(deps): bump ccxt from 4.4.59 to 4.4.61
2025-02-24 06:55:12 +01:00
Matthias d3f8bef2ef Merge pull request #11422 from freqtrade/dependabot/pip/develop/ruff-0.9.7
chore(deps-dev): bump ruff from 0.9.6 to 0.9.7
2025-02-24 06:45:43 +01:00
dependabot[bot] f78f52f78e chore(deps): bump humanize from 4.12.0 to 4.12.1
Bumps [humanize](https://github.com/python-humanize/humanize) from 4.12.0 to 4.12.1.
- [Release notes](https://github.com/python-humanize/humanize/releases)
- [Commits](https://github.com/python-humanize/humanize/compare/4.12.0...4.12.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 05:26:05 +00:00
Matthias d451dcf28b Merge pull request #11419 from freqtrade/dependabot/pip/develop/mkdocs-ab7c602706
chore(deps): bump mkdocs-material from 9.6.4 to 9.6.5 in the mkdocs group
2025-02-24 06:25:31 +01:00
Matthias 063964ed38 Merge pull request #11423 from freqtrade/dependabot/pip/develop/cachetools-5.5.2
chore(deps): bump cachetools from 5.5.1 to 5.5.2
2025-02-24 06:24:15 +01:00
dependabot[bot] ac70d559a4 chore(deps): bump pyarrow from 19.0.0 to 19.0.1
Bumps [pyarrow](https://github.com/apache/arrow) from 19.0.0 to 19.0.1.
- [Release notes](https://github.com/apache/arrow/releases)
- [Commits](https://github.com/apache/arrow/compare/apache-arrow-19.0.0...apache-arrow-19.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 03:24:23 +00:00
dependabot[bot] 685e575f6b chore(deps): bump cachetools from 5.5.1 to 5.5.2
Bumps [cachetools](https://github.com/tkem/cachetools) from 5.5.1 to 5.5.2.
- [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/tkem/cachetools/compare/v5.5.1...v5.5.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 03:24:18 +00:00
dependabot[bot] 1640267798 chore(deps-dev): bump ruff from 0.9.6 to 0.9.7
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.9.6 to 0.9.7.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.9.6...0.9.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 03:24:14 +00:00
dependabot[bot] d5585dd5e3 chore(deps): bump ccxt from 4.4.59 to 4.4.61
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.59 to 4.4.61.
- [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/go/v4.4.59...go/v4.4.61)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 03:23:46 +00:00
dependabot[bot] 90e2db708e chore(deps): bump mkdocs-material in the mkdocs group
Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `mkdocs-material` from 9.6.4 to 9.6.5
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.4...9.6.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-24 03:23:30 +00:00
Matthias 64303e90d6 feat: Be more verbose on startup regarding what the bot is doing
closes #11416
2025-02-23 17:16:46 +01:00
Matthias 901dfa21fc Merge pull request #11418 from Cygra/patch-1
fix: typo in strategy-customization.md
2025-02-23 16:24:55 +01:00
Cygra Wang 5e7d883732 fix: typo in strategy-customization.md 2025-02-23 15:11:05 +08:00
Matthias 840f074c66 chore: simplify rpc imports 2025-02-22 12:50:51 +01:00
Matthias 0408ad1095 fix: exception when rounded price is nan
closes #11414
2025-02-22 08:21:24 +01:00
Matthias e262110c3b test: add test case for #11414 2025-02-22 08:19:38 +01:00
Matthias 09b9ff2c68 fix: provide default for exit reason 2025-02-20 20:32:51 +01:00
Matthias e76574b79f docs: update documentation for adjust_order_price() 2025-02-20 20:25:35 +01:00
Matthias 08587d826e test: Long adjust-exit-price test 2025-02-20 20:07:44 +01:00
Matthias 584b84a941 test: extend backtest-detail tests for exit_adjust 2025-02-20 20:02:18 +01:00
Matthias 44182783c0 feat: don't limit backtest calls to entry orders 2025-02-20 20:02:03 +01:00
Matthias 90f52ba8ad test: add integration test for adjust_exit_price 2025-02-20 19:45:34 +01:00
Matthias f8f10f27e9 chore: improved variable naming 2025-02-20 19:39:33 +01:00
Matthias 7f393252e2 docs: update bot-basics with new callback 2025-02-20 19:24:03 +01:00
Matthias dd8938ced2 test: add test for adjust_order_price and adjust_entry_price collision 2025-02-20 19:22:03 +01:00
Matthias 67ce9a41f2 feat: implement load error when colliding methods are decected 2025-02-20 19:21:37 +01:00
Matthias 756bada570 feat: Add "replace-exit_order" to backtesting 2025-02-20 19:09:36 +01:00
Matthias 25c0036012 feat: Update strategy template 2025-02-20 07:25:38 +01:00
Matthias 3b03fae939 chore: improve variable naming 2025-02-20 07:24:07 +01:00
Matthias 0f9e61371c feat: Implement live "replace_exit_order" functionality 2025-02-20 07:23:36 +01:00
Matthias 1970cc65c0 feat: add "is_entry" attribute for order_replacement 2025-02-20 07:04:59 +01:00
Matthias 8d74e8b8dd feat: add adjust_order_price callback 2025-02-20 06:55:54 +01:00
Matthias fc4e09f398 Merge pull request #11404 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2025-02-20 06:35:26 +01:00
xmatthias a45d941b11 chore: update pre-commit hooks 2025-02-20 03:05:52 +00:00
Matthias 39fd27c7ec Merge pull request #11396 from freqtrade/fix/constant_exit_cancels
Fix constant exit cancels and replacements
2025-02-18 19:43:54 +01:00
Matthias fa939a7f66 Merge pull request #11399 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-02-18 06:36:19 +01:00
xmatthias 6fc81fa9c7 chore: update pre-commit hooks 2025-02-18 03:04:55 +00:00
Matthias 89cd46d2e5 test: adapt test for "evaluate exit signal once once" behavior 2025-02-17 20:00:44 +01:00
Matthias f287d0ad24 chore: improve variable naming 2025-02-17 20:00:27 +01:00
Matthias 93e3bbea5a fix: only evaluate the same exit-reason once
Limit this behavior to if it caused an actual exit.
2025-02-17 19:51:39 +01:00
Matthias 6be25bd86a chore: simplify IF condition 2025-02-17 19:40:56 +01:00
Matthias ec82894990 Merge pull request #11390 from freqtrade/dependabot/pip/develop/cryptography-44.0.1
chore(deps): bump cryptography from 42.0.8 to 44.0.1
2025-02-17 08:20:57 +01:00
Matthias ea1b7c692a Merge pull request #11384 from freqtrade/dependabot/pip/develop/websockets-15.0
chore(deps): bump websockets from 14.2 to 15.0
2025-02-17 08:04:01 +01:00
Matthias 0e08173235 Merge pull request #11394 from freqtrade/dependabot/pip/develop/lightgbm-4.6.0
chore(deps): bump lightgbm from 4.5.0 to 4.6.0
2025-02-17 07:46:26 +01:00
Matthias cdf33761c0 Merge pull request #11383 from freqtrade/dependabot/pip/develop/mkdocs-f2d694b1ef
chore(deps): bump mkdocs-material from 9.6.3 to 9.6.4 in the mkdocs group
2025-02-17 07:11:37 +01:00
Matthias 63ca07aeac chore: bump cryptography for armv7
Starting with 44.0.0, cryptography provides wheels for armv7.

Testing these, it installs fine on the rpi image, so the split hard pin can be removed.
2025-02-17 06:52:30 +01:00
Matthias 5022407021 Merge pull request #11392 from freqtrade/dependabot/pip/develop/psutil-7.0.0
chore(deps): bump psutil from 6.1.1 to 7.0.0
2025-02-17 06:51:50 +01:00
dependabot[bot] f54b7d56d3 chore(deps): bump lightgbm from 4.5.0 to 4.6.0
Bumps [lightgbm](https://github.com/microsoft/LightGBM) from 4.5.0 to 4.6.0.
- [Release notes](https://github.com/microsoft/LightGBM/releases)
- [Commits](https://github.com/microsoft/LightGBM/compare/v4.5.0...v4.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 05:30:51 +00:00
dependabot[bot] 382befb053 chore(deps): bump cryptography from 42.0.8 to 44.0.1
Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.8 to 44.0.1.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/42.0.8...44.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 05:30:25 +00:00
Matthias 11404ef230 Merge pull request #11385 from freqtrade/dependabot/pip/develop/scipy-1.15.2
chore(deps): bump scipy from 1.15.1 to 1.15.2
2025-02-17 06:29:32 +01:00
Matthias 8964cfc123 Merge pull request #11391 from freqtrade/dependabot/pip/develop/humanize-4.12.0
chore(deps): bump humanize from 4.11.0 to 4.12.0
2025-02-17 06:28:45 +01:00
Matthias ec97723c54 Merge pull request #11386 from freqtrade/dependabot/pip/develop/tensorboard-2.19.0
chore(deps): bump tensorboard from 2.18.0 to 2.19.0
2025-02-17 06:28:19 +01:00
Matthias 4cfbcefb33 Merge pull request #11388 from freqtrade/dependabot/pip/develop/ccxt-4.4.59
chore(deps): bump ccxt from 4.4.58 to 4.4.59
2025-02-17 06:27:48 +01:00
Matthias 1f575d95e6 Merge pull request #11389 from freqtrade/dependabot/pip/develop/ruff-0.9.6
chore(deps-dev): bump ruff from 0.9.5 to 0.9.6
2025-02-17 06:26:59 +01:00
dependabot[bot] d4900c28e1 chore(deps): bump psutil from 6.1.1 to 7.0.0
Bumps [psutil](https://github.com/giampaolo/psutil) from 6.1.1 to 7.0.0.
- [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst)
- [Commits](https://github.com/giampaolo/psutil/compare/release-6.1.1...release-7.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 03:27:29 +00:00
dependabot[bot] f12964f543 chore(deps): bump humanize from 4.11.0 to 4.12.0
Bumps [humanize](https://github.com/python-humanize/humanize) from 4.11.0 to 4.12.0.
- [Release notes](https://github.com/python-humanize/humanize/releases)
- [Commits](https://github.com/python-humanize/humanize/compare/4.11.0...4.12.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 03:27:25 +00:00
dependabot[bot] c339853ec0 chore(deps-dev): bump ruff from 0.9.5 to 0.9.6
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.9.5 to 0.9.6.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.9.5...0.9.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 03:27:13 +00:00
dependabot[bot] 3924514d2b chore(deps): bump ccxt from 4.4.58 to 4.4.59
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.58 to 4.4.59.
- [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/go/v4.4.58...go/v4.4.59)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 03:27:04 +00:00
dependabot[bot] aae92c29db chore(deps): bump tensorboard from 2.18.0 to 2.19.0
Bumps [tensorboard](https://github.com/tensorflow/tensorboard) from 2.18.0 to 2.19.0.
- [Release notes](https://github.com/tensorflow/tensorboard/releases)
- [Changelog](https://github.com/tensorflow/tensorboard/blob/2.19.0/RELEASE.md)
- [Commits](https://github.com/tensorflow/tensorboard/compare/2.18.0...2.19.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 03:26:45 +00:00
dependabot[bot] 5683063379 chore(deps): bump scipy from 1.15.1 to 1.15.2
Bumps [scipy](https://github.com/scipy/scipy) from 1.15.1 to 1.15.2.
- [Release notes](https://github.com/scipy/scipy/releases)
- [Commits](https://github.com/scipy/scipy/compare/v1.15.1...v1.15.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 03:26:40 +00:00
dependabot[bot] 5ab993b6d5 chore(deps): bump websockets from 14.2 to 15.0
Bumps [websockets](https://github.com/python-websockets/websockets) from 14.2 to 15.0.
- [Release notes](https://github.com/python-websockets/websockets/releases)
- [Commits](https://github.com/python-websockets/websockets/compare/14.2...15.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 03:26:30 +00:00
dependabot[bot] 9584f6c549 chore(deps): bump mkdocs-material in the mkdocs group
Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `mkdocs-material` from 9.6.3 to 9.6.4
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.3...9.6.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-17 03:26:27 +00:00
Matthias 2a95a3aefe Merge pull request #11306 from Vaadasch/patch-1
Add some distinction on the trailing_only_offset_is_reached
2025-02-16 17:28:24 +01:00
Matthias 36eeac365e chore: fix merge mistake 2025-02-16 17:19:21 +01:00
Matthias 2b8c4942fd Merge branch 'develop' into fix/mutable_defaults 2025-02-16 17:16:31 +01:00
Matthias 4a190bd13c chore(ci): Move permissions into job 2025-02-16 17:03:18 +01:00
Matthias cf6a89e161 chore: use zizmor to scan actions for security problems 2025-02-16 16:55:44 +01:00
Matthias a29c5322ab chore(ci): Improve CI security 2025-02-16 16:55:13 +01:00
Matthias 4e2d2639ae fix: dockerfile.armhf should pin numpy<2 2025-02-16 16:48:22 +01:00
Matthias a54f895ea9 chore(ci): don't persist credentials 2025-02-16 15:31:17 +01:00
Matthias ad55615ecb chore(ci): Improve structure in devcontainer build 2025-02-16 15:30:47 +01:00
Matthias c8aff5687d chore(ci): don't persist credentials 2025-02-16 15:30:21 +01:00
Matthias 02c2e451b1 chore(ci): don't persist credentials 2025-02-16 15:29:34 +01:00
Matthias 84d49a0f53 chore: disable all permissions on dockerhub permission update ci 2025-02-16 15:25:01 +01:00
Matthias 22b096be25 chore: Improve security of workflows 2025-02-16 15:24:23 +01:00
Matthias b285cdd491 doc: add timeframe to 101 strategy so it's "auto-runnable"
closes #11379
2025-02-16 15:11:14 +01:00
Matthias b2881affc0 fix: accidental modification 2025-02-16 15:09:02 +01:00
Matthias b3e0b42155 test: add test for backtest-from_json 2025-02-16 13:27:48 +01:00
Matthias 314c017dd3 feat: enable trade.from_json to import backtested trades 2025-02-16 13:27:42 +01:00
Matthias 9fbc413d4d chore: start downloads in 1m batches 2025-02-15 15:44:05 +01:00
Matthias ad96fa0f58 chore: improve download-trades-fast structure 2025-02-15 15:20:06 +01:00
Matthias f2283410ce chore: improved logging, don't ignore end date 2025-02-15 13:36:30 +01:00
Matthias f14c4ebcc8 chore: improve code structure in logging module 2025-02-15 08:24:35 +01:00
David Arena ceff757bc6 fix: formating 2025-02-14 12:44:42 +01:00
Robert Caulk c3c6207cda Merge pull request #11368 from freqtrade/fix/nan_metadata
Allow NAN in Freqai datadrawer metadata
2025-02-13 13:46:36 +01:00
Matthias 975ad26eb8 Merge branch 'develop' into feat/binance_trades_fast 2025-02-13 06:43:56 +01:00
Matthias 8a5ff4ed0d Merge pull request #11373 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2025-02-13 06:33:55 +01:00
xmatthias 7d19e0e692 chore: update pre-commit hooks 2025-02-13 03:05:01 +00:00
David Arena a1a5cab04e fix E501 2025-02-13 02:04:22 +01:00
David Arena 8182947f29 fix 2025-02-13 02:03:19 +01:00
David Arena 2231ba3f04 fixes: ruff 2025-02-13 02:02:15 +01:00
David Arena bf0a08f5a7 Merge branch 'develop' into api-server-list-custom-data 2025-02-12 19:32:49 +01:00
David Arena 552575c7e6 fix: key in _rpc_list_custom_data 2025-02-12 19:27:47 +01:00
David Arena d0979d560f fix: endpoints 2025-02-12 18:40:44 +01:00
Matthias e3a9b0aa17 Merge pull request #11371 from freqtrade/dependabot/pip/develop/ccxt-4.4.58
chore(deps): bump ccxt from 4.4.52 to 4.4.58
2025-02-12 06:23:01 +01:00
dependabot[bot] 7caa22c7a7 chore(deps): bump ccxt from 4.4.52 to 4.4.58
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.52 to 4.4.58.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.4.52...go/v4.4.58)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 20:13:52 +00:00
Matthias 91131d7884 Merge pull request #11370 from freqtrade/dependabot/pip/cryptography-44.0.1
chore(deps): bump cryptography from 42.0.8 to 44.0.1
2025-02-11 21:12:25 +01:00
dependabot[bot] 967ad2f8ff chore(deps): bump cryptography from 42.0.8 to 44.0.1
Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.8 to 44.0.1.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/42.0.8...44.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-11 18:41:14 +00:00
Matthias d00f82a641 chore: remove deprecated image form CI matrix 2025-02-11 18:15:16 +01:00
Matthias 2f83f07fd3 fix: use constant for metadata number mode 2025-02-11 09:22:39 +01:00
Matthias 78d213a8a0 test: add test coverage for unwatch_ohlcv 2025-02-11 09:15:03 +01:00
Matthias 44d798239d feat: unwatch_ohlcv - improved error handling 2025-02-11 09:15:03 +01:00
Matthias 08e1a616a0 feat: use unwatch to stop watching a websocket 2025-02-11 09:14:57 +01:00
Matthias 8009c03629 Merge pull request #11117 from arenstar/ftclient_timeout
feat: add timeout to FtRestClient
2025-02-11 08:09:45 +01:00
Matthias ae068bd6f5 chore: fix formatting 2025-02-11 07:53:33 +01:00
Matthias 2350aa6a27 Merge branch 'develop' into ftclient_timeout 2025-02-11 07:52:25 +01:00
Matthias 8394826d62 docs: improve wording in trailing stop docs 2025-02-11 07:21:24 +01:00
Matthias a46d41a594 Merge pull request #11367 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-02-11 06:39:25 +01:00
xmatthias a0d8055917 chore: update pre-commit hooks 2025-02-11 03:06:02 +00:00
Matthias 3ec892a5ab Merge pull request #11355 from freqtrade/dependabot/pip/develop/ta-lib-0.6.2
chore(deps): bump ta-lib from 0.4.34 to 0.4.38
2025-02-10 10:53:09 +01:00
Matthias 3deffb57ce chore: update ta-lib pre-built binaries 2025-02-10 08:25:01 +01:00
Matthias db8c1917e0 Merge pull request #11360 from freqtrade/dependabot/pip/develop/mypy-1.15.0
chore(deps-dev): bump mypy from 1.14.1 to 1.15.0
2025-02-10 07:57:42 +01:00
Matthias 57f55d416c Merge pull request #11357 from freqtrade/dependabot/pip/develop/sqlalchemy-2.0.38
chore(deps): bump sqlalchemy from 2.0.37 to 2.0.38
2025-02-10 07:35:26 +01:00
dependabot[bot] 0b4a6e7e11 chore(deps-dev): bump mypy from 1.14.1 to 1.15.0
Bumps [mypy](https://github.com/python/mypy) from 1.14.1 to 1.15.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.14.1...v1.15.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 05:57:49 +00:00
Matthias f918a66292 Merge pull request #11359 from freqtrade/dependabot/pip/develop/ruff-0.9.5
chore(deps-dev): bump ruff from 0.9.4 to 0.9.5
2025-02-10 06:56:44 +01:00
Matthias d795af5921 chore: bump sqlalchemy in pre-commit config 2025-02-10 06:36:25 +01:00
Matthias 21b8982e55 chore: ta-lib bump to 0.4.38 2025-02-10 06:34:07 +01:00
Matthias 5a9f66e6d6 Merge pull request #11351 from freqtrade/dependabot/pip/develop/mkdocs-694d3e3e45
chore(deps): bump mkdocs-material from 9.6.1 to 9.6.3 in the mkdocs group
2025-02-10 06:30:34 +01:00
Matthias 4fd77e7ee7 Merge pull request #11356 from freqtrade/dependabot/pip/develop/xgboost-2.1.4
chore(deps): bump xgboost from 2.1.3 to 2.1.4
2025-02-10 06:30:22 +01:00
dependabot[bot] 428ca972e2 chore(deps-dev): bump ruff from 0.9.4 to 0.9.5
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.9.4 to 0.9.5.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.9.4...0.9.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 03:28:24 +00:00
dependabot[bot] 3913e4fa96 chore(deps): bump sqlalchemy from 2.0.37 to 2.0.38
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.37 to 2.0.38.
- [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases)
- [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst)
- [Commits](https://github.com/sqlalchemy/sqlalchemy/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 03:27:58 +00:00
dependabot[bot] e296242072 chore(deps): bump xgboost from 2.1.3 to 2.1.4
Bumps [xgboost](https://github.com/dmlc/xgboost) from 2.1.3 to 2.1.4.
- [Release notes](https://github.com/dmlc/xgboost/releases)
- [Changelog](https://github.com/dmlc/xgboost/blob/master/NEWS.md)
- [Commits](https://github.com/dmlc/xgboost/compare/v2.1.3...v2.1.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 03:27:46 +00:00
dependabot[bot] 82001d1e8b chore(deps): bump ta-lib from 0.4.34 to 0.6.2
Bumps [ta-lib](https://github.com/ta-lib/ta-lib-python) from 0.4.34 to 0.6.2.
- [Changelog](https://github.com/TA-Lib/ta-lib-python/blob/master/CHANGELOG)
- [Commits](https://github.com/ta-lib/ta-lib-python/compare/TA_Lib-0.4.34...TA_Lib-0.6.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 03:27:40 +00:00
dependabot[bot] 137bfc2b56 chore(deps): bump mkdocs-material in the mkdocs group
Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `mkdocs-material` from 9.6.1 to 9.6.3
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.6.1...9.6.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-10 03:27:15 +00:00
Matthias a8ae767576 fix: Hyperopt-magic - allow parent classes within the same file 2025-02-09 19:48:26 +01:00
Matthias b9c77124fc chore: add typing for hyperopt-pickle-magic 2025-02-09 19:43:32 +01:00
Matthias f060548134 chore: remove unused method 2025-02-09 08:10:10 +01:00
Matthias 18ce8af084 fix: validate config when calling pair_history
closes #11271
2025-02-08 16:51:51 +01:00
Matthias 7f7cff85c1 chore: use dt_now in backtesting 2025-02-07 20:39:14 +01:00
Matthias 9a22e5c775 chore: try downgrade aiohttp
part of #11317
2025-02-07 06:56:42 +01:00
Matthias 2cfb8834b6 chore: rework docstring of MultiOutputRegressor
closes #11309
2025-02-06 06:34:19 +01:00
Matthias 013006050f chore: add comment to clarify usage of retrier 2025-02-06 06:33:55 +01:00
Matthias 99df346bf5 Merge pull request #11339 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2025-02-06 06:30:49 +01:00
xmatthias 2e57158556 chore: update pre-commit hooks 2025-02-06 03:04:59 +00:00
Matthias b75eb85e66 fix: Calculate liquidation price only once the initial order filled
closes #11318
2025-02-05 18:13:52 +01:00
Matthias dbb9f225d0 fix: Runtime error on ohlcv deepcopy
closes #11335
2025-02-05 18:07:03 +01:00
Matthias 85753be7cb chore: Bump armhf dockerversion to 3.11.11 2025-02-05 07:02:44 +01:00
Matthias 18b35b0657 chore: bump dockerfile base from 3.12.7 to 3.12.8 2025-02-05 07:01:59 +01:00
Matthias 857aeaa9eb chore: fix init sequence to avoid risk of "unbound error" 2025-02-04 19:34:26 +01:00
Matthias 3e06971038 Merge pull request #11333 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2025-02-04 06:47:37 +01:00
Matthias 323b9f8693 fix(api): only write export when backtest happened
closes freqtrade/frequi#2282
2025-02-04 06:35:37 +01:00
xmatthias a412ff76bc chore: update pre-commit hooks 2025-02-04 03:03:58 +00:00
Matthias 2bfd66d271 chore: use loop_lock for all run_until_complete usages 2025-02-03 19:43:08 +01:00
Matthias 8cad9c8436 chore: add improved exception logging 2025-02-03 19:43:08 +01:00
Matthias 3504073428 fix: random fail on parallel webserver requests 2025-02-03 19:43:08 +01:00
Matthias b4af89380b Merge pull request #11326 from freqtrade/dependabot/pip/develop/torch-2.6.0
chore(deps): bump torch from 2.5.1 to 2.6.0
2025-02-03 19:40:45 +01:00
Matthias c131493eca Merge pull request #11328 from freqtrade/dependabot/pip/develop/stable-baselines3-2.5.0
chore(deps): bump stable-baselines3 from 2.4.1 to 2.5.0
2025-02-03 11:55:31 +01:00
Matthias 37031ba83d chore: pin sb3 for macos x86 2025-02-03 11:13:36 +01:00
Matthias 69106633cd Merge pull request #11331 from freqtrade/dependabot/pip/develop/fastapi-0.115.8
chore(deps): bump fastapi from 0.115.7 to 0.115.8
2025-02-03 09:42:17 +01:00
Matthias a2e674b237 Merge pull request #11329 from freqtrade/dependabot/pip/develop/isort-6.0.0
chore(deps-dev): bump isort from 5.13.2 to 6.0.0
2025-02-03 09:20:38 +01:00
Matthias c1c9f0151e Merge pull request #11332 from freqtrade/dependabot/pip/develop/pymdown-extensions-10.14.3
chore(deps): bump pymdown-extensions from 10.14.1 to 10.14.3
2025-02-03 08:01:03 +01:00
Matthias 963ec23ae2 Merge pull request #11330 from freqtrade/dependabot/pip/develop/plotly-6.0.0
chore(deps): bump plotly from 5.24.1 to 6.0.0
2025-02-03 07:14:18 +01:00
dependabot[bot] 0c35f3a47a chore(deps): bump pymdown-extensions from 10.14.1 to 10.14.3
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.14.1 to 10.14.3.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.14.1...10.14.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 05:58:16 +00:00
Matthias f4fdcfc78d Merge pull request #11323 from freqtrade/dependabot/pip/develop/ccxt-4.4.52
chore(deps): bump ccxt from 4.4.50 to 4.4.52
2025-02-03 06:57:17 +01:00
Matthias 97c67d55c6 Merge pull request #11322 from freqtrade/dependabot/pip/develop/mkdocs-03e80ceb70
chore(deps): bump mkdocs-material from 9.5.50 to 9.6.1 in the mkdocs group
2025-02-03 06:53:46 +01:00
Matthias 9983b44e6f Merge pull request #11325 from freqtrade/dependabot/pip/develop/pytz-2025.1
chore(deps): bump pytz from 2024.2 to 2025.1
2025-02-03 06:53:05 +01:00
dependabot[bot] 78bf9bf60d chore(deps-dev): bump isort from 5.13.2 to 6.0.0
Bumps [isort](https://github.com/pycqa/isort) from 5.13.2 to 6.0.0.
- [Release notes](https://github.com/pycqa/isort/releases)
- [Changelog](https://github.com/PyCQA/isort/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pycqa/isort/compare/5.13.2...6.0.0)

---
updated-dependencies:
- dependency-name: isort
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 05:44:16 +00:00
Matthias 9d22dac28d Merge pull request #11327 from freqtrade/dependabot/pip/develop/nbconvert-7.16.6
chore(deps-dev): bump nbconvert from 7.16.5 to 7.16.6
2025-02-03 06:34:17 +01:00
Matthias 37e16acc80 Merge pull request #11324 from freqtrade/dependabot/pip/develop/ruff-0.9.4
chore(deps-dev): bump ruff from 0.9.3 to 0.9.4
2025-02-03 06:23:55 +01:00
Matthias 6d5cd69764 Merge pull request #11321 from freqtrade/dependabot/pip/develop/pytest-ccc7fb3faf
chore(deps-dev): bump pytest-asyncio from 0.25.2 to 0.25.3 in the pytest group
2025-02-03 06:23:43 +01:00
dependabot[bot] 3a8c594ae8 chore(deps): bump fastapi from 0.115.7 to 0.115.8
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.115.7 to 0.115.8.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.115.7...0.115.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 03:30:46 +00:00
dependabot[bot] 5f72ed9b3c chore(deps): bump plotly from 5.24.1 to 6.0.0
Bumps [plotly](https://github.com/plotly/plotly.py) from 5.24.1 to 6.0.0.
- [Release notes](https://github.com/plotly/plotly.py/releases)
- [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md)
- [Commits](https://github.com/plotly/plotly.py/compare/v5.24.1...v6.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 03:30:42 +00:00
dependabot[bot] bee897f5b7 chore(deps): bump stable-baselines3 from 2.4.1 to 2.5.0
Bumps [stable-baselines3](https://github.com/DLR-RM/stable-baselines3) from 2.4.1 to 2.5.0.
- [Release notes](https://github.com/DLR-RM/stable-baselines3/releases)
- [Commits](https://github.com/DLR-RM/stable-baselines3/compare/v2.4.1...v2.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 03:30:27 +00:00
dependabot[bot] 75542cdd93 chore(deps-dev): bump nbconvert from 7.16.5 to 7.16.6
Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 7.16.5 to 7.16.6.
- [Release notes](https://github.com/jupyter/nbconvert/releases)
- [Changelog](https://github.com/jupyter/nbconvert/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jupyter/nbconvert/compare/v7.16.5...v7.16.6)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 03:30:24 +00:00
dependabot[bot] 97da78c499 chore(deps): bump torch from 2.2.2 to 2.6.0
Bumps [torch](https://github.com/pytorch/pytorch) from 2.2.2 to 2.6.0.
- [Release notes](https://github.com/pytorch/pytorch/releases)
- [Changelog](https://github.com/pytorch/pytorch/blob/main/RELEASE.md)
- [Commits](https://github.com/pytorch/pytorch/compare/v2.2.2...v2.6.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 03:30:20 +00:00
dependabot[bot] d8d35ec234 chore(deps): bump pytz from 2024.2 to 2025.1
Bumps [pytz](https://github.com/stub42/pytz) from 2024.2 to 2025.1.
- [Release notes](https://github.com/stub42/pytz/releases)
- [Commits](https://github.com/stub42/pytz/compare/release_2024.2...release_2025.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 03:30:10 +00:00
dependabot[bot] 86d65f8428 chore(deps-dev): bump ruff from 0.9.3 to 0.9.4
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.9.3 to 0.9.4.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.9.3...0.9.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 03:30:06 +00:00
dependabot[bot] 973aa6a0c3 chore(deps): bump ccxt from 4.4.50 to 4.4.52
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.50 to 4.4.52.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.4.50...4.4.52)

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

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


Updates `mkdocs-material` from 9.5.50 to 9.6.1
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.5.50...9.6.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 03:29:48 +00:00
dependabot[bot] 16924525ff chore(deps-dev): bump pytest-asyncio in the pytest group
Bumps the pytest group with 1 update: [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio).


Updates `pytest-asyncio` from 0.25.2 to 0.25.3
- [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases)
- [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.25.2...v0.25.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2025-02-03 03:29:40 +00:00
Matthias f1f355330c chore: re-add candle-limit for funding fee candles 2025-02-02 17:15:21 +01:00
Matthias 4c5c054bca fix: since_ms is mandatory 2025-02-02 14:33:45 +01:00
Matthias 0181e00c19 test: add test for /markets endpoint 2025-02-02 14:33:13 +01:00
Matthias ecf013dd18 feat: include exchangeID in /markets response 2025-02-02 13:30:17 +01:00
Matthias 59837ef6ac feat: add "/markets" endpoint 2025-02-02 13:22:54 +01:00
Matthias b42a91d783 test: remove unused parameter 2025-02-02 13:17:06 +01:00
Matthias 187c8b8070 test: add test for "live" mode of /pair_history 2025-02-01 18:20:27 +01:00
Matthias a807d2d6c8 test: Add test for /pair_history without strategy 2025-02-01 18:09:35 +01:00
Matthias fda7b0f1ef chore: increment api_version 2025-02-01 17:39:40 +01:00
Matthias ae42f8d512 feat: api /pair_history - make Strategy optional 2025-02-01 17:39:28 +01:00
Matthias 6fb74fa166 feat: add "live_mode" flag to pair_history 2025-02-01 17:25:58 +01:00
Matthias 7dd0f2174c feat: Add "exchange" fields to pair_history 2025-02-01 15:47:33 +01:00
Matthias e1cc7dcfdf feat: allow pair-history from "live" candles 2025-02-01 15:47:10 +01:00
Matthias 6b3fff6b7e refactor: pair_history should only be available in webserver mode 2025-02-01 14:09:56 +01:00
Matthias 5d4324dd55 test: update percentChange pairlist test
there's little reason to have this test for a particular number
2025-02-01 13:53:14 +01:00
Matthias fc0cae21e6 fix: improve reliability when fetching from "features" 2025-02-01 13:49:17 +01:00
Matthias 89b6cc89a3 test: update test for removed manual limits 2025-02-01 13:49:04 +01:00
Matthias 26af62e4dd feat: remove manual ohlcv limit definitions where possible 2025-02-01 08:13:33 +01:00
Matthias f90cd44a9e feat: default ohlcv_limit to ccxt's features definition 2025-02-01 08:12:19 +01:00
Matthias 35f7c97c76 test: add "ohlcv_limit" live test 2025-01-31 18:27:47 +01:00
Matthias d530527f11 test: add test for exchange.features 2025-01-31 07:03:06 +01:00
Matthias 8af8260b7c feat: initial "features" support 2025-01-31 06:56:41 +01:00
Matthias 7a17cd781a chore: remove coinbasepro exchange
ccxt removed this exchange in https://github.com/ccxt/ccxt/pull/22502.
2025-01-31 06:30:39 +01:00
Matthias ba244cd2f6 chore: bump develop version to 2025.2 2025-01-30 19:43:51 +01:00
Vaadasch 7d3c8b48dd Add some distinction on the trailing_only_offset_is_reached 2025-01-30 14:48:15 +01:00
Matthias 6931b81daa test: add test for binance specific history_id 2025-01-30 07:27:55 +01:00
Matthias 92405f94ec test: further add tests for public trades dl 2025-01-30 07:13:03 +01:00
Matthias 3e1daf50b0 test: add test for download_archive_trades 2025-01-30 07:10:14 +01:00
Matthias 0caeb6b5f7 chore: improve types 2025-01-27 20:52:16 +01:00
Matthias 85d235958e Merge branch 'develop' into feat/binance_trades_fast 2025-01-27 20:40:59 +01:00
Matthias 008f875282 chore: improve edge-case bug 2025-01-27 20:26:08 +01:00
Matthias 8b42c2558a feat: the "minimum" date limit applies to trades, too 2025-01-26 14:28:49 +01:00
Matthias 40f56e43cc test: add test for get_daily_trades 2025-01-26 13:34:27 +01:00
Matthias e9c961802b test: add test for trades zip url 2025-01-26 13:18:05 +01:00
Matthias 948b6e041d test: add initial test for download-trades 2025-01-26 13:15:09 +01:00
Matthias 80d776af56 test: ensure tests use old method 2025-01-26 13:14:58 +01:00
Matthias e62d84e35d refactor: pass candle_type 2025-01-26 13:04:44 +01:00
Matthias 098a6551f6 chore: add documentation link for binance.vision 2025-01-26 13:04:08 +01:00
Matthias 6e15fd0da6 feat: trades-fast download - improvements 2025-01-26 12:59:59 +01:00
Matthias 1f2dadc333 fix: edge-case problem 2025-01-26 12:59:59 +01:00
Matthias c3b7a2b382 feat: use download-data fast if necessary 2025-01-26 12:59:59 +01:00
Matthias 3746305b5a feat: further work on download-trades-archive 2025-01-26 12:59:59 +01:00
Matthias af3f9d5a75 feat: rough draft of public trades data
This won't work though - as according to https://github.com/binance/binance-public-data - trades does not contain "side".
2025-01-26 12:59:59 +01:00
David Arena 7d5f021717 feat: add timeout to FtRestClient 2024-12-20 23:47:18 +01:00
David Arena 9207cf501c fix: returned object 2024-12-17 22:25:09 +01:00
David Arena fc1c3a8f97 fix 2024-12-17 19:22:09 +01:00
David Arena 83e56a09c2 fix: api url and rm key 2024-12-17 19:22:02 +01:00
David Arena 47613b1cf9 fix: no tradeID only returns open trades 2024-12-17 18:32:27 +01:00
David Arena ef3a7d5c92 feat: api_server and client supporting list_custom_data 2024-12-16 00:56:34 +01:00
Matthias 705d1e4cc0 chore: remove freqAI per-line-ignores 2024-09-01 08:34:46 +02:00
Matthias 5b3f348bbb chore: Don't use method call in function header 2024-09-01 08:34:44 +02:00
Matthias aa81c75bef chore: Further reduce mutable default usage 2024-09-01 08:34:42 +02:00
Matthias 6b889814ad chore: Fix further "mutable arguments" call 2024-09-01 08:34:39 +02:00
Matthias 1ade11f00b chore: Fix a few freqAI mutable defaults 2024-09-01 08:34:37 +02:00
133 changed files with 9767 additions and 3130 deletions
@@ -16,6 +16,8 @@ jobs:
name: develop
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-python@v5
with:
@@ -42,6 +44,7 @@ jobs:
branch: update/binance-leverage-tiers
title: Update Binance Leverage Tiers
commit-message: "chore: update pre-commit hooks"
committer: Freqtrade Bot <noreply@github.com>
committer: Freqtrade Bot <154552126+freqtrade-bot@users.noreply.github.com>
author: Freqtrade Bot <154552126+freqtrade-bot@users.noreply.github.com>
body: Update binance leverage tiers.
delete-branch: true
+28 -2
View File
@@ -24,11 +24,13 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ "ubuntu-20.04", "ubuntu-22.04", "ubuntu-24.04" ]
os: [ "ubuntu-22.04", "ubuntu-24.04" ]
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
@@ -157,6 +159,8 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
@@ -281,6 +285,8 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
@@ -366,6 +372,8 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
@@ -381,6 +389,8 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-python@v5
with:
@@ -391,6 +401,8 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Documentation syntax
run: |
@@ -420,6 +432,8 @@ jobs:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
@@ -506,6 +520,8 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
@@ -551,6 +567,8 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Download artifact 📦
uses: actions/download-artifact@v4
@@ -578,6 +596,8 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Download artifact 📦
uses: actions/download-artifact@v4
@@ -598,6 +618,8 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@v5
@@ -633,7 +655,9 @@ jobs:
uses: docker/setup-buildx-action@v3
- name: Available platforms
run: echo ${{ steps.buildx.outputs.platforms }}
run: echo ${PLATFORMS}
env:
PLATFORMS: ${{ steps.buildx.outputs.platforms }}
- name: Build and test and push docker images
env:
@@ -652,6 +676,8 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Extract branch name
id: extract-branch
+8 -2
View File
@@ -20,6 +20,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: true
- name: Set up Python
uses: actions/setup-python@v5
@@ -43,12 +45,16 @@ jobs:
- name: Build and push Mike
if: ${{ github.event_name == 'push' }}
run: |
mike deploy ${{ github.ref_name }} latest --push --update-aliases
mike deploy ${REF_NAME} latest --push --update-aliases
env:
REF_NAME: ${{ github.ref_name }}
- name: Build and push Mike - Release
if: ${{ github.event_name == 'release' }}
run: |
mike deploy ${{ github.ref_name }} stable --push --update-aliases
mike deploy ${REF_NAME} stable --push --update-aliases
env:
REF_NAME: ${{ github.ref_name }}
- name: Show mike versions
run: |
+18 -21
View File
@@ -17,29 +17,26 @@ concurrency:
group: "${{ github.workflow }}"
cancel-in-progress: true
permissions:
packages: write
jobs:
build-and-push:
permissions:
packages: write
runs-on: ubuntu-latest
steps:
-
name: Checkout
id: checkout
uses: actions/checkout@v4
-
name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
-
name: Pre-build dev container image
uses: devcontainers/ci@v0.3
with:
subFolder: .github
imageName: ghcr.io/${{ github.repository }}-devcontainer
cacheFrom: ghcr.io/${{ github.repository }}-devcontainer
push: always
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Login to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Pre-build dev container image
uses: devcontainers/ci@v0.3
with:
subFolder: .github
imageName: ghcr.io/${{ github.repository }}-devcontainer
cacheFrom: ghcr.io/${{ github.repository }}-devcontainer
push: always
@@ -4,11 +4,16 @@ on:
branches:
- stable
# disable permissions for all of the available permissions
permissions: {}
jobs:
dockerHubDescription:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- name: Docker Hub Description
uses: peter-evans/dockerhub-description@v4
+4 -1
View File
@@ -14,6 +14,8 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
- uses: actions/setup-python@v5
with:
@@ -36,6 +38,7 @@ jobs:
branch: update/pre-commit-hooks
title: Update pre-commit hooks
commit-message: "chore: update pre-commit hooks"
committer: Freqtrade Bot <noreply@github.com>
committer: Freqtrade Bot <154552126+freqtrade-bot@users.noreply.github.com>
author: Freqtrade Bot <154552126+freqtrade-bot@users.noreply.github.com>
body: Update versions of pre-commit hooks to latest version.
delete-branch: true
+14 -8
View File
@@ -2,28 +2,28 @@
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pycqa/flake8
rev: "7.1.1"
rev: "7.1.2"
hooks:
- id: flake8
additional_dependencies: [Flake8-pyproject]
# stages: [push]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.14.1"
rev: "v1.15.0"
hooks:
- id: mypy
exclude: build_helpers
additional_dependencies:
- types-cachetools==5.5.0.20240820
- types-filelock==3.2.7
- types-requests==2.32.0.20241016
- types-requests==2.32.0.20250306
- types-tabulate==0.9.0.20241207
- types-python-dateutil==2.9.0.20241206
- SQLAlchemy==2.0.37
- SQLAlchemy==2.0.39
# stages: [push]
- repo: https://github.com/pycqa/isort
rev: "6.0.0"
rev: "6.0.1"
hooks:
- id: isort
name: isort (python)
@@ -31,7 +31,7 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.9.3'
rev: 'v0.11.2'
hooks:
- id: ruff
- id: ruff-format
@@ -57,13 +57,19 @@ repos:
)$
- repo: https://github.com/stefmolin/exif-stripper
rev: 0.6.1
rev: 0.6.2
hooks:
- id: strip-exif
- repo: https://github.com/codespell-project/codespell
rev: v2.4.0
rev: v2.4.1
hooks:
- id: codespell
additional_dependencies:
- tomli
# Ensure github actions remain safe
- repo: https://github.com/woodruffw/zizmor-pre-commit
rev: v1.5.2
hooks:
- id: zizmor
+1 -1
View File
@@ -1,4 +1,4 @@
FROM python:3.12.7-slim-bookworm as base
FROM python:3.12.9-slim-bookworm as base
# Setup env
ENV LANG C.UTF-8
+1 -1
View File
@@ -1,6 +1,6 @@
# ![freqtrade](https://raw.githubusercontent.com/freqtrade/freqtrade/develop/docs/assets/freqtrade_poweredby.svg)
[![Freqtrade CI](https://github.com/freqtrade/freqtrade/workflows/Freqtrade%20CI/badge.svg)](https://github.com/freqtrade/freqtrade/actions/)
[![Freqtrade CI](https://github.com/freqtrade/freqtrade/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/freqtrade/freqtrade/actions/)
[![DOI](https://joss.theoj.org/papers/10.21105/joss.04864/status.svg)](https://doi.org/10.21105/joss.04864)
[![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
[![Documentation](https://readthedocs.org/projects/freqtrade/badge/)](https://www.freqtrade.io)
+79 -9
View File
@@ -257,7 +257,8 @@
"enum": [
"day",
"week",
"month"
"month",
"year"
]
}
},
@@ -541,6 +542,10 @@
"description": "Edge configuration.",
"$ref": "#/definitions/edge"
},
"log_config": {
"description": "Logging configuration.",
"$ref": "#/definitions/logging"
},
"freqai": {
"description": "FreqAI configuration.",
"$ref": "#/definitions/freqai"
@@ -612,6 +617,14 @@
"description": "Telegram topic ID - only applicable for group chats",
"type": "string"
},
"authorized_users": {
"description": "Authorized users for the bot.",
"type": "array",
"items": {
"type": "string"
},
"uniqueItems": true
},
"allow_custom_messages": {
"description": "Allow sending custom messages from the Strategy.",
"type": "boolean",
@@ -1272,6 +1285,30 @@
"allowed_risk"
]
},
"logging": {
"type": "object",
"properties": {
"version": {
"type": "number",
"const": 1
},
"formatters": {
"type": "object"
},
"handlers": {
"type": "object"
},
"root": {
"type": "object"
}
},
"required": [
"version",
"formatters",
"handlers",
"root"
]
},
"external_message_consumer": {
"description": "Configuration for external message consumer.",
"type": "object",
@@ -1366,10 +1403,10 @@
"type": "boolean",
"default": false
},
"keras": {
"description": "Use Keras for model training.",
"type": "boolean",
"default": false
"identifier": {
"description": "A unique ID for the current model. Must be changed when modifying features.",
"type": "string",
"default": "example"
},
"write_metrics_to_disk": {
"description": "Write metrics to disk?",
@@ -1399,16 +1436,49 @@
"type": "number",
"default": 7
},
"identifier": {
"description": "A unique ID for the current model. Must be changed when modifying features.",
"type": "string",
"default": "example"
"live_retrain_hours": {
"description": "Frequency of retraining during dry/live runs.",
"type": "number",
"default": 0
},
"expiration_hours": {
"description": "Avoid making predictions if a model is more than `expiration_hours` old. Defaults to 0 (no expiration).",
"type": "number",
"default": 0
},
"save_backtest_models": {
"description": "Save models to disk when running backtesting.",
"type": "boolean",
"default": false
},
"fit_live_predictions_candles": {
"description": "Number of historical candles to use for computing target (label) statistics from prediction data, instead of from the training dataset.",
"type": "integer"
},
"data_kitchen_thread_count": {
"description": "Designate the number of threads you want to use for data processing (outlier methods, normalization, etc.).",
"type": "integer"
},
"activate_tensorboard": {
"description": "Indicate whether or not to activate tensorboard",
"type": "boolean",
"default": true
},
"wait_for_training_iteration_on_reload": {
"description": "Wait for the next training iteration to complete after /reload or ctrl+c.",
"type": "boolean",
"default": true
},
"continual_learning": {
"description": "Use the final state of the most recently trained model as starting point for the new model, allowing for incremental learning.",
"type": "boolean",
"default": false
},
"keras": {
"description": "Use Keras for model training.",
"type": "boolean",
"default": false
},
"feature_parameters": {
"description": "The parameters used to engineer the feature set",
"type": "object",
+2 -2
View File
@@ -1,4 +1,4 @@
FROM python:3.11.10-slim-bookworm as base
FROM python:3.11.11-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 \
RUN pip install --user --no-cache-dir "numpy<2" \
&& pip install --user --no-index --find-links /tmp/ pyarrow TA-Lib \
&& pip install --user --no-cache-dir -r requirements.txt
+198 -13
View File
@@ -188,30 +188,111 @@ as the watchdog.
## Advanced Logging
Freqtrade uses the default logging module provided by python.
Python allows for extensive [logging configuration](https://docs.python.org/3/library/logging.config.html#logging.config.dictConfig) in this regards - way more than what can be covered here.
Default logging (Colored terminal output) is setup by default if no `log_config` is provided.
Using `--logfile logfile.log` will enable the RotatingFileHandler.
If you're not content with the log format - or with the default settings provided for the RotatingFileHandler, you can customize logging to your liking.
The default configuration looks roughly like the below - with the file handler being provided - but not enabled.
``` json hl_lines="5-7 13-16 27"
{
"log_config": {
"version": 1,
"formatters": {
"basic": {
"format": "%(message)s"
},
"standard": {
"format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
}
},
"handlers": {
"console": {
"class": "freqtrade.loggers.ft_rich_handler.FtRichHandler",
"formatter": "basic"
},
"file": {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "standard",
// "filename": "someRandomLogFile.log",
"maxBytes": 10485760,
"backupCount": 10
}
},
"root": {
"handlers": [
"console",
// "file"
],
"level": "INFO",
}
}
}
```
!!! Note "highlighted lines"
Highlighted lines in the above code-block define the Rich handler and belong together.
The formatter "standard" and "file" will belong to the FileHandler.
Each handler must use one of the defined formatters (by name) - and it's class must be available and a valid logging class.
To actually use a handler - it must be in the "handlers" section inside the "root" segment.
If this section is left out, freqtrade will provide no output (in the non-configured handler, anyway).
!!! Tip "Explicit log configuration"
We recommend to extract the logging configuration from your main configuration, and provide it to your bot via [multiple configuration files](configuration.md#multiple-configuration-files) functionality. This will avoid unnecessary code duplication.
---
On many Linux systems the bot can be configured to send its log messages to `syslog` or `journald` system services. Logging to a remote `syslog` server is also available on Windows. The special values for the `--logfile` command line option can be used for this.
### Logging to syslog
To send Freqtrade log messages to a local or remote `syslog` service use the `--logfile` command line option with the value in the following format:
To send Freqtrade log messages to a local or remote `syslog` service use the `"log_config"` setup option to configure logging.
* `--logfile syslog:<syslog_address>` -- send log messages to `syslog` service using the `<syslog_address>` as the syslog address.
``` json
{
// ...
"log_config": {
"version": 1,
"formatters": {
"syslog_fmt": {
"format": "%(name)s - %(levelname)s - %(message)s"
}
},
"handlers": {
// Other handlers?
"syslog": {
"class": "logging.handlers.SysLogHandler",
"formatter": "syslog_fmt",
// Use one of the other options above as address instead?
"address": "/dev/log"
}
},
"root": {
"handlers": [
// other handlers
"syslog",
]
}
The syslog address can be either a Unix domain socket (socket filename) or a UDP socket specification, consisting of IP address and UDP port, separated by the `:` character.
}
}
```
So, the following are the examples of possible usages:
[Additional log-handlers](#advanced-logging) may need to be configured to for example also have log output in the console.
* `--logfile syslog:/dev/log` -- log to syslog (rsyslog) using the `/dev/log` socket, suitable for most systems.
* `--logfile syslog` -- same as above, the shortcut for `/dev/log`.
* `--logfile syslog:/var/run/syslog` -- log to syslog (rsyslog) using the `/var/run/syslog` socket. Use this on MacOS.
* `--logfile syslog:localhost:514` -- log to local syslog using UDP socket, if it listens on port 514.
* `--logfile syslog:<ip>:514` -- log to remote syslog at IP address and port 514. This may be used on Windows for remote logging to an external syslog server.
#### Syslog usage
Log messages are send to `syslog` with the `user` facility. So you can see them with the following commands:
* `tail -f /var/log/user`, or
* `tail -f /var/log/user`, or
* install a comprehensive graphical viewer (for instance, 'Log File Viewer' for Ubuntu).
On many systems `syslog` (`rsyslog`) fetches data from `journald` (and vice versa), so both `--logfile syslog` or `--logfile journald` can be used and the messages be viewed with both `journalctl` and a syslog viewer utility. You can combine this in any way which suites you better.
On many systems `syslog` (`rsyslog`) fetches data from `journald` (and vice versa), so both syslog or journald can be used and the messages be viewed with both `journalctl` and a syslog viewer utility. You can combine this in any way which suites you better.
For `rsyslog` the messages from the bot can be redirected into a separate dedicated log file. To achieve this, add
@@ -228,13 +309,69 @@ For `syslog` (`rsyslog`), the reduction mode can be switched on. This will reduc
$RepeatedMsgReduction on
```
#### Syslog addressing
The syslog address can be either a Unix domain socket (socket filename) or a UDP socket specification, consisting of IP address and UDP port, separated by the `:` character.
So, the following are the examples of possible addresses:
* `"address": "/dev/log"` -- log to syslog (rsyslog) using the `/dev/log` socket, suitable for most systems.
* `"address": "/var/run/syslog"` -- log to syslog (rsyslog) using the `/var/run/syslog` socket. Use this on MacOS.
* `"address": "localhost:514"` -- log to local syslog using UDP socket, if it listens on port 514.
* `"address": "<ip>:514"` -- log to remote syslog at IP address and port 514. This may be used on Windows for remote logging to an external syslog server.
??? Info "Deprecated - configure syslog via command line"
`--logfile syslog:<syslog_address>` -- send log messages to `syslog` service using the `<syslog_address>` as the syslog address.
The syslog address can be either a Unix domain socket (socket filename) or a UDP socket specification, consisting of IP address and UDP port, separated by the `:` character.
So, the following are the examples of possible usages:
* `--logfile syslog:/dev/log` -- log to syslog (rsyslog) using the `/dev/log` socket, suitable for most systems.
* `--logfile syslog` -- same as above, the shortcut for `/dev/log`.
* `--logfile syslog:/var/run/syslog` -- log to syslog (rsyslog) using the `/var/run/syslog` socket. Use this on MacOS.
* `--logfile syslog:localhost:514` -- log to local syslog using UDP socket, if it listens on port 514.
* `--logfile syslog:<ip>:514` -- log to remote syslog at IP address and port 514. This may be used on Windows for remote logging to an external syslog server.
### Logging to journald
This needs the `cysystemd` python package installed as dependency (`pip install cysystemd`), which is not available on Windows. Hence, the whole journald logging functionality is not available for a bot running on Windows.
To send Freqtrade log messages to `journald` system service use the `--logfile` command line option with the value in the following format:
To send Freqtrade log messages to `journald` system service, add the following configuration snippet to your configuration.
* `--logfile journald` -- send log messages to `journald`.
``` json
{
// ...
"log_config": {
"version": 1,
"formatters": {
"journald_fmt": {
"format": "%(name)s - %(levelname)s - %(message)s"
}
},
"handlers": {
// Other handlers?
"journald": {
"class": "cysystemd.journal.JournaldLogHandler",
"formatter": "journald_fmt",
}
},
"root": {
"handlers": [
// ..
"journald",
]
}
}
}
```
[Additional log-handlers](#advanced-logging) may need to be configured to for example also have log output in the console.
Log messages are send to `journald` with the `user` facility. So you can see them with the following commands:
@@ -244,3 +381,51 @@ Log messages are send to `journald` with the `user` facility. So you can see the
There are many other options in the `journalctl` utility to filter the messages, see manual pages for this utility.
On many systems `syslog` (`rsyslog`) fetches data from `journald` (and vice versa), so both `--logfile syslog` or `--logfile journald` can be used and the messages be viewed with both `journalctl` and a syslog viewer utility. You can combine this in any way which suites you better.
??? Info "Deprecated - configure journald via command line"
To send Freqtrade log messages to `journald` system service use the `--logfile` command line option with the value in the following format:
`--logfile journald` -- send log messages to `journald`.
### Log format as JSON
You can also configure the default output stream to use JSON format instead.
The "fmt_dict" attribute defines the keys for the json output - as well as the [python logging LogRecord attributes](https://docs.python.org/3/library/logging.html#logrecord-attributes).
The below configuration will change the default output to JSON. The same formatter could however also be used in combination with the `RotatingFileHandler`.
We recommend to keep one format in human readable form.
``` json
{
// ...
"log_config": {
"version": 1,
"formatters": {
"json": {
"()": "freqtrade.loggers.json_formatter.JsonFormatter",
"fmt_dict": {
"timestamp": "asctime",
"level": "levelname",
"logger": "name",
"message": "message"
}
}
},
"handlers": {
// Other handlers?
"jsonStream": {
"class": "logging.StreamHandler",
"formatter": "json"
}
},
"root": {
"handlers": [
// ..
"jsonStream",
]
}
}
}
```
+17
View File
@@ -209,6 +209,7 @@ A backtesting result will look like that:
| Sortino | 1.88 |
| Sharpe | 2.97 |
| Calmar | 6.29 |
| SQN | 2.45 |
| Profit factor | 1.11 |
| Expectancy (Ratio) | -0.15 (-0.05) |
| Avg. stake amount | 0.001 BTC |
@@ -315,6 +316,7 @@ It contains some useful key metrics about performance of your strategy on backte
| Sortino | 1.88 |
| Sharpe | 2.97 |
| Calmar | 6.29 |
| SQN | 2.45 |
| Profit factor | 1.11 |
| Expectancy (Ratio) | -0.15 (-0.05) |
| Avg. stake amount | 0.001 BTC |
@@ -368,6 +370,7 @@ It contains some useful key metrics about performance of your strategy on backte
- `Sortino`: Annualized Sortino ratio.
- `Sharpe`: Annualized Sharpe ratio.
- `Calmar`: Annualized Calmar ratio.
- `SQN`: System Quality Number (SQN) - by Van Tharp.
- `Profit factor`: profit / loss.
- `Avg. stake amount`: Average stake amount, either `stake_amount` or the average when using dynamic stake amount.
- `Total trade volume`: Volume generated on the exchange to reach the above profit.
@@ -432,6 +435,20 @@ 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.
### Backtest output file
The output file freqtrade produces is a zip file containing the following files:
- The backtest report in json format
- the market change data in feather format
- a copy of the strategy file
- a copy of the strategy parameters (if a parameter file was used)
- a sanitized copy of the config file
This will ensure results are reproducible - under the assumption that the same data is available.
Only the strategy file and the config file are included in the zip file, eventual dependencies are not included.
## Assumptions made by backtesting
Since backtesting lacks some detailed information about what happens within a candle, it needs to take a few assumptions:
+7 -3
View File
@@ -54,11 +54,13 @@ By default, the bot loop runs every few seconds (`internals.process_throttle_sec
* Check timeouts for open orders.
* Calls `check_entry_timeout()` strategy callback for open entry orders.
* Calls `check_exit_timeout()` strategy callback for open exit orders.
* Calls `adjust_entry_price()` strategy callback for open entry orders.
* Calls `adjust_order_price()` strategy callback for open orders.
* Calls `adjust_entry_price()` strategy callback for open entry orders. *only called when `adjust_order_price()` is not implemented*
* Calls `adjust_exit_price()` strategy callback for open exit orders. *only called when `adjust_order_price()` is not implemented*
* Verifies existing positions and eventually places exit orders.
* Considers stoploss, ROI and exit-signal, `custom_exit()` and `custom_stoploss()`.
* Determine exit-price based on `exit_pricing` configuration setting or by using the `custom_exit_price()` callback.
* Before a exit order is placed, `confirm_trade_exit()` strategy callback is called.
* Before an exit order is placed, `confirm_trade_exit()` strategy callback is called.
* Check position adjustments for open trades if enabled by calling `adjust_trade_position()` and place additional order if required.
* Check if trade-slots are still available (if `max_open_trades` is reached).
* Verifies entry signal trying to enter new positions.
@@ -80,7 +82,9 @@ This loop will be repeated again and again until the bot is stopped.
* Loops per candle simulating entry and exit points.
* Calls `bot_loop_start()` strategy callback.
* Check for Order timeouts, either via the `unfilledtimeout` configuration, or via `check_entry_timeout()` / `check_exit_timeout()` strategy callbacks.
* Calls `adjust_entry_price()` strategy callback for open entry orders.
* Calls `adjust_order_price()` strategy callback for open orders.
* Calls `adjust_entry_price()` strategy callback for open entry orders. *only called when `adjust_order_price()` is not implemented!*
* Calls `adjust_exit_price()` strategy callback for open exit orders. *only called when `adjust_order_price()` is not implemented!*
* Check for trade entry signals (`enter_long` / `enter_short` columns).
* Confirm trade entry / exits (calls `confirm_trade_entry()` and `confirm_trade_exit()` if implemented in the strategy).
* Call `custom_entry_price()` (if implemented in the strategy) to determine entry price (Prices are moved to be within the opening candle).
+4 -3
View File
@@ -2,7 +2,7 @@
usage: freqtrade backtesting-show [-h] [-v] [--no-color] [--logfile FILE] [-V]
[-c PATH] [-d PATH] [--userdir PATH]
[--export-filename PATH] [--show-pair-list]
[--breakdown {day,week,month} [{day,week,month} ...]]
[--breakdown {day,week,month,year} [{day,week,month,year} ...]]
options:
-h, --help show this help message and exit
@@ -11,8 +11,9 @@ options:
`--export` to be set as well. Example: `--export-filen
ame=user_data/backtest_results/backtest_today.json`
--show-pair-list Show backtesting pairlist sorted by profit.
--breakdown {day,week,month} [{day,week,month} ...]
Show backtesting breakdown per [day, week, month].
--breakdown {day,week,month,year} [{day,week,month,year} ...]
Show backtesting breakdown per [day, week, month,
year].
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
+4 -3
View File
@@ -15,7 +15,7 @@ usage: freqtrade backtesting [-h] [-v] [--no-color] [--logfile FILE] [-V]
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
[--export {none,trades,signals}]
[--export-filename PATH]
[--breakdown {day,week,month} [{day,week,month} ...]]
[--breakdown {day,week,month,year} [{day,week,month,year} ...]]
[--cache {none,day,week,month}]
[--freqai-backtest-live-models]
@@ -65,8 +65,9 @@ options:
Use this filename for backtest results.Requires
`--export` to be set as well. Example: `--export-filen
ame=user_data/backtest_results/backtest_today.json`
--breakdown {day,week,month} [{day,week,month} ...]
Show backtesting breakdown per [day, week, month].
--breakdown {day,week,month,year} [{day,week,month,year} ...]
Show backtesting breakdown per [day, week, month,
year].
--cache {none,day,week,month}
Load a cached backtest result no older than specified
age (default: day).
+4 -3
View File
@@ -4,7 +4,7 @@ usage: freqtrade hyperopt-show [-h] [-v] [--no-color] [--logfile FILE] [-V]
[--profitable] [-n INT] [--print-json]
[--hyperopt-filename FILENAME] [--no-header]
[--disable-param-export]
[--breakdown {day,week,month} [{day,week,month} ...]]
[--breakdown {day,week,month,year} [{day,week,month,year} ...]]
options:
-h, --help show this help message and exit
@@ -18,8 +18,9 @@ options:
--no-header Do not print epoch details header.
--disable-param-export
Disable automatic hyperopt parameter export.
--breakdown {day,week,month} [{day,week,month} ...]
Show backtesting breakdown per [day, week, month].
--breakdown {day,week,month,year} [{day,week,month,year} ...]
Show backtesting breakdown per [day, week, month,
year].
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
+1
View File
@@ -282,6 +282,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data. <br> *Defaults to `feather`*. <br> **Datatype:** String
| `dataformat_trades` | Data format to use to store historical trades data. <br> *Defaults to `feather`*. <br> **Datatype:** String
| `reduce_df_footprint` | Recast all numeric columns to float32/int32, with the objective of reducing ram/disk usage (and decreasing train/inference timing in FreqAI). (Currently only affects FreqAI use-cases) <br> **Datatype:** Boolean. <br> Default: `False`.
| `log_config` | Dictionary containing the log config for python logging. [more info](advanced-setup.md#advanced-logging) <br> **Datatype:** dict. <br> Default: `FtRichHandler`
### Parameters in the strategy
+5
View File
@@ -88,3 +88,8 @@ Setting protections from the configuration via `"protections": [],` has been rem
Using hdf5 as data storage has been deprecated in 2024.12 and was removed in 2025.1. We recommend switching to the feather data format.
Please use the [`convert-data` subcommand](data-download.md#sub-command-convert-data) to convert your existing data to one of the supported formats before updating.
## Configuring advanced logging via config
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.
+19 -1
View File
@@ -44,9 +44,24 @@ You may also use something like `.*DOWN/BTC` or `.*UP/BTC` to exclude leveraged
By default, the `StaticPairList` method is used, which uses a statically defined pair whitelist from the configuration. The pairlist also supports wildcards (in regex-style) - so `.*/BTC` will include all pairs with BTC as a stake.
It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklist`.
It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklist`, which in the below example, will trade BTC/USDT and ETH/USDT - and will prevent BNB/USDT trading.
Both `pair_*list` parameters support regex - so values like `.*/USDT` would enable trading all pairs that are not in the blacklist.
```json
"exchange": {
"name": "...",
// ...
"pair_whitelist": [
"BTC/USDT",
"ETH/USDT",
// ...
],
"pair_blacklist": [
"BNB/USDT",
// ...
]
},
"pairlists": [
{"method": "StaticPairList"}
],
@@ -377,6 +392,9 @@ If an incorrect category string is chosen, the plugin will print the available c
!!! Warning "Many categories"
Each added category corresponds to one API call to CoinGecko. The more categories you add, the longer the pairlist generation will take, potentially causing rate limit issues.
!!! Danger "Duplicate symbols in coingecko"
Coingecko often has duplicate symbols, where the same symbol is used for different coins. Freqtrade will use the symbol as is and try to search for it on the exchange. If the symbol exists - it will be used. Freqtrade will however not check if the _intended_ symbol is the one coingecko meant. This can sometimes lead to unexpected results, especially on low volume coins or with meme coin categories.
#### AgeFilter
Removes pairs that have been listed on the exchange for less than `min_days_listed` days (defaults to `10`) or more than `max_days_listed` days (defaults `None` mean infinity).
+1 -1
View File
@@ -1,6 +1,6 @@
![freqtrade](assets/freqtrade_poweredby.svg)
[![Freqtrade CI](https://github.com/freqtrade/freqtrade/workflows/Freqtrade%20CI/badge.svg)](https://github.com/freqtrade/freqtrade/actions/)
[![Freqtrade CI](https://github.com/freqtrade/freqtrade/actions/workflows/ci.yml/badge.svg?branch=develop)](https://github.com/freqtrade/freqtrade/actions/)
[![DOI](https://joss.theoj.org/papers/10.21105/joss.04864/status.svg)](https://doi.org/10.21105/joss.04864)
[![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)
+3 -3
View File
@@ -1,7 +1,7 @@
markdown==3.7
mkdocs==1.6.1
mkdocs-material==9.5.50
mkdocs-material==9.6.9
mdx_truly_sane_lists==1.3
pymdown-extensions==10.14.1
jinja2==3.1.5
pymdown-extensions==10.14.3
jinja2==3.1.6
mike==2.1.3
+13
View File
@@ -302,6 +302,19 @@ trades
:param limit: Limits trades to the X last trades. Max 500 trades.
:param offset: Offset by this amount of trades.
list_open_trades_custom_data
Return a dict containing open trades custom-datas
:param key: str, optional - Key of the custom-data
:param limit: Limits trades to X trades.
:param offset: Offset by this amount of trades.
list_custom_data
Return a dict containing custom-datas of a specified trade
:param trade_id: int - ID of the trade
:param key: str, optional - Key of the custom-data
version
Return the version of the bot.
+6 -4
View File
@@ -154,10 +154,10 @@ For example, simplified math:
In summary: The stoploss will be adjusted to be always be -10% of the highest observed price.
### Trailing stop loss, custom positive loss
### Trailing stop loss, different positive loss
You could also have a default stop loss when you are in the red with your buy (buy - fee), but once you hit a positive result (or an offset you define) the system will utilize a new stop loss, which can have a different value.
For example, your default stop loss is -10%, but once you have more than 0% profit (example 0.1%) a different trailing stoploss will be used.
You could also have a default stop loss when you are in the red with your buy (buy - fee), but once you hit a positive result (or an offset you define) the system will utilize a new stop loss, with a different value.
For example, your default stop loss is -10%, but once you have reached profitability (example 0.1%) a different trailing stoploss will be used.
!!! Note
If you want the stoploss to only be changed when you break even of making a profit (what most users want) please refer to next section with [offset enabled](#trailing-stop-loss-only-once-the-trade-has-reached-a-certain-offset).
@@ -208,7 +208,9 @@ Before this, `stoploss` is used for the trailing stoploss.
You can also keep a static stoploss until the offset is reached, and then trail the trade to take profits once the market turns.
If `trailing_only_offset_is_reached = True` then the trailing stoploss is only activated once the offset is reached. Until then, the stoploss remains at the configured `stoploss`.
If `trailing_only_offset_is_reached = True` then the trailing stoploss is only activated once the offset is reached. Until then, the stoploss remains at the configured `stoploss` and is not trailing.
Leaving this value as `trailing_only_offset_is_reached=False` will allow the trailing stoploss to start trailing as soon as the asset price increases above the initial entry price.
This option can be used with or without `trailing_stop_positive`, but uses `trailing_stop_positive_offset` as offset.
Configuration (offset is buy-price + 3%):
+2
View File
@@ -79,6 +79,8 @@ import talib.abstract as ta
class MyStrategy(IStrategy):
timeframe = '15m'
# set the initial stoploss to -10%
stoploss = -0.10
+56 -21
View File
@@ -758,7 +758,7 @@ For performance reasons, it's disabled by default and freqtrade will show a warn
Additional orders also result in additional fees and those orders don't count towards `max_open_trades`.
This callback is also called when there is an open order (either buy or sell) waiting for execution - and will cancel the existing open order to place a new order if the amount, price or direction is different.
This callback is also called when there is an open order (either buy or sell) waiting for execution - and will cancel the existing open order to place a new order if the amount, price or direction is different. Also partially filled orders will be canceled, and will be replaced with the new amount as returned by the callback.
`adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible.
@@ -770,9 +770,10 @@ Modifications to leverage are not possible, and the stake-amount returned is ass
The combined stake currently allocated to the position is held in `trade.stake_amount`. Therefore `trade.stake_amount` will always be updated on every additional entry and partial exit made through `adjust_trade_position()`.
!!! Danger "Loose Logic"
On dry and live run, this function will be called every `throttle_process_secs` (default to 5s). If you have a loose logic, for example your logic for extra entry is only to check RSI of last candle is below 30, then when such condition fulfilled, your bot will do extra re-entry every 5 secs until either it run out of money, it hit the `max_position_adjustment` limit, or a new candle with RSI more than 30 arrived.
On dry and live run, this function will be called every `throttle_process_secs` (default to 5s). If you have a loose logic, (e.g. increase position if RSI of the last candle is below 30), your bot will do extra re-entry every 5 secs until you either it run out of money, hit the `max_position_adjustment` limit, or a new candle with RSI more than 30 arrived.
Same thing also can happen with partial exit. So be sure to have a strict logic and/or check for the last filled order.
Same thing also can happen with partial exit.
So be sure to have a strict logic and/or check for the last filled order and if an order is already open.
!!! Warning "Performance with many position adjustments"
Position adjustments can be a good approach to increase a strategy's output - but it can also have drawbacks if using this feature extensively.
@@ -876,6 +877,9 @@ class DigDeeperStrategy(IStrategy):
Return None for no action.
Optionally, return a tuple with a 2nd element with an order reason
"""
if trade.has_open_orders:
# Only act if no orders are open
return
if current_profit > 0.05 and trade.nr_of_successful_exits == 0:
# Take half of the profit at +5%
@@ -934,28 +938,25 @@ class DigDeeperStrategy(IStrategy):
The total profit for this trade was 950$ on a 3350$ investment (`100@8$ + 100@9$ + 150@11$`). As such - the final relative profit is 28.35% (`950 / 3350`).
## Adjust Entry Price
## Adjust order Price
The `adjust_entry_price()` callback may be used by strategy developer to refresh/replace limit orders upon arrival of new candles.
The `adjust_order_price()` callback may be used by strategy developer to refresh/replace limit orders upon arrival of new candles.
This callback is called once every iteration unless the order has been (re)placed within the current candle - limiting the maximum (re)placement of each order to once per candle.
This also means that the first call will be at the start of the next candle after the initial order was placed.
Be aware that `custom_entry_price()` is still the one dictating initial entry limit order price target at the time of entry trigger.
Be aware that `custom_entry_price()`/`custom_exit_price()` is still the one dictating initial limit order price target at the time of the signal.
Orders can be cancelled out of this callback by returning `None`.
Returning `current_order_rate` will keep the order on the exchange "as is".
Returning any other price will cancel the existing order, and replace it with a new order.
The trade open-date (`trade.open_date_utc`) will remain at the time of the very first order placed.
Please make sure to be aware of this - and eventually adjust your logic in other callbacks to account for this, and use the date of the first filled order instead.
If the cancellation of the original order fails, then the order will not be replaced - though the order will most likely have been canceled on exchange. Having this happen on initial entries will result in the deletion of the order, while on position adjustment orders, it'll result in the trade size remaining as is.
If the order has been partially filled, the order will not be replaced. You can however use [`adjust_trade_position()`](#adjust-trade-position) to adjust the trade size to the full, expected position size, should this be necessary / desired.
If the order has been partially filled, the order will not be replaced. You can however use [`adjust_trade_position()`](#adjust-trade-position) to adjust the trade size to the expected position size, should this be necessary / desired.
!!! Warning "Regular timeout"
Entry `unfilledtimeout` mechanism (as well as `check_entry_timeout()`) takes precedence over this.
Entry Orders that are cancelled via the above methods will not have this callback called. Be sure to update timeout values to match your expectations.
Entry `unfilledtimeout` mechanism (as well as `check_entry_timeout()`/`check_exit_timeout()`) takes precedence over this callback.
Orders that are cancelled via the above methods will not have this callback called. Be sure to update timeout values to match your expectations.
```python
# Default imports
@@ -964,14 +965,26 @@ class AwesomeStrategy(IStrategy):
# ... populate_* methods
def adjust_entry_price(self, trade: Trade, order: Order | None, pair: str,
current_time: datetime, proposed_rate: float, current_order_rate: float,
entry_tag: str | None, side: str, **kwargs) -> float:
def adjust_order_price(
self,
trade: Trade,
order: Order | None,
pair: str,
current_time: datetime,
proposed_rate: float,
current_order_rate: float,
entry_tag: str | None,
side: str,
is_entry: bool,
**kwargs,
) -> float | None:
"""
Entry price re-adjustment logic, returning the user desired limit price.
Exit and entry order price re-adjustment logic, returning the user desired limit price.
This only executes when a order was already placed, still open (unfilled fully or partially)
and not timed out on subsequent candles after entry trigger.
For full documentation please go to https://www.freqtrade.io/en/latest/strategy-callbacks/
When not implemented by a strategy, returns current_order_rate as default.
If current_order_rate is returned then the existing order is maintained.
If None is returned then order gets canceled but not replaced by a new one.
@@ -983,14 +996,16 @@ class AwesomeStrategy(IStrategy):
:param proposed_rate: Rate, calculated based on pricing settings in entry_pricing.
:param current_order_rate: Rate of the existing order in place.
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
:param side: "long" or "short" - indicating the direction of the proposed trade
:param side: 'long' or 'short' - indicating the direction of the proposed trade
:param is_entry: True if the order is an entry order, False if it's an exit order.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: New entry price value if provided
:return float or None: New entry price value if provided
"""
# Limit orders to use and follow SMA200 as price target for the first 10 minutes since entry trigger for BTC/USDT pair.
# Limit entry orders to use and follow SMA200 as price target for the first 10 minutes since entry trigger for BTC/USDT pair.
if (
pair == "BTC/USDT"
is_entry
and pair == "BTC/USDT"
and entry_tag == "long_sma200"
and side == "long"
and (current_time - timedelta(minutes=10)) <= trade.open_date_utc
@@ -1007,6 +1022,26 @@ class AwesomeStrategy(IStrategy):
return current_order_rate
```
!!! danger "Incompatibility with `adjust_*_price()`"
If you have both `adjust_order_price()` and `adjust_entry_price()`/`adjust_exit_price()` implemented, only `adjust_order_price()` will be used.
If you need to adjust entry/exit prices, you can either implement the logic in `adjust_order_price()`, or use the split `adjust_entry_price()` / `adjust_exit_price()` callbacks, but not both.
Mixing these is not supported and will raise an error during bot startup.
### Adjust Entry Price
The `adjust_entry_price()` callback may be used by strategy developer to refresh/replace entry limit orders upon arrival.
It's a sub-set of `adjust_order_price()` and is called only for entry orders.
All remaining behavior is identical to `adjust_order_price()`.
The trade open-date (`trade.open_date_utc`) will remain at the time of the very first order placed.
Please make sure to be aware of this - and eventually adjust your logic in other callbacks to account for this, and use the date of the first filled order instead.
### Adjust Exit Price
The `adjust_exit_price()` callback may be used by strategy developer to refresh/replace exit limit orders upon arrival.
It's a sub-set of `adjust_order_price()` and is called only for exit orders.
All remaining behavior is identical to `adjust_order_price()`.
## Leverage Callback
When trading in markets that allow leverage, this method must return the desired Leverage (Defaults to 1 -> No leverage).
+2 -1
View File
@@ -513,7 +513,7 @@ By default, freqtrade will attempt to load strategies from all `.py` files withi
Assuming your strategy is called `AwesomeStrategy`, stored in the file `user_data/strategies/AwesomeStrategy.py`, then you can start freqtrade in dry (or live, depending on your configuration) mode with:
```bash
freqtrade trade --strategy AwesomeStrategy`
freqtrade trade --strategy AwesomeStrategy
```
Note that we're using the class name, not the file name.
@@ -1122,6 +1122,7 @@ The following list contains some common patterns which should be avoided to prev
- don't use `.iloc[-1]` or any other absolute position in the dataframe within `populate_` functions, as this will be different between dry-run and backtesting. Absolute `iloc` indexing is safe to use in callbacks however - see [Strategy Callbacks](strategy-callbacks.md).
- don't use functions that use all dataframe or column values, e.g. `dataframe['mean_volume'] = dataframe['volume'].mean()`. As backtesting uses the full dataframe, at any point in the dataframe, the `'mean_volume'` series would include data from the future. Use rolling() calculations instead, e.g. `dataframe['volume'].rolling(<window>).mean()`.
- don't use `.resample('1h')`. This uses the left border of the period interval, so moves data from an hour boundary to the start of the hour. Use `.resample('1h', label='right')` instead.
- don't use `.merge()` to combine longer timeframes onto shorter ones. Instead, use the [informative pair](#informative-pairs) helpers. (A plain merge can implicitly cause a lookahead bias as date refers to open date, not close date).
!!! Tip "Identifying problems"
You should always use the two helper commands [lookahead-analysis](lookahead-analysis.md) and [recursive-analysis](recursive-analysis.md), which can each help you figure out problems with your strategy in different ways.
+13
View File
@@ -81,6 +81,19 @@ Without this, the bot will always respond to the general channel in the group if
Similar to the group-id - you can use `/tg_info` from the topic/thread to get the correct topic-id.
#### Authorized users
For groups, it can be useful to limit who can send commands to the bot.
If `"authorized_users": []` is present and empty, no user will be allowed to control the bot.
In the below example, only the user with the id "1234567" is allowed to control the bot - all other users will only be able to receive messages.
```json
"chat_id": "-1001332619709",
"topic_id": "3",
"authorized_users": ["1234567"]
```
## Control telegram noise
Freqtrade provides means to control the verbosity of your telegram bot.
+1
View File
@@ -35,6 +35,7 @@ The following attributes / properties are available for each individual trade -
| `trade_direction` | "long" / "short" | Trade direction in text - long or short. |
| `nr_of_successful_entries` | int | Number of successful (filled) entry orders. |
| `nr_of_successful_exits` | int | Number of successful (filled) exit orders. |
| `has_open_orders` | boolean | Has the trade open orders (excluding stoploss orders). |
## Class methods
+1 -1
View File
@@ -1,6 +1,6 @@
"""Freqtrade bot"""
__version__ = "2025.1"
__version__ = "2025.3"
if "dev" in __version__:
from pathlib import Path
+1 -1
View File
@@ -224,7 +224,7 @@ AVAILABLE_CLI_OPTIONS = {
),
"backtest_breakdown": Arg(
"--breakdown",
help="Show backtesting breakdown per [day, week, month].",
help="Show backtesting breakdown per [day, week, month, year].",
nargs="+",
choices=constants.BACKTEST_BREAKDOWNS,
),
+94 -85
View File
@@ -17,11 +17,11 @@ def start_list_exchanges(args: dict[str, Any]) -> None:
:param args: Cli args from Arguments()
:return: None
"""
from rich.console import Console
from rich.table import Table
from rich.text import Text
from freqtrade.exchange import list_available_exchanges
from freqtrade.loggers.rich_console import get_rich_console
available_exchanges: list[ValidExchangesType] = list_available_exchanges(
args["list_exchanges_all"]
@@ -77,15 +77,16 @@ def start_list_exchanges(args: dict[str, Any]) -> None:
)
# table.add_row(*[exchange[header] for header in headers])
console = Console()
console = get_rich_console()
console.print(table)
def _print_objs_tabular(objs: list, print_colorized: bool) -> None:
from rich.console import Console
from rich.table import Table
from rich.text import Text
from freqtrade.loggers.rich_console import get_rich_console
names = [s["name"] for s in objs]
objs_to_print: list[dict[str, Text | str]] = [
{
@@ -118,10 +119,7 @@ def _print_objs_tabular(objs: list, print_colorized: bool) -> None:
for row in objs_to_print:
table.add_row(*[row[header] for header in objs_to_print[0].keys()])
console = Console(
color_system="auto" if print_colorized else None,
width=200 if "pytest" in sys.modules else None,
)
console = get_rich_console(color_system="auto" if print_colorized else None)
console.print(table)
@@ -219,7 +217,7 @@ def start_list_markets(args: dict[str, Any], pairs_only: bool = False) -> None:
"""
from freqtrade.configuration import setup_utils_configuration
from freqtrade.exchange import market_is_active
from freqtrade.misc import plural
from freqtrade.misc import plural, safe_value_fallback
from freqtrade.resolvers import ExchangeResolver
from freqtrade.util import print_rich_table
@@ -246,88 +244,99 @@ def start_list_markets(args: dict[str, Any], pairs_only: bool = False) -> None:
except Exception as e:
raise OperationalException(f"Cannot get markets. Reason: {e}") from e
else:
summary_str = (
(f"Exchange {exchange.name} has {len(pairs)} ")
+ ("active " if active_only else "")
+ (plural(len(pairs), "pair" if pairs_only else "market"))
+ (
f" with {', '.join(base_currencies)} as base "
f"{plural(len(base_currencies), 'currency', 'currencies')}"
if base_currencies
else ""
)
+ (" and" if base_currencies and quote_currencies else "")
+ (
f" with {', '.join(quote_currencies)} as quote "
f"{plural(len(quote_currencies), 'currency', 'currencies')}"
if quote_currencies
else ""
)
tickers = exchange.get_tickers()
summary_str = (
(f"Exchange {exchange.name} has {len(pairs)} ")
+ ("active " if active_only else "")
+ (plural(len(pairs), "pair" if pairs_only else "market"))
+ (
f" with {', '.join(base_currencies)} as base "
f"{plural(len(base_currencies), 'currency', 'currencies')}"
if base_currencies
else ""
)
+ (" and" if base_currencies and quote_currencies else "")
+ (
f" with {', '.join(quote_currencies)} as quote "
f"{plural(len(quote_currencies), 'currency', 'currencies')}"
if quote_currencies
else ""
)
)
headers = [
"Id",
"Symbol",
"Base",
"Quote",
"Active",
"Spot",
"Margin",
"Future",
"Leverage",
]
headers = [
"Id",
"Symbol",
"Base",
"Quote",
"Active",
"Spot",
"Margin",
"Future",
"Leverage",
"Min Stake",
]
tabular_data = [
{
"Id": v["id"],
"Symbol": v["symbol"],
"Base": v["base"],
"Quote": v["quote"],
"Active": market_is_active(v),
"Spot": "Spot" if exchange.market_is_spot(v) else "",
"Margin": "Margin" if exchange.market_is_margin(v) else "",
"Future": "Future" if exchange.market_is_future(v) else "",
"Leverage": exchange.get_max_leverage(v["symbol"], 20),
}
for _, v in pairs.items()
]
tabular_data = [
{
"Id": v["id"],
"Symbol": v["symbol"],
"Base": v["base"],
"Quote": v["quote"],
"Active": market_is_active(v),
"Spot": "Spot" if exchange.market_is_spot(v) else "",
"Margin": "Margin" if exchange.market_is_margin(v) else "",
"Future": "Future" if exchange.market_is_future(v) else "",
"Leverage": exchange.get_max_leverage(v["symbol"], 20),
"Min Stake": round(
exchange.get_min_pair_stake_amount(
v["symbol"],
safe_value_fallback(tickers.get(v["symbol"], {}), "last", "ask", 0.0),
0.0,
)
or 0.0,
8,
),
}
for _, v in pairs.items()
]
if (
args.get("print_one_column", False)
or args.get("list_pairs_print_json", False)
or args.get("print_csv", False)
):
# Print summary string in the log in case of machine-readable
# regular formats.
logger.info(f"{summary_str}.")
if (
args.get("print_one_column", False)
or args.get("list_pairs_print_json", False)
or args.get("print_csv", False)
):
# Print summary string in the log in case of machine-readable
# regular formats.
logger.info(f"{summary_str}.")
else:
# Print empty string separating leading logs and output in case of
# human-readable formats.
print()
if pairs:
if args.get("print_list", False):
# print data as a list, with human-readable summary
print(f"{summary_str}: {', '.join(pairs.keys())}.")
elif args.get("print_one_column", False):
print("\n".join(pairs.keys()))
elif args.get("list_pairs_print_json", False):
import rapidjson
print(rapidjson.dumps(list(pairs.keys()), default=str))
elif args.get("print_csv", False):
writer = csv.DictWriter(sys.stdout, fieldnames=headers)
writer.writeheader()
writer.writerows(tabular_data)
else:
# Print empty string separating leading logs and output in case of
# human-readable formats.
print()
if pairs:
if args.get("print_list", False):
# print data as a list, with human-readable summary
print(f"{summary_str}: {', '.join(pairs.keys())}.")
elif args.get("print_one_column", False):
print("\n".join(pairs.keys()))
elif args.get("list_pairs_print_json", False):
import rapidjson
print(rapidjson.dumps(list(pairs.keys()), default=str))
elif args.get("print_csv", False):
writer = csv.DictWriter(sys.stdout, fieldnames=headers)
writer.writeheader()
writer.writerows(tabular_data)
else:
print_rich_table(tabular_data, headers, summary_str)
elif not (
args.get("print_one_column", False)
or args.get("list_pairs_print_json", False)
or args.get("print_csv", False)
):
print(f"{summary_str}.")
print_rich_table(tabular_data, headers, summary_str)
elif not (
args.get("print_one_column", False)
or args.get("list_pairs_print_json", False)
or args.get("print_csv", False)
):
print(f"{summary_str}.")
def start_show_trades(args: dict[str, Any]) -> None:
+87 -9
View File
@@ -425,6 +425,10 @@ CONF_SCHEMA = {
"description": "Edge configuration.",
"$ref": "#/definitions/edge",
},
"log_config": {
"description": "Logging configuration.",
"$ref": "#/definitions/logging",
},
"freqai": {
"description": "FreqAI configuration.",
"$ref": "#/definitions/freqai",
@@ -471,6 +475,12 @@ CONF_SCHEMA = {
"description": "Telegram topic ID - only applicable for group chats",
"type": "string",
},
"authorized_users": {
"description": "Authorized users for the bot.",
"type": "array",
"items": {"type": "string"},
"uniqueItems": True,
},
"allow_custom_messages": {
"description": "Allow sending custom messages from the Strategy.",
"type": "boolean",
@@ -877,6 +887,28 @@ CONF_SCHEMA = {
},
"required": ["process_throttle_secs", "allowed_risk"],
},
"logging": {
"type": "object",
"properties": {
"version": {"type": "number", "const": 1},
"formatters": {
"type": "object",
# In theory the below, but can be more flexible
# based on logging.config documentation
# "additionalProperties": {
# "type": "object",
# "properties": {
# "format": {"type": "string"},
# "datefmt": {"type": "string"},
# },
# "required": ["format"],
# },
},
"handlers": {"type": "object"},
"root": {"type": "object"},
},
"required": ["version", "formatters", "handlers", "root"],
},
"external_message_consumer": {
"description": "Configuration for external message consumer.",
"type": "object",
@@ -965,10 +997,13 @@ CONF_SCHEMA = {
"type": "boolean",
"default": False,
},
"keras": {
"description": "Use Keras for model training.",
"type": "boolean",
"default": False,
"identifier": {
"description": (
"A unique ID for the current model. "
"Must be changed when modifying features."
),
"type": "string",
"default": "example",
},
"write_metrics_to_disk": {
"description": "Write metrics to disk?",
@@ -1000,13 +1035,42 @@ CONF_SCHEMA = {
"type": "number",
"default": 7,
},
"identifier": {
"live_retrain_hours": {
"description": "Frequency of retraining during dry/live runs.",
"type": "number",
"default": 0,
},
"expiration_hours": {
"description": (
"A unique ID for the current model. "
"Must be changed when modifying features."
"Avoid making predictions if a model is more than `expiration_hours` "
"old. Defaults to 0 (no expiration)."
),
"type": "string",
"default": "example",
"type": "number",
"default": 0,
},
"save_backtest_models": {
"description": "Save models to disk when running backtesting.",
"type": "boolean",
"default": False,
},
"fit_live_predictions_candles": {
"description": (
"Number of historical candles to use for computing target (label) "
"statistics from prediction data, instead of from the training dataset."
),
"type": "integer",
},
"data_kitchen_thread_count": {
"description": (
"Designate the number of threads you want to use for data processing "
"(outlier methods, normalization, etc.)."
),
"type": "integer",
},
"activate_tensorboard": {
"description": "Indicate whether or not to activate tensorboard",
"type": "boolean",
"default": True,
},
"wait_for_training_iteration_on_reload": {
"description": (
@@ -1015,6 +1079,20 @@ CONF_SCHEMA = {
"type": "boolean",
"default": True,
},
"continual_learning": {
"description": (
"Use the final state of the most recently trained model "
"as starting point for the new model, allowing for "
"incremental learning."
),
"type": "boolean",
"default": False,
},
"keras": {
"description": "Use Keras for model training.",
"type": "boolean",
"default": False,
},
"feature_parameters": {
"description": "The parameters used to engineer the feature set",
"type": "object",
+1 -1
View File
@@ -59,7 +59,7 @@ AVAILABLE_PAIRLISTS = [
"VolatilityFilter",
]
AVAILABLE_DATAHANDLERS = ["json", "jsongz", "feather", "parquet"]
BACKTEST_BREAKDOWNS = ["day", "week", "month"]
BACKTEST_BREAKDOWNS = ["day", "week", "month", "year"]
BACKTEST_CACHE_AGE = ["none", "day", "week", "month"]
BACKTEST_CACHE_DEFAULT = "day"
DRY_RUN_WALLET = 1000
+7 -6
View File
@@ -49,7 +49,7 @@ class DataProvider:
self._pairlists = pairlists
self.__rpc = rpc
self.__cached_pairs: dict[PairWithTimeframe, tuple[DataFrame, datetime]] = {}
self.__slice_index: int | None = None
self.__slice_index: dict[str, int] = {}
self.__slice_date: datetime | None = None
self.__cached_pairs_backtesting: dict[PairWithTimeframe, DataFrame] = {}
@@ -69,13 +69,13 @@ class DataProvider:
self.producers = self._config.get("external_message_consumer", {}).get("producers", [])
self.external_data_enabled = len(self.producers) > 0
def _set_dataframe_max_index(self, limit_index: int):
def _set_dataframe_max_index(self, pair: str, limit_index: int):
"""
Limit analyzed dataframe to max specified index.
Only relevant in backtesting.
:param limit_index: dataframe index.
"""
self.__slice_index = limit_index
self.__slice_index[pair] = limit_index
def _set_dataframe_max_date(self, limit_date: datetime):
"""
@@ -393,9 +393,10 @@ class DataProvider:
df, date = self.__cached_pairs[pair_key]
else:
df, date = self.__cached_pairs[pair_key]
if self.__slice_index is not None:
max_index = self.__slice_index
if (max_index := self.__slice_index.get(pair)) is not None:
df = df.iloc[max(0, max_index - MAX_DATAFRAME_CANDLES) : max_index]
else:
return (DataFrame(), datetime.fromtimestamp(0, tz=timezone.utc))
return df, date
else:
return (DataFrame(), datetime.fromtimestamp(0, tz=timezone.utc))
@@ -430,7 +431,7 @@ class DataProvider:
# Don't reset backtesting pairs -
# otherwise they're reloaded each time during hyperopt due to with analyze_per_epoch
# self.__cached_pairs_backtesting = {}
self.__slice_index = 0
self.__slice_index = {}
# Exchange functions
+1 -2
View File
@@ -18,8 +18,7 @@ from freqtrade.enums import CandleType, TradingMode
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import Exchange
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
from freqtrade.util import dt_now, dt_ts, format_ms_time
from freqtrade.util.datetime_helpers import format_ms_time_det
from freqtrade.util import dt_now, dt_ts, format_ms_time, format_ms_time_det
from freqtrade.util.migrations import migrate_data
from freqtrade.util.progress_tracker import CustomProgress, retrieve_progress_tracker
+29
View File
@@ -375,3 +375,32 @@ def calculate_calmar(
# print(expected_returns_mean, max_drawdown, calmar_ratio)
return calmar_ratio
def calculate_sqn(trades: pd.DataFrame, starting_balance: float) -> float:
"""
Calculate System Quality Number (SQN) - Van K. Tharp.
SQN measures systematic trading quality and takes into account both
the number of trades and their standard deviation.
:param trades: DataFrame containing trades (requires column profit_abs)
:param starting_balance: Starting balance of the trading system
:return: SQN value
"""
if len(trades) == 0:
return 0.0
total_profit = trades["profit_abs"] / starting_balance
number_of_trades = len(trades)
# Calculate average trade and standard deviation
average_profits = total_profit.mean()
profits_std = total_profit.std()
if profits_std != 0 and not np.isnan(profits_std):
sqn = math.sqrt(number_of_trades) * (average_profits / profits_std)
else:
# Define negative SQN to indicate this is NOT optimal
sqn = -100.0
return round(sqn, 4)
-1
View File
@@ -10,7 +10,6 @@ from freqtrade.exchange.bitmart import Bitmart
from freqtrade.exchange.bitpanda import Bitpanda
from freqtrade.exchange.bitvavo import Bitvavo
from freqtrade.exchange.bybit import Bybit
from freqtrade.exchange.coinbasepro import Coinbasepro
from freqtrade.exchange.cryptocom import Cryptocom
from freqtrade.exchange.exchange_utils import (
ROUND_DOWN,
+67 -18
View File
@@ -11,7 +11,11 @@ from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS
from freqtrade.enums import CandleType, MarginMode, PriceType, TradingMode
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
from freqtrade.exchange import Exchange
from freqtrade.exchange.binance_public_data import concat_safe, download_archive_ohlcv
from freqtrade.exchange.binance_public_data import (
concat_safe,
download_archive_ohlcv,
download_archive_trades,
)
from freqtrade.exchange.common import retrier
from freqtrade.exchange.exchange_types import FtHas, Tickers
from freqtrade.exchange.exchange_utils_timeframe import timeframe_to_msecs
@@ -29,7 +33,6 @@ class Binance(Exchange):
"stop_price_prop": "stopPrice",
"stoploss_order_types": {"limit": "stop_loss_limit"},
"order_time_in_force": ["GTC", "FOK", "IOC", "PO"],
"ohlcv_candle_limit": 1000,
"trades_pagination": "id",
"trades_pagination_arg": "fromId",
"trades_has_history": True,
@@ -37,6 +40,7 @@ class Binance(Exchange):
"ws_enabled": True,
}
_ft_has_futures: FtHas = {
"funding_fee_candle_limit": 1000,
"stoploss_order_types": {"limit": "stop", "market": "stop_market"},
"order_time_in_force": ["GTC", "FOK", "IOC"],
"tickers_have_price": False,
@@ -140,9 +144,10 @@ class Binance(Exchange):
:param candle_type: Any of the enum CandleType (must match trading mode!)
"""
if is_new_pair:
x = self.loop.run_until_complete(
self._async_get_candle_history(pair, timeframe, candle_type, 0)
)
with self._loop_lock:
x = self.loop.run_until_complete(
self._async_get_candle_history(pair, timeframe, candle_type, 0)
)
if x and x[3] and x[3][0] and x[3][0][0] > since_ms:
# Set starting date to first available candle.
since_ms = x[3][0][0]
@@ -201,16 +206,17 @@ class Binance(Exchange):
"""
Fastly fetch OHLCV data by leveraging https://data.binance.vision.
"""
df = self.loop.run_until_complete(
download_archive_ohlcv(
candle_type=candle_type,
pair=pair,
timeframe=timeframe,
since_ms=since_ms,
until_ms=until_ms,
markets=self.markets,
with self._loop_lock:
df = self.loop.run_until_complete(
download_archive_ohlcv(
candle_type=candle_type,
pair=pair,
timeframe=timeframe,
since_ms=since_ms,
until_ms=until_ms,
markets=self.markets,
)
)
)
# download the remaining data from rest API
if df.empty:
@@ -268,12 +274,12 @@ class Binance(Exchange):
def dry_run_liquidation_price(
self,
pair: str,
open_rate: float, # Entry price of position
open_rate: float,
is_short: bool,
amount: float,
stake_amount: float,
leverage: float,
wallet_balance: float, # Or margin balance
wallet_balance: float,
open_trades: list,
) -> float | None:
"""
@@ -287,8 +293,6 @@ class Binance(Exchange):
:param amount: Absolute value of position size incl. leverage (in base currency)
:param stake_amount: Stake amount - Collateral in settle currency.
:param leverage: Leverage used for this position.
:param trading_mode: SPOT, MARGIN, FUTURES, etc.
:param margin_mode: Either ISOLATED or CROSS
:param wallet_balance: Amount of margin_mode in the wallet being used to trade
Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance
@@ -377,3 +381,48 @@ class Binance(Exchange):
if not t:
return [], "0"
return t, from_id
async def _async_get_trade_history_id(
self, pair: str, until: int, since: int, from_id: str | None = None
) -> tuple[str, list[list]]:
logger.info(f"Fetching trades from Binance, {from_id=}, {since=}, {until=}")
if not self._config["exchange"].get("only_from_ccxt", False):
if from_id is None or not since:
trades = await self._api_async.fetch_trades(
pair,
params={
self._trades_pagination_arg: "0",
},
limit=5,
)
listing_date: int = trades[0]["timestamp"]
since = max(since, listing_date)
_, res = await download_archive_trades(
CandleType.SPOT,
pair,
since_ms=since,
until_ms=until,
markets=self.markets,
)
if not res:
end_time = since
end_id = from_id
else:
end_time = res[-1][0]
end_id = res[-1][1]
if end_time and end_time >= until:
return pair, res
else:
_, res2 = await super()._async_get_trade_history_id(
pair, until=until, since=end_time, from_id=end_id
)
res.extend(res2)
return pair, res
return await super()._async_get_trade_history_id(
pair, until=until, since=since, from_id=from_id
)
File diff suppressed because it is too large Load Diff
+224 -3
View File
@@ -1,5 +1,6 @@
"""
Fetch daily-archived OHLCV data from https://data.binance.vision/
Documentation can be found in https://github.com/binance/binance-public-data
"""
import asyncio
@@ -10,9 +11,11 @@ from io import BytesIO
from typing import Any
import aiohttp
import numpy as np
import pandas as pd
from pandas import DataFrame
from freqtrade.constants import DEFAULT_TRADES_COLUMNS
from freqtrade.enums import CandleType
from freqtrade.misc import chunks
from freqtrade.util.datetime_helpers import dt_from_ts, dt_now
@@ -157,8 +160,8 @@ async def _download_archive_ohlcv(
return concat_safe(dfs)
else:
dfs.append(None)
except BaseException as e:
logger.warning(f"An exception raised: : {e}")
except Exception as e:
logger.warning(f"An exception raised: {e}")
# Directly return the existing data, do not allow the gap within the data
await cancel_and_await_tasks(tasks[tasks.index(task) + 1 :])
return concat_safe(dfs)
@@ -212,6 +215,20 @@ def binance_vision_ohlcv_zip_url(
return url
def binance_vision_trades_zip_url(symbol: str, candle_type: CandleType, date: date) -> str:
"""
example urls:
https://data.binance.vision/data/spot/daily/aggTrades/BTCUSDT/BTCUSDT-aggTrades-2023-10-27.zip
https://data.binance.vision/data/futures/um/daily/aggTrades/BTCUSDT/BTCUSDT-aggTrades-2023-10-27.zip
"""
asset_type_url_segment = candle_type_to_url_segment(candle_type)
url = (
f"https://data.binance.vision/data/{asset_type_url_segment}/daily/aggTrades/{symbol}"
f"/{symbol}-aggTrades-{date.strftime('%Y-%m-%d')}.zip"
)
return url
async def get_daily_ohlcv(
symbol: str,
timeframe: str,
@@ -268,7 +285,11 @@ async def get_daily_ohlcv(
names=["date", "open", "high", "low", "close", "volume"],
header=header,
)
df["date"] = pd.to_datetime(df["date"], unit="ms", utc=True)
df["date"] = pd.to_datetime(
np.where(df["date"] > 1e13, df["date"] // 1000, df["date"]),
unit="ms",
utc=True,
)
return df
elif resp.status == 404:
logger.debug(f"Failed to download {url}")
@@ -280,3 +301,203 @@ async def get_daily_ohlcv(
if isinstance(e, Http404) or retry > retry_count:
logger.debug(f"Failed to get data from {url}: {e}")
raise
async def download_archive_trades(
candle_type: CandleType,
pair: str,
*,
since_ms: int,
until_ms: int | None,
markets: dict[str, Any],
stop_on_404: bool = True,
) -> tuple[str, list[list]]:
try:
symbol = markets[pair]["id"]
last_available_date = dt_now() - timedelta(days=2)
start = dt_from_ts(since_ms)
end = dt_from_ts(until_ms) if until_ms else dt_now()
end = min(end, last_available_date)
if start >= end:
return pair, []
result_list = await _download_archive_trades(
symbol, pair, candle_type, start, end, stop_on_404
)
return pair, result_list
except Exception as e:
logger.warning(
"An exception occurred during fast trades download from Binance, falling back to "
"the slower REST API, this can take a lot more time.",
exc_info=e,
)
return pair, []
def parse_trades_from_zip(csvf):
# https://github.com/binance/binance-public-data/issues/283
first_byte = csvf.read(1)[0]
if chr(first_byte).isdigit():
# spot
header = None
names = [
"id",
"price",
"amount",
"first_trade_id",
"last_trade_id",
"timestamp",
"is_buyer_maker",
"is_best_match",
]
else:
# futures
header = 0
names = [
"id",
"price",
"amount",
"first_trade_id",
"last_trade_id",
"timestamp",
"is_buyer_maker",
]
csvf.seek(0)
df = pd.read_csv(
csvf,
names=names,
header=header,
)
df.loc[:, "cost"] = df["price"] * df["amount"]
# Side is reversed intentionally
# based on ccxt parseTrade logic.
df.loc[:, "side"] = np.where(df["is_buyer_maker"], "sell", "buy")
df.loc[:, "type"] = None
# Convert timestamp to ms
df.loc[:, "timestamp"] = np.where(
df["timestamp"] > 1e13,
df["timestamp"] // 1000,
df["timestamp"],
)
return df.loc[:, DEFAULT_TRADES_COLUMNS].to_records(index=False).tolist()
async def get_daily_trades(
symbol: str,
candle_type: CandleType,
date: date,
session: aiohttp.ClientSession,
retry_count: int = 3,
retry_delay: float = 0.0,
) -> list[list]:
"""
Get daily OHLCV from https://data.binance.vision
See https://github.com/binance/binance-public-data
:symbol: binance symbol name, e.g. BTCUSDT
:candle_type: SPOT or FUTURES
:date: the returned DataFrame will cover the entire day of `date` in UTC
:session: an aiohttp.ClientSession instance
:retry_count: times to retry before returning the exceptions
:retry_delay: the time to wait before every retry
:return: a list containing trades in DEFAULT_TRADES_COLUMNS format
"""
url = binance_vision_trades_zip_url(symbol, candle_type, date)
logger.debug(f"download trades data from binance: {url}")
retry = 0
while True:
if retry > 0:
sleep_secs = retry * retry_delay
logger.debug(
f"[{retry}/{retry_count}] retry to download {url} after {sleep_secs} seconds"
)
await asyncio.sleep(sleep_secs)
try:
async with session.get(url) as resp:
if resp.status == 200:
content = await resp.read()
logger.debug(f"Successfully downloaded {url}")
with zipfile.ZipFile(BytesIO(content)) as zipf:
with zipf.open(zipf.namelist()[0]) as csvf:
return parse_trades_from_zip(csvf)
elif resp.status == 404:
logger.debug(f"Failed to download {url}")
raise Http404(f"404: {url}", date, url)
else:
raise BadHttpStatus(f"{resp.status} - {resp.reason}")
except Exception as e:
logger.info("download Daily_trades raised: %s", e)
retry += 1
if isinstance(e, Http404) or retry > retry_count:
logger.debug(f"Failed to get data from {url}: {e}")
raise
async def _download_archive_trades(
symbol: str,
pair: str,
candle_type: CandleType,
start: date,
end: date,
stop_on_404: bool,
) -> list[list]:
# daily dataframes, `None` indicates missing data in that day (when `stop_on_404` is False)
results: list[list] = []
# the current day being processing, starting at 1.
current_day = 0
connector = aiohttp.TCPConnector(limit=100)
async with aiohttp.ClientSession(connector=connector, trust_env=True) as session:
# the HTTP connections has been throttled by TCPConnector
for dates in chunks(list(date_range(start, end)), 30):
tasks = [
asyncio.create_task(get_daily_trades(symbol, candle_type, date, session))
for date in dates
]
for task in tasks:
current_day += 1
try:
result = await task
except Http404 as e:
if stop_on_404:
logger.debug(f"Failed to download {e.url} due to 404.")
# A 404 error on the first day indicates missing data
# on https://data.binance.vision, we provide the warning and the advice.
# https://github.com/freqtrade/freqtrade/blob/acc53065e5fa7ab5197073276306dc9dc3adbfa3/tests/exchange_online/test_binance_compare_ohlcv.py#L7
if current_day == 1:
logger.warning(
f"Fast download is unavailable due to missing data: "
f"{e.url}. Falling back to the slower REST API, "
"which may take more time."
)
if pair in ["BTC/USDT:USDT", "ETH/USDT:USDT", "BCH/USDT:USDT"]:
logger.warning(
f"To avoid the delay, you can first download {pair} using "
"`--timerange <start date>-20200101`, and then download the "
"remaining data with `--timerange 20200101-<end date>`."
)
else:
logger.warning(
f"Binance fast download for {pair} stopped at {e.date} due to "
f"missing data: {e.url}, falling back to rest API for the "
"remaining data, this can take more time."
)
await cancel_and_await_tasks(tasks[tasks.index(task) + 1 :])
return results
except Exception as e:
logger.warning(f"An exception raised: {e}")
# Directly return the existing data, do not allow the gap within the data
await cancel_and_await_tasks(tasks[tasks.index(task) + 1 :])
return results
else:
# Happy case
results.extend(result)
return results
+11 -10
View File
@@ -31,7 +31,6 @@ class Bybit(Exchange):
unified_account = False
_ft_has: FtHas = {
"ohlcv_candle_limit": 1000,
"ohlcv_has_history": True,
"order_time_in_force": ["GTC", "FOK", "IOC", "PO"],
"ws_enabled": True,
@@ -167,15 +166,16 @@ class Bybit(Exchange):
PERPETUAL:
bybit:
https://www.bybithelp.com/HelpCenterKnowledge/bybitHC_Article?language=en_US&id=000001067
https://www.bybit.com/en/help-center/article/Liquidation-Price-Calculation-under-Isolated-Mode-Unified-Trading-Account#b
Long:
Liquidation Price = (
Entry Price * (1 - Initial Margin Rate + Maintenance Margin Rate)
- Extra Margin Added/ Contract)
Entry Price - [(Initial Margin - Maintenance Margin)/Contract Quantity]
- (Extra Margin Added/Contract Quantity))
Short:
Liquidation Price = (
Entry Price * (1 + Initial Margin Rate - Maintenance Margin Rate)
+ Extra Margin Added/ Contract)
Entry Price + [(Initial Margin - Maintenance Margin)/Contract Quantity]
+ (Extra Margin Added/Contract Quantity))
Implementation Note: Extra margin is currently not used.
@@ -185,8 +185,6 @@ class Bybit(Exchange):
:param amount: Absolute value of position size incl. leverage (in base currency)
:param stake_amount: Stake amount - Collateral in settle currency.
:param leverage: Leverage used for this position.
:param trading_mode: SPOT, MARGIN, FUTURES, etc.
:param margin_mode: Either ISOLATED or CROSS
:param wallet_balance: Amount of margin_mode in the wallet being used to trade
Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance
@@ -199,13 +197,16 @@ class Bybit(Exchange):
if self.trading_mode == TradingMode.FUTURES and self.margin_mode == MarginMode.ISOLATED:
if market["inverse"]:
raise OperationalException("Freqtrade does not yet support inverse contracts")
initial_margin_rate = 1 / leverage
position_value = amount * open_rate
initial_margin = position_value / leverage
maintenance_margin = position_value * mm_ratio
margin_diff_per_contract = (initial_margin - maintenance_margin) / amount
# See docstring - ignores extra margin!
if is_short:
return open_rate * (1 + initial_margin_rate - mm_ratio)
return open_rate + margin_diff_per_contract
else:
return open_rate * (1 - initial_margin_rate + mm_ratio)
return open_rate - margin_diff_per_contract
else:
raise OperationalException(
-24
View File
@@ -1,24 +0,0 @@
"""CoinbasePro exchange subclass"""
import logging
from freqtrade.exchange import Exchange
from freqtrade.exchange.exchange_types import FtHas
logger = logging.getLogger(__name__)
class Coinbasepro(Exchange):
"""
CoinbasePro exchange class. Contains adjustments needed for Freqtrade to work
with this exchange.
Please note that this exchange is not included in the list of exchanges
officially supported by the Freqtrade development team. So some features
may still not work as expected.
"""
_ft_has: FtHas = {
"ohlcv_candle_limit": 300,
}
+49 -24
View File
@@ -12,7 +12,7 @@ from copy import deepcopy
from datetime import datetime, timedelta, timezone
from math import floor, isnan
from threading import Lock
from typing import Any, Literal, TypeGuard
from typing import Any, Literal, TypeGuard, TypeVar
import ccxt
import ccxt.pro as ccxt_pro
@@ -113,6 +113,8 @@ from freqtrade.util.periodic_cache import PeriodicCache
logger = logging.getLogger(__name__)
T = TypeVar("T")
class Exchange:
# Parameters to add directly to buy/sell calls (like agreeing to trading agreement)
@@ -131,7 +133,6 @@ class Exchange:
"stoploss_order_types": {},
"order_time_in_force": ["GTC"],
"ohlcv_params": {},
"ohlcv_candle_limit": 500,
"ohlcv_has_history": True, # Some exchanges (Kraken) don't provide history via ohlcv
"ohlcv_partial_candle": True,
"ohlcv_require_since": False,
@@ -276,6 +277,11 @@ class Exchange:
logger.info(f'Using Exchange "{self.name}"')
self.required_candle_call_count = 1
# Converts the interval provided in minutes in config to seconds
self.markets_refresh_interval: int = (
exchange_conf.get("markets_refresh_interval", 60) * 60 * 1000
)
if validate:
# Initial markets load
self.reload_markets(True, load_leverage_tiers=False)
@@ -285,11 +291,6 @@ class Exchange:
self._startup_candle_count, config.get("timeframe", "")
)
# Converts the interval provided in minutes in config to seconds
self.markets_refresh_interval: int = (
exchange_conf.get("markets_refresh_interval", 60) * 60 * 1000
)
if self.trading_mode != TradingMode.SPOT and load_leverage_tiers:
self.fill_leverage_tiers()
self.additional_exchange_init()
@@ -466,7 +467,12 @@ class Exchange:
:return: Candle limit as integer
"""
fallback_val = self._ft_has.get("ohlcv_candle_limit")
ccxt_val = self.features(
"spot" if candle_type == CandleType.SPOT else "futures", "fetchOHLCV", "limit", 500
)
if not isinstance(ccxt_val, float | int):
ccxt_val = 500
fallback_val = self._ft_has.get("ohlcv_candle_limit", ccxt_val)
if candle_type == CandleType.FUNDING_RATE:
fallback_val = self._ft_has.get("funding_fee_candle_limit", fallback_val)
return int(
@@ -642,7 +648,8 @@ class Exchange:
def _load_async_markets(self, reload: bool = False) -> dict[str, Any]:
try:
markets = self.loop.run_until_complete(self._api_reload_markets(reload=reload))
with self._loop_lock:
markets = self.loop.run_until_complete(self._api_reload_markets(reload=reload))
if isinstance(markets, Exception):
raise markets
@@ -887,6 +894,24 @@ class Exchange:
return self._ft_has["exchange_has_overrides"][endpoint]
return endpoint in self._api_async.has and self._api_async.has[endpoint]
def features(
self, market_type: Literal["spot", "futures"], endpoint, attribute, default: T
) -> T:
"""
Returns the exchange features for the given markettype
https://docs.ccxt.com/#/README?id=features
attributes are in a nested dict, with spot and swap.linear
e.g. spot.fetchOHLCV.limit
swap.linear.fetchOHLCV.limit
"""
feat = (
self._api_async.features.get("spot", {})
if market_type == "spot"
else self._api_async.features.get("swap", {}).get("linear", {})
)
return feat.get(endpoint, {}).get(attribute, default)
def get_precision_amount(self, pair: str) -> float | None:
"""
Returns the amount precision of the exchange.
@@ -2318,15 +2343,17 @@ class Exchange:
:param until_ms: Timestamp in milliseconds to get history up to
:return: Dataframe with candle (OHLCV) data
"""
pair, _, _, data, _ = self.loop.run_until_complete(
self._async_get_historic_ohlcv(
pair=pair,
timeframe=timeframe,
since_ms=since_ms,
until_ms=until_ms,
candle_type=candle_type,
with self._loop_lock:
pair, _, _, data, _ = self.loop.run_until_complete(
self._async_get_historic_ohlcv(
pair=pair,
timeframe=timeframe,
since_ms=since_ms,
until_ms=until_ms,
candle_type=candle_type,
raise_=True,
)
)
)
logger.debug(f"Downloaded data for {pair} from ccxt with length {len(data)}.")
return ohlcv_to_dataframe(data, timeframe, pair, fill_missing=False, drop_incomplete=True)
@@ -2365,7 +2392,7 @@ class Exchange:
if isinstance(res, BaseException):
logger.warning(f"Async code raised an exception: {repr(res)}")
if raise_:
raise
raise res
continue
else:
# Deconstruct tuple if it's not an exception
@@ -2414,8 +2441,8 @@ class Exchange:
return self._exchange_ws.get_ohlcv(pair, timeframe, candle_type, candle_ts)
logger.info(
f"Failed to reuse watch {pair}, {timeframe}, {candle_ts < last_refresh_time},"
f" {candle_ts}, {last_refresh_time}, "
f"Couldn't reuse watch for {pair}, {timeframe}, falling back to REST api. "
f"{candle_ts < last_refresh_time}, {candle_ts}, {last_refresh_time}, "
f"{format_ms_time(candle_ts)}, {format_ms_time(last_refresh_time)} "
)
@@ -3661,12 +3688,12 @@ class Exchange:
def dry_run_liquidation_price(
self,
pair: str,
open_rate: float, # Entry price of position
open_rate: float,
is_short: bool,
amount: float,
stake_amount: float,
leverage: float,
wallet_balance: float, # Or margin balance
wallet_balance: float,
open_trades: list,
) -> float | None:
"""
@@ -3687,8 +3714,6 @@ class Exchange:
:param amount: Absolute value of position size incl. leverage (in base currency)
:param stake_amount: Stake amount - Collateral in settle currency.
:param leverage: Leverage used for this position.
:param trading_mode: SPOT, MARGIN, FUTURES, etc.
:param margin_mode: Either ISOLATED or CROSS
:param wallet_balance: Amount of margin_mode in the wallet being used to trade
Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance
+2 -2
View File
@@ -4,7 +4,7 @@ Exchange support utils
import inspect
from datetime import datetime, timedelta, timezone
from math import ceil, floor
from math import ceil, floor, isnan
from typing import Any
import ccxt
@@ -305,7 +305,7 @@ def price_to_precision(
:param rounding_mode: rounding mode to use. Defaults to ROUND
:return: price rounded up to the precision the Exchange accepts
"""
if price_precision is not None and precisionMode is not None:
if price_precision is not None and precisionMode is not None and not isnan(price):
if rounding_mode not in (ROUND_UP, ROUND_DOWN):
# Use CCXT code where possible.
return float(
+23 -1
View File
@@ -9,6 +9,8 @@ import ccxt
from freqtrade.constants import Config, PairWithTimeframe
from freqtrade.enums.candletype import CandleType
from freqtrade.exceptions import TemporaryError
from freqtrade.exchange.common import retrier
from freqtrade.exchange.exchange import timeframe_to_seconds
from freqtrade.exchange.exchange_types import OHLCVResponse
from freqtrade.util import dt_ts, format_ms_time, format_ms_time_det
@@ -82,14 +84,21 @@ class ExchangeWS:
Remove history for a pair/timeframe combination from ccxt cache
"""
self._ccxt_object.ohlcvs.get(paircomb[0], {}).pop(paircomb[1], None)
self.klines_last_refresh.pop(paircomb, None)
@retrier(retries=3)
def ohlcvs(self, pair: str, timeframe: str) -> list[list]:
"""
Returns a copy of the klines for a pair/timeframe combination
Note: this will only contain the data received from the websocket
so the data will build up over time.
"""
return deepcopy(self._ccxt_object.ohlcvs.get(pair, {}).get(timeframe, []))
try:
return deepcopy(self._ccxt_object.ohlcvs.get(pair, {}).get(timeframe, []))
except RuntimeError as e:
# Capture runtime errors and retry
# TemporaryError does not cause backoff - so we're essentially retrying immediately
raise TemporaryError(f"Error deepcopying: {e}") from e
def cleanup_expired(self) -> None:
"""
@@ -130,6 +139,15 @@ class ExchangeWS:
)
)
async def _unwatch_ohlcv(self, pair: str, timeframe: str, candle_type: CandleType) -> None:
try:
await self._ccxt_object.un_watch_ohlcv_for_symbols([[pair, timeframe]])
except ccxt.NotSupported as e:
logger.debug("un_watch_ohlcv_for_symbols not supported: %s", e)
pass
except Exception:
logger.exception("Exception in _unwatch_ohlcv")
def _continuous_stopped(
self, task: asyncio.Task, pair: str, timeframe: str, candle_type: CandleType
):
@@ -142,6 +160,10 @@ class ExchangeWS:
result = str(result1)
logger.info(f"{pair}, {timeframe}, {candle_type} - Task finished - {result}")
asyncio.run_coroutine_threadsafe(
self._unwatch_ohlcv(pair, timeframe, candle_type), loop=self._loop
)
self._klines_scheduled.discard((pair, timeframe, candle_type))
self._pop_history((pair, timeframe, candle_type))
-1
View File
@@ -30,7 +30,6 @@ class Gate(Exchange):
unified_account = False
_ft_has: FtHas = {
"ohlcv_candle_limit": 1000,
"order_time_in_force": ["GTC", "IOC"],
"stoploss_on_exchange": True,
"stoploss_order_types": {"limit": "limit"},
-1
View File
@@ -21,7 +21,6 @@ class Htx(Exchange):
"stop_price_param": "stopPrice",
"stop_price_prop": "stopPrice",
"stoploss_order_types": {"limit": "stop-limit"},
"ohlcv_candle_limit": 1000,
"l2_limit_range": [5, 10, 20],
"l2_limit_range_required": False,
"ohlcv_candle_limit_per_timeframe": {
-1
View File
@@ -22,7 +22,6 @@ class Hyperliquid(Exchange):
_ft_has: FtHas = {
"ohlcv_has_history": False,
"ohlcv_candle_limit": 5000,
"l2_limit_range": [20],
"trades_has_history": False,
"tickers_have_bid_ask": False,
-1
View File
@@ -26,7 +26,6 @@ class Kraken(Exchange):
"stop_price_prop": "stopLossPrice",
"stoploss_order_types": {"limit": "limit", "market": "market"},
"order_time_in_force": ["GTC", "IOC", "PO"],
"ohlcv_candle_limit": 720,
"ohlcv_has_history": False,
"trades_pagination": "id",
"trades_pagination_arg": "since",
-1
View File
@@ -28,7 +28,6 @@ class Kucoin(Exchange):
"l2_limit_range": [20, 100],
"l2_limit_range_required": False,
"order_time_in_force": ["GTC", "FOK", "IOC"],
"ohlcv_candle_limit": 1500,
}
def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> dict:
+6 -5
View File
@@ -46,19 +46,20 @@ class BaseEnvironment(gym.Env):
def __init__(
self,
df: DataFrame = DataFrame(),
prices: DataFrame = DataFrame(),
reward_kwargs: dict = {},
*,
df: DataFrame,
prices: DataFrame,
reward_kwargs: dict,
window_size=10,
starting_point=True,
id: str = "baseenv-1", # noqa: A002
seed: int = 1,
config: dict = {},
config: dict,
live: bool = False,
fee: float = 0.0015,
can_short: bool = False,
pair: str = "",
df_raw: DataFrame = DataFrame(),
df_raw: DataFrame,
):
"""
Initializes the training/eval environment.
@@ -488,7 +488,7 @@ def make_env(
seed: int,
train_df: DataFrame,
price: DataFrame,
env_info: dict[str, Any] = {},
env_info: dict[str, Any],
) -> Callable:
"""
Utility function for multiprocessed env.
@@ -6,26 +6,21 @@ from sklearn.utils.validation import has_fit_parameter, validate_data
class FreqaiMultiOutputRegressor(MultiOutputRegressor):
def fit(self, X, y, sample_weight=None, fit_params=None):
"""Fit the model to data, separately for each output variable.
Parameters
----------
X : {array-like, sparse matrix} of shape (n_samples, n_features)
:param X: {array-like, sparse matrix} of shape (n_samples, n_features)
The input data.
y : {array-like, sparse matrix} of shape (n_samples, n_outputs)
:param y: {array-like, sparse matrix} of shape (n_samples, n_outputs)
Multi-output targets. An indicator matrix turns on multilabel
estimation.
sample_weight : array-like of shape (n_samples,), default=None
:param sample_weight: array-like of shape (n_samples,), default=None
Sample weights. If `None`, then samples are equally weighted.
Only supported if the underlying regressor supports sample
weights.
fit_params : A list of dicts for the fit_params
:param fit_params: A list of dicts for the fit_params
Parameters passed to the ``estimator.fit`` method of each step.
Each dict may contain same or different values (e.g. different
eval_sets or init_models)
.. versionadded:: 0.23
Returns
-------
self : object
Returns a fitted instance.
"""
if not hasattr(self.estimator, "fit"):
+6 -4
View File
@@ -33,6 +33,8 @@ LABEL_PIPELINE = "label_pipeline"
TRAINDF = "trained_df"
METADATA = "metadata"
METADATA_NUMBER_MODE = rapidjson.NM_NATIVE | rapidjson.NM_NAN
class pair_info(TypedDict):
model_filename: str
@@ -495,7 +497,7 @@ class FreqaiDataDrawer:
dk.data["label_list"] = dk.label_list
with (save_path / f"{dk.model_filename}_{METADATA}.json").open("w") as fp:
rapidjson.dump(dk.data, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE)
rapidjson.dump(dk.data, fp, default=self.np_encoder, number_mode=METADATA_NUMBER_MODE)
return
@@ -526,7 +528,7 @@ class FreqaiDataDrawer:
dk.data["label_list"] = dk.label_list
# store the metadata
with (save_path / f"{dk.model_filename}_{METADATA}.json").open("w") as fp:
rapidjson.dump(dk.data, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE)
rapidjson.dump(dk.data, fp, default=self.np_encoder, number_mode=METADATA_NUMBER_MODE)
# save the pipelines to pickle files
with (save_path / f"{dk.model_filename}_{FEATURE_PIPELINE}.pkl").open("wb") as fp:
@@ -563,7 +565,7 @@ class FreqaiDataDrawer:
presaved backtesting (prediction file loading).
"""
with (dk.data_path / f"{dk.model_filename}_{METADATA}.json").open("r") as fp:
dk.data = rapidjson.load(fp, number_mode=rapidjson.NM_NATIVE)
dk.data = rapidjson.load(fp, number_mode=METADATA_NUMBER_MODE)
dk.training_features_list = dk.data["training_features_list"]
dk.label_list = dk.data["label_list"]
@@ -587,7 +589,7 @@ class FreqaiDataDrawer:
dk.label_pipeline = self.meta_data_dictionary[coin][LABEL_PIPELINE]
else:
with (dk.data_path / f"{dk.model_filename}_{METADATA}.json").open("r") as fp:
dk.data = rapidjson.load(fp, number_mode=rapidjson.NM_NATIVE)
dk.data = rapidjson.load(fp, number_mode=METADATA_NUMBER_MODE)
with (dk.data_path / f"{dk.model_filename}_{FEATURE_PIPELINE}.pkl").open("rb") as fp:
dk.feature_pipeline = cloudpickle.load(fp)
+12 -8
View File
@@ -214,7 +214,7 @@ class FreqaiDataKitchen:
self,
unfiltered_df: DataFrame,
training_feature_list: list,
label_list: list = list(),
label_list: list | None = None,
training_filter: bool = True,
) -> tuple[DataFrame, DataFrame]:
"""
@@ -244,7 +244,7 @@ class FreqaiDataKitchen:
# we don't care about total row number (total no. datapoints) in training, we only care
# about removing any row with NaNs
# if labels has multiple columns (user wants to train multiple modelEs), we detect here
labels = unfiltered_df.filter(label_list, axis=1)
labels = unfiltered_df.filter(label_list or [], axis=1)
drop_index_labels = pd.isnull(labels).any(axis=1)
drop_index_labels = (
drop_index_labels.replace(True, 1).replace(False, 0).infer_objects(copy=False)
@@ -654,8 +654,8 @@ class FreqaiDataKitchen:
pair: str,
tf: str,
strategy: IStrategy,
corr_dataframes: dict = {},
base_dataframes: dict = {},
corr_dataframes: dict,
base_dataframes: dict,
is_corr_pairs: bool = False,
) -> DataFrame:
"""
@@ -773,10 +773,10 @@ class FreqaiDataKitchen:
def use_strategy_to_populate_indicators( # noqa: C901
self,
strategy: IStrategy,
corr_dataframes: dict = {},
base_dataframes: dict = {},
corr_dataframes: dict[str, DataFrame] | None = None,
base_dataframes: dict[str, dict[str, DataFrame]] | None = None,
pair: str = "",
prediction_dataframe: DataFrame = pd.DataFrame(),
prediction_dataframe: DataFrame | None = None,
do_corr_pairs: bool = True,
) -> DataFrame:
"""
@@ -793,6 +793,10 @@ class FreqaiDataKitchen:
:return:
dataframe: DataFrame = dataframe containing populated indicators
"""
if not corr_dataframes:
corr_dataframes = {}
if not base_dataframes:
base_dataframes = {}
# check if the user is using the deprecated populate_any_indicators function
new_version = inspect.getsource(strategy.populate_any_indicators) == (
@@ -822,7 +826,7 @@ class FreqaiDataKitchen:
if tf not in corr_dataframes[p]:
corr_dataframes[p][tf] = pd.DataFrame()
if not prediction_dataframe.empty:
if prediction_dataframe is not None and not prediction_dataframe.empty:
dataframe = prediction_dataframe.copy()
base_dataframes[self.config["timeframe"]] = dataframe.copy()
else:
+1 -1
View File
@@ -618,7 +618,7 @@ class IFreqaiModel(ABC):
)
unfiltered_dataframe = dk.use_strategy_to_populate_indicators(
strategy, corr_dataframes, base_dataframes, pair
strategy, corr_dataframes=corr_dataframes, base_dataframes=base_dataframes, pair=pair
)
trained_timestamp = new_trained_timerange.stopts
@@ -25,7 +25,7 @@ class PyTorchModelTrainer(PyTorchTrainerInterface):
criterion: nn.Module,
device: str,
data_convertor: PyTorchDataConvertor,
model_meta_data: dict[str, Any] = {},
model_meta_data: dict[str, Any] | None = None,
window_size: int = 1,
tb_logger: Any = None,
**kwargs,
@@ -45,6 +45,8 @@ class PyTorchModelTrainer(PyTorchTrainerInterface):
:param n_epochs: The maximum number batches to use for evaluation.
:param batch_size: The size of the batches to use during training.
"""
if model_meta_data is None:
model_meta_data = {}
self.model = model
self.optimizer = optimizer
self.criterion = criterion
+127 -59
View File
@@ -64,7 +64,7 @@ from freqtrade.rpc.rpc_types import (
)
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from freqtrade.util import FtPrecise, MeasureTime, dt_from_ts
from freqtrade.util import FtPrecise, MeasureTime, PeriodicCache, dt_from_ts, dt_now
from freqtrade.util.migrations.binance_mig import migrate_binance_futures_names
from freqtrade.wallets import Wallets
@@ -145,7 +145,11 @@ class FreqtradeBot(LoggingMixin):
else None
)
self.active_pair_whitelist = self._refresh_active_whitelist()
logger.info("Starting initial pairlist refresh")
with MeasureTime(
lambda duration, _: logger.info(f"Initial Pairlist refresh took {duration:.2f}s"), 0
):
self.active_pair_whitelist = self._refresh_active_whitelist()
# Set initial bot state from config
initial_state = self.config.get("initial_state")
@@ -154,6 +158,7 @@ class FreqtradeBot(LoggingMixin):
# Protect exit-logic from forcesell and vice versa
self._exit_lock = Lock()
timeframe_secs = timeframe_to_seconds(self.strategy.timeframe)
self._exit_reason_cache = PeriodicCache(100, ttl=timeframe_secs)
LoggingMixin.__init__(self, logger, timeframe_secs)
self._schedule = Scheduler()
@@ -784,6 +789,7 @@ class FreqtradeBot(LoggingMixin):
return
else:
logger.debug("Max adjustment entries is set to unlimited.")
self.execute_entry(
trade.pair,
stake_amount,
@@ -898,14 +904,14 @@ class FreqtradeBot(LoggingMixin):
msg = (
f"Position adjust: about to create a new order for {pair} with stake_amount: "
f"{stake_amount} for {trade}"
f"{stake_amount} and price: {enter_limit_requested} for {trade}"
if mode == "pos_adjust"
else (
f"Replacing {side} order: about create a new order for {pair} with stake_amount: "
f"{stake_amount} ..."
f"{stake_amount} and price: {enter_limit_requested} ..."
if mode == "replace"
else f"{name} signal found: about create a new trade for {pair} with stake_amount: "
f"{stake_amount} ..."
f"{stake_amount} and price: {enter_limit_requested} ..."
)
)
logger.info(msg)
@@ -1374,6 +1380,15 @@ class FreqtradeBot(LoggingMixin):
for should_exit in exits:
if should_exit.exit_flag:
exit_tag1 = exit_tag if should_exit.exit_type == ExitType.EXIT_SIGNAL else None
if trade.has_open_orders:
if prev_eval := self._exit_reason_cache.get(
f"{trade.pair}_{trade.id}_{exit_tag1 or should_exit.exit_reason}", None
):
logger.debug(
f"Exit reason already seen this candle, first seen at {prev_eval}"
)
continue
logger.info(
f"Exit for {trade.pair} detected. Reason: {should_exit.exit_type}"
f"{f' Tag: {exit_tag1}' if exit_tag1 is not None else ''}"
@@ -1593,27 +1608,29 @@ class FreqtradeBot(LoggingMixin):
self.replace_order(order, open_order, trade)
def handle_cancel_order(
self, order: CcxtOrder, order_obj: Order, trade: Trade, reason: str
) -> None:
self, order: CcxtOrder, order_obj: Order, trade: Trade, reason: str, replacing: bool = False
) -> bool:
"""
Check if current analyzed order timed out and cancel if necessary.
:param order: Order dict grabbed with exchange.fetch_order()
:param order_obj: Order object from the database.
:param trade: Trade object.
:return: None
:return: True if the order was canceled, False otherwise.
"""
if order["side"] == trade.entry_side:
self.handle_cancel_enter(trade, order, order_obj, reason)
return self.handle_cancel_enter(trade, order, order_obj, reason, replacing)
else:
canceled = self.handle_cancel_exit(trade, order, order_obj, reason)
canceled_count = trade.get_canceled_exit_order_count()
max_timeouts = self.config.get("unfilledtimeout", {}).get("exit_timeout_count", 0)
if canceled and max_timeouts > 0 and canceled_count >= max_timeouts:
logger.warning(
f"Emergency exiting trade {trade}, as the exit order "
f"timed out {max_timeouts} times. force selling {order['amount']}."
)
self.emergency_exit(trade, order["price"], order["amount"])
if not replacing:
canceled_count = trade.get_canceled_exit_order_count()
max_timeouts = self.config.get("unfilledtimeout", {}).get("exit_timeout_count", 0)
if canceled and max_timeouts > 0 and canceled_count >= max_timeouts:
logger.warning(
f"Emergency exiting trade {trade}, as the exit order "
f"timed out {max_timeouts} times. force selling {order['amount']}."
)
self.emergency_exit(trade, order["price"], order["amount"])
return canceled
def emergency_exit(
self, trade: Trade, price: float, sub_trade_amt: float | None = None
@@ -1649,9 +1666,9 @@ class FreqtradeBot(LoggingMixin):
def replace_order(self, order: CcxtOrder, order_obj: Order | None, trade: Trade) -> None:
"""
Check if current analyzed entry order should be replaced or simply cancelled.
To simply cancel the existing order(no replacement) adjust_entry_price() should return None
To maintain existing order adjust_entry_price() should return order_obj.price
To replace existing order adjust_entry_price() should return desired price for limit order
To simply cancel the existing order(no replacement) adjust_order_price() should return None
To maintain existing order adjust_order_price() should return order_obj.price
To replace existing order adjust_order_price() should return desired price for limit order
:param order: Order dict grabbed with exchange.fetch_order()
:param order_obj: Order object.
:param trade: Trade object.
@@ -1665,17 +1682,17 @@ class FreqtradeBot(LoggingMixin):
self.strategy.timeframe, latest_candle_open_date
)
# Check if new candle
if (
order_obj
and order_obj.side == trade.entry_side
and latest_candle_close_date > order_obj.order_date_utc
):
if order_obj and latest_candle_close_date > order_obj.order_date_utc:
is_entry = order_obj.side == trade.entry_side
# New candle
proposed_rate = self.exchange.get_rate(
trade.pair, side="entry", is_short=trade.is_short, refresh=True
trade.pair,
side="entry" if is_entry else "exit",
is_short=trade.is_short,
refresh=True,
)
adjusted_entry_price = strategy_safe_wrapper(
self.strategy.adjust_entry_price, default_retval=order_obj.safe_placement_price
adjusted_price = strategy_safe_wrapper(
self.strategy.adjust_order_price, default_retval=order_obj.safe_placement_price
)(
trade=trade,
order=order_obj,
@@ -1685,42 +1702,78 @@ class FreqtradeBot(LoggingMixin):
current_order_rate=order_obj.safe_placement_price,
entry_tag=trade.enter_tag,
side=trade.trade_direction,
is_entry=is_entry,
)
replacing = True
cancel_reason = constants.CANCEL_REASON["REPLACE"]
if not adjusted_entry_price:
if not adjusted_price:
replacing = False
cancel_reason = constants.CANCEL_REASON["USER_CANCEL"]
if order_obj.safe_placement_price != adjusted_entry_price:
# cancel existing order if new price is supplied or None
res = self.handle_cancel_enter(
trade, order, order_obj, cancel_reason, replacing=replacing
if order_obj.safe_placement_price != adjusted_price:
self.handle_replace_order(
order,
order_obj,
trade,
adjusted_price,
is_entry,
cancel_reason,
replacing=replacing,
)
if not res:
self.replace_order_failed(
trade, f"Could not fully cancel order for {trade}, therefore not replacing."
def handle_replace_order(
self,
order: CcxtOrder | None,
order_obj: Order,
trade: Trade,
new_order_price: float | None,
is_entry: bool,
cancel_reason: str,
replacing: bool = False,
) -> None:
"""
Cancel existing order if new price is supplied, and if the cancel is successful,
places a new order with the remaining capital.
"""
if not order:
order = self.exchange.fetch_order(order_obj.order_id, trade.pair)
res = self.handle_cancel_order(order, order_obj, trade, cancel_reason, replacing=replacing)
if not res:
self.replace_order_failed(
trade, f"Could not fully cancel order for {trade}, therefore not replacing."
)
return
if new_order_price:
# place new order only if new price is supplied
try:
if is_entry:
succeeded = self.execute_entry(
pair=trade.pair,
stake_amount=(
order_obj.safe_remaining * order_obj.safe_price / trade.leverage
),
price=new_order_price,
trade=trade,
is_short=trade.is_short,
mode="replace",
)
return
if adjusted_entry_price:
# place new order only if new price is supplied
try:
if not self.execute_entry(
pair=trade.pair,
stake_amount=(
order_obj.safe_remaining * order_obj.safe_price / trade.leverage
),
price=adjusted_entry_price,
trade=trade,
is_short=trade.is_short,
mode="replace",
):
self.replace_order_failed(
trade, f"Could not replace order for {trade}."
)
except DependencyException as exception:
logger.warning(f"Unable to replace order for {trade.pair}: {exception}")
self.replace_order_failed(trade, f"Could not replace order for {trade}.")
else:
succeeded = self.execute_trade_exit(
trade,
new_order_price,
exit_check=ExitCheckTuple(
exit_type=ExitType.CUSTOM_EXIT,
exit_reason=order_obj.ft_order_tag or "order_replaced",
),
ordertype="limit",
sub_trade_amt=order_obj.safe_remaining,
)
if not succeeded:
self.replace_order_failed(trade, f"Could not replace order for {trade}.")
except DependencyException as exception:
logger.warning(f"Unable to replace order for {trade.pair}: {exception}")
self.replace_order_failed(trade, f"Could not replace order for {trade}.")
def cancel_open_orders_of_trade(
self, trade: Trade, sides: list[str], reason: str, replacing: bool = False
@@ -1774,7 +1827,7 @@ class FreqtradeBot(LoggingMixin):
if trade.has_open_orders:
oo = trade.select_order(side, True)
if oo is not None:
if (price == oo.price) and (side == oo.side) and (amount == oo.amount):
if price == oo.price and side == oo.side and amount == oo.amount:
logger.info(
f"A similar open order was found for {trade.pair}. "
f"Keeping existing {trade.exit_side} order. {price=}, {amount=}"
@@ -1870,7 +1923,10 @@ class FreqtradeBot(LoggingMixin):
# to the trade object
self.update_trade_state(trade, order_id, corder)
logger.info(f"Partial {trade.entry_side} order timeout for {trade}.")
logger.info(
f"Partial {trade.entry_side} order timeout for {trade}. Filled: {filled_amount}, "
f"total: {order_obj.ft_amount}"
)
order_obj.ft_cancel_reason += f", {constants.CANCEL_REASON['PARTIALLY_FILLED']}"
self.wallets.update()
@@ -2092,6 +2148,7 @@ class FreqtradeBot(LoggingMixin):
self.handle_insufficient_funds(trade)
return False
self._exit_reason_cache[f"{trade.pair}_{trade.id}_{exit_reason}"] = dt_now()
order_obj = Order.parse_from_ccxt_object(order, trade.pair, trade.exit_side, amount, limit)
order_obj.ft_order_tag = exit_reason
trade.orders.append(order_obj)
@@ -2555,4 +2612,15 @@ class FreqtradeBot(LoggingMixin):
max_custom_price_allowed = proposed_price + (proposed_price * cust_p_max_dist_r)
# Bracket between min_custom_price_allowed and max_custom_price_allowed
return max(min(valid_custom_price, max_custom_price_allowed), min_custom_price_allowed)
final_price = max(
min(valid_custom_price, max_custom_price_allowed), min_custom_price_allowed
)
# Log a warning if the custom price was adjusted by clamping.
if final_price != valid_custom_price:
logger.info(
f"Custom price adjusted from {valid_custom_price} to {final_price} based on "
"custom_price_max_distance_ratio of {cust_p_max_dist_r}."
)
return final_price
+12 -6
View File
@@ -1,4 +1,5 @@
from typing import Any
from copy import deepcopy
from typing import Any, cast
from typing_extensions import TypedDict
@@ -15,11 +16,16 @@ class BacktestResultType(TypedDict):
def get_BacktestResultType_default() -> BacktestResultType:
return {
"metadata": {},
"strategy": {},
"strategy_comparison": [],
}
return cast(
BacktestResultType,
deepcopy(
{
"metadata": {},
"strategy": {},
"strategy_comparison": [],
}
),
)
class BacktestHistoryEntryType(BacktestMetadataType):
+13 -12
View File
@@ -35,19 +35,20 @@ def update_liquidation_prices(
open_trades: list[Trade] = Trade.get_open_trades()
for t in open_trades:
# TODO: This should be done in a batch update
t.set_liquidation_price(
exchange.get_liquidation_price(
pair=t.pair,
open_rate=t.open_rate,
is_short=t.is_short,
amount=t.amount,
stake_amount=t.stake_amount,
leverage=t.leverage,
wallet_balance=total_wallet_stake,
open_trades=open_trades,
if t.has_open_position:
# TODO: This should be done in a batch update
t.set_liquidation_price(
exchange.get_liquidation_price(
pair=t.pair,
open_rate=t.open_rate,
is_short=t.is_short,
amount=t.amount,
stake_amount=t.stake_amount,
leverage=t.leverage,
wallet_balance=total_wallet_stake,
open_trades=open_trades,
)
)
)
elif trade:
trade.set_liquidation_price(
exchange.get_liquidation_price(
+154 -54
View File
@@ -1,15 +1,16 @@
import logging
import logging.config
import os
from copy import deepcopy
from logging import Formatter
from logging.handlers import RotatingFileHandler, SysLogHandler
from pathlib import Path
from rich.console import Console
from typing import Any
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException
from freqtrade.loggers.buffering_handler import FTBufferingHandler
from freqtrade.loggers.ft_rich_handler import FtRichHandler
from freqtrade.loggers.set_log_levels import set_loggers
from freqtrade.loggers.rich_console import get_rich_console
# from freqtrade.loggers.std_err_stream_handler import FTStdErrStreamHandler
@@ -22,7 +23,8 @@ LOGFORMAT = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
bufferHandler = FTBufferingHandler(1000)
bufferHandler.setFormatter(Formatter(LOGFORMAT))
error_console = Console(stderr=True, color_system=None)
error_console = get_rich_console(stderr=True, color_system=None)
def get_existing_handlers(handlertype):
@@ -53,66 +55,140 @@ def setup_logging_pre() -> None:
)
def setup_logging(config: Config) -> None:
"""
Process -v/--verbose, --logfile options
"""
# Log level
verbosity = config["verbosity"]
logging.root.addHandler(bufferHandler)
if config.get("print_colorized", True):
logger.info("Enabling colorized output.")
error_console._color_system = error_console._detect_color_system()
FT_LOGGING_CONFIG = {
"version": 1,
# "incremental": True,
# "disable_existing_loggers": False,
"formatters": {
"basic": {"format": "%(message)s"},
"standard": {
"format": LOGFORMAT,
},
},
"handlers": {
"console": {
"class": "freqtrade.loggers.ft_rich_handler.FtRichHandler",
"formatter": "basic",
},
},
"root": {
"handlers": [
"console",
# "file",
],
"level": "INFO",
},
}
logfile = config.get("logfile")
if logfile:
def _set_log_levels(
log_config: dict[str, Any], verbosity: int = 0, api_verbosity: str = "info"
) -> None:
"""
Set the logging level for the different loggers
"""
if "loggers" not in log_config:
log_config["loggers"] = {}
# Set default levels for third party libraries
third_party_loggers = {
"freqtrade": logging.INFO if verbosity <= 1 else logging.DEBUG,
"requests": logging.INFO if verbosity <= 1 else logging.DEBUG,
"urllib3": logging.INFO if verbosity <= 1 else logging.DEBUG,
"httpcore": logging.INFO if verbosity <= 1 else logging.DEBUG,
"ccxt.base.exchange": logging.INFO if verbosity <= 2 else logging.DEBUG,
"telegram": logging.INFO,
"httpx": logging.WARNING,
"werkzeug": logging.ERROR if api_verbosity == "error" else logging.INFO,
}
# Add third party loggers to the configuration
for logger_name, level in third_party_loggers.items():
if logger_name not in log_config["loggers"]:
log_config["loggers"][logger_name] = {
"level": logging.getLevelName(level),
"propagate": True,
}
def _add_root_handler(log_config: dict[str, Any], handler_name: str):
if handler_name not in log_config["root"]["handlers"]:
log_config["root"]["handlers"].append(handler_name)
def _add_formatter(log_config: dict[str, Any], format_name: str, format_: str):
if format_name not in log_config["formatters"]:
log_config["formatters"][format_name] = {"format": format_}
def _create_log_config(config: Config) -> dict[str, Any]:
# Get log_config from user config or use default
log_config = config.get("log_config", deepcopy(FT_LOGGING_CONFIG))
if logfile := config.get("logfile"):
s = logfile.split(":")
if s[0] == "syslog":
# Address can be either a string (socket filename) for Unix domain socket or
# a tuple (hostname, port) for UDP socket.
# Address can be omitted (i.e. simple 'syslog' used as the value of
# config['logfilename']), which defaults to '/dev/log', applicable for most
# of the systems.
address = (s[1], int(s[2])) if len(s) > 2 else s[1] if len(s) > 1 else "/dev/log"
handler_sl = get_existing_handlers(SysLogHandler)
if handler_sl:
logging.root.removeHandler(handler_sl)
handler_sl = SysLogHandler(address=address)
# No datetime field for logging into syslog, to allow syslog
# to perform reduction of repeating messages if this is set in the
# syslog config. The messages should be equal for this.
handler_sl.setFormatter(Formatter("%(name)s - %(levelname)s - %(message)s"))
logging.root.addHandler(handler_sl)
logger.warning(
"DEPRECATED: Configuring syslog logging via command line is deprecated."
"Please use the log_config option in the configuration file instead."
)
# Add syslog handler to the config
log_config["handlers"]["syslog"] = {
"class": "logging.handlers.SysLogHandler",
"formatter": "syslog_format",
"address": (s[1], int(s[2])) if len(s) > 2 else s[1] if len(s) > 1 else "/dev/log",
}
_add_formatter(log_config, "syslog_format", "%(name)s - %(levelname)s - %(message)s")
_add_root_handler(log_config, "syslog")
elif s[0] == "journald": # pragma: no cover
# Check if we have the module available
logger.warning(
"DEPRECATED: Configuring Journald logging via command line is deprecated."
"Please use the log_config option in the configuration file instead."
)
try:
from cysystemd.journal import JournaldLogHandler
from cysystemd.journal import JournaldLogHandler # noqa: F401
except ImportError:
raise OperationalException(
"You need the cysystemd python package be installed in "
"order to use logging to journald."
)
handler_jd = get_existing_handlers(JournaldLogHandler)
if handler_jd:
logging.root.removeHandler(handler_jd)
handler_jd = JournaldLogHandler()
# No datetime field for logging into journald, to allow syslog
# to perform reduction of repeating messages if this is set in the
# syslog config. The messages should be equal for this.
handler_jd.setFormatter(Formatter("%(name)s - %(levelname)s - %(message)s"))
logging.root.addHandler(handler_jd)
# Add journald handler to the config
log_config["handlers"]["journald"] = {
"class": "cysystemd.journal.JournaldLogHandler",
"formatter": "journald_format",
}
_add_formatter(log_config, "journald_format", "%(name)s - %(levelname)s - %(message)s")
_add_root_handler(log_config, "journald")
else:
handler_rf = get_existing_handlers(RotatingFileHandler)
if handler_rf:
logging.root.removeHandler(handler_rf)
# Regular file logging
# Update existing file handler configuration
if "file" in log_config["handlers"]:
log_config["handlers"]["file"]["filename"] = logfile
else:
log_config["handlers"]["file"] = {
"class": "logging.handlers.RotatingFileHandler",
"formatter": "standard",
"filename": logfile,
"maxBytes": 1024 * 1024 * 10, # 10Mb
"backupCount": 10,
}
_add_root_handler(log_config, "file")
# Dynamically update some handlers
for handler_config in log_config.get("handlers", {}).values():
if handler_config.get("class") == "freqtrade.loggers.ft_rich_handler.FtRichHandler":
handler_config["console"] = error_console
elif handler_config.get("class") == "logging.handlers.RotatingFileHandler":
logfile_path = Path(handler_config["filename"])
try:
logfile_path = Path(logfile)
# Create parent for filehandler
logfile_path.parent.mkdir(parents=True, exist_ok=True)
handler_rf = RotatingFileHandler(
logfile_path,
maxBytes=1024 * 1024 * 10, # 10Mb
backupCount=10,
)
except PermissionError:
raise OperationalException(
f'Failed to create or access log file "{logfile_path.absolute()}". '
@@ -122,10 +198,34 @@ def setup_logging(config: Config) -> None:
"non-root user, delete and recreate the directories you need, and then try "
"again."
)
handler_rf.setFormatter(Formatter(LOGFORMAT))
logging.root.addHandler(handler_rf)
return log_config
def setup_logging(config: Config) -> None:
"""
Process -v/--verbose, --logfile options
"""
verbosity = config["verbosity"]
if os.environ.get("PYTEST_VERSION") is None or config.get("ft_tests_force_logging"):
log_config = _create_log_config(config)
_set_log_levels(
log_config, verbosity, config.get("api_server", {}).get("verbosity", "info")
)
logging.config.dictConfig(log_config)
# Add buffer handler to root logger
if bufferHandler not in logging.root.handlers:
logging.root.addHandler(bufferHandler)
# Set color system for console output
if config.get("print_colorized", True):
logger.info("Enabling colorized output.")
error_console._color_system = error_console._detect_color_system()
logging.info("Logfile configured")
# Set verbosity levels
logging.root.setLevel(logging.INFO if verbosity < 1 else logging.DEBUG)
set_loggers(verbosity, config.get("api_server", {}).get("verbosity", "info"))
logger.info("Verbosity set to %s", verbosity)
+74
View File
@@ -0,0 +1,74 @@
import json
import logging
class JsonFormatter(logging.Formatter):
"""
Formatter that outputs JSON strings after parsing the LogRecord.
@param dict fmt_dict: Key: logging format attribute pairs. Defaults to {"message": "message"}.
@param str time_format: time.strftime() format string. Default: "%Y-%m-%dT%H:%M:%S"
@param str msec_format: Microsecond formatting. Appended at the end. Default: "%s.%03dZ"
"""
def __init__(
self,
fmt_dict: dict | None = None,
time_format: str = "%Y-%m-%dT%H:%M:%S",
msec_format: str = "%s.%03dZ",
):
self.fmt_dict = (
fmt_dict
if fmt_dict is not None
else {
"timestamp": "asctime",
"level": "levelname",
"logger": "name",
"message": "message",
}
)
self.default_time_format = time_format
self.default_msec_format = msec_format
self.datefmt = None
def usesTime(self) -> bool:
"""
Look for the attribute in the format dict values instead of the fmt string.
"""
return "asctime" in self.fmt_dict.values()
def formatMessage(self, record) -> str:
raise NotImplementedError()
def formatMessageDict(self, record) -> dict:
"""
Return a dictionary of the relevant LogRecord attributes instead of a string.
KeyError is raised if an unknown attribute is provided in the fmt_dict.
"""
return {fmt_key: record.__dict__[fmt_val] for fmt_key, fmt_val in self.fmt_dict.items()}
def format(self, record) -> str:
"""
Mostly the same as the parent's class method, the difference being that a dict is
manipulated and dumped as JSON instead of a string.
"""
record.message = record.getMessage()
if self.usesTime():
record.asctime = self.formatTime(record, self.datefmt)
message_dict = self.formatMessageDict(record)
if record.exc_info:
# Cache the traceback text to avoid converting it multiple times
# (it's constant anyway)
if not record.exc_text:
record.exc_text = self.formatException(record.exc_info)
if record.exc_text:
message_dict["exc_info"] = record.exc_text
if record.stack_info:
message_dict["stack_info"] = self.formatStack(record.stack_info)
return json.dumps(message_dict, default=str)
+26
View File
@@ -0,0 +1,26 @@
import sys
from shutil import get_terminal_size
from rich.console import Console
def console_width() -> int | None:
"""
Get the width of the console
"""
if any(module in ["pytest", "ipykernel"] for module in sys.modules):
return 200
width, _ = get_terminal_size((1, 24))
# Fall back to 200 if terminal size is not available.
# This is determined by assuming an insane width of 1char, which is unlikely.
w = None if width > 1 else 200
return w
def get_rich_console(**kwargs) -> Console:
"""
Get a rich console with default settings
"""
kwargs["width"] = kwargs.get("width", console_width())
return Console(**kwargs)
-19
View File
@@ -4,25 +4,6 @@ import logging
logger = logging.getLogger(__name__)
def set_loggers(verbosity: int = 0, api_verbosity: str = "info") -> None:
"""
Set the logging level for third party libraries
:param verbosity: Verbosity level. amount of `-v` passed to the command line
:return: None
"""
for logger_name in ("requests", "urllib3", "httpcore"):
logging.getLogger(logger_name).setLevel(logging.INFO if verbosity <= 1 else logging.DEBUG)
logging.getLogger("ccxt.base.exchange").setLevel(
logging.INFO if verbosity <= 2 else logging.DEBUG
)
logging.getLogger("telegram").setLevel(logging.INFO)
logging.getLogger("httpx").setLevel(logging.WARNING)
logging.getLogger("werkzeug").setLevel(
logging.ERROR if api_verbosity == "error" else logging.INFO
)
__BIAS_TESTER_LOGGERS = [
"freqtrade.resolvers",
"freqtrade.strategy.hyper",
+47 -27
View File
@@ -7,7 +7,7 @@ This module contains the backtesting logic
import logging
from collections import defaultdict
from copy import deepcopy
from datetime import datetime, timedelta, timezone
from datetime import datetime, timedelta
from typing import Any
from numpy import nan
@@ -63,7 +63,7 @@ from freqtrade.plugins.protectionmanager import ProtectionManager
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from freqtrade.util import FtPrecise
from freqtrade.util import FtPrecise, dt_now
from freqtrade.util.migrations import migrate_data
from freqtrade.wallets import Wallets
@@ -396,6 +396,8 @@ class Backtesting:
self.canceled_trade_entries = 0
self.canceled_entry_orders = 0
self.replaced_entry_orders = 0
self.canceled_exit_orders = 0
self.replaced_exit_orders = 0
self.dataprovider.clear_cache()
if enable_protections:
self._load_protections(self.strategy)
@@ -601,7 +603,7 @@ class Backtesting:
# This should not be reached...
return row[OPEN_IDX]
def _get_adjust_trade_entry_for_candle(
def _check_adjust_trade_for_candle(
self, trade: LocalTrade, row: tuple, current_time: datetime
) -> LocalTrade:
current_rate: float = row[OPEN_IDX]
@@ -712,7 +714,7 @@ class Backtesting:
exchange=self.exchange,
wallets=self.wallets,
stake_currency=self.config["stake_currency"],
dry_run=self.config["dry_run"],
dry_run=True,
)
if not (order.ft_order_side == trade.exit_side and order.safe_amount == trade.amount):
self._call_adjust_stop(current_date, trade, order.ft_price)
@@ -869,7 +871,7 @@ class Backtesting:
# Check if we need to adjust our current positions
if self.strategy.position_adjustment_enable:
trade = self._get_adjust_trade_entry_for_candle(trade, row, current_time)
trade = self._check_adjust_trade_for_candle(trade, row, current_time)
if trade.is_open:
enter = row[SHORT_IDX] if trade.is_short else row[LONG_IDX]
@@ -1234,8 +1236,8 @@ class Backtesting:
for order in [o for o in trade.orders if o.ft_is_open]:
if order.side == trade.entry_side:
self.canceled_entry_orders += 1
# elif order.side == trade.exit_side:
# self.canceled_exit_orders += 1
elif order.side == trade.exit_side:
self.canceled_exit_orders += 1
# canceled orders are removed from the trade
del trade.orders[trade.orders.index(order)]
@@ -1299,9 +1301,10 @@ class Backtesting:
Returns True if the trade should be deleted.
"""
# only check on new candles for open entry orders
if order.side == trade.entry_side and current_time > order.order_date_utc:
if current_time > order.order_date_utc:
is_entry = order.side == trade.entry_side
requested_rate = strategy_safe_wrapper(
self.strategy.adjust_entry_price, default_retval=order.ft_price
self.strategy.adjust_order_price, default_retval=order.ft_price
)(
trade=trade, # type: ignore[arg-type]
order=order,
@@ -1311,6 +1314,7 @@ class Backtesting:
current_order_rate=order.ft_price,
entry_tag=trade.enter_tag,
side=trade.trade_direction,
is_entry=is_entry,
) # default value is current order price
# cancel existing order whenever a new rate is requested (or None)
@@ -1319,22 +1323,35 @@ class Backtesting:
return False
else:
del trade.orders[trade.orders.index(order)]
self.canceled_entry_orders += 1
if is_entry:
self.canceled_entry_orders += 1
else:
self.canceled_exit_orders += 1
# place new order if result was not None
if requested_rate:
self._enter_trade(
pair=trade.pair,
row=row,
trade=trade,
requested_rate=requested_rate,
requested_stake=(order.safe_remaining * order.ft_price / trade.leverage),
direction="short" if trade.is_short else "long",
)
if is_entry:
self._enter_trade(
pair=trade.pair,
row=row,
trade=trade,
requested_rate=requested_rate,
requested_stake=(order.safe_remaining * order.ft_price / trade.leverage),
direction="short" if trade.is_short else "long",
)
self.replaced_entry_orders += 1
else:
self._exit_trade(
trade=trade,
sell_row=row,
close_rate=requested_rate,
amount=order.safe_remaining,
exit_reason=order.ft_order_tag,
)
self.replaced_exit_orders += 1
# Delete trade if no successful entries happened (if placing the new order failed)
if not trade.has_open_orders and trade.nr_of_successful_entries == 0:
if not trade.has_open_orders and is_entry and trade.nr_of_successful_entries == 0:
return True
self.replaced_entry_orders += 1
else:
# assumption: there can't be multiple open entry orders at any given time
return trade.nr_of_successful_entries == 0
@@ -1535,7 +1552,9 @@ class Backtesting:
row_index += 1
indexes[pair] = row_index
is_last_row = current_time == end_date
self.dataprovider._set_dataframe_max_index(self.required_startup + row_index)
self.dataprovider._set_dataframe_max_index(
pair, self.required_startup + row_index
)
trade_dir = self.check_for_trade_entry(row)
pair_tradedir_cache[pair] = trade_dir
@@ -1656,7 +1675,7 @@ class Backtesting:
self.progress.init_step(BacktestState.ANALYZE, 0)
strategy_name = strat.get_strategy_name()
logger.info(f"Running backtesting for Strategy {strategy_name}")
backtest_start_time = datetime.now(timezone.utc)
backtest_start_time = dt_now()
self._set_strategy(strat)
# need to reprocess data every time to populate signals
@@ -1683,7 +1702,7 @@ class Backtesting:
start_date=min_date,
end_date=max_date,
)
backtest_end_time = datetime.now(timezone.utc)
backtest_end_time = dt_now()
results.update(
{
"run_id": self.run_ids.get(strategy_name, ""),
@@ -1710,14 +1729,14 @@ class Backtesting:
def _get_min_cached_backtest_date(self):
min_backtest_date = None
backtest_cache_age = self.config.get("backtest_cache", constants.BACKTEST_CACHE_DEFAULT)
if self.timerange.stopts == 0 or self.timerange.stopdt > datetime.now(tz=timezone.utc):
if self.timerange.stopts == 0 or self.timerange.stopdt > dt_now():
logger.warning("Backtest result caching disabled due to use of open-ended timerange.")
elif backtest_cache_age == "day":
min_backtest_date = datetime.now(tz=timezone.utc) - timedelta(days=1)
min_backtest_date = dt_now() - timedelta(days=1)
elif backtest_cache_age == "week":
min_backtest_date = datetime.now(tz=timezone.utc) - timedelta(weeks=1)
min_backtest_date = dt_now() - timedelta(weeks=1)
elif backtest_cache_age == "month":
min_backtest_date = datetime.now(tz=timezone.utc) - timedelta(weeks=4)
min_backtest_date = dt_now() - timedelta(weeks=4)
return min_backtest_date
def load_prior_backtest(self):
@@ -1773,6 +1792,7 @@ class Backtesting:
dt_appendix,
market_change_data=combined_res,
analysis_results=self.analysis_results,
strategy_files={s.get_strategy_name(): s.__file__ for s in self.strategylist},
)
# Results may be mixed up now. Sort them so they follow --strategy-list order.
-13
View File
@@ -6,7 +6,6 @@ This module contains the hyperopt logic
import logging
import random
import sys
from datetime import datetime
from math import ceil
from multiprocessing import Manager
@@ -15,7 +14,6 @@ from typing import Any
import rapidjson
from joblib import Parallel, cpu_count, delayed, wrap_non_picklable_objects
from joblib.externals import cloudpickle
from freqtrade.constants import FTHYPT_FILEVERSION, LAST_BT_RESULT_FN, Config
from freqtrade.enums import HyperoptState
@@ -110,17 +108,6 @@ class Hyperopt:
logger.info(f"Removing `{p}`.")
p.unlink()
def hyperopt_pickle_magic(self, bases) -> None:
"""
Hyperopt magic to allow strategy inheritance across files.
For this to properly work, we need to register the module of the imported class
to pickle as value.
"""
for modules in bases:
if modules.__name__ != "IStrategy":
cloudpickle.register_pickle_by_value(sys.modules[modules.__module__])
self.hyperopt_pickle_magic(modules.__bases__)
def _save_result(self, epoch: dict) -> None:
"""
Save hyperopt results to file
@@ -114,7 +114,7 @@ class HyperOptimizer:
def get_strategy_name(self) -> str:
return self.backtesting.strategy.get_strategy_name()
def hyperopt_pickle_magic(self, bases) -> None:
def hyperopt_pickle_magic(self, bases: tuple[type, ...]) -> None:
"""
Hyperopt magic to allow strategy inheritance across files.
For this to properly work, we need to register the module of the imported class
@@ -122,7 +122,8 @@ class HyperOptimizer:
"""
for modules in bases:
if modules.__name__ != "IStrategy":
cloudpickle.register_pickle_by_value(sys.modules[modules.__module__])
if mod := sys.modules.get(modules.__module__):
cloudpickle.register_pickle_by_value(mod)
self.hyperopt_pickle_magic(modules.__bases__)
def _get_params_dict(
@@ -132,18 +132,18 @@ def text_table_periodic_breakdown(
"""
headers = [
period.capitalize(),
"Trades",
f"Tot Profit {stake_currency}",
"Wins",
"Draws",
"Losses",
"Profit Factor",
"Win Draw Loss Win%",
]
output = [
[
d["date"],
d.get("trades", "N/A"),
fmt_coin(d["profit_abs"], stake_currency, False),
d["wins"],
d["draws"],
d["loses"],
round(d["profit_factor"], 2) if "profit_factor" in d else "N/A",
generate_wins_draws_losses(d["wins"], d["draws"], d.get("losses", d.get("loses", 0))),
]
for d in days_breakdown_stats
]
@@ -312,6 +312,7 @@ def text_table_add_metrics(strat_results: dict) -> None:
("Sortino", f"{strat_results['sortino']:.2f}" if "sortino" in strat_results else "N/A"),
("Sharpe", f"{strat_results['sharpe']:.2f}" if "sharpe" in strat_results else "N/A"),
("Calmar", f"{strat_results['calmar']:.2f}" if "calmar" in strat_results else "N/A"),
("SQN", f"{strat_results['sqn']:.2f}" if "sqn" in strat_results else "N/A"),
(
"Profit factor",
(
@@ -6,6 +6,7 @@ from zipfile import ZIP_DEFLATED, ZipFile
from pandas import DataFrame
from freqtrade.configuration import sanitize_config
from freqtrade.constants import LAST_BT_RESULT_FN
from freqtrade.enums.runmode import RunMode
from freqtrade.ft_types import BacktestResultType
@@ -52,6 +53,7 @@ def store_backtest_results(
*,
market_change_data: DataFrame | None = None,
analysis_results: dict[str, dict[str, DataFrame]] | None = None,
strategy_files: dict[str, str] | None = None,
) -> Path:
"""
Stores backtest results and analysis data in a zip file, with metadata stored separately
@@ -85,6 +87,32 @@ def store_backtest_results(
dump_json_to_file(stats_buf, stats_copy)
zipf.writestr(json_filename.name, stats_buf.getvalue())
config_buf = StringIO()
dump_json_to_file(config_buf, sanitize_config(config["original_config"]))
zipf.writestr(f"{base_filename.stem}_config.json", config_buf.getvalue())
for strategy_name, strategy_file in (strategy_files or {}).items():
# Store the strategy file and its parameters
strategy_buf = BytesIO()
strategy_path = Path(strategy_file)
if not strategy_path.is_file():
logger.warning(f"Strategy file '{strategy_path}' does not exist. Skipping.")
continue
with strategy_path.open("rb") as strategy_file_obj:
strategy_buf.write(strategy_file_obj.read())
strategy_buf.seek(0)
zipf.writestr(f"{base_filename.stem}_{strategy_name}.py", strategy_buf.getvalue())
strategy_params = strategy_path.with_suffix(".json")
if strategy_params.is_file():
strategy_params_buf = BytesIO()
with strategy_params.open("rb") as strategy_params_obj:
strategy_params_buf.write(strategy_params_obj.read())
strategy_params_buf.seek(0)
zipf.writestr(
f"{base_filename.stem}_{strategy_name}.json",
strategy_params_buf.getvalue(),
)
# Add market change data if present
if market_change_data is not None:
market_change_name = f"{base_filename.stem}_market_change.feather"
@@ -16,8 +16,9 @@ from freqtrade.data.metrics import (
calculate_max_drawdown,
calculate_sharpe,
calculate_sortino,
calculate_sqn,
)
from freqtrade.ft_types import BacktestResultType
from freqtrade.ft_types import BacktestResultType, get_BacktestResultType_default
from freqtrade.util import decimals_per_coin, fmt_coin, get_dry_run_wallet
@@ -211,6 +212,8 @@ def _get_resample_from_period(period: str) -> str:
return "1W-MON"
if period == "month":
return "1ME"
if period == "year":
return "1YE"
raise ValueError(f"Period {period} is not supported.")
@@ -228,8 +231,11 @@ def generate_periodic_breakdown_stats(
profit_abs = day["profit_abs"].sum().round(10)
wins = sum(day["profit_abs"] > 0)
draws = sum(day["profit_abs"] == 0)
loses = sum(day["profit_abs"] < 0)
trades = wins + draws + loses
losses = sum(day["profit_abs"] < 0)
trades = wins + draws + losses
winning_profit = day.loc[day["profit_abs"] > 0, "profit_abs"].sum()
losing_profit = day.loc[day["profit_abs"] < 0, "profit_abs"].sum()
profit_factor = winning_profit / abs(losing_profit) if losing_profit else 0.0
stats.append(
{
"date": name.strftime("%d/%m/%Y"),
@@ -237,8 +243,9 @@ def generate_periodic_breakdown_stats(
"profit_abs": profit_abs,
"wins": wins,
"draws": draws,
"loses": loses,
"winrate": wins / trades if trades else 0.0,
"losses": losses,
"trades": trades,
"profit_factor": round(profit_factor, 8),
}
)
return stats
@@ -468,6 +475,7 @@ def generate_strategy_stats(
"sortino": calculate_sortino(results, min_date, max_date, start_balance),
"sharpe": calculate_sharpe(results, min_date, max_date, start_balance),
"calmar": calculate_calmar(results, min_date, max_date, start_balance),
"sqn": calculate_sqn(results, start_balance),
"profit_factor": profit_factor,
"backtest_start": min_date.strftime(DATETIME_PRINT_FORMAT),
"backtest_start_ts": int(min_date.timestamp() * 1000),
@@ -579,11 +587,7 @@ def generate_backtest_stats(
:param max_date: Backtest end date
:return: Dictionary containing results per strategy and a strategy summary.
"""
result: BacktestResultType = {
"metadata": {},
"strategy": {},
"strategy_comparison": [],
}
result: BacktestResultType = get_BacktestResultType_default()
market_change = calculate_market_change(btdata, "close")
metadata = {}
pairlist = list(btdata.keys())
+25 -3
View File
@@ -124,6 +124,7 @@ def migrate_trades_and_orders_table(
funding_fees = get_column_def(cols, "funding_fees", "0.0")
funding_fee_running = get_column_def(cols, "funding_fee_running", "null")
max_stake_amount = get_column_def(cols, "max_stake_amount", "stake_amount")
record_version = get_column_def(cols, "record_version", "1")
# If ticker-interval existed use that, else null.
if has_column(cols, "ticker_interval"):
@@ -180,7 +181,7 @@ def migrate_trades_and_orders_table(
trading_mode, leverage, liquidation_price, is_short,
interest_rate, funding_fees, funding_fee_running, realized_profit,
amount_precision, price_precision, precision_mode, precision_mode_price, contract_size,
max_stake_amount
max_stake_amount, record_version
)
select id, lower(exchange), pair, {base_currency} base_currency,
{stake_currency} stake_currency,
@@ -210,7 +211,8 @@ def migrate_trades_and_orders_table(
{realized_profit} realized_profit,
{amount_precision} amount_precision, {price_precision} price_precision,
{precision_mode} precision_mode, {precision_mode_price} precision_mode_price,
{contract_size} contract_size, {max_stake_amount} max_stake_amount
{contract_size} contract_size, {max_stake_amount} max_stake_amount,
{record_version} record_version
from {trade_back_name}
"""
)
@@ -329,6 +331,25 @@ def fix_old_dry_orders(engine):
connection.execute(stmt)
def fix_wrong_max_stake_amount(engine):
"""
Fix max_stake_amount for leveraged closed trades
This caused record_version to be bumped to 2.
"""
with engine.begin() as connection:
stmt = (
update(Trade)
.where(
Trade.record_version < 2,
Trade.leverage > 1,
Trade.is_open.is_(False),
Trade.max_stake_amount != 0,
)
.values(max_stake_amount=Trade.max_stake_amount / Trade.leverage, record_version=2)
)
connection.execute(stmt)
def check_migrate(engine, decl_base, previous_tables) -> None:
"""
Checks if migration is necessary and migrates if necessary
@@ -350,7 +371,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
# if ('orders' not in previous_tables
# or not has_column(cols_orders, 'funding_fee')):
migrating = False
if not has_column(cols_trades, "precision_mode_price"):
if not has_column(cols_trades, "record_version"):
# if not has_column(cols_orders, "ft_order_tag"):
migrating = True
logger.info(
@@ -383,6 +404,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
set_sqlite_to_wal(engine)
fix_old_dry_orders(engine)
fix_wrong_max_stake_amount(engine)
if migrating:
logger.info("Database migration finished.")
+45 -35
View File
@@ -464,6 +464,8 @@ class LocalTrade:
# Used to keep running funding fees - between the last filled order and now
# Shall not be used for calculations!
funding_fee_running: float | None = None
# v 2 -> correct max_stake_amount calculation for leveraged trades
record_version: int = 2
@property
def stoploss_or_liquidation(self) -> float:
@@ -1243,7 +1245,7 @@ class LocalTrade:
total_stake += self._calc_open_trade_value(tmp_amount, price)
max_stake_amount += tmp_amount * price
self.funding_fees = funding_fees
self.max_stake_amount = float(max_stake_amount)
self.max_stake_amount = float(max_stake_amount) / (self.leverage or 1.0)
if close_profit:
self.close_profit = close_profit
@@ -1351,8 +1353,10 @@ class LocalTrade:
def get_custom_data(self, key: str, default: Any = None) -> Any:
"""
Get custom data for this trade
Get custom data for this trade.
:param key: key of the custom data
:param default: value to return if no data is found
"""
data = CustomDataWrapper.get_custom_data(trade_id=self.id, key=key)
if data:
@@ -1535,45 +1539,47 @@ class LocalTrade:
:param json_str: json string to parse
:return: Trade instance
"""
from uuid import uuid4
import rapidjson
data = rapidjson.loads(json_str)
trade = cls(
__FROM_JSON=True,
id=data["trade_id"],
id=data.get("trade_id"),
pair=data["pair"],
base_currency=data["base_currency"],
stake_currency=data["quote_currency"],
base_currency=data.get("base_currency"),
stake_currency=data.get("quote_currency"),
is_open=data["is_open"],
exchange=data["exchange"],
exchange=data.get("exchange", "import"),
amount=data["amount"],
amount_requested=data["amount_requested"],
amount_requested=data.get("amount_requested", data["amount"]),
stake_amount=data["stake_amount"],
strategy=data["strategy"],
strategy=data.get("strategy"),
enter_tag=data["enter_tag"],
timeframe=data["timeframe"],
timeframe=data.get("timeframe"),
fee_open=data["fee_open"],
fee_open_cost=data["fee_open_cost"],
fee_open_currency=data["fee_open_currency"],
fee_open_cost=data.get("fee_open_cost"),
fee_open_currency=data.get("fee_open_currency"),
fee_close=data["fee_close"],
fee_close_cost=data["fee_close_cost"],
fee_close_currency=data["fee_close_currency"],
fee_close_cost=data.get("fee_close_cost"),
fee_close_currency=data.get("fee_close_currency"),
open_date=datetime.fromtimestamp(data["open_timestamp"] // 1000, tz=timezone.utc),
open_rate=data["open_rate"],
open_rate_requested=data["open_rate_requested"],
open_trade_value=data["open_trade_value"],
open_rate_requested=data.get("open_rate_requested", data["open_rate"]),
open_trade_value=data.get("open_trade_value"),
close_date=(
datetime.fromtimestamp(data["close_timestamp"] // 1000, tz=timezone.utc)
if data["close_timestamp"]
else None
),
realized_profit=data["realized_profit"],
realized_profit=data.get("realized_profit", 0),
close_rate=data["close_rate"],
close_rate_requested=data["close_rate_requested"],
close_profit=data["close_profit"],
close_profit_abs=data["close_profit_abs"],
close_rate_requested=data.get("close_rate_requested", data["close_rate"]),
close_profit=data.get("close_profit", data.get("profit_ratio")),
close_profit_abs=data.get("close_profit_abs", data.get("profit_abs")),
exit_reason=data["exit_reason"],
exit_order_status=data["exit_order_status"],
exit_order_status=data.get("exit_order_status"),
stop_loss=data["stop_loss_abs"],
stop_loss_pct=data["stop_loss_ratio"],
initial_stop_loss=data["initial_stop_loss_abs"],
@@ -1581,11 +1587,11 @@ class LocalTrade:
min_rate=data["min_rate"],
max_rate=data["max_rate"],
leverage=data["leverage"],
interest_rate=data["interest_rate"],
liquidation_price=data["liquidation_price"],
interest_rate=data.get("interest_rate"),
liquidation_price=data.get("liquidation_price"),
is_short=data["is_short"],
trading_mode=data["trading_mode"],
funding_fees=data["funding_fees"],
trading_mode=data.get("trading_mode"),
funding_fees=data.get("funding_fees"),
amount_precision=data.get("amount_precision", None),
price_precision=data.get("price_precision", None),
precision_mode=data.get("precision_mode", None),
@@ -1597,23 +1603,25 @@ class LocalTrade:
amount=order["amount"],
ft_amount=order["amount"],
ft_order_side=order["ft_order_side"],
ft_pair=order["pair"],
ft_is_open=order["is_open"],
order_id=order["order_id"],
status=order["status"],
average=order["average"],
ft_pair=order.get("pair", data["pair"]),
ft_is_open=order.get("is_open", False),
order_id=order.get("order_id", uuid4().hex),
status=order.get("status"),
average=order.get("average", order.get("safe_price")),
cost=order["cost"],
filled=order["filled"],
order_date=datetime.strptime(order["order_date"], DATETIME_PRINT_FORMAT),
filled=order.get("filled", order["amount"]),
order_date=datetime.strptime(order["order_date"], DATETIME_PRINT_FORMAT)
if order.get("order_date")
else None,
order_filled_date=(
datetime.fromtimestamp(order["order_filled_timestamp"] // 1000, tz=timezone.utc)
if order["order_filled_timestamp"]
else None
),
order_type=order["order_type"],
price=order["price"],
ft_price=order["price"],
remaining=order["remaining"],
order_type=order.get("order_type"),
price=order.get("price", order.get("safe_price")),
ft_price=order.get("price", order.get("safe_price")),
remaining=order.get("remaining", 0.0),
funding_fee=order.get("funding_fee", None),
ft_order_tag=order.get("ft_order_tag", None),
)
@@ -1748,6 +1756,8 @@ class Trade(ModelBase, LocalTrade):
Float(), nullable=True, default=None
)
record_version: Mapped[int] = mapped_column(Integer, nullable=False, default=2) # type: ignore
def __init__(self, **kwargs):
from_json = kwargs.pop("__FROM_JSON", None)
super().__init__(**kwargs)
+1 -1
View File
@@ -9,7 +9,7 @@ from typing import Any
import freqtrade.exchange as exchanges
from freqtrade.constants import Config, ExchangeConfig
from freqtrade.exchange import MAP_EXCHANGE_CHILDCLASS, Exchange
from freqtrade.resolvers import IResolver
from freqtrade.resolvers.iresolver import IResolver
logger = logging.getLogger(__name__)
+9 -1
View File
@@ -16,7 +16,7 @@ from freqtrade.configuration.config_validation import validate_migrated_strategy
from freqtrade.constants import REQUIRED_ORDERTIF, REQUIRED_ORDERTYPES, USERPATH_STRATEGIES, Config
from freqtrade.enums import TradingMode
from freqtrade.exceptions import OperationalException
from freqtrade.resolvers import IResolver
from freqtrade.resolvers.iresolver import IResolver
from freqtrade.strategy.interface import IStrategy
@@ -242,6 +242,14 @@ class StrategyResolver(IResolver):
if has_after_fill:
strategy._ft_stop_uses_after_fill = True
if check_override(strategy, IStrategy, "adjust_order_price") and (
check_override(strategy, IStrategy, "adjust_entry_price")
or check_override(strategy, IStrategy, "adjust_exit_price")
):
raise OperationalException(
"If you implement `adjust_order_price`, `adjust_entry_price` and "
"`adjust_exit_price` will not be used. Please pick one approach for your strategy."
)
return strategy
@staticmethod
+15 -10
View File
@@ -99,16 +99,21 @@ def __run_backtest_bg(btconfig: Config):
ApiBG.bt["data"], ApiBG.bt["bt"].all_results, min_date=min_date, max_date=max_date
)
if btconfig.get("export", "none") == "trades":
combined_res = combined_dataframes_with_rel_mean(ApiBG.bt["data"], min_date, max_date)
fn = store_backtest_results(
btconfig,
ApiBG.bt["bt"].results,
datetime.now().strftime("%Y-%m-%d_%H-%M-%S"),
market_change_data=combined_res,
)
ApiBG.bt["bt"].results["metadata"][strategy_name]["filename"] = str(fn.stem)
ApiBG.bt["bt"].results["metadata"][strategy_name]["strategy"] = strategy_name
if btconfig.get("export", "none") == "trades":
combined_res = combined_dataframes_with_rel_mean(
ApiBG.bt["data"], min_date, max_date
)
fn = store_backtest_results(
btconfig,
ApiBG.bt["bt"].results,
datetime.now().strftime("%Y-%m-%d_%H-%M-%S"),
market_change_data=combined_res,
strategy_files={
s.get_strategy_name(): s.__file__ for s in ApiBG.bt["bt"].strategylist
},
)
ApiBG.bt["bt"].results["metadata"][strategy_name]["filename"] = str(fn.stem)
ApiBG.bt["bt"].results["metadata"][strategy_name]["strategy"] = strategy_name
logger.info("Backtest finished.")
@@ -0,0 +1,77 @@
import logging
from copy import deepcopy
from fastapi import APIRouter, Depends, HTTPException
from freqtrade.configuration import validate_config_consistency
from freqtrade.rpc.api_server.api_pairlists import handleExchangePayload
from freqtrade.rpc.api_server.api_schemas import PairHistory, PairHistoryRequest
from freqtrade.rpc.api_server.deps import get_config, get_exchange
from freqtrade.rpc.rpc import RPC
logger = logging.getLogger(__name__)
router = APIRouter()
@router.get("/pair_history", response_model=PairHistory, tags=["candle data"])
def pair_history(
pair: str,
timeframe: str,
timerange: str,
strategy: str,
freqaimodel: str | None = None,
config=Depends(get_config),
exchange=Depends(get_exchange),
):
# The initial call to this endpoint can be slow, as it may need to initialize
# the exchange class.
config_loc = deepcopy(config)
config_loc.update(
{
"timeframe": timeframe,
"strategy": strategy,
"timerange": timerange,
"freqaimodel": freqaimodel if freqaimodel else config_loc.get("freqaimodel"),
}
)
validate_config_consistency(config_loc)
try:
return RPC._rpc_analysed_history_full(config_loc, pair, timeframe, exchange, None, False)
except Exception as e:
raise HTTPException(status_code=502, detail=str(e))
@router.post("/pair_history", response_model=PairHistory, tags=["candle data"])
def pair_history_filtered(payload: PairHistoryRequest, config=Depends(get_config)):
# The initial call to this endpoint can be slow, as it may need to initialize
# the exchange class.
config_loc = deepcopy(config)
config_loc.update(
{
"timeframe": payload.timeframe,
"strategy": payload.strategy,
"timerange": payload.timerange,
"freqaimodel": (
payload.freqaimodel if payload.freqaimodel else config_loc.get("freqaimodel")
),
}
)
handleExchangePayload(payload, config_loc)
exchange = get_exchange(config_loc)
validate_config_consistency(config_loc)
try:
return RPC._rpc_analysed_history_full(
config_loc,
payload.pair,
payload.timeframe,
exchange,
payload.columns,
payload.live_mode,
)
except Exception as e:
logger.exception("Error in pair_history_filtered")
raise HTTPException(status_code=502, detail=str(e))
@@ -110,13 +110,17 @@ def handleExchangePayload(payload: ExchangeModePayloadMixin, config_loc: Config)
Handle exchange and trading mode payload.
Updates the configuration with the payload values.
"""
from freqtrade.configuration.directory_operations import create_datadir
if payload.exchange:
config_loc["exchange"]["name"] = payload.exchange
config_loc.update({"datadir": create_datadir(config_loc, None)})
if payload.trading_mode:
config_loc["trading_mode"] = payload.trading_mode
config_loc["candle_type_def"] = CandleType.get_default(
config_loc.get("trading_mode", "spot") or "spot"
)
if payload.margin_mode:
config_loc["margin_mode"] = payload.margin_mode
+34 -2
View File
@@ -524,10 +524,11 @@ class PairCandlesRequest(BaseModel):
columns: list[str] | None = None
class PairHistoryRequest(PairCandlesRequest):
class PairHistoryRequest(PairCandlesRequest, ExchangeModePayloadMixin):
timerange: str
strategy: str
strategy: str | None = None
freqaimodel: str | None = None
live_mode: bool = False
class PairHistory(BaseModel):
@@ -606,6 +607,24 @@ class BacktestMarketChange(BaseModel):
data: list[list[Any]]
class MarketRequest(ExchangeModePayloadMixin, BaseModel):
base: str | None = None
quote: str | None = None
class MarketModel(BaseModel):
symbol: str
base: str
quote: str
spot: bool
swap: bool
class MarketResponse(BaseModel):
markets: dict[str, MarketModel]
exchange_id: str
class SysInfo(BaseModel):
cpu_pct: list[float]
ram_pct: float
@@ -618,3 +637,16 @@ class Health(BaseModel):
bot_start_ts: int | None = None
bot_startup: datetime | None = None
bot_startup_ts: int | None = None
class CustomDataEntry(BaseModel):
key: str
type: str
value: Any
created_at: datetime
updated_at: datetime | None = None
class ListCustomData(BaseModel):
trade_id: int
custom_data: list[CustomDataEntry]
+81 -59
View File
@@ -1,14 +1,16 @@
import logging
from copy import deepcopy
from typing import Annotated
from fastapi import APIRouter, Depends, Query
from fastapi.exceptions import HTTPException
from freqtrade import __version__
from freqtrade.data.history import get_datahandler
from freqtrade.enums import CandleType, State, TradingMode
from freqtrade.enums import CandleType, RunMode, State, TradingMode
from freqtrade.exceptions import OperationalException
from freqtrade.rpc import RPC
from freqtrade.rpc.api_server.api_pairlists import handleExchangePayload
from freqtrade.rpc.api_server.api_schemas import (
AvailablePairs,
Balances,
@@ -27,14 +29,16 @@ from freqtrade.rpc.api_server.api_schemas import (
FreqAIModelListResponse,
Health,
HyperoptLossListResponse,
ListCustomData,
Locks,
LocksPayload,
Logs,
MarketRequest,
MarketResponse,
MixTag,
OpenTradeSchema,
PairCandlesRequest,
PairHistory,
PairHistoryRequest,
PerformanceEntry,
Ping,
PlotConfig,
@@ -84,7 +88,8 @@ logger = logging.getLogger(__name__)
# 2.35: pair_candles and pair_history endpoints as Post variant
# 2.40: Add hyperopt-loss endpoint
# 2.41: Add download-data endpoint
API_VERSION = 2.41
# 2.42: Add /pair_history endpoint with live data
API_VERSION = 2.42
# Public API, requires no auth.
router_public = APIRouter()
@@ -149,21 +154,33 @@ def stats(rpc: RPC = Depends(get_rpc)):
@router.get("/daily", response_model=DailyWeeklyMonthly, tags=["info"])
def daily(timescale: int = 7, rpc: RPC = Depends(get_rpc), config=Depends(get_config)):
def daily(
timescale: int = Query(7, ge=1, description="Number of days to fetch data for"),
rpc: RPC = Depends(get_rpc),
config=Depends(get_config),
):
return rpc._rpc_timeunit_profit(
timescale, config["stake_currency"], config.get("fiat_display_currency", "")
)
@router.get("/weekly", response_model=DailyWeeklyMonthly, tags=["info"])
def weekly(timescale: int = 4, rpc: RPC = Depends(get_rpc), config=Depends(get_config)):
def weekly(
timescale: int = Query(4, ge=1, description="Number of weeks to fetch data for"),
rpc: RPC = Depends(get_rpc),
config=Depends(get_config),
):
return rpc._rpc_timeunit_profit(
timescale, config["stake_currency"], config.get("fiat_display_currency", ""), "weeks"
)
@router.get("/monthly", response_model=DailyWeeklyMonthly, tags=["info"])
def monthly(timescale: int = 3, rpc: RPC = Depends(get_rpc), config=Depends(get_config)):
def monthly(
timescale: int = Query(3, ge=1, description="Number of months to fetch data for"),
rpc: RPC = Depends(get_rpc),
config=Depends(get_config),
):
return rpc._rpc_timeunit_profit(
timescale, config["stake_currency"], config.get("fiat_display_currency", ""), "months"
)
@@ -180,7 +197,11 @@ def status(rpc: RPC = Depends(get_rpc)):
# Using the responsemodel here will cause a ~100% increase in response time (from 1s to 2s)
# on big databases. Correct response model: response_model=TradeResponse,
@router.get("/trades", tags=["info", "trading"])
def trades(limit: int = 500, offset: int = 0, rpc: RPC = Depends(get_rpc)):
def trades(
limit: int = Query(500, ge=1, description="Maximum number of different trades to return data"),
offset: int = Query(0, ge=0, description="Number of trades to skip for pagination"),
rpc: RPC = Depends(get_rpc),
):
return rpc._rpc_trade_history(limit, offset=offset, order_by_id=True)
@@ -209,6 +230,36 @@ def trade_reload(tradeid: int, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_trade_status([tradeid])[0]
@router.get("/trades/open/custom-data", response_model=list[ListCustomData], tags=["trading"])
def list_open_trades_custom_data(
key: str | None = Query(None, description="Optional key to filter data"),
limit: int = Query(100, ge=1, description="Maximum number of different trades to return data"),
offset: int = Query(0, ge=0, description="Number of trades to skip for pagination"),
rpc: RPC = Depends(get_rpc),
):
"""
Fetch custom data for all open trades.
If a key is provided, it will be used to filter data accordingly.
Pagination is implemented via the `limit` and `offset` parameters.
"""
try:
return rpc._rpc_list_custom_data(key=key, limit=limit, offset=offset)
except RPCException as e:
raise HTTPException(status_code=404, detail=str(e))
@router.get("/trades/{trade_id}/custom-data", response_model=list[ListCustomData], tags=["trading"])
def list_custom_data(trade_id: int, key: str | None = Query(None), rpc: RPC = Depends(get_rpc)):
"""
Fetch custom data for a specific trade.
If a key is provided, it will be used to filter data accordingly.
"""
try:
return rpc._rpc_list_custom_data(trade_id, key=key)
except RPCException as e:
raise HTTPException(status_code=404, detail=str(e))
# TODO: Missing response model
@router.get("/edge", tags=["info"])
def edge(rpc: RPC = Depends(get_rpc)):
@@ -342,58 +393,6 @@ def pair_candles_filtered(payload: PairCandlesRequest, rpc: RPC = Depends(get_rp
)
@router.get("/pair_history", response_model=PairHistory, tags=["candle data"])
def pair_history(
pair: str,
timeframe: str,
timerange: str,
strategy: str,
freqaimodel: str | None = None,
config=Depends(get_config),
exchange=Depends(get_exchange),
):
# The initial call to this endpoint can be slow, as it may need to initialize
# the exchange class.
config = deepcopy(config)
config.update(
{
"timeframe": timeframe,
"strategy": strategy,
"timerange": timerange,
"freqaimodel": freqaimodel if freqaimodel else config.get("freqaimodel"),
}
)
try:
return RPC._rpc_analysed_history_full(config, pair, timeframe, exchange, None)
except Exception as e:
raise HTTPException(status_code=502, detail=str(e))
@router.post("/pair_history", response_model=PairHistory, tags=["candle data"])
def pair_history_filtered(
payload: PairHistoryRequest, config=Depends(get_config), exchange=Depends(get_exchange)
):
# The initial call to this endpoint can be slow, as it may need to initialize
# the exchange class.
config = deepcopy(config)
config.update(
{
"timeframe": payload.timeframe,
"strategy": payload.strategy,
"timerange": payload.timerange,
"freqaimodel": (
payload.freqaimodel if payload.freqaimodel else config.get("freqaimodel")
),
}
)
try:
return RPC._rpc_analysed_history_full(
config, payload.pair, payload.timeframe, exchange, payload.columns
)
except Exception as e:
raise HTTPException(status_code=502, detail=str(e))
@router.get("/plot_config", response_model=PlotConfig, tags=["candle data"])
def plot_config(
strategy: str | None = None,
@@ -525,6 +524,29 @@ def list_available_pairs(
return result
@router.get("/markets", response_model=MarketResponse, tags=["candle data", "webserver"])
def markets(
query: Annotated[MarketRequest, Query()],
config=Depends(get_config),
rpc: RPC | None = Depends(get_rpc_optional),
):
if not rpc or config["runmode"] == RunMode.WEBSERVER:
# webserver mode
config_loc = deepcopy(config)
handleExchangePayload(query, config_loc)
exchange = get_exchange(config_loc)
else:
exchange = rpc._freqtrade.exchange
return {
"markets": exchange.get_markets(
base_currencies=[query.base] if query.base else None,
quote_currencies=[query.quote] if query.quote else None,
),
"exchange_id": exchange.id,
}
@router.get("/sysinfo", response_model=SysInfo, tags=["info"])
def sysinfo():
return RPC._rpc_sysinfo()
+6
View File
@@ -120,6 +120,7 @@ class ApiServer(RPCHandler):
from freqtrade.rpc.api_server.api_background_tasks import router as api_bg_tasks
from freqtrade.rpc.api_server.api_backtest import router as api_backtest
from freqtrade.rpc.api_server.api_download_data import router as api_download_data
from freqtrade.rpc.api_server.api_pair_history import router as api_pair_history
from freqtrade.rpc.api_server.api_pairlists import router as api_pairlists
from freqtrade.rpc.api_server.api_v1 import router as api_v1
from freqtrade.rpc.api_server.api_v1 import router_public as api_v1_public
@@ -145,6 +146,11 @@ class ApiServer(RPCHandler):
prefix="/api/v1",
dependencies=[Depends(http_basic_or_jwt_token), Depends(is_webserver_mode)],
)
app.include_router(
api_pair_history,
prefix="/api/v1",
dependencies=[Depends(http_basic_or_jwt_token), Depends(is_webserver_mode)],
)
app.include_router(
api_pairlists,
prefix="/api/v1",
+115 -49
View File
@@ -6,13 +6,12 @@ import logging
from abc import abstractmethod
from collections.abc import Generator, Sequence
from datetime import date, datetime, timedelta, timezone
from math import isnan
from typing import TYPE_CHECKING, Any
import psutil
from dateutil.relativedelta import relativedelta
from dateutil.tz import tzlocal
from numpy import inf, int64, mean, nan
from numpy import inf, int64, isnan, mean, nan
from pandas import DataFrame, NaT
from sqlalchemy import func, select
@@ -31,10 +30,10 @@ from freqtrade.enums import (
TradingMode,
)
from freqtrade.exceptions import ExchangeError, PricingError
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_msecs
from freqtrade.exchange import Exchange, timeframe_to_minutes, timeframe_to_msecs
from freqtrade.exchange.exchange_utils import price_to_precision
from freqtrade.loggers import bufferHandler
from freqtrade.persistence import KeyStoreKeys, KeyValueStore, PairLocks, Trade
from freqtrade.persistence import CustomDataWrapper, KeyStoreKeys, KeyValueStore, PairLocks, Trade
from freqtrade.persistence.models import PairLock
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
@@ -42,12 +41,13 @@ from freqtrade.rpc.rpc_types import RPCSendMsg
from freqtrade.util import (
decimals_per_coin,
dt_from_ts,
dt_humanize_delta,
dt_now,
dt_ts,
dt_ts_def,
format_date,
shorten_date,
)
from freqtrade.util.datetime_helpers import dt_humanize_delta
from freqtrade.wallets import PositionWallet, Wallet
@@ -1115,31 +1115,70 @@ class RPC:
"cancel_order_count": c_count,
}
def _rpc_list_custom_data(self, trade_id: int, key: str | None) -> list[dict[str, Any]]:
# Query for trade
trade = Trade.get_trades(trade_filter=[Trade.id == trade_id]).first()
if trade is None:
return []
# Query custom_data
custom_data = []
if key:
data = trade.get_custom_data(key=key)
if data:
custom_data = [data]
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]]:
"""
Fetch custom data for a specific trade, or all open trades if `trade_id` is not provided.
Pagination is applied via `limit` and `offset`.
Returns an array of dictionaries, each containing:
- "trade_id": the ID of the trade (int)
- "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
trades = Trade.session.scalars(
Trade.get_trades_query([Trade.is_open.is_(True)])
.order_by(Trade.id)
.limit(limit)
.offset(offset)
).all()
else:
custom_data = trade.get_all_custom_data()
return [
{
"id": data_entry.id,
"ft_trade_id": data_entry.ft_trade_id,
"cd_key": data_entry.cd_key,
"cd_type": data_entry.cd_type,
"cd_value": data_entry.cd_value,
"created_at": data_entry.created_at,
"updated_at": data_entry.updated_at,
}
for data_entry in custom_data
]
trades = Trade.get_trades(trade_filter=[Trade.id == trade_id]).all()
if not trades:
raise RPCException(
f"No trade found for trade_id: {trade_id}" if trade_id else "No open trades found."
)
results = []
for trade in trades:
# Depending on whether a specific key is provided, retrieve custom data accordingly.
if key:
data = trade.get_custom_data_entry(key=key)
# If data exists, wrap it in a list so the output remains consistent.
custom_data = [data] if data else []
else:
custom_data = trade.get_all_custom_data()
# Format and Append result for the trade if any custom data was found.
if custom_data:
formatted_custom_data = [
{
"key": data_entry.cd_key,
"type": data_entry.cd_type,
"value": CustomDataWrapper._convert_custom_data(data_entry).value,
"created_at": data_entry.created_at,
"updated_at": data_entry.updated_at,
}
for data_entry in custom_data
]
results.append({"trade_id": trade.id, "custom_data": formatted_custom_data})
# Handle case when there is no custom data found across trades.
if not results:
message_details = ""
if key:
message_details += f"with key '{key}' "
message_details += (
f"found for Trade ID: {trade_id}." if trade_id else "found for any open trades."
)
raise RPCException(f"No custom-data {message_details}")
return results
def _rpc_performance(self) -> list[dict[str, Any]]:
"""
@@ -1436,7 +1475,12 @@ class RPC:
@staticmethod
def _rpc_analysed_history_full(
config: Config, pair: str, timeframe: str, exchange, selected_cols: list[str] | None
config: Config,
pair: str,
timeframe: str,
exchange: Exchange,
selected_cols: list[str] | None,
live: bool,
) -> dict[str, Any]:
timerange_parsed = TimeRange.parse_timerange(config.get("timerange"))
@@ -1444,31 +1488,53 @@ class RPC:
from freqtrade.data.dataprovider import DataProvider
from freqtrade.resolvers.strategy_resolver import StrategyResolver
strategy = StrategyResolver.load_strategy(config)
startup_candles = strategy.startup_candle_count
strategy_name = ""
startup_candles = 0
if config.get("strategy"):
strategy = StrategyResolver.load_strategy(config)
startup_candles = strategy.startup_candle_count
strategy_name = strategy.get_strategy_name()
_data = load_data(
datadir=config["datadir"],
pairs=[pair],
timeframe=timeframe,
timerange=timerange_parsed,
data_format=config["dataformat_ohlcv"],
candle_type=config.get("candle_type_def", CandleType.SPOT),
startup_candles=startup_candles,
)
if pair not in _data:
raise RPCException(
f"No data for {pair}, {timeframe} in {config.get('timerange')} found."
if live:
data = exchange.get_historic_ohlcv(
pair=pair,
timeframe=timeframe,
since_ms=timerange_parsed.startts * 1000
if timerange_parsed.startts
else dt_ts(dt_now() - timedelta(days=30)),
is_new_pair=True, # history is never available - so always treat as new pair
candle_type=config.get("candle_type_def", CandleType.SPOT),
until_ms=timerange_parsed.stopts,
)
else:
_data = load_data(
datadir=config["datadir"],
pairs=[pair],
timeframe=timeframe,
timerange=timerange_parsed,
data_format=config["dataformat_ohlcv"],
candle_type=config.get("candle_type_def", CandleType.SPOT),
startup_candles=startup_candles,
)
if pair not in _data:
raise RPCException(
f"No data for {pair}, {timeframe} in {config.get('timerange')} found."
)
data = _data[pair]
strategy.dp = DataProvider(config, exchange=exchange, pairlists=None)
strategy.ft_bot_start()
if config.get("strategy"):
strategy.dp = DataProvider(config, exchange=exchange, pairlists=None)
strategy.ft_bot_start()
df_analyzed = strategy.analyze_ticker(_data[pair], {"pair": pair})
df_analyzed = trim_dataframe(df_analyzed, timerange_parsed, startup_candles=startup_candles)
df_analyzed = strategy.analyze_ticker(data, {"pair": pair})
df_analyzed = trim_dataframe(
df_analyzed, timerange_parsed, startup_candles=startup_candles
)
else:
df_analyzed = data
return RPC._convert_dataframe_to_dict(
strategy.get_strategy_name(),
strategy_name,
pair,
timeframe,
df_analyzed.copy(),
+27 -17
View File
@@ -25,6 +25,7 @@ from telegram import (
InlineKeyboardButton,
InlineKeyboardMarkup,
KeyboardButton,
Message,
ReplyKeyboardMarkup,
Update,
)
@@ -96,17 +97,17 @@ def authorized_only(command_handler: Callable[..., Coroutine[Any, Any, None]]):
"""
@wraps(command_handler)
async def wrapper(self, *args, **kwargs):
async def wrapper(self, *args, **kwargs) -> None:
"""Decorator logic"""
update = kwargs.get("update") or args[0]
# Reject unauthorized messages
if update.callback_query:
cchat_id = int(update.callback_query.message.chat.id)
ctopic_id = update.callback_query.message.message_thread_id
else:
cchat_id = int(update.message.chat_id)
ctopic_id = update.message.message_thread_id
message: Message = (
update.message if update.callback_query is None else update.callback_query.message
)
cchat_id: int = int(message.chat_id)
ctopic_id: int | None = message.message_thread_id
from_user_id: str = str(update.effective_user.id if update.effective_user else "")
chat_id = int(self._config["telegram"]["chat_id"])
if cchat_id != chat_id:
@@ -118,6 +119,10 @@ def authorized_only(command_handler: Callable[..., Coroutine[Any, Any, None]]):
logger.debug(f"Rejected message from wrong channel: {cchat_id}, {ctopic_id}")
return None
authorized = self._config["telegram"].get("authorized_users", None)
if authorized is not None and from_user_id not in authorized:
logger.info(f"Unauthorized user tried to control the bot: {from_user_id}")
return None
# Rollback session to avoid getting data stored in a transaction.
Trade.rollback()
logger.debug("Executing handler: %s for chat_id: %s", command_handler.__name__, chat_id)
@@ -1976,16 +1981,17 @@ class Telegram(RPCHandler):
results = self._rpc._rpc_list_custom_data(trade_id, key)
messages = []
if len(results) > 0:
messages.append("Found custom-data entr" + ("ies: " if len(results) > 1 else "y: "))
for result in results:
trade_custom_data = results[0]["custom_data"]
messages.append(
"Found custom-data entr" + ("ies: " if len(trade_custom_data) > 1 else "y: ")
)
for custom_data in trade_custom_data:
lines = [
f"*Key:* `{result['cd_key']}`",
f"*ID:* `{result['id']}`",
f"*Trade ID:* `{result['ft_trade_id']}`",
f"*Type:* `{result['cd_type']}`",
f"*Value:* `{result['cd_value']}`",
f"*Create Date:* `{format_date(result['created_at'])}`",
f"*Update Date:* `{format_date(result['updated_at'])}`",
f"*Key:* `{custom_data['key']}`",
f"*Type:* `{custom_data['type']}`",
f"*Value:* `{custom_data['value']}`",
f"*Create Date:* `{format_date(custom_data['created_at'])}`",
f"*Update Date:* `{format_date(custom_data['updated_at'])}`",
]
# Filter empty lines using list-comprehension
messages.append("\n".join([line for line in lines if line]))
@@ -2153,6 +2159,9 @@ class Telegram(RPCHandler):
return
chat_id = update.message.chat_id
topic_id = update.message.message_thread_id
user_id = (
update.effective_user.id if topic_id is not None and update.effective_user else None
)
msg = f"""Freqtrade Bot Info:
```json
@@ -2160,7 +2169,8 @@ class Telegram(RPCHandler):
"enabled": true,
"token": "********",
"chat_id": "{chat_id}",
{f'"topic_id": "{topic_id}"' if topic_id else ""}
{f'"topic_id": "{topic_id}",' if topic_id else ""}
{f'//"authorized_users": ["{user_id}"]' if topic_id and user_id else ""}
}}
```
"""
+101 -2
View File
@@ -132,6 +132,7 @@ class IStrategy(ABC, HyperStrategyMixin):
stake_currency: str
# container variable for strategy source code
__source__: str = ""
__file__: str = ""
# Definition of plot_config. See plotting documentation for more details.
plot_config: dict = {}
@@ -664,7 +665,7 @@ class IStrategy(ABC, HyperStrategyMixin):
entry_tag: str | None,
side: str,
**kwargs,
) -> float:
) -> float | None:
"""
Entry price re-adjustment logic, returning the user desired limit price.
This only executes when a order was already placed, still open (unfilled fully or partially)
@@ -685,11 +686,109 @@ class IStrategy(ABC, HyperStrategyMixin):
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
:param side: 'long' or 'short' - indicating the direction of the proposed trade
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: New entry price value if provided
:return float or None: New entry price value if provided
"""
return current_order_rate
def adjust_exit_price(
self,
trade: Trade,
order: Order | None,
pair: str,
current_time: datetime,
proposed_rate: float,
current_order_rate: float,
entry_tag: str | None,
side: str,
**kwargs,
) -> float | None:
"""
Exit price re-adjustment logic, returning the user desired limit price.
This only executes when a order was already placed, still open (unfilled fully or partially)
and not timed out on subsequent candles after entry trigger.
For full documentation please go to https://www.freqtrade.io/en/latest/strategy-callbacks/
When not implemented by a strategy, returns current_order_rate as default.
If current_order_rate is returned then the existing order is maintained.
If None is returned then order gets canceled but not replaced by a new one.
:param pair: Pair that's currently analyzed
:param trade: Trade object.
:param order: Order object
:param current_time: datetime object, containing the current datetime
:param proposed_rate: Rate, calculated based on pricing settings in entry_pricing.
:param current_order_rate: Rate of the existing order in place.
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
:param side: 'long' or 'short' - indicating the direction of the proposed trade
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float or None: New exit price value if provided
"""
return current_order_rate
def adjust_order_price(
self,
trade: Trade,
order: Order | None,
pair: str,
current_time: datetime,
proposed_rate: float,
current_order_rate: float,
entry_tag: str | None,
side: str,
is_entry: bool,
**kwargs,
) -> float | None:
"""
Exit and entry order price re-adjustment logic, returning the user desired limit price.
This only executes when a order was already placed, still open (unfilled fully or partially)
and not timed out on subsequent candles after entry trigger.
For full documentation please go to https://www.freqtrade.io/en/latest/strategy-callbacks/
When not implemented by a strategy, returns current_order_rate as default.
If current_order_rate is returned then the existing order is maintained.
If None is returned then order gets canceled but not replaced by a new one.
:param pair: Pair that's currently analyzed
:param trade: Trade object.
:param order: Order object
:param current_time: datetime object, containing the current datetime
:param proposed_rate: Rate, calculated based on pricing settings in entry_pricing.
:param current_order_rate: Rate of the existing order in place.
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
:param side: 'long' or 'short' - indicating the direction of the proposed trade
:param is_entry: True if the order is an entry order, False if it's an exit order.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float or None: New entry price value if provided
"""
if is_entry:
return self.adjust_entry_price(
trade=trade,
order=order,
pair=pair,
current_time=current_time,
proposed_rate=proposed_rate,
current_order_rate=current_order_rate,
entry_tag=entry_tag,
side=side,
**kwargs,
)
else:
return self.adjust_exit_price(
trade=trade,
order=order,
pair=pair,
current_time=current_time,
proposed_rate=proposed_rate,
current_order_rate=current_order_rate,
entry_tag=entry_tag,
side=side,
**kwargs,
)
def leverage(
self,
pair: str,
@@ -40,7 +40,7 @@ def custom_entry_price(
"""
return proposed_rate
def adjust_entry_price(
def adjust_order_price(
self,
trade: Trade,
order: Order | None,
@@ -50,10 +50,11 @@ def adjust_entry_price(
current_order_rate: float,
entry_tag: str | None,
side: str,
is_entry: bool,
**kwargs,
) -> float:
) -> float | None:
"""
Entry price re-adjustment logic, returning the user desired limit price.
Exit and entry order price re-adjustment logic, returning the user desired limit price.
This only executes when a order was already placed, still open (unfilled fully or partially)
and not timed out on subsequent candles after entry trigger.
@@ -71,9 +72,9 @@ def adjust_entry_price(
:param current_order_rate: Rate of the existing order in place.
:param entry_tag: Optional entry_tag (buy_tag) if provided with the buy signal.
:param side: 'long' or 'short' - indicating the direction of the proposed trade
:param is_entry: True if the order is an entry order, False if it's an exit order.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: New entry price value if provided
:return float or None: New entry price value if provided
"""
return current_order_rate
+2 -1
View File
@@ -7,7 +7,6 @@ from rich.progress import (
TimeRemainingColumn,
)
from freqtrade.loggers import error_console
from freqtrade.util.rich_progress import CustomProgress
@@ -21,6 +20,8 @@ def get_progress_tracker(**kwargs) -> CustomProgress:
"""
Get progress Bar with custom columns.
"""
from freqtrade.loggers import error_console
return CustomProgress(
TextColumn("[progress.description]{task.description}"),
BarColumn(bar_width=None),
+4 -12
View File
@@ -1,12 +1,12 @@
import sys
from collections.abc import Sequence
from typing import Any, TypeAlias
from pandas import DataFrame
from rich.console import Console
from rich.table import Column, Table
from rich.text import Text
from freqtrade.loggers.rich_console import get_rich_console
TextOrString: TypeAlias = str | Text
@@ -38,11 +38,7 @@ def print_rich_table(
row_to_add: list[str | Text] = [r if isinstance(r, Text) else str(r) for r in row]
table.add_row(*row_to_add)
width = None
if any(module in ["pytest", "ipykernel"] for module in sys.modules):
width = 200
console = Console(width=width)
console = get_rich_console()
console.print(table)
@@ -74,9 +70,5 @@ def print_df_rich_table(
row = [_format_value(x, floatfmt=".3f") for x in value_list]
table.add_row(*row)
width = None
if any(module in ["pytest", "ipykernel"] for module in sys.modules):
width = 200
console = Console(width=width)
console = get_rich_console()
console.print(table)
+1 -1
View File
@@ -197,7 +197,7 @@ class Wallets:
# Position is not open ...
continue
size = self._exchange._contracts_to_amount(symbol, position["contracts"])
collateral = safe_value_fallback(position, "collateral", "initialMargin", 0.0)
collateral = safe_value_fallback(position, "initialMargin", "collateral", 0.0)
leverage = position.get("leverage")
_parsed_positions[symbol] = PositionWallet(
symbol,
+1 -1
View File
@@ -1,7 +1,7 @@
from freqtrade_client.ft_rest_client import FtRestClient
__version__ = "2025.1"
__version__ = "2025.3"
if "dev" in __version__:
from pathlib import Path
+42 -2
View File
@@ -23,10 +23,18 @@ PostDataT = dict[str, Any] | list[dict[str, Any]] | None
class FtRestClient:
def __init__(
self, serverurl, username=None, password=None, *, pool_connections=10, pool_maxsize=10
self,
serverurl,
username=None,
password=None,
*,
pool_connections=10,
pool_maxsize=10,
timeout=10,
):
self._serverurl = serverurl
self._session = requests.Session()
self._timeout = timeout
# allow configuration of pool
adapter = HTTPAdapter(pool_connections=pool_connections, pool_maxsize=pool_maxsize)
@@ -50,7 +58,9 @@ class FtRestClient:
url = urlunparse((schema, netloc, path, par, query, fragment))
try:
resp = self._session.request(method, url, headers=hd, data=json.dumps(data))
resp = self._session.request(
method, url, headers=hd, timeout=self._timeout, data=json.dumps(data)
)
# return resp.text
return resp.json()
except RequestConnectionError:
@@ -259,6 +269,36 @@ class FtRestClient:
params["offset"] = offset
return self._get("trades", params)
def list_open_trades_custom_data(self, key=None, limit=100, offset=0):
"""List open trades custom-data of the running bot.
:param key: str, optional - Key of the custom-data
:param limit: limit of trades
:param offset: trades offset for pagination
:return: json object
"""
params = {}
params["limit"] = limit
params["offset"] = offset
if key is not None:
params["key"] = key
return self._get("trades/open/custom-data", params=params)
def list_custom_data(self, trade_id, key=None):
"""List custom-data of the running bot for a specific trade.
:param trade_id: ID of the trade
:param key: str, optional - Key of the custom-data
:return: JSON object
"""
params = {}
params["trade_id"] = trade_id
if key is not None:
params["key"] = key
return self._get(f"trades/{trade_id}/custom-data", params=params)
def trade(self, trade_id):
"""Return specific trade
+1 -3
View File
@@ -29,7 +29,7 @@ classifiers = [
dependencies = [
# from requirements.txt
"ccxt>=4.3.24",
"ccxt>=4.4.60",
"SQLAlchemy>=2.0.6",
"python-telegram-bot>=20.1",
"humanize>=4.0.0",
@@ -287,8 +287,6 @@ max-complexity = 12
[tool.ruff.lint.per-file-ignores]
"freqtrade/freqai/**/*.py" = [
"S311", # Standard pseudo-random generators are not suitable for cryptographic purposes
"B006", # Bugbear - mutable default argument
"B008", # bugbear - Do not perform function calls in argument defaults
]
"tests/**/*.py" = [
"S101", # allow assert in tests
+8 -8
View File
@@ -7,26 +7,26 @@
-r docs/requirements-docs.txt
coveralls==4.0.1
ruff==0.9.3
mypy==1.14.1
pre-commit==4.1.0
pytest==8.3.4
pytest-asyncio==0.25.2
ruff==0.11.2
mypy==1.15.0
pre-commit==4.2.0
pytest==8.3.5
pytest-asyncio==0.25.3
pytest-cov==6.0.0
pytest-mock==3.14.0
pytest-random-order==1.1.1
pytest-timeout==2.3.1
pytest-xdist==3.6.1
isort==5.13.2
isort==6.0.1
# For datetime mocking
time-machine==2.16.0
# Convert jupyter notebooks to markdown documents
nbconvert==7.16.5
nbconvert==7.16.6
# mypy types
types-cachetools==5.5.0.20240820
types-filelock==3.2.7
types-requests==2.32.0.20241016
types-requests==2.32.0.20250306
types-tabulate==0.9.0.20241207
types-python-dateutil==2.9.0.20241206
+4 -2
View File
@@ -3,9 +3,11 @@
# Required for freqai-rl
torch==2.2.2; sys_platform == 'darwin' and platform_machine == 'x86_64'
torch==2.5.1; sys_platform != 'darwin' or platform_machine != 'x86_64'
torch==2.6.0; sys_platform != 'darwin' or platform_machine != 'x86_64'
gymnasium==0.29.1
stable_baselines3==2.4.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'
sb3_contrib>=2.2.1
# Progress bar for stable-baselines3 and sb3-contrib
tqdm==4.67.1
+3 -3
View File
@@ -6,7 +6,7 @@
scikit-learn==1.6.1
joblib==1.4.2
catboost==1.2.7; 'arm' not in platform_machine
lightgbm==4.5.0
xgboost==2.1.3
tensorboard==2.18.0
lightgbm==4.6.0
xgboost==2.1.4
tensorboard==2.19.0
datasieve==0.1.7
+2 -2
View File
@@ -2,7 +2,7 @@
-r requirements.txt
# Required for hyperopt
scipy==1.15.1
scipy==1.15.2
scikit-learn==1.6.1
ft-scikit-optimize==0.9.2
filelock==3.17.0
filelock==3.18.0

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