Compare commits

...

273 Commits

Author SHA1 Message Date
Matthias 2152a95451 Merge pull request #10163 from freqtrade/new_release
New release 2024.4
2024-04-30 14:01:30 +02:00
Matthias 39eda1b1dc Bump version to 2024.4 2024-04-30 11:32:00 +02:00
Matthias ad22716374 Merge branch 'stable' into new_release 2024-04-30 11:31:48 +02:00
Matthias 927d9b5d5b Merge pull request #10161 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-04-30 06:20:10 +02:00
xmatthias cb06dd8933 chore: update pre-commit hooks 2024-04-30 03:03:40 +00:00
Matthias 33c0fb3a2c Merge pull request #10160 from froggleston/develop
Add new column selection to ft_rest_client pair_candles
2024-04-29 16:53:00 +02:00
froggleston 9bd5aff879 Add new column selection to ft_rest_client pair_candles 2024-04-29 14:50:48 +01:00
Matthias bc5c27ce6e Merge pull request #10146 from freqtrade/dependabot/pip/develop/pytest-112dcd2f94
Bump the pytest group with 2 updates
2024-04-29 11:13:51 +02:00
Matthias 478cc84c8a Merge pull request #10157 from freqtrade/dependabot/github_actions/develop/docker/login-action-3
Bump docker/login-action from 2 to 3
2024-04-29 10:34:23 +02:00
dependabot[bot] 1805fcac30 Bump the pytest group with 2 updates
Bumps the pytest group with 2 updates: [pytest](https://github.com/pytest-dev/pytest) and [pytest-xdist](https://github.com/pytest-dev/pytest-xdist).


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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 07:58:48 +00:00
Matthias ced9448d7f Merge pull request #10154 from freqtrade/dependabot/pip/develop/ruff-0.4.2
Bump ruff from 0.4.1 to 0.4.2
2024-04-29 09:56:57 +02:00
Matthias 9e0d015b1f Merge pull request #10158 from freqtrade/dependabot/pip/develop/mkdocs-641c6ff211
Bump the mkdocs group with 2 updates
2024-04-29 09:25:48 +02:00
Matthias d3c57ca721 Merge pull request #10156 from freqtrade/dependabot/github_actions/develop/devcontainers/ci-0.3
Bump devcontainers/ci from 0.2 to 0.3
2024-04-29 09:04:24 +02:00
dependabot[bot] cccc4b5b85 Bump docker/login-action from 2 to 3
Bumps [docker/login-action](https://github.com/docker/login-action) from 2 to 3.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](https://github.com/docker/login-action/compare/v2...v3)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 06:41:03 +00:00
Matthias 54ed2f9fd2 Merge pull request #10155 from freqtrade/dependabot/github_actions/develop/actions/checkout-4
Bump actions/checkout from 1 to 4
2024-04-29 08:40:16 +02:00
Matthias 5d723e341d Merge pull request #10153 from freqtrade/dependabot/pip/develop/pydantic-2.7.1
Bump pydantic from 2.7.0 to 2.7.1
2024-04-29 08:39:47 +02:00
dependabot[bot] 81ae8ac830 Bump ruff from 0.4.1 to 0.4.2
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.4.1 to 0.4.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/v0.4.1...v0.4.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 05:52:52 +00:00
Matthias 763dc48606 Merge pull request #10151 from freqtrade/dependabot/pip/develop/mypy-1.10.0
Bump mypy from 1.9.0 to 1.10.0
2024-04-29 07:52:02 +02:00
dependabot[bot] df183c9ccd Bump the mkdocs group with 2 updates
Bumps the mkdocs group with 2 updates: [mkdocs](https://github.com/mkdocs/mkdocs) and [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `mkdocs` from 1.5.3 to 1.6.0
- [Release notes](https://github.com/mkdocs/mkdocs/releases)
- [Commits](https://github.com/mkdocs/mkdocs/compare/1.5.3...1.6.0)

Updates `mkdocs-material` from 9.5.18 to 9.5.19
- [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.18...9.5.19)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 05:50:22 +00:00
Matthias 582032b2e4 Merge pull request #10149 from freqtrade/dependabot/pip/develop/pymdown-extensions-10.8.1
Bump pymdown-extensions from 10.8 to 10.8.1
2024-04-29 07:49:10 +02:00
Matthias 8c4fd4911a Merge pull request #10148 from freqtrade/dependabot/pip/develop/ccxt-4.3.11
Bump ccxt from 4.3.4 to 4.3.11
2024-04-29 07:25:01 +02:00
Matthias 05068fe463 Merge pull request #10147 from freqtrade/dependabot/pip/develop/stable-baselines3-2.3.2
Bump stable-baselines3 from 2.3.0 to 2.3.2
2024-04-29 07:24:43 +02:00
Matthias 6934088a59 Fix some non-working links 2024-04-29 07:12:19 +02:00
Matthias 70396fc346 Fix some non-working links 2024-04-29 06:53:09 +02:00
Matthias a544fb1eb3 Update mkdocs as group 2024-04-29 06:32:45 +02:00
dependabot[bot] 6f1d993721 Bump devcontainers/ci from 0.2 to 0.3
Bumps [devcontainers/ci](https://github.com/devcontainers/ci) from 0.2 to 0.3.
- [Release notes](https://github.com/devcontainers/ci/releases)
- [Commits](https://github.com/devcontainers/ci/compare/v0.2...v0.3)

---
updated-dependencies:
- dependency-name: devcontainers/ci
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 03:43:12 +00:00
dependabot[bot] 18e3673a1b Bump actions/checkout from 1 to 4
Bumps [actions/checkout](https://github.com/actions/checkout) from 1 to 4.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v1...v4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 03:43:09 +00:00
dependabot[bot] e452a2389f Bump pydantic from 2.7.0 to 2.7.1
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.7.0 to 2.7.1.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.7.0...v2.7.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 03:29:50 +00:00
dependabot[bot] 7846361068 Bump mypy from 1.9.0 to 1.10.0
Bumps [mypy](https://github.com/python/mypy) from 1.9.0 to 1.10.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/1.9.0...v1.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 03:29:37 +00:00
dependabot[bot] d6738da6ec Bump pymdown-extensions from 10.8 to 10.8.1
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.8 to 10.8.1.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.8...10.8.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 03:29:22 +00:00
dependabot[bot] e5fadb180d Bump ccxt from 4.3.4 to 4.3.11
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.3.4 to 4.3.11.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.3.4...4.3.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 03:29:16 +00:00
dependabot[bot] 31fb009998 Bump stable-baselines3 from 2.3.0 to 2.3.2
Bumps [stable-baselines3](https://github.com/DLR-RM/stable-baselines3) from 2.3.0 to 2.3.2.
- [Release notes](https://github.com/DLR-RM/stable-baselines3/releases)
- [Commits](https://github.com/DLR-RM/stable-baselines3/compare/v2.3.0...v2.3.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-29 03:29:00 +00:00
Matthias 43aee2147a Update backtest assumption wording
closes #10039
2024-04-28 20:42:14 +02:00
Matthias 6e443a42cd Merge pull request #10143 from freqtrade/feat/pairhistory_advanced
RPC: Advanced pairhistory endpoint
2024-04-28 20:07:24 +02:00
Matthias 1649aca411 Support empty array to return only the base columns 2024-04-28 18:22:37 +02:00
Matthias 71ef6f70e5 Improve test coverage of strategy test 2024-04-28 17:06:36 +02:00
Matthias 9d57e3930d Extend error testing to post call 2024-04-28 17:01:16 +02:00
Matthias ab10379833 Add pair_history post endpoint, too 2024-04-28 16:14:09 +02:00
Matthias ccd788e2ce Improve naming of the schema 2024-04-28 16:14:09 +02:00
Matthias 8c39740105 Test Post pair_candles endpoint 2024-04-28 16:14:09 +02:00
Matthias eeaa9061e5 Adapt test for to also test post endpoint 2024-04-28 16:14:09 +02:00
Matthias 3e20770446 Slightly change returned column scheme 2024-04-28 16:14:09 +02:00
Matthias 54d467d2bf Add Post endpoint for to filter dataframe by columns 2024-04-28 16:14:09 +02:00
Matthias cabdfabf03 Eliminate duplicate word in wallets docstring 2024-04-28 15:16:45 +02:00
Matthias d0624570af Merge pull request #10142 from goodmost/develop
chore: remove repetitive words
2024-04-28 15:07:03 +02:00
goodmost 38b35ec528 chore: remove repetitive words
Signed-off-by: goodmost <zhaohaiyang@outlook.com>
2024-04-28 18:14:19 +08:00
Matthias 21b1f5aab8 Update wording 2024-04-28 09:31:28 +02:00
Matthias 37da714610 Don't set zsh as default shell 2024-04-27 15:21:42 +02:00
Matthias bd608bedf0 Use devcontainer features for most setup work 2024-04-27 11:55:37 +00:00
Matthias ad370cbbcd Add github action to pre-build containers 2024-04-27 09:37:25 +00:00
Matthias 6d46d17e30 Add pre-built devcontainer 2024-04-27 09:37:08 +00:00
Matthias bc5ded4400 Improve devcontainer Dockerfile 2024-04-27 11:29:24 +02:00
Matthias 19284e1e91 Update vscode debug config for new mode 2024-04-27 10:12:44 +02:00
Matthias e17258940a Add isort to devcontainer proposals 2024-04-27 07:13:05 +00:00
Matthias 52403ceacd add GHA extension to devcontainer 2024-04-27 07:13:05 +00:00
Matthias 3b41c7e7f3 Fix devcontainer config (use vscode submenu) 2024-04-27 07:13:05 +00:00
Matthias 73905b33ad Ensure consistent program naming
(`python -m freqtrade --version`)
2024-04-27 08:48:40 +02:00
Matthias dd04c51c42 Use time() instead of creating a temporary time object 2024-04-26 20:28:59 +02:00
Matthias 9bc866e6b2 Loader should be passed as kwarg for clarity 2024-04-26 08:36:10 +02:00
Matthias 33454e2f23 Merge pull request #10135 from Pixee-Bot-Python/pixeebot/drip-2023-11-14-pixee-python/harden-pyyaml
Use SafeLoader in yaml.load() Calls
2024-04-26 08:35:01 +02:00
Pixee OSS Assistant c37a0706d7 Merge branch 'freqtrade:develop' into pixeebot/drip-2023-11-14-pixee-python/harden-pyyaml 2024-04-25 17:48:04 -04:00
Matthias 8e5c574224 Don't handle case where base_currency is not set
(very old trades)
2024-04-25 19:56:27 +02:00
Matthias a3e6abef8d Add test for "trade amount adjust" behavior 2024-04-25 13:44:01 +02:00
Matthias adbf1b5e6f Adjust trade amount by 2% if trade recovery remains above the trade amount
closes #10002
2024-04-25 13:43:46 +02:00
Matthias 0a89efd002 Only cancel stoploss if really necessary
partial workaround for #10002
2024-04-25 11:43:24 +02:00
Matthias 35408966f4 Fix remaining user_data leaks 2024-04-25 11:02:34 +02:00
Matthias ad12c98d10 Avoid having test-result influence from user_data/
this avoids potential deprecation warnings (or even errors)
 caused by strategies in user_data/strategies
2024-04-25 10:48:27 +02:00
Matthias 96bb4db68e Fix remaining coingecko spellings 2024-04-25 10:28:25 +02:00
Matthias 2d9be6c818 Fix some coingecko spellings 2024-04-25 10:27:58 +02:00
Matthias 3ca0be7543 Fix misspelled coingecko variable 2024-04-25 10:26:33 +02:00
Matthias d6dcd8adca use explicit macos version
macos-latest changed to align to macos-14 - so it's no longer capable of running python 3.9
2024-04-25 08:18:21 +02:00
Matthias 5f1ab7508f Add idex subclass defining the applicable ohlcv limit 2024-04-25 08:18:21 +02:00
Matthias 2906f2f8e0 Merge pull request #10132 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-04-25 08:10:26 +02:00
xmatthias 652819309d chore: update pre-commit hooks 2024-04-25 03:02:27 +00:00
Pixee OSS Assistant 1901da688e Merge pull request #1 from Pixee-Bot-Python/pixeebot/drip-2023-11-14-pixee-python/harden-pyyaml
Use SafeLoader in `yaml.load()` Calls
2024-04-24 18:33:16 -04:00
Matthias 365b9c9cfe kwargs for re.sub count 2024-04-24 07:02:26 +02:00
Matthias fa6c17c067 Remove mutable default-args from generate-candlestick 2024-04-24 07:00:17 +02:00
Matthias 86f3b649b9 Update security warning in docker section 2024-04-24 06:42:17 +02:00
Matthias 115cb4bf40 Don't use datetime.utcnow ... 2024-04-23 20:08:28 +02:00
Matthias 226eaf0d0a Filter deprecation warning - it's a dependency ... 2024-04-23 19:52:44 +02:00
Matthias 6113e47991 Merge pull request #10090 from CoolCu/develop
chore: remove repetitive words
2024-04-23 19:29:38 +02:00
Matthias 7b78912d71 Merge pull request #10105 from freqtrade/feat/humanize
Replace arrow with humanize
2024-04-23 09:46:35 +02:00
Matthias 81d80ff5c7 Merge pull request #10124 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-04-23 07:04:57 +02:00
xmatthias 914d630a22 chore: update pre-commit hooks 2024-04-23 03:03:47 +00:00
Matthias 9441eb9b53 Merge pull request #10115 from freqtrade/dependabot/pip/develop/pymdown-extensions-10.8
Bump pymdown-extensions from 10.7.1 to 10.8
2024-04-22 16:41:15 +02:00
Matthias b7f65a77c7 Merge pull request #10111 from freqtrade/dependabot/pip/develop/ruff-0.4.1
Bump ruff from 0.3.7 to 0.4.1
2024-04-22 15:56:30 +02:00
Matthias a750e4c107 Merge pull request #10108 from freqtrade/dependabot/pip/develop/pyarrow-16.0.0
Bump pyarrow from 15.0.2 to 16.0.0
2024-04-22 15:46:20 +02:00
Matthias fd5a4d71a0 Merge pull request #10114 from freqtrade/dependabot/pip/develop/aiohttp-3.9.5
Bump aiohttp from 3.9.4 to 3.9.5
2024-04-22 12:07:38 +02:00
Matthias 73c9b04dcd Update pyarrow prebuilt wheels 2024-04-22 11:04:10 +02:00
Matthias aaee5a2abf Merge pull request #10118 from freqtrade/dependabot/pip/develop/plotly-5.21.0
Bump plotly from 5.20.0 to 5.21.0
2024-04-22 10:22:51 +02:00
Matthias 6f2ec3ced2 Merge pull request #10117 from freqtrade/dependabot/pip/develop/fastapi-0.110.2
Bump fastapi from 0.110.1 to 0.110.2
2024-04-22 09:50:14 +02:00
dependabot[bot] 7e78a103cf Bump aiohttp from 3.9.4 to 3.9.5
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.9.4 to 3.9.5.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.9.4...v3.9.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 07:11:35 +00:00
Matthias aaab37cf52 Merge pull request #10116 from freqtrade/dependabot/pip/develop/python-telegram-bot-21.1.1
Bump python-telegram-bot from 21.1 to 21.1.1
2024-04-22 09:10:43 +02:00
Matthias 1167c078cf Merge pull request #10113 from freqtrade/dependabot/pip/develop/catboost-1.2.5
Bump catboost from 1.2.3 to 1.2.5
2024-04-22 08:46:34 +02:00
Matthias 1223862ede Merge pull request #10112 from freqtrade/dependabot/pip/develop/orjson-3.10.1
Bump orjson from 3.10.0 to 3.10.1
2024-04-22 08:01:17 +02:00
dependabot[bot] 3842b4697f Bump pymdown-extensions from 10.7.1 to 10.8
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.7.1 to 10.8.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.7.1...10.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 05:18:03 +00:00
Matthias a095aa2374 Merge pull request #10110 from freqtrade/dependabot/pip/develop/ccxt-4.3.4
Bump ccxt from 4.2.97 to 4.3.4
2024-04-22 07:18:01 +02:00
Matthias d4c37fb59d Merge pull request #10107 from freqtrade/dependabot/pip/develop/mkdocs-material-9.5.18
Bump mkdocs-material from 9.5.17 to 9.5.18
2024-04-22 07:17:14 +02:00
Matthias 472d5d8703 Improve format_ms_time logic by reusing dt_from_ts 2024-04-22 07:08:49 +02:00
dependabot[bot] 6f3bb7f2f8 Bump plotly from 5.20.0 to 5.21.0
Bumps [plotly](https://github.com/plotly/plotly.py) from 5.20.0 to 5.21.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.20.0...v5.21.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 03:17:14 +00:00
dependabot[bot] ac5cd778ca Bump fastapi from 0.110.1 to 0.110.2
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.110.1 to 0.110.2.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.110.1...0.110.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 03:17:10 +00:00
dependabot[bot] 938259ffde Bump python-telegram-bot from 21.1 to 21.1.1
Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 21.1 to 21.1.1.
- [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases)
- [Changelog](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/CHANGES.rst)
- [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v21.1...v21.1.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 03:17:03 +00:00
dependabot[bot] 8f26793b7a Bump catboost from 1.2.3 to 1.2.5
Bumps [catboost](https://github.com/catboost/catboost) from 1.2.3 to 1.2.5.
- [Release notes](https://github.com/catboost/catboost/releases)
- [Changelog](https://github.com/catboost/catboost/blob/master/RELEASE.md)
- [Commits](https://github.com/catboost/catboost/compare/v1.2.3...v1.2.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 03:16:44 +00:00
dependabot[bot] 8888fd190a Bump orjson from 3.10.0 to 3.10.1
Bumps [orjson](https://github.com/ijl/orjson) from 3.10.0 to 3.10.1.
- [Release notes](https://github.com/ijl/orjson/releases)
- [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ijl/orjson/compare/3.10.0...3.10.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 03:16:39 +00:00
dependabot[bot] 4fee0eae6a Bump ruff from 0.3.7 to 0.4.1
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.3.7 to 0.4.1.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.3.7...v0.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 03:16:32 +00:00
dependabot[bot] 8932aab20b Bump ccxt from 4.2.97 to 4.3.4
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.2.97 to 4.3.4.
- [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.2.97...4.3.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 03:16:21 +00:00
dependabot[bot] 12ae190fef Bump pyarrow from 15.0.2 to 16.0.0
Bumps [pyarrow](https://github.com/apache/arrow) from 15.0.2 to 16.0.0.
- [Commits](https://github.com/apache/arrow/compare/go/v15.0.2...go/v16.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 03:16:02 +00:00
dependabot[bot] 98807b100c Bump mkdocs-material from 9.5.17 to 9.5.18
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.17 to 9.5.18.
- [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.17...9.5.18)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 03:15:57 +00:00
Matthias 03835350c0 Remove arrow as dependency
it seems to be no longer maintained
2024-04-21 15:52:02 +02:00
Matthias 54c2febe1c Remove dt_humanize 2024-04-21 15:51:08 +02:00
Matthias d48cff3b9a Update to dt_humanize_delta for all usages 2024-04-21 15:49:50 +02:00
Matthias cc534c5000 switch from arrow to humanize 2024-04-21 15:29:16 +02:00
Matthias 4320c7a3cc Add humanize as dependency 2024-04-21 14:04:50 +02:00
Matthias e52caa2a36 Merge pull request #10103 from freqtrade/robcaulk-patch-1
docs: update freqai-parameter-table.md
2024-04-21 13:43:09 +02:00
Robert Caulk 6b15ebd4c2 docs: update freqai-parameter-table.md
Correct parameter table for label_period_candles
2024-04-21 13:14:18 +02:00
Matthias c802d39f67 Merge pull request #10097 from freqtrade/feat/measure_time
Add "measure time" to warn users about strategies taking excessive time
2024-04-21 11:28:54 +02:00
Matthias 608088bc03 Simplify trade_statistics 2024-04-20 10:30:04 +02:00
Matthias b9b044585d Add but comment a few ruff rules 2024-04-20 10:25:23 +02:00
Matthias d4ce774532 Add requests timeout to deploy commands 2024-04-20 10:25:05 +02:00
Matthias a375e686de Add some ruff configurations 2024-04-20 10:13:14 +02:00
Matthias 08f2eebd6e Extract httpBasic from function header 2024-04-20 10:12:06 +02:00
Matthias 927c54094a Improve some bugbear findings 2024-04-20 10:10:36 +02:00
Matthias 6dfc34d227 Fix test due to missing assert 2024-04-20 09:46:40 +02:00
Matthias 6a1c33e168 Don't use setattr 2024-04-20 09:46:32 +02:00
Matthias e0aefb9b54 Fix faulty asserts in freqai tests 2024-04-20 09:39:43 +02:00
Matthias df712c0168 Fix some faulty assertions 2024-04-20 09:39:11 +02:00
Matthias 53c548f7b6 Fix test to actually work 2024-04-20 09:35:05 +02:00
Matthias 1abd8fb8fc Fix wallets test 2024-04-20 09:30:55 +02:00
Matthias 7c3feeddc4 Fix faulty assert statements 2024-04-20 09:30:20 +02:00
Matthias 0c99ff7f66 Fix more default arg usages 2024-04-20 09:26:50 +02:00
Matthias a078088ea3 Fix some more default argument usage in exchange classes 2024-04-20 09:24:51 +02:00
Matthias 31f6030c67 Reduce some mutable default argument usage 2024-04-20 09:22:28 +02:00
Matthias 8004829696 Further reduce problematic default args 2024-04-20 09:15:11 +02:00
Matthias 448b74840e Don't use mutable default arguments to functions 2024-04-20 09:09:42 +02:00
Matthias 9b1def604f Fix mutable arg in freqtradebot 2024-04-20 09:08:04 +02:00
Matthias 3d4250ca99 Fix type error 2024-04-20 09:04:23 +02:00
Matthias 6a1ca75a69 Add codespell to pre-commit hooks 2024-04-19 18:12:24 +02:00
Matthias 22c61458a4 Fix codespell in ci.yml 2024-04-19 18:12:10 +02:00
Matthias f2cfb127ae Merge pull request #10099 from cclauss/codespell
Fix typos discovered by codespell
2024-04-19 07:37:29 +02:00
Matthias be1e1453da Further mutable-default fix to template-renderer 2024-04-19 07:27:10 +02:00
Matthias 87d50243e2 Update tests 2024-04-19 07:25:41 +02:00
Matthias a451b94387 Remove mutable default from template-rendere 2024-04-19 07:24:11 +02:00
Matthias a41b38e94f Add configuration for codespell 2024-04-19 07:02:39 +02:00
Matthias 40aa0800de Additional fix to eliminate some word-ignores 2024-04-19 07:02:31 +02:00
Matthias 862d4c8857 Revert relationship to "selectin" 2024-04-19 06:36:32 +02:00
Matthias 37feede6a5 Fix some codespell introduced syntax errors 2024-04-19 06:32:59 +02:00
Christian Clauss 99b4767bf4 Fix typos discovered by codespell 2024-04-18 22:51:25 +02:00
Matthias 0a7ed55ade Reduce duplicate warning 2024-04-18 21:01:48 +02:00
Matthias 298f9b225d Fix broken sample of @informative decorator
closes #7095
2024-04-18 19:08:17 +02:00
Matthias bc85352098 Split timeframe_to_secs from loggingmixin init 2024-04-18 06:55:05 +02:00
Matthias 6a570bd82e Fix excessive whitespace 2024-04-18 06:41:14 +02:00
Matthias a15793a6ba Merge pull request #10096 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-04-18 06:31:46 +02:00
xmatthias 77682808ea chore: update pre-commit hooks 2024-04-18 03:02:26 +00:00
Matthias 82482ec159 Fix / improve types 2024-04-17 20:58:58 +02:00
Matthias 5eeb96fa96 Add test for measure_time 2024-04-17 20:57:18 +02:00
Matthias c6a50b3c06 Use MeasureTime to warn if a strategy takes too long
using 25% of the candle as reference time for now.
2024-04-17 20:53:49 +02:00
Matthias 3c8366e609 Add measure_time module to measure time taken by functions 2024-04-17 20:52:37 +02:00
Matthias 2297b3ed55 Split timeframe_to_secs from loggingmixin init 2024-04-17 20:13:02 +02:00
Matthias f8a2569739 Merge pull request #10093 from freqtrade/feat/btmarketchange
Market change visualization
2024-04-17 18:55:16 +02:00
Matthias 393d4b8eb3 Add test for market_change endpoint 2024-04-17 07:22:36 +02:00
Matthias 3dd7c1e492 Add test for market_change writing 2024-04-17 07:01:32 +02:00
Matthias 0b1f702ef9 Improve testcase 2024-04-17 06:46:44 +02:00
Matthias 54d9dbaea8 Formatting 2024-04-16 20:54:12 +02:00
Matthias ba27c41c93 Support market-change for "online" backtesting 2024-04-16 20:53:46 +02:00
Matthias d7920c4b64 Simplify backtest storage 2024-04-16 20:53:06 +02:00
Matthias 3338fdece3 Relative profit should be relative cumulative profit 2024-04-16 20:34:07 +02:00
Matthias 523054e8ea Add endpoint to fetch market_change data 2024-04-16 19:27:55 +02:00
Matthias c8a5904959 Store and load backtest-market-change data 2024-04-16 19:27:41 +02:00
Matthias 7f386874ad Trim dataframes accordingly ... 2024-04-16 18:17:20 +02:00
Matthias 18a4d6972d generate_filename should be private 2024-04-16 18:02:00 +02:00
Matthias a0a22f62e0 Update typehint for backtesting 2024-04-16 18:00:24 +02:00
CoolCu 7fc1d38ffb chore: remove repetitive words
Signed-off-by: CoolCu <coolcui@qq.com>
2024-04-16 15:55:36 +08:00
Matthias 3f8078618e add Combine dataframes with pct_change 2024-04-16 07:19:26 +02:00
Matthias e7b907a175 Enhance / simplify bt-storage logic
Removes repeated filename generation pattern
2024-04-16 06:57:58 +02:00
Matthias 7796884119 Merge pull request #10087 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-04-16 06:50:28 +02:00
xmatthias 264669df1d chore: update pre-commit hooks 2024-04-16 03:04:21 +00:00
Matthias eddca5a694 Merge pull request #10083 from freqtrade/dependabot/pip/develop/pandas-2.2.2
Bump pandas from 2.2.1 to 2.2.2
2024-04-15 18:09:23 +02:00
Matthias ca758c28b7 Merge pull request #10084 from freqtrade/dependabot/pip/develop/scikit-learn-1.4.2
Bump scikit-learn from 1.4.1.post1 to 1.4.2
2024-04-15 16:08:30 +02:00
dependabot[bot] 39ef97a2ac Bump pandas from 2.2.1 to 2.2.2
Bumps [pandas](https://github.com/pandas-dev/pandas) from 2.2.1 to 2.2.2.
- [Release notes](https://github.com/pandas-dev/pandas/releases)
- [Commits](https://github.com/pandas-dev/pandas/compare/v2.2.1...v2.2.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 07:38:58 +00:00
Matthias 8f78329a23 Merge pull request #10086 from freqtrade/dependabot/pip/develop/aiohttp-3.9.4
Bump aiohttp from 3.9.3 to 3.9.4
2024-04-15 09:37:11 +02:00
dependabot[bot] 409a6236d4 Bump scikit-learn from 1.4.1.post1 to 1.4.2
Bumps [scikit-learn](https://github.com/scikit-learn/scikit-learn) from 1.4.1.post1 to 1.4.2.
- [Release notes](https://github.com/scikit-learn/scikit-learn/releases)
- [Commits](https://github.com/scikit-learn/scikit-learn/compare/1.4.1.post1...1.4.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 06:58:06 +00:00
Matthias 42fb9ad281 Merge pull request #10085 from freqtrade/dependabot/pip/develop/joblib-1.4.0
Bump joblib from 1.3.2 to 1.4.0
2024-04-15 08:57:09 +02:00
Matthias 4946e5978e Merge pull request #10082 from freqtrade/dependabot/pip/develop/pydantic-2.7.0
Bump pydantic from 2.6.4 to 2.7.0
2024-04-15 08:15:48 +02:00
Matthias f0606bfe90 Merge pull request #10081 from freqtrade/dependabot/pip/develop/ruff-0.3.7
Bump ruff from 0.3.5 to 0.3.7
2024-04-15 08:14:53 +02:00
dependabot[bot] 4df847fa0f Bump aiohttp from 3.9.3 to 3.9.4
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.9.3 to 3.9.4.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.9.3...v3.9.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 05:34:49 +00:00
Matthias c0a684ee7c Merge pull request #10080 from freqtrade/dependabot/pip/develop/ccxt-4.2.97
Bump ccxt from 4.2.91 to 4.2.97
2024-04-15 07:34:15 +02:00
Matthias c74faec6d0 Merge pull request #10079 from freqtrade/dependabot/pip/develop/filelock-3.13.4
Bump filelock from 3.13.3 to 3.13.4
2024-04-15 07:33:44 +02:00
Matthias a266c0832b Merge pull request #10078 from freqtrade/dependabot/pip/develop/python-telegram-bot-21.1
Bump python-telegram-bot from 21.0.1 to 21.1
2024-04-15 07:33:38 +02:00
Matthias e0d0b17583 Merge pull request #10077 from freqtrade/fix/ensure-high-price-included
Ensure we include `high_price` in the historic predictions
2024-04-15 06:42:28 +02:00
dependabot[bot] abfed990d9 Bump joblib from 1.3.2 to 1.4.0
Bumps [joblib](https://github.com/joblib/joblib) from 1.3.2 to 1.4.0.
- [Release notes](https://github.com/joblib/joblib/releases)
- [Changelog](https://github.com/joblib/joblib/blob/main/CHANGES.rst)
- [Commits](https://github.com/joblib/joblib/compare/1.3.2...1.4.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 03:48:28 +00:00
dependabot[bot] 183c82903f Bump pydantic from 2.6.4 to 2.7.0
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.6.4 to 2.7.0.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.6.4...v2.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 03:48:04 +00:00
dependabot[bot] 3225c051a2 Bump ruff from 0.3.5 to 0.3.7
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.3.5 to 0.3.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/v0.3.5...v0.3.7)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 03:47:59 +00:00
dependabot[bot] a4f2ea810d Bump ccxt from 4.2.91 to 4.2.97
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.2.91 to 4.2.97.
- [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.2.91...4.2.97)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 03:47:47 +00:00
dependabot[bot] caf50c0bd0 Bump filelock from 3.13.3 to 3.13.4
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.13.3 to 3.13.4.
- [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.13.3...3.13.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-15 03:47:32 +00:00
dependabot[bot] 76814250e4 Bump python-telegram-bot from 21.0.1 to 21.1
Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 21.0.1 to 21.1.
- [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases)
- [Changelog](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/CHANGES.rst)
- [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v21.0.1...v21.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>
2024-04-15 03:47:27 +00:00
robcaulk 7a401415e5 fix: for some reason we werent including high_price 2024-04-14 14:19:12 +02:00
Matthias 65654a4a44 Update ccxt exception handlers
OperationFailed is the new NetworkError

closes #10071
2024-04-13 11:12:10 +02:00
Matthias 0c7e89aede Update tests for OperationFailed error 2024-04-13 11:11:09 +02:00
Matthias 094bbb3a50 Remove pointless docstring comment 2024-04-11 21:02:54 +02:00
Matthias df2432bc18 Re-align adjust_trade_position bt with live. 2024-04-11 20:57:03 +02:00
Matthias e09abfdc75 Split enter and exit rows to use different values 2024-04-11 20:54:16 +02:00
Matthias eaec5065a1 Define liquidation price only once in adjustment test 2024-04-11 20:53:58 +02:00
Matthias 6fa38e4d69 Merge pull request #10064 from freqtrade/dependabot/docker/python-3.12.3-slim-bookworm
Bump python from 3.12.2-slim-bookworm to 3.12.3-slim-bookworm
2024-04-11 06:28:48 +02:00
Matthias 00b80a8b0d Merge pull request #10063 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-04-11 06:28:33 +02:00
dependabot[bot] 9e55690ab3 Bump python from 3.12.2-slim-bookworm to 3.12.3-slim-bookworm
Bumps python from 3.12.2-slim-bookworm to 3.12.3-slim-bookworm.

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-11 03:10:33 +00:00
xmatthias 78535bc7e8 chore: update pre-commit hooks 2024-04-11 03:02:25 +00:00
Matthias 24ba337a56 Merge pull request #10050 from freqtrade/fix/pandas-future-warnings
FreqAI pandas futures warnings
2024-04-10 19:26:06 +02:00
Matthias e5f3d04437 Add some imports
closes #10053
2024-04-09 19:26:52 +02:00
robcaulk 081e50ec48 fix: ensure full hist-preds is kept 2024-04-09 17:17:11 +02:00
Matthias 859dd6b7ec Merge pull request #10061 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-04-09 06:31:00 +02:00
xmatthias c924399e96 chore: update pre-commit hooks 2024-04-09 03:03:47 +00:00
Matthias 3073e8810f Slightly update developer docs 2024-04-08 19:22:09 +02:00
Matthias c1726c4401 Merge pull request #10055 from freqtrade/dependabot/pip/develop/types-7a5049024f
Bump types-requests from 2.31.0.20240311 to 2.31.0.20240406 in the types group
2024-04-08 08:15:41 +02:00
Matthias b1f540964c Merge pull request #10059 from freqtrade/dependabot/pip/develop/scipy-1.13.0
Bump scipy from 1.12.0 to 1.13.0
2024-04-08 07:57:57 +02:00
Matthias 6d9a1a9b98 Merge pull request #10058 from freqtrade/dependabot/pip/develop/ccxt-4.2.91
Bump ccxt from 4.2.87 to 4.2.91
2024-04-08 07:34:49 +02:00
Matthias 811c66e8dc Merge pull request #10057 from freqtrade/dependabot/pip/develop/fastapi-0.110.1
Bump fastapi from 0.110.0 to 0.110.1
2024-04-08 06:50:27 +02:00
Matthias 4c3df2f73d Merge pull request #10056 from freqtrade/dependabot/pip/develop/mkdocs-material-9.5.17
Bump mkdocs-material from 9.5.16 to 9.5.17
2024-04-08 06:50:10 +02:00
Matthias d5bb2e4eb1 types-requests pre-commit update 2024-04-08 06:33:13 +02:00
dependabot[bot] 16045870a8 Bump scipy from 1.12.0 to 1.13.0
Bumps [scipy](https://github.com/scipy/scipy) from 1.12.0 to 1.13.0.
- [Release notes](https://github.com/scipy/scipy/releases)
- [Commits](https://github.com/scipy/scipy/compare/v1.12.0...v1.13.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 03:42:05 +00:00
dependabot[bot] f741f95c36 Bump ccxt from 4.2.87 to 4.2.91
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.2.87 to 4.2.91.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.2.87...4.2.91)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 03:41:44 +00:00
dependabot[bot] 564d77f239 Bump fastapi from 0.110.0 to 0.110.1
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.110.0 to 0.110.1.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.110.0...0.110.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 03:41:36 +00:00
dependabot[bot] 095150f6b2 Bump mkdocs-material from 9.5.16 to 9.5.17
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.16 to 9.5.17.
- [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.16...9.5.17)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-08 03:41:32 +00:00
dependabot[bot] c935bd057e 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.31.0.20240311 to 2.31.0.20240406
- [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>
2024-04-08 03:40:49 +00:00
robcaulk d8aaaa34d3 fix: pandas. 2024-04-07 14:20:52 +02:00
robcaulk 08d0e76187 fix: pandas. 2024-04-07 13:50:59 +02:00
Robert Caulk 24717b1609 Merge pull request #10029 from freqtrade/dependabot/pip/develop/stable-baselines3-2.3.0
Bump stable-baselines3 from 2.2.1 to 2.3.0
2024-04-07 00:22:06 +02:00
robcaulk 6a8abd5d45 fix: astype handling 2024-04-06 23:43:29 +02:00
robcaulk b19b4d001c fix: ensure future pandas will be happy 2024-04-06 23:34:07 +02:00
Matthias 8c12a1fd8b Rename TRADING_MODES constant to avoid naming collision 2024-04-06 16:47:55 +02:00
Matthias a584854d46 Update docs to align with actual workings. 2024-04-05 20:15:43 +02:00
Matthias 1df21d3cdf Pin pandas version to < 3.0 2024-04-04 19:28:41 +02:00
Matthias e6da491cac Merge pull request #10044 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-04-04 06:29:59 +02:00
xmatthias d4872eae16 chore: update pre-commit hooks 2024-04-04 03:02:18 +00:00
Matthias 569a87ca28 Fix site Url string to have a working 404 page 2024-04-03 18:10:49 +02:00
Matthias 4affb1eade Fix wrong site url string 2024-04-03 18:05:48 +02:00
Matthias 15cbca51e1 Test with dev url 2024-04-03 07:03:24 +02:00
Matthias 2980503493 use "/en/stable" as mkdocs site url 2024-04-03 07:01:23 +02:00
Matthias 8d7a21e9d3 Merge pull request #10037 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-04-02 07:16:04 +02:00
Matthias 1cbd3f52f1 Run online-tests against bingx 2024-04-02 07:15:14 +02:00
Matthias b284abf9e0 Add bingx exchange subclass 2024-04-02 07:14:52 +02:00
Matthias 55c0cfefec Add missing typehint to telegram class 2024-04-02 07:14:30 +02:00
Matthias 9de4731585 Bump ruff to 0.3.5 2024-04-02 06:42:53 +02:00
xmatthias 2ef854741c chore: update pre-commit hooks 2024-04-02 03:03:32 +00:00
Matthias 7bb6c5e7bd Merge pull request #10032 from freqtrade/dependabot/pip/develop/ast-comments-1.2.2
Bump ast-comments from 1.2.1 to 1.2.2
2024-04-01 08:50:41 +02:00
Matthias fb5f81e3ca Merge pull request #10028 from freqtrade/dependabot/pip/develop/mkdocs-material-9.5.16
Bump mkdocs-material from 9.5.15 to 9.5.16
2024-04-01 07:54:53 +02:00
Matthias 4cbbc97b2b Merge pull request #10027 from freqtrade/dependabot/pip/develop/filelock-3.13.3
Bump filelock from 3.13.1 to 3.13.3
2024-04-01 07:42:19 +02:00
Matthias eccedaf6c9 Merge pull request #10033 from freqtrade/dependabot/pip/develop/torch-2.2.2
Bump torch from 2.2.1 to 2.2.2
2024-04-01 07:42:12 +02:00
Matthias 500dfec5c0 Merge pull request #10030 from freqtrade/dependabot/pip/develop/ccxt-4.2.87
Bump ccxt from 4.2.82 to 4.2.87
2024-04-01 07:40:46 +02:00
Matthias d5b4740023 Merge pull request #10031 from freqtrade/dependabot/pip/develop/orjson-3.10.0
Bump orjson from 3.9.15 to 3.10.0
2024-04-01 07:40:26 +02:00
dependabot[bot] 62a15580ce Bump torch from 2.2.1 to 2.2.2
Bumps [torch](https://github.com/pytorch/pytorch) from 2.2.1 to 2.2.2.
- [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.1...v2.2.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 03:14:47 +00:00
dependabot[bot] 41db2aec71 Bump ast-comments from 1.2.1 to 1.2.2
Bumps [ast-comments](https://github.com/t3rn0/ast-comments) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/t3rn0/ast-comments/releases)
- [Commits](https://github.com/t3rn0/ast-comments/compare/1.2.1...1.2.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 03:14:38 +00:00
dependabot[bot] 04a25e0f39 Bump orjson from 3.9.15 to 3.10.0
Bumps [orjson](https://github.com/ijl/orjson) from 3.9.15 to 3.10.0.
- [Release notes](https://github.com/ijl/orjson/releases)
- [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ijl/orjson/compare/3.9.15...3.10.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 03:14:35 +00:00
dependabot[bot] 1d8ebc7ec1 Bump ccxt from 4.2.82 to 4.2.87
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.2.82 to 4.2.87.
- [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.2.82...4.2.87)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 03:14:27 +00:00
dependabot[bot] 66a05ee672 Bump stable-baselines3 from 2.2.1 to 2.3.0
Bumps [stable-baselines3](https://github.com/DLR-RM/stable-baselines3) from 2.2.1 to 2.3.0.
- [Release notes](https://github.com/DLR-RM/stable-baselines3/releases)
- [Commits](https://github.com/DLR-RM/stable-baselines3/compare/v2.2.1...v2.3.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 03:14:19 +00:00
dependabot[bot] b076ec6c8e Bump mkdocs-material from 9.5.15 to 9.5.16
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.5.15 to 9.5.16.
- [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.15...9.5.16)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 03:14:12 +00:00
dependabot[bot] b277372da4 Bump filelock from 3.13.1 to 3.13.3
Bumps [filelock](https://github.com/tox-dev/py-filelock) from 3.13.1 to 3.13.3.
- [Release notes](https://github.com/tox-dev/py-filelock/releases)
- [Changelog](https://github.com/tox-dev/filelock/blob/main/docs/changelog.rst)
- [Commits](https://github.com/tox-dev/py-filelock/compare/3.13.1...3.13.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-04-01 03:14:07 +00:00
Matthias 7b6e72ab57 Update download-artifact to pattern. 2024-03-31 17:35:13 +02:00
Matthias fcfd25d50b Merge pull request #10023 from freqtrade/feat/lock_api
Add lock post endpoint
2024-03-31 14:05:05 +02:00
Matthias d6aa7f1b8b Merge pull request #10022 from freqtrade/align-exitreasons
Align exitreasons, remove duplicated code
2024-03-31 14:04:16 +02:00
Matthias d112a8c0ed Bump version to 2024.4-dev 2024-03-31 09:58:35 +02:00
Matthias dcb9d5d611 Ft Rest client - fix typing 2024-03-31 09:37:52 +02:00
Matthias b1c83ce189 Exclude build dir from mypy 2024-03-31 09:35:17 +02:00
Matthias 8fae64f373 Update typehints 2024-03-30 18:31:09 +01:00
Matthias 747266fe42 Remove unused imports 2024-03-30 18:14:51 +01:00
Matthias ad06f93501 Add locks_add to rest api client 2024-03-30 14:37:34 +01:00
Matthias f4074d2960 Add tests for lock endpoints 2024-03-30 14:30:00 +01:00
Matthias 05f8bb357f Add Lock post endpoint
(allows manual locking through API)

closes #10018
2024-03-30 14:29:58 +01:00
Matthias 7b5e444333 Improve code stability 2024-03-30 13:36:19 +01:00
Matthias 0906f050e5 Fix remaining tests 2024-03-30 13:28:13 +01:00
Matthias 1551f92832 Fix a few tests 2024-03-30 13:20:53 +01:00
Matthias be26e31235 Remove obsolete code, improve resilience 2024-03-30 13:20:43 +01:00
Matthias 36f1111d92 Remove custom handling for exit reason stats.
It's not different from regular tag outputs, really
2024-03-30 13:11:59 +01:00
pixeebot[bot] 0bc84c6a0d Use SafeLoader in yaml.load() Calls 2023-11-14 03:05:54 +00:00
149 changed files with 4362 additions and 1326 deletions
-22
View File
@@ -1,22 +0,0 @@
FROM freqtradeorg/freqtrade:develop_freqairl
USER root
# Install dependencies
COPY requirements-dev.txt /freqtrade/
RUN apt-get update \
&& apt-get -y install --no-install-recommends apt-utils dialog \
&& apt-get -y install --no-install-recommends git sudo vim build-essential \
&& apt-get clean \
&& mkdir -p /home/ftuser/.vscode-server /home/ftuser/.vscode-server-insiders /home/ftuser/commandhistory \
&& echo "export PROMPT_COMMAND='history -a'" >> /home/ftuser/.bashrc \
&& echo "export HISTFILE=~/commandhistory/.bash_history" >> /home/ftuser/.bashrc \
&& chown ftuser:ftuser -R /home/ftuser/.local/ \
&& chown ftuser: -R /home/ftuser/
USER ftuser
RUN pip install --user autopep8 -r docs/requirements-docs.txt -r requirements-dev.txt --no-cache-dir
# Empty the ENTRYPOINT to allow all commands
ENTRYPOINT []
+21 -26
View File
@@ -1,42 +1,37 @@
{
"name": "freqtrade Develop",
"build": {
"dockerfile": "Dockerfile",
"context": ".."
},
"image": "ghcr.io/freqtrade/freqtrade-devcontainer:latest",
// Use 'forwardPorts' to make a list of ports inside the container available locally.
"forwardPorts": [
8080
],
"mounts": [
"source=freqtrade-bashhistory,target=/home/ftuser/commandhistory,type=volume"
],
"workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/freqtrade,type=bind,consistency=cached",
// Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "ftuser",
"onCreateCommand": "pip install --user -e .",
"postCreateCommand": "freqtrade create-userdir --userdir user_data/",
"workspaceFolder": "/workspaces/freqtrade",
"customizations": {
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"editor.insertSpaces": true,
"files.trimTrailingWhitespace": true,
"[markdown]": {
"files.trimTrailingWhitespace": false,
"vscode": {
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"editor.insertSpaces": true,
"files.trimTrailingWhitespace": true,
"[markdown]": {
"files.trimTrailingWhitespace": false,
},
"python.pythonPath": "/usr/local/bin/python",
},
"python.pythonPath": "/usr/local/bin/python",
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"davidanson.vscode-markdownlint",
"ms-azuretools.vscode-docker",
"vscode-icons-team.vscode-icons",
],
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"ms-python.isort",
"davidanson.vscode-markdownlint",
"ms-azuretools.vscode-docker",
"vscode-icons-team.vscode-icons",
"github.vscode-github-actions",
],
}
}
}
+21
View File
@@ -0,0 +1,21 @@
FROM freqtradeorg/freqtrade:develop_freqairl
USER root
# Install dependencies
COPY requirements-dev.txt /freqtrade/
ARG USERNAME=ftuser
RUN apt-get update \
&& apt-get -y install --no-install-recommends apt-utils dialog git ssh vim build-essential zsh \
&& apt-get clean \
&& mkdir -p /home/${USERNAME}/.vscode-server /home/${USERNAME}/.vscode-server-insiders /home/${USERNAME}/commandhistory \
&& chown ${USERNAME}:${USERNAME} -R /home/${USERNAME}/.local/ \
&& chown ${USERNAME}: -R /home/${USERNAME}/
USER ftuser
RUN pip install --user autopep8 -r docs/requirements-docs.txt -r requirements-dev.txt --no-cache-dir
# Empty the ENTRYPOINT to allow all commands
ENTRYPOINT []
+12
View File
@@ -0,0 +1,12 @@
{
"name": "freqtrade Dev container image builder",
"build": {
"dockerfile": "Dockerfile",
"context": "../../"
},
"features": {
"ghcr.io/devcontainers/features/common-utils:2": {
},
"ghcr.io/stuartleeks/dev-container-features/shell-history:0.0.3": {}
}
}
+3
View File
@@ -21,6 +21,9 @@ updates:
pytest:
patterns:
- "pytest*"
mkdocs:
patterns:
- "mkdocs*"
- package-ecosystem: "github-actions"
directory: "/"
+2 -2
View File
@@ -129,7 +129,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ "macos-latest", "macos-13", "macos-14" ]
os: [ "macos-12", "macos-13", "macos-14" ]
python-version: ["3.9", "3.10", "3.11", "3.12"]
exclude:
- os: "macos-14"
@@ -414,7 +414,7 @@ jobs:
pytest --random-order --longrun --durations 20 -n auto
# Notify only once - when CI completes (and after deploy) in case it's successfull
# Notify only once - when CI completes (and after deploy) in case it's successful
notify-complete:
needs: [
build-linux,
+43
View File
@@ -0,0 +1,43 @@
name: Devcontainer Pre-Build
on:
workflow_dispatch:
# push:
# branches:
# - "master"
# tags:
# - "v*.*.*"
# pull_requests:
# branches:
# - "master"
concurrency:
group: "${{ github.workflow }}"
cancel-in-progress: true
permissions:
packages: write
jobs:
build-and-push:
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
+11 -4
View File
@@ -9,14 +9,14 @@ repos:
# stages: [push]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.9.0"
rev: "v1.10.0"
hooks:
- id: mypy
exclude: build_helpers
additional_dependencies:
- types-cachetools==5.3.0.7
- types-filelock==3.2.7
- types-requests==2.31.0.20240311
- types-requests==2.31.0.20240406
- types-tabulate==0.9.0.20240106
- types-python-dateutil==2.9.0.20240316
- SQLAlchemy==2.0.29
@@ -31,12 +31,12 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.3.4'
rev: 'v0.4.2'
hooks:
- id: ruff
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.5.0
rev: v4.6.0
hooks:
- id: end-of-file-fixer
exclude: |
@@ -54,3 +54,10 @@ repos:
(?x)^(
.*\.md
)$
- repo: https://github.com/codespell-project/codespell
rev: v2.2.6
hooks:
- id: codespell
additional_dependencies:
- tomli
+1 -1
View File
@@ -1,4 +1,4 @@
FROM python:3.12.2-slim-bookworm as base
FROM python:3.12.3-slim-bookworm as base
# Setup env
ENV LANG C.UTF-8
+2 -2
View File
@@ -1,4 +1,4 @@
# File used in CI to ensure pre-commit dependencies are kept uptodate.
# File used in CI to ensure pre-commit dependencies are kept up-to-date.
import sys
from pathlib import Path
@@ -21,7 +21,7 @@ type_reqs = [r.strip('\n') for r in requirements if r.startswith(
'types-') or r.startswith('SQLAlchemy')]
with pre_commit_file.open('r') as file:
f = yaml.load(file, Loader=yaml.FullLoader)
f = yaml.load(file, Loader=yaml.SafeLoader)
mypy_repo = [repo for repo in f['repos'] if repo['repo']
+1 -1
View File
@@ -36,7 +36,7 @@ freqtrade backtesting-analysis -c <config.json> --analysis-groups 0 1 2 3 4 5
```
This command will read from the last backtesting results. The `--analysis-groups` option is
used to specify the various tabular outputs showing the profit fo each group or trade,
used to specify the various tabular outputs showing the profit of each group or trade,
ranging from the simplest (0) to the most detailed per pair, per buy and per sell tag (4):
* 0: overall winrate and profit summary by enter_tag
+7 -6
View File
@@ -522,8 +522,8 @@ To save time, by default backtest will reuse a cached result from within the las
### Further backtest-result analysis
To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file).
You can then load the trades to perform further analysis as shown in the [data analysis](data-analysis.md#backtesting) backtesting section.
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.
## Assumptions made by backtesting
@@ -531,12 +531,13 @@ Since backtesting lacks some detailed information about what happens within a ca
- Exchange [trading limits](#trading-limits-in-backtesting) are respected
- Entries happen at open-price
- All orders are filled at the requested price (no slippage, no unfilled orders)
- All orders are filled at the requested price (no slippage) as long as the price is within the candle's high/low range
- Exit-signal exits happen at open-price of the consecutive candle
- Exits don't free their trade slot for a new trade until the next candle
- Exit-signal is favored over Stoploss, because exit-signals are assumed to trigger on candle's open
- ROI
- exits are compared to high - but the ROI value is used (e.g. ROI = 2%, high=5% - so the exit will be at 2%)
- exits are never "below the candle", so a ROI of 2% may result in a exit at 2.4% if low was at 2.4% profit
- Exits are compared to high - but the ROI value is used (e.g. ROI = 2%, high=5% - so the exit will be at 2%)
- Exits are never "below the candle", so a ROI of 2% may result in a exit at 2.4% if low was at 2.4% profit
- ROI entries which came into effect on the triggering candle (e.g. `120: 0.02` for 1h candles, from `60: 0.05`) will use the candle's open as exit rate
- Force-exits caused by `<N>=-1` ROI entries use low as exit value, unless N falls on the candle open (e.g. `120: -1` for 1h candles)
- Stoploss exits happen exactly at stoploss price, even if low was lower, but the loss will be `2 * fees` higher than the stoploss price
@@ -587,7 +588,7 @@ These precision values are based on current exchange limits (as described in the
## Improved backtest accuracy
One big limitation of backtesting is it's inability to know how prices moved intra-candle (was high before close, or viceversa?).
One big limitation of backtesting is it's inability to know how prices moved intra-candle (was high before close, or vice-versa?).
So assuming you run backtesting with a 1h timeframe, there will be 4 prices for that candle (Open, High, Low, Close).
While backtesting does take some assumptions (read above) about this - this can never be perfect, and will always be biased in one way or the other.
+4 -4
View File
@@ -197,7 +197,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `position_adjustment_enable` | Enables the strategy to use position adjustments (additional buys or sells). [More information here](strategy-callbacks.md#adjust-trade-position). <br> [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.*<br> **Datatype:** Boolean
| `max_entry_position_adjustment` | Maximum additional order(s) for each open trade on top of the first entry Order. Set it to `-1` for unlimited additional orders. [More information here](strategy-callbacks.md#adjust-trade-position). <br> [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `-1`.*<br> **Datatype:** Positive Integer or -1
| | **Exchange**
| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> **Datatype:** String
| `exchange.name` | **Required.** Name of the exchange class to use. <br> **Datatype:** String
| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
@@ -252,7 +252,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `disable_dataframe_checks` | Disable checking the OHLCV dataframe returned from the strategy methods for correctness. Only use when intentionally changing the dataframe and understand what you are doing. [Strategy Override](#parameters-in-the-strategy).<br> *Defaults to `False`*. <br> **Datatype:** Boolean
| `internals.process_throttle_secs` | Set the process throttle, or minimum loop duration for one bot iteration loop. Value in second. <br>*Defaults to `5` seconds.* <br> **Datatype:** Positive Integer
| `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages. <br>*Defaults to `60` seconds.* <br> **Datatype:** Positive Integer or 0
| `internals.sd_notify` | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details. <br> **Datatype:** Boolean
| `internals.sd_notify` | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](advanced-setup.md#configure-the-bot-running-as-a-systemd-service) for more details. <br> **Datatype:** Boolean
| `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`. <br> **Datatype:** ClassName
| `strategy_path` | Adds an additional strategy lookup path (must be a directory). <br> **Datatype:** String
| `recursive_strategy_search` | Set to `true` to recursively search sub-directories inside `user_data/strategies` for a strategy. <br> **Datatype:** Boolean
@@ -370,7 +370,7 @@ This setting works in combination with `max_open_trades`. The maximum capital en
For example, the bot will at most use (0.05 BTC x 3) = 0.15 BTC, assuming a configuration of `max_open_trades=3` and `stake_amount=0.05`.
!!! Note
This setting respects the [available balance configuration](#available-balance).
This setting respects the [available balance configuration](#tradable-balance).
#### Dynamic stake amount
@@ -547,7 +547,7 @@ is automatically cancelled by the exchange.
**PO (Post only):**
Post only order. The order is either placed as a maker order, or it is canceled.
This means the order must be placed on orderbook for at at least time in an unfilled state.
This means the order must be placed on orderbook for at least time in an unfilled state.
#### time_in_force config
+3 -3
View File
@@ -83,7 +83,7 @@ Details will obviously vary between setups - but this should work to get you sta
``` json
{
"name": "freqtrade trade",
"type": "python",
"type": "debugpy",
"request": "launch",
"module": "freqtrade",
"console": "integratedTerminal",
@@ -261,7 +261,7 @@ For that reason, they must implement the following methods:
The `until` portion should be calculated using the provided `calculate_lock_end()` method.
All Protections should use `"stop_duration"` / `"stop_duration_candles"` to define how long a a pair (or all pairs) should be locked.
All Protections should use `"stop_duration"` / `"stop_duration_candles"` to define how long a pair (or all pairs) should be locked.
The content of this is made available as `self._stop_duration` to the each Protection.
If your protection requires a look-back period, please use `"lookback_period"` / `"lockback_period_candles"` to keep all protections aligned.
@@ -305,7 +305,7 @@ The `IProtection` parent class provides a helper method for this in `calculate_l
Most exchanges supported by CCXT should work out of the box.
To quickly test the public endpoints of an exchange, add a configuration for your exchange to `test_ccxt_compat.py` and run these tests with `pytest --longrun tests/exchange/test_ccxt_compat.py`.
To quickly test the public endpoints of an exchange, add a configuration for your exchange to `tests/exchange_online/conftest.py` and run these tests with `pytest --longrun tests/exchange_online/test_ccxt_compat.py`.
Completing these tests successfully a good basis point (it's a requirement, actually), however these won't guarantee correct exchange functioning, as this only tests public endpoints, but no private endpoint (like generate order or similar).
Also try to use `freqtrade download-data` for an extended timerange (multiple months) and verify that the data downloaded correctly (no holes, the specified timerange was actually downloaded).
+1 -1
View File
@@ -137,7 +137,7 @@ $$ R = \frac{\text{average_profit}}{\text{average_loss}} = \frac{\mu_{win}}{\mu_
### Expectancy
By combining the Win Rate $W$ and and the Risk Reward ratio $R$ to create an expectancy ratio $E$. A expectance ratio is the expected return of the investment made in a trade. We can compute the value of $E$ as follows:
By combining the Win Rate $W$ and the Risk Reward ratio $R$ to create an expectancy ratio $E$. A expectance ratio is the expected return of the investment made in a trade. We can compute the value of $E$ as follows:
$$E = R * W - L$$
+1 -1
View File
@@ -299,7 +299,7 @@ $ pip3 install web3
Most exchanges return current incomplete candle via their OHLCV/klines API interface.
By default, Freqtrade assumes that incomplete candle is fetched from the exchange and removes the last candle assuming it's the incomplete candle.
Whether your exchange returns incomplete candles or not can be checked using [the helper script](developer.md#Incomplete-candles) from the Contributor documentation.
Whether your exchange returns incomplete candles or not can be checked using [the helper script](developer.md#incomplete-candles) from the Contributor documentation.
Due to the danger of repainting, Freqtrade does not allow you to use this incomplete candle.
+2 -2
View File
@@ -2,7 +2,7 @@
## Supported Markets
Freqtrade supports spot trading, as well as (isolated) futures trading for some selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges-experimental) for an uptodate list of supported exchanges.
Freqtrade supports spot trading, as well as (isolated) futures trading for some selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges-experimental) for an up-to-date list of supported exchanges.
### Can my bot open short positions?
@@ -14,7 +14,7 @@ In spot markets, you can in some cases use leveraged spot tokens, which reflect
### Can my bot trade options or futures?
Futures trading is supported for selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges-experimental) for an uptodate list of supported exchanges.
Futures trading is supported for selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges-experimental) for an up-to-date list of supported exchanges.
## Beginner Tips & Tricks
+1 -1
View File
@@ -235,7 +235,7 @@ By default, FreqAI builds a dynamic pipeline based on user congfiguration settin
Users are encouraged to customize the data pipeline to their needs by building their own data pipeline. This can be done by simply setting `dk.feature_pipeline` to their desired `Pipeline` object inside their `IFreqaiModel` `train()` function, or if they prefer not to touch the `train()` function, they can override `define_data_pipeline`/`define_label_pipeline` functions in their `IFreqaiModel`:
!!! note "More information available"
FreqAI uses the the [`DataSieve`](https://github.com/emergentmethods/datasieve) pipeline, which follows the SKlearn pipeline API, but adds, among other features, coherence between the X, y, and sample_weight vector point removals, feature removal, feature name following.
FreqAI uses the [`DataSieve`](https://github.com/emergentmethods/datasieve) pipeline, which follows the SKlearn pipeline API, but adds, among other features, coherence between the X, y, and sample_weight vector point removals, feature removal, feature name following.
```python
from datasieve.transforms import SKLearnWrapper, DissimilarityIndex
+2 -2
View File
@@ -31,7 +31,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
| `feature_parameters` | A dictionary containing the parameters used to engineer the feature set. Details and examples are shown [here](freqai-feature-engineering.md). <br> **Datatype:** Dictionary.
| `include_timeframes` | A list of timeframes that all indicators in `feature_engineering_expand_*()` will be created for. The list is added as features to the base indicators dataset. <br> **Datatype:** List of timeframes (strings).
| `include_corr_pairlist` | A list of correlated coins that FreqAI will add as additional features to all `pair_whitelist` coins. All indicators set in `feature_engineering_expand_*()` during feature engineering (see details [here](freqai-feature-engineering.md)) will be created for each correlated coin. The correlated coins features are added to the base indicators dataset. <br> **Datatype:** List of assets (strings).
| `label_period_candles` | Number of candles into the future that the labels are created for. This is used in `feature_engineering_expand_all()` (see `templates/FreqaiExampleStrategy.py` for detailed usage). You can create custom labels and choose whether to make use of this parameter or not. <br> **Datatype:** Positive integer.
| `label_period_candles` | Number of candles into the future that the labels are created for. This can be used in `set_freqai_targets()` (see `templates/FreqaiExampleStrategy.py` for detailed usage). This parameter is not necessarily required, you can create custom labels and choose whether to make use of this parameter or not. Please see `templates/FreqaiExampleStrategy.py` to see the example usage. <br> **Datatype:** Positive integer.
| `include_shifted_candles` | Add features from previous candles to subsequent candles with the intent of adding historical information. If used, FreqAI will duplicate and shift all features from the `include_shifted_candles` previous candles so that the information is available for the subsequent candle. <br> **Datatype:** Positive integer.
| `weight_factor` | Weight training data points according to their recency (see details [here](freqai-feature-engineering.md#weighting-features-for-temporal-importance)). <br> **Datatype:** Positive float (typically < 1).
| `indicator_max_period_candles` | **No longer used (#7325)**. Replaced by `startup_candle_count` which is set in the [strategy](freqai-configuration.md#building-a-freqai-strategy). `startup_candle_count` is timeframe independent and defines the maximum *period* used in `feature_engineering_*()` for indicator creation. FreqAI uses this parameter together with the maximum timeframe in `include_time_frames` to calculate how many data points to download such that the first data point does not include a NaN. <br> **Datatype:** Positive integer.
@@ -55,7 +55,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
| | **Data split parameters within the `freqai.data_split_parameters` sub dictionary**
| `data_split_parameters` | Include any additional parameters available from scikit-learn `test_train_split()`, which are shown [here](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html) (external website). <br> **Datatype:** Dictionary.
| `test_size` | The fraction of data that should be used for testing instead of training. <br> **Datatype:** Positive float < 1.
| `shuffle` | Shuffle the training data points during training. Typically, to not remove the chronological order of data in time-series forecasting, this is set to `False`. <br> **Datatype:** Boolean. <br> Defaut: `False`.
| `shuffle` | Shuffle the training data points during training. Typically, to not remove the chronological order of data in time-series forecasting, this is set to `False`. <br> **Datatype:** Boolean. <br> Default: `False`.
### Model training parameters
+3 -4
View File
@@ -14,8 +14,7 @@ To learn how to get data for the pairs and exchange you're interested in, head o
!!! Note
Since 2021.4 release you no longer have to write a separate hyperopt class, but can configure the parameters directly in the strategy.
The legacy method is still supported, but it is no longer the recommended way of setting up hyperopt.
The legacy documentation is available at [Legacy Hyperopt](advanced-hyperopt.md#legacy-hyperopt).
The legacy method was supported up to 2021.8 and has been removed in 2021.9.
## Install hyperopt dependencies
@@ -765,7 +764,7 @@ Override the `roi_space()` method if you need components of the ROI tables to va
A sample for these methods can be found in the [overriding pre-defined spaces section](advanced-hyperopt.md#overriding-pre-defined-spaces).
!!! Note "Reduced search space"
To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs.
To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#overriding-pre-defined-spaces) to change this to your needs.
### Understand Hyperopt Stoploss results
@@ -807,7 +806,7 @@ If you have the `stoploss_space()` method in your custom hyperopt file, remove i
Override the `stoploss_space()` method and define the desired range in it if you need stoploss values to vary in other range during hyperoptimization. A sample for this method can be found in the [overriding pre-defined spaces section](advanced-hyperopt.md#overriding-pre-defined-spaces).
!!! Note "Reduced search space"
To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs.
To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#overriding-pre-defined-spaces) to change this to your needs.
### Understand Hyperopt Trailing Stop results
+1 -1
View File
@@ -51,7 +51,7 @@ These requirements apply to both [Script Installation](#script-installation) and
### Install code
We've included/collected install instructions for Ubuntu, MacOS, and Windows. These are guidelines and your success may vary with other distros.
OS Specific steps are listed first, the [Common](#common) section below is necessary for all systems.
OS Specific steps are listed first, the common section below is necessary for all systems.
!!! Note
Python3.9 or higher and the corresponding pip are assumed to be available.
+1 -1
View File
@@ -17,7 +17,7 @@ If you already have an existing strategy, please read the [strategy migration gu
## Shorting
Shorting is not possible when trading with [`trading_mode`](#understand-tradingmode) set to `spot`. To short trade, `trading_mode` must be set to `margin`(currently unavailable) or [`futures`](#futures), with [`margin_mode`](#margin-mode) set to `cross`(currently unavailable) or [`isolated`](#isolated-margin-mode)
Shorting is not possible when trading with [`trading_mode`](#leverage-trading-modes) set to `spot`. To short trade, `trading_mode` must be set to `margin`(currently unavailable) or [`futures`](#futures), with [`margin_mode`](#margin-mode) set to `cross`(currently unavailable) or [`isolated`](#isolated-margin-mode)
For a strategy to short, the strategy class must set the class variable `can_short = True`
+3 -3
View File
@@ -1,6 +1,6 @@
markdown==3.6
mkdocs==1.5.3
mkdocs-material==9.5.15
mkdocs==1.6.0
mkdocs-material==9.5.19
mdx_truly_sane_lists==1.3
pymdown-extensions==10.7.1
pymdown-extensions==10.8.1
jinja2==3.1.3
+4 -2
View File
@@ -89,7 +89,8 @@ Make sure that the following 2 lines are available in your docker-compose file:
```
!!! Danger "Security warning"
By using `8080:8080` in the docker port mapping, the API will be available to everyone connecting to the server under the correct port, so others may be able to control your bot.
By using `"8080:8080"` (or `"0.0.0.0:8080:8080"`) in the docker port mapping, the API will be available to everyone connecting to the server under the correct port, so others may be able to control your bot.
This **may** be safe if you're running the bot in a secure environment (like your home network), but it's not recommended to expose the API to the internet.
## Rest API
@@ -166,6 +167,7 @@ freqtrade-client --config rest_config.json <command> [optional parameters]
| `mix_tags [pair]` | Shows profit statistics for each combinations of enter tag + exit reasons for given pair (or all pairs if pair isn't given). Pair is optional.
| `locks` | Displays currently locked pairs.
| `delete_lock <lock_id>` | Deletes (disables) the lock by id.
| `locks add <pair>, <until>, [side], [reason]` | Locks a pair until "until". (Until will be rounded up to the nearest timeframe).
| `profit` | Display a summary of your profit/loss from close trades and some stats about your performance.
| `forceexit <trade_id>` | Instantly exits the given trade (Ignoring `minimum_roi`).
| `forceexit all` | Instantly exits all open trades (Ignoring `minimum_roi`).
@@ -453,7 +455,7 @@ To properly configure your reverse proxy (securely), please consult it's documen
- **Caddy**: Caddy v2 supports websockets out of the box, see the [documentation](https://caddyserver.com/docs/v2-upgrade#proxy)
!!! Tip "SSL certificates"
You can use tools like certbot to setup ssl certificates to access your bot's UI through encrypted connection by using any fo the above reverse proxies.
You can use tools like certbot to setup ssl certificates to access your bot's UI through encrypted connection by using any of the above reverse proxies.
While this will protect your data in transit, we do not recommend to run the freqtrade API outside of your private network (VPN, SSH tunnel).
### OpenAPI interface
+2 -2
View File
@@ -158,7 +158,7 @@ You could also have a default stop loss when you are in the red with your buy (b
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.
!!! 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).
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).
Both values require `trailing_stop` to be set to true and `trailing_stop_positive` with a value.
@@ -240,7 +240,7 @@ When using leverage, the same principle is applied - with stoploss defining the
Therefore, a stoploss of 10% on a 10x trade would trigger on a 1% price move.
If your stake amount (own capital) was 100$ - this trade would be 1000$ at 10x (after leverage).
If price moves 1% - you've lost 10$ of your own capital - therfore stoploss will trigger in this case.
If price moves 1% - you've lost 10$ of your own capital - therefore stoploss will trigger in this case.
Make sure to be aware of this, and avoid using too tight stoploss (at 10x leverage, 10% risk may be too little to allow the trade to "breath" a little).
+2 -2
View File
@@ -209,7 +209,7 @@ def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_r
## Exit tag
Similar to [Buy Tagging](#buy-tag), you can also specify a sell tag.
Similar to [Entry Tagging](#enter-tag), you can also specify an exit tag.
``` python
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
@@ -326,4 +326,4 @@ for val in self.buy_ema_short.range:
dataframe = pd.concat(frames, axis=1)
```
Freqtrade does however also counter this by running `dataframe.copy()` on the dataframe right after the `populate_indicators()` method - so performance implications of this should be low to non-existant.
Freqtrade does however also counter this by running `dataframe.copy()` on the dataframe right after the `populate_indicators()` method - so performance implications of this should be low to non-existent.
+5 -4
View File
@@ -167,7 +167,7 @@ During backtesting, `current_rate` (and `current_profit`) are provided against t
The absolute value of the return value is used (the sign is ignored), so returning `0.05` or `-0.05` have the same result, a stoploss 5% below the current price.
Returning None will be interpreted as "no desire to change", and is the only safe way to return when you'd like to not modify the stoploss.
Stoploss on exchange works similar to `trailing_stop`, and the stoploss on exchange is updated as configured in `stoploss_on_exchange_interval` ([More details about stoploss on exchange](stoploss.md#stop-loss-on-exchange-freqtrade)).
Stoploss on exchange works similar to `trailing_stop`, and the stoploss on exchange is updated as configured in `stoploss_on_exchange_interval` ([More details about stoploss on exchange](stoploss.md#stop-loss-on-exchangefreqtrade)).
!!! Note "Use of dates"
All time-based calculations should be done based on `current_time` - using `datetime.now()` or `datetime.utcnow()` is discouraged, as this will break backtesting support.
@@ -332,7 +332,7 @@ class AwesomeStrategy(IStrategy):
**kwargs) -> Optional[float]:
if current_profit < 0.04:
return -1 # return a value bigger than the initial stoploss to keep using the initial stoploss
return None # return None to keep using the initial stoploss
# After reaching the desired offset, allow the stoploss to trail by half the profit
desired_stoploss = current_profit / 2
@@ -450,7 +450,7 @@ Stoploss values returned from `custom_stoploss()` must specify a percentage rela
```
Full examples can be found in the [Custom stoploss](strategy-advanced.md#custom-stoploss) section of the Documentation.
Full examples can be found in the [Custom stoploss](strategy-callbacks.md#custom-stoploss) section of the Documentation.
!!! Note
Providing invalid input to `stoploss_from_open()` may produce "CustomStoploss function did not return valid stoploss" warnings.
@@ -809,6 +809,7 @@ Returning a value more than the above (so remaining stake_amount would become ne
``` python
from freqtrade.persistence import Trade
from typing import Optional, Tuple, Union
class DigDeeperStrategy(IStrategy):
@@ -948,7 +949,7 @@ If the cancellation of the original order fails, then the order will not be repl
```python
from freqtrade.persistence import Trade
from datetime import timedelta
from datetime import timedelta, datetime
class AwesomeStrategy(IStrategy):
+4 -4
View File
@@ -405,7 +405,7 @@ The metadata-dict (available for `populate_entry_trend`, `populate_exit_trend`,
Currently this is `pair`, which can be accessed using `metadata['pair']` - and will return a pair in the format `XRP/BTC`.
The Metadata-dict should not be modified and does not persist information across multiple calls.
Instead, have a look at the [Storing information](strategy-advanced.md#Storing-information) section.
Instead, have a look at the [Storing information](strategy-advanced.md#storing-information-persistent) section.
## Strategy file loading
@@ -551,8 +551,8 @@ for more information.
# Define BTC/STAKE informative pair. A custom formatter may be specified for formatting
# column names. A callable `fmt(**kwargs) -> str` may be specified, to implement custom
# formatting. Available in populate_indicators and other methods as 'rsi_upper'.
@informative('1h', 'BTC/{stake}', '{column}')
# formatting. Available in populate_indicators and other methods as 'rsi_upper_1h'.
@informative('1h', 'BTC/{stake}', '{column}_{timeframe}')
def populate_indicators_btc_1h_2(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['rsi_upper'] = ta.RSI(dataframe, timeperiod=14)
return dataframe
@@ -776,7 +776,7 @@ The orderbook structure is aligned with the order structure from [ccxt](https://
Therefore, using `ob['bids'][0][0]` as demonstrated above will result in using the best bid price. `ob['bids'][0][1]` would look at the amount at this orderbook position.
!!! Warning "Warning about backtesting"
The order book is not part of the historic data which means backtesting and hyperopt will not work correctly if this method is used, as the method will return uptodate values.
The order book is not part of the historic data which means backtesting and hyperopt will not work correctly if this method is used, as the method will return up-to-date values.
### *ticker(pair)*
+1 -1
View File
@@ -53,7 +53,7 @@ You can use bots in telegram groups by just adding them to the group. You can fi
}
```
For the Freqtrade configuration, you can then use the the full value (including `-` if it's there) as string:
For the Freqtrade configuration, you can then use the full value (including `-` if it's there) as string:
```json
"chat_id": "-1001332619709"
+2 -2
View File
@@ -126,7 +126,7 @@ An `Order` object will always be tied to it's corresponding [`Trade`](#trade-obj
### Order - Available attributes
an Order object is typically attached to a trade.
Most properties here can be None as they are dependant on the exchange response.
Most properties here can be None as they are dependent on the exchange response.
| Attribute | DataType | Description |
|------------|-------------|-------------|
@@ -141,7 +141,7 @@ Most properties here can be None as they are dependant on the exchange response.
`amount` | float | Amount in base currency
`filled` | float | Filled amount (in base currency)
`remaining` | float | Remaining amount
`cost` | float | Cost of the order - usually average * filled (*Exchange dependant on futures, may contain the cost with or without leverage and may be in contracts.*)
`cost` | float | Cost of the order - usually average * filled (*Exchange dependent on futures, may contain the cost with or without leverage and may be in contracts.*)
`stake_amount` | float | Stake amount used for this order. *Added in 2023.7.*
`order_date` | datetime | Order creation date **use `order_date_utc` instead**
`order_date_utc` | datetime | Order creation date (in UTC)
+1 -1
View File
@@ -1,5 +1,5 @@
""" Freqtrade bot """
__version__ = '2024.3'
__version__ = '2024.4'
if 'dev' in __version__:
from pathlib import Path
+4 -1
View File
@@ -197,7 +197,10 @@ class Arguments:
self._build_args(optionlist=ARGS_STRATEGY, parser=strategy_group)
# Build main command
self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot')
self.parser = argparse.ArgumentParser(
prog="freqtrade",
description='Free, open source crypto trading bot'
)
self._build_args(optionlist=['version'], parser=self.parser)
from freqtrade.commands import (start_analysis_entries_exits, start_backtesting,
+7 -3
View File
@@ -16,6 +16,10 @@ from freqtrade.util import render_template, render_template_with_fallback
logger = logging.getLogger(__name__)
# Timeout for requests
req_timeout = 30
def start_create_userdir(args: Dict[str, Any]) -> None:
"""
Create "user_data" directory to contain user data strategies, hyperopt, ...)
@@ -119,7 +123,7 @@ def download_and_install_ui(dest_folder: Path, dl_url: str, version: str):
from zipfile import ZipFile
logger.info(f"Downloading {dl_url}")
resp = requests.get(dl_url).content
resp = requests.get(dl_url, timeout=req_timeout).content
dest_folder.mkdir(parents=True, exist_ok=True)
with ZipFile(BytesIO(resp)) as zf:
for fn in zf.filelist:
@@ -137,7 +141,7 @@ def get_ui_download_url(version: Optional[str] = None) -> Tuple[str, str]:
base_url = 'https://api.github.com/repos/freqtrade/frequi/'
# Get base UI Repo path
resp = requests.get(f"{base_url}releases")
resp = requests.get(f"{base_url}releases", timeout=req_timeout)
resp.raise_for_status()
r = resp.json()
@@ -158,7 +162,7 @@ def get_ui_download_url(version: Optional[str] = None) -> Tuple[str, str]:
# URL not found - try assets url
if not dl_url:
assets = r[0]['assets_url']
resp = requests.get(assets)
resp = requests.get(assets, timeout=req_timeout)
r = resp.json()
dl_url = r[0]['browser_download_url']
+3 -3
View File
@@ -13,7 +13,7 @@ from freqtrade.configuration.directory_operations import create_datadir, create_
from freqtrade.configuration.environment_vars import enironment_vars_to_dict
from freqtrade.configuration.load_config import load_file, load_from_files
from freqtrade.constants import Config
from freqtrade.enums import NON_UTIL_MODES, TRADING_MODES, CandleType, RunMode, TradingMode
from freqtrade.enums import NON_UTIL_MODES, TRADE_MODES, CandleType, RunMode, TradingMode
from freqtrade.exceptions import OperationalException
from freqtrade.loggers import setup_logging
from freqtrade.misc import deep_merge_dicts, parse_db_uri_for_logging
@@ -127,7 +127,7 @@ class Configuration:
setup_logging(config)
def _process_trading_options(self, config: Config) -> None:
if config['runmode'] not in TRADING_MODES:
if config['runmode'] not in TRADE_MODES:
return
if config.get('dry_run', False):
@@ -202,7 +202,7 @@ class Configuration:
if self.args.get('show_sensitive'):
logger.warning(
"Sensitive information will be shown in the upcomming output. "
"Sensitive information will be shown in the upcoming output. "
"Please make sure to never share this output without redacting "
"the information yourself.")
+10
View File
@@ -238,6 +238,16 @@ def update_backtest_metadata(filename: Path, strategy: str, content: Dict[str, A
file_dump_json(get_backtest_metadata_filename(filename), metadata)
def get_backtest_market_change(filename: Path, include_ts: bool = True) -> pd.DataFrame:
"""
Read backtest market change file.
"""
df = pd.read_feather(filename)
if include_ts:
df.loc[:, '__date_ts'] = df.loc[:, 'date'].astype(np.int64) // 1000 // 1000
return df
def find_existing_backtest_stats(dirname: Union[Path, str], run_ids: Dict[str, str],
min_backtest_date: Optional[datetime] = None) -> Dict[str, Any]:
"""
+1 -1
View File
@@ -523,7 +523,7 @@ class DataProvider:
Send custom RPC Notifications from your bot.
Will not send any bot in modes other than Dry-run or Live.
:param message: Message to be sent. Must be below 4096.
:param always_send: If False, will send the message only once per candle, and surpress
:param always_send: If False, will send the message only once per candle, and suppress
identical messages.
Careful as this can end up spaming your chat.
Defaults to False
@@ -302,8 +302,8 @@ class IDataHandler(ABC):
Rebuild pair name from filename
Assumes a asset name of max. 7 length to also support BTC-PERP and BTC-PERP:USD names.
"""
res = re.sub(r'^(([A-Za-z\d]{1,10})|^([A-Za-z\-]{1,6}))(_)', r'\g<1>/', pair, 1)
res = re.sub('_', ':', res, 1)
res = re.sub(r'^(([A-Za-z\d]{1,10})|^([A-Za-z\-]{1,6}))(_)', r'\g<1>/', pair, count=1)
res = re.sub('_', ':', res, count=1)
return res
def ohlcv_load(self, pair, timeframe: str,
+39 -4
View File
@@ -30,8 +30,25 @@ def calculate_market_change(data: Dict[str, pd.DataFrame], column: str = "close"
return float(np.mean(tmp_means))
def combine_dataframes_with_mean(data: Dict[str, pd.DataFrame],
column: str = "close") -> pd.DataFrame:
def combine_dataframes_by_column(
data: Dict[str, pd.DataFrame], column: str = "close") -> pd.DataFrame:
"""
Combine multiple dataframes "column"
:param data: Dict of Dataframes, dict key should be pair.
:param column: Column in the original dataframes to use
:return: DataFrame with the column renamed to the dict key.
:raise: ValueError if no data is provided.
"""
if not data:
raise ValueError("No data provided.")
df_comb = pd.concat([data[pair].set_index('date').rename(
{column: pair}, axis=1)[pair] for pair in data], axis=1)
return df_comb
def combined_dataframes_with_rel_mean(
data: Dict[str, pd.DataFrame], fromdt: datetime, todt: datetime,
column: str = "close") -> pd.DataFrame:
"""
Combine multiple dataframes "column"
:param data: Dict of Dataframes, dict key should be pair.
@@ -40,8 +57,26 @@ def combine_dataframes_with_mean(data: Dict[str, pd.DataFrame],
named mean, containing the mean of all pairs.
:raise: ValueError if no data is provided.
"""
df_comb = pd.concat([data[pair].set_index('date').rename(
{column: pair}, axis=1)[pair] for pair in data], axis=1)
df_comb = combine_dataframes_by_column(data, column)
# Trim dataframes to the given timeframe
df_comb = df_comb.iloc[(df_comb.index >= fromdt) & (df_comb.index < todt)]
df_comb['count'] = df_comb.count(axis=1)
df_comb['mean'] = df_comb.mean(axis=1)
df_comb['rel_mean'] = df_comb['mean'].pct_change().fillna(0).cumsum()
return df_comb[['mean', 'rel_mean', 'count']]
def combine_dataframes_with_mean(
data: Dict[str, pd.DataFrame], column: str = "close") -> pd.DataFrame:
"""
Combine multiple dataframes "column"
:param data: Dict of Dataframes, dict key should be pair.
:param column: Column in the original dataframes to use
:return: DataFrame with the column renamed to the dict key, and a column
named mean, containing the mean of all pairs.
:raise: ValueError if no data is provided.
"""
df_comb = combine_dataframes_by_column(data, column)
df_comb['mean'] = df_comb.mean(axis=1)
+1 -1
View File
@@ -9,7 +9,7 @@ from freqtrade.enums.marketstatetype import MarketDirection
from freqtrade.enums.ordertypevalue import OrderTypeValues
from freqtrade.enums.pricetype import PriceType
from freqtrade.enums.rpcmessagetype import NO_ECHO_MESSAGES, RPCMessageType, RPCRequestType
from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode
from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADE_MODES, RunMode
from freqtrade.enums.signaltype import SignalDirection, SignalTagType, SignalType
from freqtrade.enums.state import State
from freqtrade.enums.tradingmode import TradingMode
+2 -2
View File
@@ -18,6 +18,6 @@ class RunMode(Enum):
OTHER = "other"
TRADING_MODES = [RunMode.LIVE, RunMode.DRY_RUN]
TRADE_MODES = [RunMode.LIVE, RunMode.DRY_RUN]
OPTIMIZE_MODES = [RunMode.BACKTEST, RunMode.EDGE, RunMode.HYPEROPT]
NON_UTIL_MODES = TRADING_MODES + OPTIMIZE_MODES
NON_UTIL_MODES = TRADE_MODES + OPTIMIZE_MODES
+2
View File
@@ -4,6 +4,7 @@ from freqtrade.exchange.common import remove_exchange_credentials, MAP_EXCHANGE_
from freqtrade.exchange.exchange import Exchange
# isort: on
from freqtrade.exchange.binance import Binance
from freqtrade.exchange.bingx import Bingx
from freqtrade.exchange.bitmart import Bitmart
from freqtrade.exchange.bitpanda import Bitpanda
from freqtrade.exchange.bitvavo import Bitvavo
@@ -24,6 +25,7 @@ from freqtrade.exchange.exchange_utils_timeframe import (timeframe_to_minutes, t
from freqtrade.exchange.gate import Gate
from freqtrade.exchange.hitbtc import Hitbtc
from freqtrade.exchange.htx import Htx
from freqtrade.exchange.idex import Idex
from freqtrade.exchange.kraken import Kraken
from freqtrade.exchange.kucoin import Kucoin
from freqtrade.exchange.okx import Okx
+2 -2
View File
@@ -84,7 +84,7 @@ class Binance(Exchange):
raise OperationalException(msg)
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Error in additional_exchange_init due to {e.__class__.__name__}. Message: {e}'
) from e
@@ -210,7 +210,7 @@ class Binance(Exchange):
return self._api.fetch_leverage_tiers()
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(f'Could not fetch leverage amounts due to'
f'{e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
File diff suppressed because it is too large Load Diff
+19
View File
@@ -0,0 +1,19 @@
""" Bingx exchange subclass """
import logging
from typing import Dict
from freqtrade.exchange import Exchange
logger = logging.getLogger(__name__)
class Bingx(Exchange):
"""
Bingx exchange class. Contains adjustments needed for Freqtrade to work
with this exchange.
"""
_ft_has: Dict = {
"ohlcv_candle_limit": 1000,
}
+2 -2
View File
@@ -99,7 +99,7 @@ class Bybit(Exchange):
logger.info("Bybit: Standard account.")
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Error in additional_exchange_init due to {e.__class__.__name__}. Message: {e}'
) from e
@@ -239,7 +239,7 @@ class Bybit(Exchange):
return orders
def fetch_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict:
def fetch_order(self, order_id: str, pair: str, params: Optional[Dict] = None) -> Dict:
order = super().fetch_order(order_id, pair, params)
if (
order.get('status') == 'canceled'
+1 -1
View File
@@ -56,7 +56,7 @@ def check_exchange(config: Config, check_for_bad: bool = True) -> bool:
logger.info(f'Exchange "{exchange}" is officially supported '
f'by the Freqtrade development team.')
else:
logger.warning(f'Exchange "{exchange}" is known to the the ccxt library, '
logger.warning(f'Exchange "{exchange}" is known to the ccxt library, '
f'available for the bot, but not officially supported '
f'by the Freqtrade development team. '
f'It may work flawlessly (please report back) or have serious issues. '
+53 -43
View File
@@ -44,7 +44,7 @@ from freqtrade.misc import (chunks, deep_merge_dicts, file_dump_json, file_load_
safe_value_fallback2)
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.util import dt_from_ts, dt_now
from freqtrade.util.datetime_helpers import dt_humanize, dt_ts
from freqtrade.util.datetime_helpers import dt_humanize_delta, dt_ts
from freqtrade.util.periodic_cache import PeriodicCache
@@ -239,8 +239,8 @@ class Exchange:
self.validate_pricing(config['exit_pricing'])
self.validate_pricing(config['entry_pricing'])
def _init_ccxt(self, exchange_config: Dict[str, Any], ccxt_module: CcxtModuleType = ccxt,
ccxt_kwargs: Dict = {}) -> ccxt.Exchange:
def _init_ccxt(self, exchange_config: Dict[str, Any], ccxt_module: CcxtModuleType = ccxt, *,
ccxt_kwargs: Dict) -> ccxt.Exchange:
"""
Initialize ccxt with given config and return valid
ccxt instance.
@@ -348,10 +348,13 @@ class Exchange:
return int(self._ft_has.get('ohlcv_candle_limit_per_timeframe', {}).get(
timeframe, self._ft_has.get('ohlcv_candle_limit')))
def get_markets(self, base_currencies: List[str] = [], quote_currencies: List[str] = [],
spot_only: bool = False, margin_only: bool = False, futures_only: bool = False,
tradable_only: bool = True,
active_only: bool = False) -> Dict[str, Any]:
def get_markets(
self,
base_currencies: Optional[List[str]] = None,
quote_currencies: Optional[List[str]] = None,
spot_only: bool = False, margin_only: bool = False, futures_only: bool = False,
tradable_only: bool = True,
active_only: bool = False) -> Dict[str, Any]:
"""
Return exchange ccxt markets, filtered out by base currency and quote currency
if this was requested in parameters.
@@ -758,7 +761,7 @@ class Exchange:
def price_get_one_pip(self, pair: str, price: float) -> float:
"""
Get's the "1 pip" value for this pair.
Gets the "1 pip" value for this pair.
Used in PriceFilter to calculate the 1pip movements.
"""
precision = self.markets[pair]['precision']['price']
@@ -848,7 +851,7 @@ class Exchange:
# Dry-run methods
def create_dry_run_order(self, pair: str, ordertype: str, side: str, amount: float,
rate: float, leverage: float, params: Dict = {},
rate: float, leverage: float, params: Optional[Dict] = None,
stop_loss: bool = False) -> Dict[str, Any]:
now = dt_now()
order_id = f'dry_run_{side}_{pair}_{now.timestamp()}'
@@ -1122,7 +1125,7 @@ class Exchange:
f'Message: {e}') from e
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place {side} order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
@@ -1259,7 +1262,7 @@ class Exchange:
f'stop-price {stop_price_norm}. Message: {e}') from e
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f"Could not place stoploss order due to {e.__class__.__name__}. "
f"Message: {e}") from e
@@ -1290,16 +1293,18 @@ class Exchange:
f'Tried to get an invalid order (pair: {pair} id: {order_id}). Message: {e}') from e
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
@retrier(retries=API_FETCH_ORDER_RETRY_COUNT)
def fetch_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict:
def fetch_order(self, order_id: str, pair: str, params: Optional[Dict] = None) -> Dict:
if self._config['dry_run']:
return self.fetch_dry_run_order(order_id)
if params is None:
params = {}
try:
if not self.exchange_has('fetchOrder'):
return self.fetch_order_emulated(order_id, pair, params)
@@ -1315,13 +1320,13 @@ class Exchange:
f'Tried to get an invalid order (pair: {pair} id: {order_id}). Message: {e}') from e
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
def fetch_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict:
def fetch_stoploss_order(self, order_id: str, pair: str, params: Optional[Dict] = None) -> Dict:
return self.fetch_order(order_id, pair, params)
def fetch_order_or_stoploss_order(self, order_id: str, pair: str,
@@ -1347,7 +1352,7 @@ class Exchange:
and order.get('filled') == 0.0)
@retrier
def cancel_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict:
def cancel_order(self, order_id: str, pair: str, params: Optional[Dict] = None) -> Dict:
if self._config['dry_run']:
try:
order = self.fetch_dry_run_order(order_id)
@@ -1357,6 +1362,8 @@ class Exchange:
except InvalidOrderException:
return {}
if params is None:
params = {}
try:
order = self._api.cancel_order(order_id, pair, params=params)
self._log_exchange_response('cancel_order', order)
@@ -1367,13 +1374,14 @@ class Exchange:
f'Could not cancel order. Message: {e}') from e
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not cancel order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
def cancel_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict:
def cancel_stoploss_order(
self, order_id: str, pair: str, params: Optional[Dict] = None) -> Dict:
return self.cancel_order(order_id, pair, params)
def is_cancel_order_result_suitable(self, corder) -> bool:
@@ -1449,7 +1457,7 @@ class Exchange:
return balances
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get balance due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
@@ -1473,7 +1481,7 @@ class Exchange:
return positions
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get positions due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
@@ -1517,7 +1525,7 @@ class Exchange:
return orders
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not fetch positions due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
@@ -1538,7 +1546,7 @@ class Exchange:
return trading_fees
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not fetch trading fees due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
@@ -1569,7 +1577,7 @@ class Exchange:
f'Message: {e}') from e
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load bids/asks due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
@@ -1606,7 +1614,7 @@ class Exchange:
raise TemporaryError from e
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load tickers due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
@@ -1624,7 +1632,7 @@ class Exchange:
return data
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load ticker due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
@@ -1665,7 +1673,7 @@ class Exchange:
f'Message: {e}') from e
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get order book due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
@@ -1844,7 +1852,7 @@ class Exchange:
return matched_trades
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get trades due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
@@ -1878,7 +1886,7 @@ class Exchange:
price=price, takerOrMaker=taker_or_maker)['rate']
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get fee info due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
@@ -2000,14 +2008,14 @@ class Exchange:
logger.debug(
"one_call: %s msecs (%s)",
one_call,
dt_humanize(dt_now() - timedelta(milliseconds=one_call), only_distance=True)
dt_humanize_delta(dt_now() - timedelta(milliseconds=one_call))
)
input_coroutines = [self._async_get_candle_history(
pair, timeframe, candle_type, since) for since in
range(since_ms, until_ms or dt_ts(), one_call)]
data: List = []
# Chunk requests into batches of 100 to avoid overwelming ccxt Throttling
# Chunk requests into batches of 100 to avoid overwhelming ccxt Throttling
for input_coro in chunks(input_coroutines, 100):
results = await asyncio.gather(*input_coro, return_exceptions=True)
@@ -2124,7 +2132,7 @@ class Exchange:
Only used in the dataprovider.refresh() method.
:param pair_list: List of 2 element tuples containing pair, interval to refresh
:param since_ms: time since when to download, in milliseconds
:param cache: Assign result to _klines. Usefull for one-off downloads like for pairlists
:param cache: Assign result to _klines. Useful for one-off downloads like for pairlists
:param drop_incomplete: Control candle dropping.
Specifying None defaults to _ohlcv_partial_candle
:return: Dict of [{(pair, timeframe): Dataframe}]
@@ -2135,7 +2143,7 @@ class Exchange:
input_coroutines, cached_pairs = self._build_ohlcv_dl_jobs(pair_list, since_ms, cache)
results_df = {}
# Chunk requests into batches of 100 to avoid overwelming ccxt Throttling
# Chunk requests into batches of 100 to avoid overwhelming ccxt Throttling
for input_coro in chunks(input_coroutines, 100):
async def gather_stuff():
return await asyncio.gather(*input_coro, return_exceptions=True)
@@ -2262,7 +2270,7 @@ class Exchange:
f'candle (OHLCV) data. Message: {e}') from e
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(f'Could not fetch historical candle (OHLCV) data '
f'for pair {pair} due to {e.__class__.__name__}. '
f'Message: {e}') from e
@@ -2295,7 +2303,7 @@ class Exchange:
since: Optional[int] = None,
params: Optional[dict] = None) -> Tuple[List[List], Any]:
"""
Asyncronously gets trade history using fetch_trades.
Asynchronously gets trade history using fetch_trades.
Handles exchange errors, does one call to the exchange.
:param pair: Pair to fetch trade data for
:param since: Since as integer timestamp in milliseconds
@@ -2322,7 +2330,7 @@ class Exchange:
f'Message: {e}') from e
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(f'Could not load trade history due to {e.__class__.__name__}. '
f'Message: {e}') from e
except ccxt.BaseError as e:
@@ -2352,7 +2360,7 @@ class Exchange:
since: Optional[int] = None,
from_id: Optional[str] = None) -> Tuple[str, List[List]]:
"""
Asyncronously gets trade history using fetch_trades
Asynchronously gets trade history using fetch_trades
use this when exchange uses id-based iteration (check `self._trades_pagination`)
:param pair: Pair to fetch trade data for
:param since: Since as integer timestamp in milliseconds
@@ -2403,7 +2411,7 @@ class Exchange:
async def _async_get_trade_history_time(self, pair: str, until: int,
since: Optional[int] = None) -> Tuple[str, List[List]]:
"""
Asyncronously gets trade history using fetch_trades,
Asynchronously gets trade history using fetch_trades,
when the exchange uses time-based iteration (check `self._trades_pagination`)
:param pair: Pair to fetch trade data for
:param since: Since as integer timestamp in milliseconds
@@ -2521,7 +2529,7 @@ class Exchange:
return sum(fee['amount'] for fee in funding_history)
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get funding fees due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
@@ -2533,7 +2541,7 @@ class Exchange:
return self._api.fetch_leverage_tiers()
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load leverage tiers due to {e.__class__.__name__}. Message: {e}'
) from e
@@ -2548,7 +2556,7 @@ class Exchange:
return symbol, tier
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load leverage tiers for {symbol}'
f' due to {e.__class__.__name__}. Message: {e}'
@@ -2762,7 +2770,7 @@ class Exchange:
if not accept_fail:
raise TemporaryError(
f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not set leverage due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
@@ -2786,7 +2794,7 @@ class Exchange:
@retrier
def set_margin_mode(self, pair: str, margin_mode: MarginMode, accept_fail: bool = False,
params: dict = {}):
params: Optional[Dict] = None):
"""
Set's the margin mode on the exchange to cross or isolated for a specific pair
:param pair: base/quote currency pair (e.g. "ADA/USDT")
@@ -2795,6 +2803,8 @@ class Exchange:
# Some exchanges only support one margin_mode type
return
if params is None:
params = {}
try:
res = self._api.set_margin_mode(margin_mode.value, pair, params)
self._log_exchange_response('set_margin_mode', res)
@@ -2804,7 +2814,7 @@ class Exchange:
if not accept_fail:
raise TemporaryError(
f'Could not set margin mode due to {e.__class__.__name__}. Message: {e}') from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not set margin mode due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
+4 -3
View File
@@ -79,7 +79,7 @@ class Gate(Exchange):
# As such, futures orders on gate will not contain a fee, which causes
# a repeated "update fee" cycle and wrong calculations.
# Therefore we patch the response with fees if it's not available.
# An alternative also contianing fees would be
# An alternative also containing fees would be
# privateFuturesGetSettleAccountBook({"settle": "usdt"})
pair_fees = self._trading_fees.get(pair, {})
if pair_fees:
@@ -98,7 +98,7 @@ class Gate(Exchange):
def get_order_id_conditional(self, order: Dict[str, Any]) -> str:
return safe_value_fallback2(order, order, 'id_stop', 'id')
def fetch_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict:
def fetch_stoploss_order(self, order_id: str, pair: str, params: Optional[Dict] = None) -> Dict:
order = self.fetch_order(
order_id=order_id,
pair=pair,
@@ -119,7 +119,8 @@ class Gate(Exchange):
return order1
return order
def cancel_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict:
def cancel_stoploss_order(
self, order_id: str, pair: str, params: Optional[Dict] = None) -> Dict:
return self.cancel_order(
order_id=order_id,
pair=pair,
+19
View File
@@ -0,0 +1,19 @@
""" Idex exchange subclass """
import logging
from typing import Dict
from freqtrade.exchange import Exchange
logger = logging.getLogger(__name__)
class Idex(Exchange):
"""
Idex exchange class. Contains adjustments needed for Freqtrade to work
with this exchange.
"""
_ft_has: Dict = {
"ohlcv_candle_limit": 1000,
}
+1 -1
View File
@@ -84,7 +84,7 @@ class Kraken(Exchange):
return balances
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get balance due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
+6 -5
View File
@@ -56,7 +56,7 @@ class Okx(Exchange):
"""
Exchange ohlcv candle limit
OKX has the following behaviour:
* 300 candles for uptodate data
* 300 candles for up-to-date data
* 100 candles for historic data
* 100 candles for additional candles (not futures or spot).
:param timeframe: Timeframe to check
@@ -87,7 +87,7 @@ class Okx(Exchange):
self.net_only = accounts[0].get('info', {}).get('posMode') == 'net_mode'
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Error in additional_exchange_init due to {e.__class__.__name__}. Message: {e}'
) from e
@@ -153,7 +153,7 @@ class Okx(Exchange):
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
already_set = self.__fetch_leverage_already_set(pair, leverage, side)
if not already_set:
raise TemporaryError(
@@ -202,7 +202,7 @@ class Okx(Exchange):
order['type'] = 'stoploss'
return order
def fetch_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict:
def fetch_stoploss_order(self, order_id: str, pair: str, params: Optional[Dict] = None) -> Dict:
if self._config['dry_run']:
return self.fetch_dry_run_order(order_id)
@@ -232,7 +232,8 @@ class Okx(Exchange):
return safe_value_fallback2(order, order, 'id_stop', 'id')
return order['id']
def cancel_stoploss_order(self, order_id: str, pair: str, params: Dict = {}) -> Dict:
def cancel_stoploss_order(
self, order_id: str, pair: str, params: Optional[Dict] = None) -> Dict:
params1 = {'stop': True}
# 'ordType': 'conditional'
#
+3 -3
View File
@@ -222,7 +222,7 @@ class BaseEnvironment(gym.Env):
@abstractmethod
def step(self, action: int):
"""
Step depeneds on action types, this must be inherited.
Step depends on action types, this must be inherited.
"""
return
@@ -326,7 +326,7 @@ class BaseEnvironment(gym.Env):
def _update_unrealized_total_profit(self):
"""
Update the unrealized total profit incase of episode end.
Update the unrealized total profit in case of episode end.
"""
if self._position in (Positions.Long, Positions.Short):
pnl = self.get_unrealized_profit()
@@ -357,7 +357,7 @@ class BaseEnvironment(gym.Env):
"""
return self.actions
# Keeping around incase we want to start building more complex environment
# Keeping around in case we want to start building more complex environment
# templates in the future.
# def most_recent_return(self):
# """
@@ -311,7 +311,7 @@ class BaseReinforcementLearningModel(IFreqaiModel):
if not prices_train_old.empty:
prices_train = prices_train_old
rename_dict = rename_dict_old
logger.warning('Reinforcement learning module didnt find the correct raw prices '
logger.warning('Reinforcement learning module didn\'t find the correct raw prices '
'assigned in feature_engineering_standard(). '
'Please assign them with:\n'
'dataframe["%-raw_close"] = dataframe["close"]\n'
@@ -458,7 +458,7 @@ def make_env(MyRLEnv: Type[BaseEnvironment], env_id: str, rank: int,
:param env_id: (str) the environment ID
:param num_env: (int) the number of environment you wish to have in subprocesses
:param seed: (int) the inital seed for RNG
:param seed: (int) the initial seed for RNG
:param rank: (int) index of the subprocess
:param env_info: (dict) all required arguments to instantiate the environment.
:return: (Callable)
+61 -27
View File
@@ -4,6 +4,7 @@ import logging
import re
import shutil
import threading
import warnings
from datetime import datetime, timedelta, timezone
from pathlib import Path
from typing import Any, Dict, Tuple, TypedDict
@@ -262,10 +263,11 @@ class FreqaiDataDrawer:
self.pair_dict[metadata["pair"]] = self.empty_pair_dict.copy()
return
def set_initial_return_values(self, pair: str,
pred_df: DataFrame,
dataframe: DataFrame
) -> None:
def set_initial_return_values(
self, pair: str,
pred_df: DataFrame,
dataframe: DataFrame
) -> None:
"""
Set the initial return values to the historical predictions dataframe. This avoids needing
to repredict on historical candles, and also stores historical predictions despite
@@ -278,10 +280,15 @@ class FreqaiDataDrawer:
new_pred = pred_df.copy()
# set new_pred values to nans (we want to signal to user that there was nothing
# historically made during downtime. The newest pred will get appeneded later in
# historically made during downtime. The newest pred will get appended later in
# append_model_predictions)
new_pred.iloc[:, :] = np.nan
new_pred["date_pred"] = dataframe["date"]
# set everything to nan except date_pred
columns_to_nan = new_pred.columns.difference(['date_pred', 'date'])
new_pred[columns_to_nan] = new_pred[columns_to_nan].astype(
float).values * np.nan
hist_preds = self.historic_predictions[pair].copy()
# ensure both dataframes have the same date format so they can be merged
@@ -290,7 +297,8 @@ class FreqaiDataDrawer:
# find the closest common date between new_pred and historic predictions
# and cut off the new_pred dataframe at that date
common_dates = pd.merge(new_pred, hist_preds, on="date_pred", how="inner")
common_dates = pd.merge(new_pred, hist_preds,
on="date_pred", how="inner")
if len(common_dates.index) > 0:
new_pred = new_pred.iloc[len(common_dates):]
else:
@@ -298,15 +306,23 @@ class FreqaiDataDrawer:
"predictions. You likely left your FreqAI instance offline "
f"for more than {len(dataframe.index)} candles.")
# reindex new_pred columns to match the historic predictions dataframe
new_pred_reindexed = new_pred.reindex(columns=hist_preds.columns)
df_concat = pd.concat([hist_preds, new_pred_reindexed], ignore_index=True)
# Pandas warns that its keeping dtypes of non NaN columns...
# yea we know and we already want that behavior. Ignoring.
with warnings.catch_warnings():
warnings.filterwarnings("ignore", category=FutureWarning)
# reindex new_pred columns to match the historic predictions dataframe
new_pred_reindexed = new_pred.reindex(columns=hist_preds.columns)
df_concat = pd.concat(
[hist_preds, new_pred_reindexed],
ignore_index=True
)
# any missing values will get zeroed out so users can see the exact
# downtime in FreqUI
df_concat = df_concat.fillna(0)
self.historic_predictions[pair] = df_concat
self.model_return_values[pair] = df_concat.tail(len(dataframe.index)).reset_index(drop=True)
self.model_return_values[pair] = df_concat.tail(
len(dataframe.index)).reset_index(drop=True)
def append_model_predictions(self, pair: str, predictions: DataFrame,
do_preds: NDArray[np.int_],
@@ -323,38 +339,56 @@ class FreqaiDataDrawer:
index = self.historic_predictions[pair].index[-1:]
columns = self.historic_predictions[pair].columns
zeros_df = pd.DataFrame(np.zeros((1, len(columns))), index=index, columns=columns)
zeros_df = pd.DataFrame(
np.zeros((1, len(columns))),
index=index,
columns=columns
)
self.historic_predictions[pair] = pd.concat(
[self.historic_predictions[pair], zeros_df], ignore_index=True, axis=0)
[self.historic_predictions[pair], zeros_df],
ignore_index=True,
axis=0
)
df = self.historic_predictions[pair]
# model outputs and associated statistics
for label in predictions.columns:
df[label].iloc[-1] = predictions[label].iloc[-1]
label_loc = df.columns.get_loc(label)
pred_label_loc = predictions.columns.get_loc(label)
df.iloc[-1, label_loc] = predictions.iloc[-1, pred_label_loc]
if df[label].dtype == object:
continue
df[f"{label}_mean"].iloc[-1] = dk.data["labels_mean"][label]
df[f"{label}_std"].iloc[-1] = dk.data["labels_std"][label]
label_mean_loc = df.columns.get_loc(f"{label}_mean")
label_std_loc = df.columns.get_loc(f"{label}_std")
df.iloc[-1, label_mean_loc] = dk.data["labels_mean"][label]
df.iloc[-1, label_std_loc] = dk.data["labels_std"][label]
# outlier indicators
df["do_predict"].iloc[-1] = do_preds[-1]
do_predict_loc = df.columns.get_loc("do_predict")
df.iloc[-1, do_predict_loc] = do_preds[-1]
if self.freqai_info["feature_parameters"].get("DI_threshold", 0) > 0:
df["DI_values"].iloc[-1] = dk.DI_values[-1]
DI_values_loc = df.columns.get_loc("DI_values")
df.iloc[-1, DI_values_loc] = dk.DI_values[-1]
# extra values the user added within custom prediction model
if dk.data['extra_returns_per_train']:
rets = dk.data['extra_returns_per_train']
for return_str in rets:
df[return_str].iloc[-1] = rets[return_str]
return_loc = df.columns.get_loc(return_str)
df.iloc[-1, return_loc] = rets[return_str]
# this logic carries users between version without needing to
# change their identifier
if 'close_price' not in df.columns:
df['close_price'] = np.nan
df['date_pred'] = np.nan
df['close_price'].iloc[-1] = strat_df['close'].iloc[-1]
df['date_pred'].iloc[-1] = strat_df['date'].iloc[-1]
high_price_loc = df.columns.get_loc("high_price")
high_loc = strat_df.columns.get_loc("high")
df.iloc[-1, high_price_loc] = strat_df.iloc[-1, high_loc]
low_price_loc = df.columns.get_loc("low_price")
low_loc = strat_df.columns.get_loc("low")
df.iloc[-1, low_price_loc] = strat_df.iloc[-1, low_loc]
close_price_loc = df.columns.get_loc("close_price")
close_loc = strat_df.columns.get_loc("close")
df.iloc[-1, close_price_loc] = strat_df.iloc[-1, close_loc]
date_pred_loc = df.columns.get_loc("date_pred")
date_loc = strat_df.columns.get_loc("date")
df.iloc[-1, date_pred_loc] = strat_df.iloc[-1, date_loc]
self.model_return_values[pair] = df.tail(len_df).reset_index(drop=True)
+9 -5
View File
@@ -24,6 +24,8 @@ from freqtrade.strategy import merge_informative_pair
from freqtrade.strategy.interface import IStrategy
pd.set_option('future.no_silent_downcasting', True)
SECONDS_IN_DAY = 86400
SECONDS_IN_HOUR = 3600
@@ -221,7 +223,7 @@ class FreqaiDataKitchen:
filtered_df = filtered_df.replace([np.inf, -np.inf], np.nan)
drop_index = pd.isnull(filtered_df).any(axis=1) # get the rows that have NaNs,
drop_index = drop_index.replace(True, 1).replace(False, 0) # pep8 requirement.
drop_index = drop_index.replace(True, 1).replace(False, 0).infer_objects(copy=False)
if (training_filter):
# we don't care about total row number (total no. datapoints) in training, we only care
@@ -229,7 +231,9 @@ class FreqaiDataKitchen:
# if labels has multiple columns (user wants to train multiple modelEs), we detect here
labels = unfiltered_df.filter(label_list, axis=1)
drop_index_labels = pd.isnull(labels).any(axis=1)
drop_index_labels = drop_index_labels.replace(True, 1).replace(False, 0)
drop_index_labels = drop_index_labels.replace(
True, 1
).replace(False, 0).infer_objects(copy=False)
dates = unfiltered_df['date']
filtered_df = filtered_df[
(drop_index == 0) & (drop_index_labels == 0)
@@ -608,7 +612,7 @@ class FreqaiDataKitchen:
pairs = self.freqai_config["feature_parameters"].get("include_corr_pairlist", [])
for pair in pairs:
pair = pair.replace(':', '') # lightgbm doesnt like colons
pair = pair.replace(':', '') # lightgbm does not like colons
pair_cols = [col for col in dataframe.columns if col.startswith("%")
and f"{pair}_" in col]
@@ -634,7 +638,7 @@ class FreqaiDataKitchen:
pairs = self.freqai_config["feature_parameters"].get("include_corr_pairlist", [])
current_pair = current_pair.replace(':', '')
for pair in pairs:
pair = pair.replace(':', '') # lightgbm doesnt work with colons
pair = pair.replace(':', '') # lightgbm does not work with colons
if current_pair != pair:
dataframe = dataframe.merge(corr_dataframes[pair], how='left', on='date')
@@ -837,7 +841,7 @@ class FreqaiDataKitchen:
f = spy.stats.norm.fit(self.data_dictionary["train_labels"][label])
self.data["labels_mean"][label], self.data["labels_std"][label] = f[0], f[1]
# incase targets are classifications
# in case targets are classifications
for label in self.unique_class_list:
self.data["labels_mean"][label], self.data["labels_std"][label] = 0, 0
+3 -4
View File
@@ -222,7 +222,7 @@ class IFreqaiModel(ABC):
time.sleep(1)
pair = self.train_queue[0]
# ensure pair is avaialble in dp
# ensure pair is available in dp
if pair not in strategy.dp.current_whitelist():
self.train_queue.popleft()
logger.warning(f'{pair} not in current whitelist, removing from train queue.')
@@ -675,6 +675,8 @@ class IFreqaiModel(ABC):
for return_str in dk.data['extra_returns_per_train']:
hist_preds_df[return_str] = dk.data['extra_returns_per_train'][return_str]
hist_preds_df['high_price'] = strat_df['high']
hist_preds_df['low_price'] = strat_df['low']
hist_preds_df['close_price'] = strat_df['close']
hist_preds_df['date_pred'] = strat_df['date']
@@ -716,9 +718,6 @@ class IFreqaiModel(ABC):
if self.pair_it == self.total_pairs:
logger.info(
f'Total time spent inferencing pairlist {self.inference_time:.2f} seconds')
if self.inference_time > 0.25 * self.base_tf_seconds:
logger.warning("Inference took over 25% of the candle time. Reduce pairlist to"
" avoid blinding open trades and degrading performance.")
self.pair_it = 0
self.inference_time = 0
return
@@ -74,7 +74,7 @@ class PyTorchMLPClassifier(BasePyTorchClassifier):
model.to(self.device)
optimizer = torch.optim.AdamW(model.parameters(), lr=self.learning_rate)
criterion = torch.nn.CrossEntropyLoss()
# check if continual_learning is activated, and retreive the model to continue training
# check if continual_learning is activated, and retrieve the model to continue training
trainer = self.get_init_model(dk.pair)
if trainer is None:
trainer = PyTorchModelTrainer(
@@ -69,7 +69,7 @@ class PyTorchMLPRegressor(BasePyTorchRegressor):
model.to(self.device)
optimizer = torch.optim.AdamW(model.parameters(), lr=self.learning_rate)
criterion = torch.nn.MSELoss()
# check if continual_learning is activated, and retreive the model to continue training
# check if continual_learning is activated, and retrieve the model to continue training
trainer = self.get_init_model(dk.pair)
if trainer is None:
trainer = PyTorchModelTrainer(
@@ -80,7 +80,7 @@ class PyTorchTransformerRegressor(BasePyTorchRegressor):
model.to(self.device)
optimizer = torch.optim.AdamW(model.parameters(), lr=self.learning_rate)
criterion = torch.nn.MSELoss()
# check if continual_learning is activated, and retreive the model to continue training
# check if continual_learning is activated, and retrieve the model to continue training
trainer = self.get_init_model(dk.pair)
if trainer is None:
trainer = PyTorchTransformerTrainer(
@@ -63,6 +63,6 @@ class ReinforcementLearner_multiproc(ReinforcementLearner):
is_masking_supported(self.eval_env)))
# TENSORBOARD CALLBACK DOES NOT RECOMMENDED TO USE WITH MULTIPLE ENVS,
# IT WILL RETURN FALSE INFORMATIONS, NEVERTHLESS NOT THREAD SAFE WITH SB3!!!
# IT WILL RETURN FALSE INFORMATION, NEVERTHELESS NOT THREAD SAFE WITH SB3!!!
actions = self.train_env.env_method("get_actions")[0]
self.tensorboard_callback = TensorboardCallback(verbose=1, actions=actions)
@@ -38,7 +38,7 @@ class PyTorchModelTrainer(PyTorchTrainerInterface):
:param init_model: A dictionary containing the initial model/optimizer
state_dict and model_meta_data saved by self.save() method.
:param model_meta_data: Additional metadata about the model (optional).
:param data_convertor: convertor from pd.DataFrame to torch.tensor.
:param data_convertor: converter from pd.DataFrame to torch.tensor.
:param n_steps: used to calculate n_epochs. The number of training iterations to run.
iteration here refers to the number of times optimizer.step() is called.
ignored if n_epochs is set.
+1 -1
View File
@@ -178,7 +178,7 @@ def record_params(config: Dict[str, Any], full_path: Path) -> None:
def get_timerange_backtest_live_models(config: Config) -> str:
"""
Returns a formated timerange for backtest live/ready models
Returns a formatted timerange for backtest live/ready models
:param config: Configuration dictionary
:return: a string timerange (format example: '20220801-20220822')
+62 -26
View File
@@ -37,6 +37,7 @@ from freqtrade.rpc.rpc_types import (ProfitLossStr, RPCCancelMsg, RPCEntryMsg, R
RPCExitMsg, RPCProtectionMsg)
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
from freqtrade.util import MeasureTime
from freqtrade.util.migrations import migrate_binance_futures_names
from freqtrade.wallets import Wallets
@@ -64,7 +65,7 @@ class FreqtradeBot(LoggingMixin):
# Init objects
self.config = config
exchange_config: ExchangeConfig = deepcopy(config['exchange'])
# Remove credentials from original exchange config to avoid accidental credentail exposure
# Remove credentials from original exchange config to avoid accidental credential exposure
remove_exchange_credentials(config['exchange'], True)
self.strategy: IStrategy = StrategyResolver.load_strategy(self.config)
@@ -117,7 +118,8 @@ class FreqtradeBot(LoggingMixin):
# Protect exit-logic from forcesell and vice versa
self._exit_lock = Lock()
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe))
timeframe_secs = timeframe_to_seconds(self.strategy.timeframe)
LoggingMixin.__init__(self, logger, timeframe_secs)
self._schedule = Scheduler()
@@ -139,6 +141,16 @@ class FreqtradeBot(LoggingMixin):
# Initialize protections AFTER bot start - otherwise parameters are not loaded.
self.protections = ProtectionManager(self.config, self.strategy.protections)
def log_took_too_long(duration: float, time_limit: float):
logger.warning(
f"Strategy analysis took {duration:.2f}, which is 25% of the timeframe. "
"This can lead to delayed orders and missed signals."
"Consider either reducing the amount of work your strategy performs "
"or reduce the amount of pairs in the Pairlist."
)
self._measure_execution = MeasureTime(log_took_too_long, timeframe_secs * 0.25)
def notify_status(self, msg: str, msg_type=RPCMessageType.STATUS) -> None:
"""
Public method for users of this class (worker, etc.) to send notifications
@@ -175,7 +187,7 @@ class FreqtradeBot(LoggingMixin):
try:
Trade.commit()
except Exception:
# Exeptions here will be happening if the db disappeared.
# Exceptions here will be happening if the db disappeared.
# At which point we can no longer commit anyway.
pass
@@ -223,10 +235,11 @@ class FreqtradeBot(LoggingMixin):
strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)(
current_time=datetime.now(timezone.utc))
self.strategy.analyze(self.active_pair_whitelist)
with self._measure_execution:
self.strategy.analyze(self.active_pair_whitelist)
with self._exit_lock:
# Check for exchange cancelations, timeouts and user requested replace
# Check for exchange cancellations, timeouts and user requested replace
self.manage_open_orders()
# Protect from collisions with force_exit.
@@ -237,12 +250,12 @@ class FreqtradeBot(LoggingMixin):
# First process current opened trades (positions)
self.exit_positions(trades)
# Check if we need to adjust our current positions before attempting to buy new trades.
# Check if we need to adjust our current positions before attempting to enter new trades.
if self.strategy.position_adjustment_enable:
with self._exit_lock:
self.process_open_trade_positions()
# Then looking for buy opportunities
# Then looking for entry opportunities
if self.get_free_open_trades():
self.enter_positions()
if self.trading_mode == TradingMode.FUTURES:
@@ -277,7 +290,7 @@ class FreqtradeBot(LoggingMixin):
}
self.rpc.send_msg(msg)
def _refresh_active_whitelist(self, trades: List[Trade] = []) -> List[str]:
def _refresh_active_whitelist(self, trades: Optional[List[Trade]] = None) -> List[str]:
"""
Refresh active whitelist from pairlist or edge and extend it with
pairs that have open trades.
@@ -449,6 +462,7 @@ class FreqtradeBot(LoggingMixin):
trade.pair, trade.open_date_utc - timedelta(seconds=10))
prev_exit_reason = trade.exit_reason
prev_trade_state = trade.is_open
prev_trade_amount = trade.amount
for order in orders:
trade_order = [o for o in trade.orders if o.order_id == order['id']]
@@ -480,6 +494,26 @@ class FreqtradeBot(LoggingMixin):
send_msg=prev_trade_state != trade.is_open)
else:
trade.exit_reason = prev_exit_reason
total = self.wallets.get_total(trade.base_currency) if trade.base_currency else 0
if total < trade.amount:
if total > trade.amount * 0.98:
logger.warning(
f"{trade} has a total of {trade.amount} {trade.base_currency}, "
f"but the Wallet shows a total of {total} {trade.base_currency}. "
f"Adjusting trade amount to {total}."
"This may however lead to further issues."
)
trade.amount = total
else:
logger.warning(
f"{trade} has a total of {trade.amount} {trade.base_currency}, "
f"but the Wallet shows a total of {total} {trade.base_currency}. "
"Refusing to adjust as the difference is too large."
"This may however lead to further issues."
)
if prev_trade_amount != trade.amount:
# Cancel stoploss on exchange if the amount changed
trade = self.cancel_stoploss_on_exchange(trade)
Trade.commit()
except ExchangeError:
@@ -488,7 +522,7 @@ class FreqtradeBot(LoggingMixin):
# catching https://github.com/freqtrade/freqtrade/issues/9025
logger.warning("Error finding onexchange order", exc_info=True)
#
# BUY / enter positions / open trades logic and methods
# enter positions / open trades logic and methods
#
def enter_positions(self) -> int:
@@ -538,10 +572,10 @@ class FreqtradeBot(LoggingMixin):
def create_trade(self, pair: str) -> bool:
"""
Check the implemented trading strategy for buy signals.
Check the implemented trading strategy for entry signals.
If the pair triggers the buy signal a new trade record gets created
and the buy-order opening the trade gets issued towards the exchange.
If the pair triggers the enter signal a new trade record gets created
and the entry-order opening the trade gets issued towards the exchange.
:return: True if a trade has been created.
"""
@@ -600,7 +634,7 @@ class FreqtradeBot(LoggingMixin):
return False
#
# BUY / increase positions / DCA logic and methods
# Modify positions / DCA logic and methods
#
def process_open_trade_positions(self):
"""
@@ -683,7 +717,7 @@ class FreqtradeBot(LoggingMixin):
def _check_depth_of_market(self, pair: str, conf: Dict, side: SignalDirection) -> bool:
"""
Checks depth of market before executing a buy
Checks depth of market before executing an entry
"""
conf_bids_to_ask_delta = conf.get('bids_to_ask_delta', 0)
logger.info(f"Checking depth of market for {pair} ...")
@@ -727,10 +761,10 @@ class FreqtradeBot(LoggingMixin):
leverage_: Optional[float] = None,
) -> bool:
"""
Executes a limit buy for the given pair
:param pair: pair for which we want to create a LIMIT_BUY
Executes an entry for the given pair
:param pair: pair for which we want to create a LIMIT order
:param stake_amount: amount of stake-currency for the pair
:return: True if a buy order is created, false if it fails.
:return: True if an entry order is created, False if it fails.
:raise: DependencyException or it's subclasses like ExchangeError.
"""
time_in_force = self.strategy.order_time_in_force['entry']
@@ -859,7 +893,7 @@ class FreqtradeBot(LoggingMixin):
trade.adjust_stop_loss(trade.open_rate, stoploss, initial=True)
else:
# This is additional buy, we reset fee_open_currency so timeout checking can work
# This is additional entry, we reset fee_open_currency so timeout checking can work
trade.is_open = True
trade.fee_open_currency = None
trade.open_rate_requested = enter_limit_requested
@@ -1232,7 +1266,7 @@ class FreqtradeBot(LoggingMixin):
return True
if trade.has_open_orders or not trade.is_open:
# Trade has an open Buy or Sell order, Stoploss-handling can't happen in this case
# Trade has an open order, Stoploss-handling can't happen in this case
# as the Amount on the exchange is tied up in another trade.
# The trade can be closed already (sell-order fill confirmation came in this iteration)
return False
@@ -1290,12 +1324,12 @@ class FreqtradeBot(LoggingMixin):
def manage_trade_stoploss_orders(self, trade: Trade, stoploss_orders: List[Dict]):
"""
Perform required actions acording to existing stoploss orders of trade
Perform required actions according to existing stoploss orders of trade
:param trade: Corresponding Trade
:param stoploss_orders: Current on exchange stoploss orders
:return: None
"""
# If all stoploss orderd are canceled for some reason we add it again
# If all stoploss ordered are canceled for some reason we add it again
canceled_sl_orders = [o for o in stoploss_orders
if o['status'] in ('canceled', 'cancelled')]
if (
@@ -1935,21 +1969,23 @@ class FreqtradeBot(LoggingMixin):
trade.update_trade(order_obj, not send_msg)
trade = self._update_trade_after_fill(trade, order_obj)
trade = self._update_trade_after_fill(trade, order_obj, send_msg)
Trade.commit()
self.order_close_notify(trade, order_obj, stoploss_order, send_msg)
return False
def _update_trade_after_fill(self, trade: Trade, order: Order) -> Trade:
def _update_trade_after_fill(self, trade: Trade, order: Order, send_msg: bool) -> Trade:
if order.status in constants.NON_OPEN_EXCHANGE_STATES:
strategy_safe_wrapper(
self.strategy.order_filled, default_retval=None)(
pair=trade.pair, trade=trade, order=order, current_time=datetime.now(timezone.utc))
# If a entry order was closed, force update on stoploss on exchange
if order.ft_order_side == trade.entry_side:
trade = self.cancel_stoploss_on_exchange(trade)
if send_msg:
# Don't cancel stoploss in recovery modes immediately
trade = self.cancel_stoploss_on_exchange(trade)
if not self.edge:
# TODO: should shorting/leverage be supported by Edge,
# then this will need to be fixed.
@@ -1999,7 +2035,7 @@ class FreqtradeBot(LoggingMixin):
self._notify_enter(trade, order, order.order_type, fill=True, sub_trade=sub_trade)
def handle_protections(self, pair: str, side: LongShort) -> None:
# Lock pair for one candle to prevent immediate rebuys
# Lock pair for one candle to prevent immediate re-entries
self.strategy.lock_pair(pair, datetime.now(timezone.utc), reason='Auto lock')
prot_trig = self.protections.stop_per_pair(pair, side=side)
if prot_trig:
@@ -2035,7 +2071,7 @@ class FreqtradeBot(LoggingMixin):
amount_ = trade.amount - amount
if trade.nr_of_successful_entries >= 1 and order_obj.ft_order_side == trade.entry_side:
# In case of rebuy's, trade.amount doesn't contain the amount of the last entry.
# In case of re-entry's, trade.amount doesn't contain the amount of the last entry.
amount_ = trade.amount + amount
if fee_abs != 0 and self.wallets.get_free(trade_base_currency) >= amount_:
+9 -6
View File
@@ -19,6 +19,7 @@ from freqtrade.data import history
from freqtrade.data.btanalysis import find_existing_backtest_stats, trade_list_to_dataframe
from freqtrade.data.converter import trim_dataframe, trim_dataframes
from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.metrics import combined_dataframes_with_rel_mean
from freqtrade.enums import (BacktestState, CandleType, ExitCheckTuple, ExitType, RunMode,
TradingMode)
from freqtrade.exceptions import DependencyException, OperationalException
@@ -296,7 +297,7 @@ class Backtesting:
candle_type=CandleType.FUNDING_RATE
)
# For simplicity, assign to CandleType.Mark (might contian index candles!)
# For simplicity, assign to CandleType.Mark (might contain index candles!)
mark_rates_dict = history.load_data(
datadir=self.config['datadir'],
pairs=self.pairlists.whitelist,
@@ -565,7 +566,8 @@ class Backtesting:
if stake_amount is not None and stake_amount < 0.0:
amount = amount_to_contract_precision(
abs(stake_amount * trade.leverage) / current_rate, trade.amount_precision,
abs(stake_amount * trade.amount / trade.stake_amount),
trade.amount_precision,
self.precision_mode, trade.contract_size)
if amount == 0.0:
return trade
@@ -1215,7 +1217,7 @@ class Backtesting:
:return: DataFrame with trades (results of backtesting)
"""
self.prepare_backtest(self.enable_protections)
# Ensure wallets are uptodate (important for --strategy-list)
# Ensure wallets are up-to-date (important for --strategy-list)
self.wallets.update()
# Use dict of lists with data for performance
# (looping lists is a lot faster than pandas DataFrames)
@@ -1392,9 +1394,8 @@ class Backtesting:
def start(self) -> None:
"""
Run backtesting end-to-end
:return: None
"""
data: Dict[str, Any] = {}
data: Dict[str, DataFrame] = {}
data, timerange = self.load_bt_data()
self.load_bt_data_detail()
@@ -1421,7 +1422,9 @@ class Backtesting:
self.results = results
dt_appendix = datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
if self.config.get('export', 'none') in ('trades', 'signals'):
store_backtest_stats(self.config['exportfilename'], self.results, dt_appendix)
combined_res = combined_dataframes_with_rel_mean(data, min_date, max_date)
store_backtest_stats(self.config['exportfilename'], self.results, dt_appendix,
market_change_data=combined_res)
if (self.config.get('export', 'none') == 'signals' and
self.dataprovider.runmode == RunMode.BACKTEST):
+4 -2
View File
@@ -237,8 +237,10 @@ class HyperoptTools:
result_dict.update(all_space_params)
@staticmethod
def _params_pretty_print(params, space: str, header: str, non_optimized={}) -> None:
if space in params or space in non_optimized:
def _params_pretty_print(
params, space: str, header: str, non_optimized: Optional[Dict] = None) -> None:
if space in params or (non_optimized and space in non_optimized):
space_params = HyperoptTools._space_params(params, space, 5)
no_params = HyperoptTools._space_params(non_optimized, space, 5)
appendix = ''
@@ -6,13 +6,12 @@ from freqtrade.optimize.optimize_reports.bt_output import (generate_edge_table,
show_sorted_pairlist,
text_table_add_metrics,
text_table_bt_results,
text_table_exit_reason,
text_table_periodic_breakdown,
text_table_strategy, text_table_tags)
from freqtrade.optimize.optimize_reports.bt_storage import (store_backtest_analysis_results,
store_backtest_stats)
from freqtrade.optimize.optimize_reports.optimize_reports import (
generate_all_periodic_breakdown_stats, generate_backtest_stats, generate_daily_stats,
generate_exit_reason_stats, generate_pair_metrics, generate_periodic_breakdown_stats,
generate_rejected_signals, generate_strategy_comparison, generate_strategy_stats,
generate_tag_metrics, generate_trade_signal_candles, generate_trading_stats)
generate_pair_metrics, generate_periodic_breakdown_stats, generate_rejected_signals,
generate_strategy_comparison, generate_strategy_stats, generate_tag_metrics,
generate_trade_signal_candles, generate_trading_stats)
@@ -60,32 +60,6 @@ def text_table_bt_results(pair_results: List[Dict[str, Any]], stake_currency: st
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right")
def text_table_exit_reason(exit_reason_stats: List[Dict[str, Any]], stake_currency: str) -> str:
"""
Generate small table outlining Backtest results
:param exit_reason_stats: Exit reason metrics
:param stake_currency: Stakecurrency used
:return: pretty printed table with tabulate as string
"""
headers = [
'Exit Reason',
'Exits',
'Win Draws Loss Win%',
'Avg Profit %',
f'Tot Profit {stake_currency}',
'Tot Profit %',
]
output = [[
t.get('exit_reason', t.get('sell_reason')), t['trades'],
generate_wins_draws_losses(t['wins'], t['draws'], t['losses']),
t['profit_mean_pct'],
fmt_coin(t['profit_total_abs'], stake_currency, False),
t['profit_total_pct'],
] for t in exit_reason_stats]
return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right")
def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_currency: str) -> str:
"""
Generates and returns a text table for the given backtest data and the results dataframe
@@ -93,20 +67,23 @@ def text_table_tags(tag_type: str, tag_results: List[Dict[str, Any]], stake_curr
:param stake_currency: stake-currency - used to correctly name headers
:return: pretty printed table with tabulate as string
"""
fallback: str = ''
if (tag_type == "enter_tag"):
headers = _get_line_header("TAG", stake_currency)
else:
headers = _get_line_header("TAG", stake_currency, 'Exits')
headers = _get_line_header("Exit Reason", stake_currency, 'Exits')
fallback = 'exit_reason'
floatfmt = _get_line_floatfmt(stake_currency)
output = [
[
t['key'] if t['key'] is not None and len(
t['key']) > 0 else "OTHER",
t['key'] if t.get('key') is not None and len(
str(t['key'])) > 0 else t.get(fallback, "OTHER"),
t['trades'],
t['profit_mean_pct'],
t['profit_total_abs'],
t['profit_total_pct'],
t['duration_avg'],
t.get('duration_avg'),
generate_wins_draws_losses(
t['wins'],
t['draws'],
@@ -301,7 +278,7 @@ def text_table_add_metrics(strat_results: Dict) -> str:
def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency: str,
backtest_breakdown=[]):
backtest_breakdown: List[str]):
"""
Print results for one strategy
"""
@@ -317,17 +294,16 @@ def show_backtest_result(strategy: str, results: Dict[str, Any], stake_currency:
print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
print(table)
if (results.get('results_per_enter_tag') is not None):
table = text_table_tags("enter_tag", results['results_per_enter_tag'], stake_currency)
if (enter_tags := results.get('results_per_enter_tag')) is not None:
table = text_table_tags("enter_tag", enter_tags, stake_currency)
if isinstance(table, str) and len(table) > 0:
print(' ENTER TAG STATS '.center(len(table.splitlines()[0]), '='))
print(table)
exit_reasons = results.get('exit_reason_summary')
if exit_reasons:
table = text_table_exit_reason(exit_reason_stats=exit_reasons,
stake_currency=stake_currency)
if (exit_reasons := results.get('exit_reason_summary')) is not None:
table = text_table_tags("exit_tag", exit_reasons, stake_currency)
if isinstance(table, str) and len(table) > 0:
print(' EXIT REASON STATS '.center(len(table.splitlines()[0]), '='))
print(table)
@@ -1,6 +1,8 @@
import logging
from pathlib import Path
from typing import Dict
from typing import Dict, Optional
from pandas import DataFrame
from freqtrade.constants import LAST_BT_RESULT_FN
from freqtrade.misc import file_dump_joblib, file_dump_json
@@ -11,8 +13,26 @@ from freqtrade.types import BacktestResultType
logger = logging.getLogger(__name__)
def _generate_filename(recordfilename: Path, appendix: str, suffix: str) -> Path:
"""
Generates a filename based on the provided parameters.
:param recordfilename: Path object, which can either be a filename or a directory.
:param appendix: use for the filename. e.g. backtest-result-<datetime>
:param suffix: Suffix to use for the file, e.g. .json, .pkl
:return: Generated filename as a Path object
"""
if recordfilename.is_dir():
filename = (recordfilename / f'backtest-result-{appendix}').with_suffix(suffix)
else:
filename = Path.joinpath(
recordfilename.parent, f'{recordfilename.stem}-{appendix}'
).with_suffix(suffix)
return filename
def store_backtest_stats(
recordfilename: Path, stats: BacktestResultType, dtappendix: str) -> Path:
recordfilename: Path, stats: BacktestResultType, dtappendix: str, *,
market_change_data: Optional[DataFrame] = None) -> Path:
"""
Stores backtest results
:param recordfilename: Path object, which can either be a filename or a directory.
@@ -21,12 +41,7 @@ def store_backtest_stats(
:param stats: Dataframe containing the backtesting statistics
:param dtappendix: Datetime to use for the filename
"""
if recordfilename.is_dir():
filename = (recordfilename / f'backtest-result-{dtappendix}.json')
else:
filename = Path.joinpath(
recordfilename.parent, f'{recordfilename.stem}-{dtappendix}'
).with_suffix(recordfilename.suffix)
filename = _generate_filename(recordfilename, dtappendix, '.json')
# Store metadata separately.
file_dump_json(get_backtest_metadata_filename(filename), stats['metadata'])
@@ -41,6 +56,11 @@ def store_backtest_stats(
latest_filename = Path.joinpath(filename.parent, LAST_BT_RESULT_FN)
file_dump_json(latest_filename, {'latest_backtest': str(filename.name)})
if market_change_data is not None:
filename_mc = _generate_filename(recordfilename, f"{dtappendix}_market_change", '.feather')
market_change_data.reset_index().to_feather(
filename_mc, compression_level=9, compression='lz4')
return filename
@@ -57,12 +77,7 @@ def _store_backtest_analysis_data(
:param dtappendix: Datetime to use for the filename
:param name: Name to use for the file, e.g. signals, rejected
"""
if recordfilename.is_dir():
filename = (recordfilename / f'backtest-result-{dtappendix}_{name}.pkl')
else:
filename = Path.joinpath(
recordfilename.parent, f'{recordfilename.stem}-{dtappendix}_{name}.pkl'
)
filename = _generate_filename(recordfilename, f"{dtappendix}_{name}", '.pkl')
file_dump_joblib(filename, data)
@@ -6,7 +6,7 @@ from typing import Any, Dict, List, Tuple, Union
import numpy as np
from pandas import DataFrame, Series, concat, to_datetime
from freqtrade.constants import BACKTEST_BREAKDOWNS, DATETIME_PRINT_FORMAT, IntOrInf
from freqtrade.constants import BACKTEST_BREAKDOWNS, DATETIME_PRINT_FORMAT
from freqtrade.data.metrics import (calculate_cagr, calculate_calmar, calculate_csum,
calculate_expectancy, calculate_market_change,
calculate_max_drawdown, calculate_sharpe, calculate_sortino)
@@ -71,7 +71,8 @@ def _generate_result_line(result: DataFrame, starting_balance: int, first_column
'key': first_column,
'trades': len(result),
'profit_mean': result['profit_ratio'].mean() if len(result) > 0 else 0.0,
'profit_mean_pct': result['profit_ratio'].mean() * 100.0 if len(result) > 0 else 0.0,
'profit_mean_pct': round(result['profit_ratio'].mean() * 100.0, 2
) if len(result) > 0 else 0.0,
'profit_sum': profit_sum,
'profit_sum_pct': round(profit_sum * 100.0, 2),
'profit_total_abs': result['profit_abs'].sum(),
@@ -154,42 +155,6 @@ def generate_tag_metrics(tag_type: str,
return []
def generate_exit_reason_stats(max_open_trades: IntOrInf, results: DataFrame) -> List[Dict]:
"""
Generate small table outlining Backtest results
:param max_open_trades: Max_open_trades parameter
:param results: Dataframe containing the backtest result for one strategy
:return: List of Dicts containing the metrics per Sell reason
"""
tabular_data = []
for reason, count in results['exit_reason'].value_counts().items():
result = results.loc[results['exit_reason'] == reason]
profit_mean = result['profit_ratio'].mean()
profit_sum = result['profit_ratio'].sum()
profit_total = profit_sum / max_open_trades
tabular_data.append(
{
'exit_reason': reason,
'trades': count,
'wins': len(result[result['profit_abs'] > 0]),
'draws': len(result[result['profit_abs'] == 0]),
'losses': len(result[result['profit_abs'] < 0]),
'winrate': len(result[result['profit_abs'] > 0]) / count if count else 0.0,
'profit_mean': profit_mean,
'profit_mean_pct': round(profit_mean * 100, 2),
'profit_sum': profit_sum,
'profit_sum_pct': round(profit_sum * 100, 2),
'profit_total_abs': result['profit_abs'].sum(),
'profit_total': profit_total,
'profit_total_pct': round(profit_total * 100, 2),
}
)
return tabular_data
def generate_strategy_comparison(bt_stats: Dict) -> List[Dict]:
"""
Generate summary per strategy
@@ -383,9 +348,8 @@ def generate_strategy_stats(pairlist: List[str],
enter_tag_results = generate_tag_metrics("enter_tag", starting_balance=start_balance,
results=results, skip_nan=False)
exit_reason_stats = generate_exit_reason_stats(max_open_trades=max_open_trades,
results=results)
exit_reason_stats = generate_tag_metrics('exit_reason', starting_balance=start_balance,
results=results, skip_nan=False)
left_open_results = generate_pair_metrics(
pairlist, stake_currency=stake_currency, starting_balance=start_balance,
results=results.loc[results['exit_reason'] == 'force_exit'], skip_nan=True)
+1 -1
View File
@@ -18,7 +18,7 @@ class _CustomData(ModelBase):
"""
CustomData database model
Keeps records of metadata as key/value store
for trades or global persistant values
for trades or global persistent values
One to many relationship with Trades:
- One trade can have many metadata entries
- One metadata entry can only be associated with one Trade
+3 -3
View File
@@ -847,7 +847,7 @@ class LocalTrade:
isclose(order.safe_amount_after_fee, amount_tr, abs_tol=MATH_CLOSE_PREC)
or (not recalculating and order.safe_amount_after_fee > amount_tr)
):
# When recalculating a trade, only comming out to 0 can force a close
# When recalculating a trade, only coming out to 0 can force a close
self.close(order.safe_price)
else:
self.recalc_trade_from_orders()
@@ -1125,7 +1125,7 @@ class LocalTrade:
prof = self.calculate_profit(exit_rate, exit_amount, float(avg_price))
close_profit_abs += prof.profit_abs
if total_stake > 0:
# This needs to be calculated based on the last occuring exit to be aligned
# This needs to be calculated based on the last occurring exit to be aligned
# with realized_profit.
close_profit = (close_profit_abs / total_stake) * self.leverage
else:
@@ -1538,7 +1538,7 @@ class Trade(ModelBase, LocalTrade):
amount: Mapped[float] = mapped_column(Float()) # type: ignore
amount_requested: Mapped[Optional[float]] = mapped_column(Float()) # type: ignore
open_date: Mapped[datetime] = mapped_column(
nullable=False, default=datetime.utcnow) # type: ignore
nullable=False, default=datetime.now) # type: ignore
close_date: Mapped[Optional[datetime]] = mapped_column() # type: ignore
# absolute value of the stop loss
stop_loss: Mapped[float] = mapped_column(Float(), nullable=True, default=0.0) # type: ignore
+9 -5
View File
@@ -440,12 +440,12 @@ def create_scatter(
def generate_candlestick_graph(
pair: str, data: pd.DataFrame, trades: Optional[pd.DataFrame] = None, *,
indicators1: List[str] = [], indicators2: List[str] = [],
plot_config: Dict[str, Dict] = {},
indicators1: Optional[List[str]] = None, indicators2: Optional[List[str]] = None,
plot_config: Optional[Dict[str, Dict]] = None,
) -> go.Figure:
"""
Generate the graph from the data generated by Backtesting or from DB
Volume will always be ploted in row2, so Row 1 and 3 are to our disposal for custom indicators
Volume will always be plotted in row2, so Row 1 and 3 are to our disposal for custom indicators
:param pair: Pair to Display on the graph
:param data: OHLCV DataFrame containing indicators and entry/exit signals
:param trades: All trades created
@@ -454,7 +454,11 @@ def generate_candlestick_graph(
:param plot_config: Dict of Dicts containing advanced plot configuration
:return: Plotly figure
"""
plot_config = create_plotconfig(indicators1, indicators2, plot_config)
plot_config = create_plotconfig(
indicators1 or [],
indicators2 or [],
plot_config or {},
)
rows = 2 + len(plot_config['subplots'])
row_widths = [1 for _ in plot_config['subplots']]
# Define the graph
@@ -673,7 +677,7 @@ def plot_profit(config: Config) -> None:
"""
Plots the total profit for all pairs.
Note, the profit calculation isn't realistic.
But should be somewhat proportional, and therefor useful
But should be somewhat proportional, and therefore useful
in helping out to find a good algorithm.
"""
if 'timeframe' not in config:
@@ -38,7 +38,7 @@ class MarketCapPairList(IPairList):
self._refresh_period = self._pairlistconfig.get('refresh_period', 86400)
self._marketcap_cache: TTLCache = TTLCache(maxsize=1, ttl=self._refresh_period)
self._def_candletype = self._config['candle_type_def']
self._coingekko: CoinGeckoAPI = CoinGeckoAPI()
self._coingecko: CoinGeckoAPI = CoinGeckoAPI()
if self._max_rank > 250:
raise OperationalException(
@@ -127,7 +127,7 @@ class MarketCapPairList(IPairList):
marketcap_list = self._marketcap_cache.get('marketcap')
if marketcap_list is None:
data = self._coingekko.get_coins_markets(vs_currency='usd', order='market_cap_desc',
data = self._coingecko.get_coins_markets(vs_currency='usd', order='market_cap_desc',
per_page='250', page='1', sparkline='false',
locale='en')
if data:
+1 -1
View File
@@ -101,7 +101,7 @@ class PriceFilter(IPairList):
def _validate_pair(self, pair: str, ticker: Optional[Ticker]) -> bool:
"""
Check if if one price-step (pip) is > than a certain barrier.
Check if one price-step (pip) is > than a certain barrier.
:param pair: Pair that's currently validated
:param ticker: ticker dict as returned from ccxt.fetch_ticker
:return: True if the pair can stay, false if it should be removed
+1 -1
View File
@@ -116,7 +116,7 @@ class RemotePairList(IPairList):
"default": "filter",
"options": ["filter", "append"],
"description": "Processing mode",
"help": "Append pairs to incomming pairlist or filter them?",
"help": "Append pairs to incoming pairlist or filter them?",
},
**IPairList.refresh_period_parameter(),
"keep_pairlist_on_failure": {
+1 -1
View File
@@ -65,7 +65,7 @@ class VolumePairList(IPairList):
self._tf_in_min = timeframe_to_minutes(self._lookback_timeframe)
_tf_in_sec = self._tf_in_min * 60
# wether to use range lookback or not
# whether to use range lookback or not
self._use_range = (self._tf_in_min > 0) & (self._lookback_period > 0)
if self._use_range & (self._refresh_period < _tf_in_sec):
+1 -1
View File
@@ -110,7 +110,7 @@ class IProtection(LoggingMixin, ABC):
Get lock end time
"""
max_date: datetime = max([trade.close_date for trade in trades if trade.close_date])
# comming from Database, tzinfo is not set.
# coming from Database, tzinfo is not set.
if max_date.tzinfo is None:
max_date = max_date.replace(tzinfo=timezone.utc)
+6 -5
View File
@@ -47,7 +47,7 @@ class IResolver:
@classmethod
def build_search_paths(cls, config: Config, user_subdir: Optional[str] = None,
extra_dirs: List[str] = []) -> List[Path]:
extra_dirs: Optional[List[str]] = None) -> List[Path]:
abs_paths: List[Path] = []
if cls.initial_search_path:
@@ -57,8 +57,9 @@ class IResolver:
abs_paths.insert(0, config['user_data_dir'].joinpath(user_subdir))
# Add extra directory to the top of the search paths
for dir in extra_dirs:
abs_paths.insert(0, Path(dir).resolve())
if extra_dirs:
for dir in extra_dirs:
abs_paths.insert(0, Path(dir).resolve())
if cls.extra_path and (extra := config.get(cls.extra_path)):
abs_paths.insert(0, Path(extra).resolve())
@@ -139,7 +140,7 @@ class IResolver:
@classmethod
def _load_object(cls, paths: List[Path], *, object_name: str, add_source: bool = False,
kwargs: dict = {}) -> Optional[Any]:
kwargs: Dict) -> Optional[Any]:
"""
Try to load object from path list.
"""
@@ -163,7 +164,7 @@ class IResolver:
def load_object(cls, object_name: str, config: Config, *, kwargs: dict,
extra_dir: Optional[str] = None) -> Any:
"""
Search and loads the specified object as configured in hte child class.
Search and loads the specified object as configured in the child class.
:param object_name: name of the module to import
:param config: configuration dictionary
:param extra_dir: additional directory to search for the given pairlist
+2 -1
View File
@@ -26,6 +26,7 @@ def verify_auth(api_config, username: str, password: str):
httpbasic = HTTPBasic(auto_error=False)
security = HTTPBasic()
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token", auto_error=False)
@@ -117,7 +118,7 @@ def http_basic_or_jwt_token(form_data: HTTPBasicCredentials = Depends(httpbasic)
@router_login.post('/token/login', response_model=AccessAndRefreshToken)
def token_login(form_data: HTTPBasicCredentials = Depends(HTTPBasic()),
def token_login(form_data: HTTPBasicCredentials = Depends(security),
api_config=Depends(get_api_config)):
if verify_auth(api_config, form_data.username, form_data.password):
+31 -8
View File
@@ -10,15 +10,16 @@ from fastapi.exceptions import HTTPException
from freqtrade.configuration.config_validation import validate_config_consistency
from freqtrade.constants import Config
from freqtrade.data.btanalysis import (delete_backtest_result, get_backtest_result,
get_backtest_resultlist, load_and_merge_backtest_result,
update_backtest_metadata)
from freqtrade.data.btanalysis import (delete_backtest_result, get_backtest_market_change,
get_backtest_result, get_backtest_resultlist,
load_and_merge_backtest_result, update_backtest_metadata)
from freqtrade.enums import BacktestState
from freqtrade.exceptions import ConfigurationError, DependencyException, OperationalException
from freqtrade.exchange.common import remove_exchange_credentials
from freqtrade.misc import deep_merge_dicts, is_file_in_dir
from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestMetadataUpdate,
BacktestRequest, BacktestResponse)
from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestMarketChange,
BacktestMetadataUpdate, BacktestRequest,
BacktestResponse)
from freqtrade.rpc.api_server.deps import get_config
from freqtrade.rpc.api_server.webserver_bgwork import ApiBG
from freqtrade.rpc.rpc import RPCException
@@ -32,8 +33,10 @@ router = APIRouter()
def __run_backtest_bg(btconfig: Config):
from freqtrade.data.metrics import combined_dataframes_with_rel_mean
from freqtrade.optimize.optimize_reports import generate_backtest_stats, store_backtest_stats
from freqtrade.resolvers import StrategyResolver
asyncio.set_event_loop(asyncio.new_event_loop())
try:
# Reload strategy
@@ -89,11 +92,14 @@ def __run_backtest_bg(btconfig: Config):
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_stats(
btconfig['exportfilename'], ApiBG.bt['bt'].results,
datetime.now().strftime("%Y-%m-%d_%H-%M-%S")
btconfig['exportfilename'],
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.name)
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.")
@@ -308,3 +314,20 @@ def api_update_backtest_history_entry(file: str, body: BacktestMetadataUpdate,
raise HTTPException(status_code=400, detail=str(e))
return get_backtest_result(file_abs)
@router.get('/backtest/history/{file}/market_change', response_model=BacktestMarketChange,
tags=['webserver', 'backtest'])
def api_get_backtest_market_change(file: str, config=Depends(get_config)):
bt_results_base: Path = config['user_data_dir'] / 'backtest_results'
file_abs = (bt_results_base / f"{file}_market_change").with_suffix('.feather')
# Ensure file is in backtest_results directory
if not is_file_in_dir(file_abs, bt_results_base):
raise HTTPException(status_code=404, detail="File not found.")
df = get_backtest_market_change(file_abs)
return {
'columns': df.columns.tolist(),
'data': df.values.tolist(),
'length': len(df),
}
+28 -1
View File
@@ -1,7 +1,7 @@
from datetime import date, datetime
from typing import Any, Dict, List, Optional, Union
from pydantic import BaseModel, RootModel, SerializeAsAny
from pydantic import AwareDatetime, BaseModel, RootModel, SerializeAsAny
from freqtrade.constants import IntOrInf
from freqtrade.enums import MarginMode, OrderTypeValues, SignalDirection, TradingMode
@@ -378,6 +378,13 @@ class Locks(BaseModel):
locks: List[LockModel]
class LocksPayload(BaseModel):
pair: str
side: str = '*' # Default to both sides
until: AwareDatetime
reason: Optional[str] = None
class DeleteLockRequest(BaseModel):
pair: Optional[str] = None
lockid: Optional[int] = None
@@ -482,12 +489,26 @@ class AvailablePairs(BaseModel):
pair_interval: List[List[str]]
class PairCandlesRequest(BaseModel):
pair: str
timeframe: str
limit: Optional[int] = None
columns: Optional[List[str]] = None
class PairHistoryRequest(PairCandlesRequest):
timerange: str
strategy: str
freqaimodel: Optional[str] = None
class PairHistory(BaseModel):
strategy: str
pair: str
timeframe: str
timeframe_ms: int
columns: List[str]
all_columns: List[str] = []
data: SerializeAsAny[List[Any]]
length: int
buy_signals: int
@@ -551,6 +572,12 @@ class BacktestMetadataUpdate(BaseModel):
notes: str = ''
class BacktestMarketChange(BaseModel):
columns: List[str]
length: int
data: List[List[Any]]
class SysInfo(BaseModel):
cpu_pct: List[float]
ram_pct: float
+43 -9
View File
@@ -15,12 +15,13 @@ from freqtrade.rpc.api_server.api_schemas import (AvailablePairs, Balances, Blac
DeleteLockRequest, DeleteTrade, Entry,
ExchangeListResponse, Exit, ForceEnterPayload,
ForceEnterResponse, ForceExitPayload,
FreqAIModelListResponse, Health, Locks, Logs,
MixTag, OpenTradeSchema, PairHistory,
PerformanceEntry, Ping, PlotConfig, Profit,
ResultMsg, ShowConfig, Stats, StatusMsg,
StrategyListResponse, StrategyResponse, SysInfo,
Version, WhitelistResponse)
FreqAIModelListResponse, Health, Locks,
LocksPayload, Logs, MixTag, OpenTradeSchema,
PairCandlesRequest, PairHistory,
PairHistoryRequest, PerformanceEntry, Ping,
PlotConfig, Profit, ResultMsg, ShowConfig, Stats,
StatusMsg, StrategyListResponse, StrategyResponse,
SysInfo, Version, WhitelistResponse)
from freqtrade.rpc.api_server.deps import get_config, get_exchange, get_rpc, get_rpc_optional
from freqtrade.rpc.rpc import RPCException
@@ -53,7 +54,8 @@ logger = logging.getLogger(__name__)
# 2.32: new /backtest/history/ patch endpoint
# 2.33: Additional weekly/monthly metrics
# 2.34: new entries/exits/mix_tags endpoints
API_VERSION = 2.34
# 2.35: pair_candles and pair_history endpoints as Post variant
API_VERSION = 2.35
# Public API, requires no auth.
router_public = APIRouter()
@@ -255,6 +257,13 @@ def delete_lock_pair(payload: DeleteLockRequest, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_delete_lock(lockid=payload.lockid, pair=payload.pair)
@router.post('/locks', response_model=Locks, tags=['info', 'locks'])
def add_locks(payload: List[LocksPayload], rpc: RPC = Depends(get_rpc)):
for lock in payload:
rpc._rpc_add_lock(lock.pair, lock.until, lock.reason, lock.side)
return rpc._rpc_locks()
@router.get('/logs', response_model=Logs, tags=['info'])
def logs(limit: Optional[int] = None):
return RPC._rpc_get_logs(limit)
@@ -284,7 +293,14 @@ def reload_config(rpc: RPC = Depends(get_rpc)):
@router.get('/pair_candles', response_model=PairHistory, tags=['candle data'])
def pair_candles(
pair: str, timeframe: str, limit: Optional[int] = None, rpc: RPC = Depends(get_rpc)):
return rpc._rpc_analysed_dataframe(pair, timeframe, limit)
return rpc._rpc_analysed_dataframe(pair, timeframe, limit, None)
@router.post('/pair_candles', response_model=PairHistory, tags=['candle data'])
def pair_candles_filtered(payload: PairCandlesRequest, rpc: RPC = Depends(get_rpc)):
# Advanced pair_candles endpoint with column filtering
return rpc._rpc_analysed_dataframe(
payload.pair, payload.timeframe, payload.limit, payload.columns)
@router.get('/pair_history', response_model=PairHistory, tags=['candle data'])
@@ -300,7 +316,25 @@ def pair_history(pair: str, timeframe: str, timerange: str, strategy: str,
'freqaimodel': freqaimodel if freqaimodel else config.get('freqaimodel'),
})
try:
return RPC._rpc_analysed_history_full(config, pair, timeframe, exchange)
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({
'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))
+1 -1
View File
@@ -152,7 +152,7 @@ class WebSocketChannel:
"""
return self._closed.is_set()
def set_subscriptions(self, subscriptions: List[str] = []) -> None:
def set_subscriptions(self, subscriptions: List[str]) -> None:
"""
Set which subscriptions this channel is subscribed to
+2 -2
View File
@@ -237,7 +237,7 @@ class ExternalMessageConsumer:
continue
except Exception as e:
# An unforseen error has occurred, log and continue
# An unforeseen error has occurred, log and continue
logger.error("Unexpected error has occurred:")
logger.exception(e)
await asyncio.sleep(self.sleep_time)
@@ -387,7 +387,7 @@ class ExternalMessageConsumer:
)
if not did_append:
# We want an overlap in candles incase some data has changed
# We want an overlap in candles in case some data has changed
n_missing += 1
# Set to None for all candles if we missed a full df's worth of candles
n_missing = n_missing if n_missing < FULL_DATAFRAME_THRESHOLD else 1500
+10 -10
View File
@@ -39,7 +39,7 @@ class CryptoToFiatConverter(LoggingMixin):
This object is also a Singleton
"""
__instance = None
_coingekko: CoinGeckoAPI = None
_coingecko: CoinGeckoAPI = None
_coinlistings: List[Dict] = []
_backoff: float = 0.0
@@ -52,9 +52,9 @@ class CryptoToFiatConverter(LoggingMixin):
try:
# Limit retires to 1 (0 and 1)
# otherwise we risk bot impact if coingecko is down.
CryptoToFiatConverter._coingekko = CoinGeckoAPI(retries=1)
CryptoToFiatConverter._coingecko = CoinGeckoAPI(retries=1)
except BaseException:
CryptoToFiatConverter._coingekko = None
CryptoToFiatConverter._coingecko = None
return CryptoToFiatConverter.__instance
def __init__(self) -> None:
@@ -67,7 +67,7 @@ class CryptoToFiatConverter(LoggingMixin):
def _load_cryptomap(self) -> None:
try:
# Use list-comprehension to ensure we get a list.
self._coinlistings = [x for x in self._coingekko.get_coins_list()]
self._coinlistings = [x for x in self._coingecko.get_coins_list()]
except RequestException as request_exception:
if "429" in str(request_exception):
logger.warning(
@@ -84,7 +84,7 @@ class CryptoToFiatConverter(LoggingMixin):
logger.error(
f"Could not load FIAT Cryptocurrency map for the following problem: {exception}")
def _get_gekko_id(self, crypto_symbol):
def _get_gecko_id(self, crypto_symbol):
if not self._coinlistings:
if self._backoff <= datetime.now().timestamp():
self._load_cryptomap()
@@ -180,9 +180,9 @@ class CryptoToFiatConverter(LoggingMixin):
if crypto_symbol == fiat_symbol:
return 1.0
_gekko_id = self._get_gekko_id(crypto_symbol)
_gecko_id = self._get_gecko_id(crypto_symbol)
if not _gekko_id:
if not _gecko_id:
# return 0 for unsupported stake currencies (fiat-convert should not break the bot)
self.log_once(
f"unsupported crypto-symbol {crypto_symbol.upper()} - returning 0.0",
@@ -191,10 +191,10 @@ class CryptoToFiatConverter(LoggingMixin):
try:
return float(
self._coingekko.get_price(
ids=_gekko_id,
self._coingecko.get_price(
ids=_gecko_id,
vs_currencies=fiat_symbol
)[_gekko_id][fiat_symbol]
)[_gecko_id][fiat_symbol]
)
except Exception as exception:
logger.error("Error in _find_price: %s", exception)
+42 -17
View File
@@ -16,7 +16,7 @@ from sqlalchemy import func, select
from freqtrade import __version__
from freqtrade.configuration.timerange import TimeRange
from freqtrade.constants import CANCEL_REASON, Config
from freqtrade.constants import CANCEL_REASON, DEFAULT_DATAFRAME_COLUMNS, Config
from freqtrade.data.history import load_data
from freqtrade.data.metrics import calculate_expectancy, calculate_max_drawdown
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, MarketDirection, SignalDirection,
@@ -30,8 +30,8 @@ from freqtrade.persistence.models import PairLock
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
from freqtrade.rpc.rpc_types import RPCSendMsg
from freqtrade.util import (decimals_per_coin, dt_humanize, dt_now, dt_ts_def, format_date,
shorten_date)
from freqtrade.util import decimals_per_coin, dt_now, dt_ts_def, format_date, shorten_date
from freqtrade.util.datetime_helpers import dt_humanize_delta
from freqtrade.wallets import PositionWallet, Wallet
@@ -155,7 +155,7 @@ class RPC:
}
return val
def _rpc_trade_status(self, trade_ids: List[int] = []) -> List[Dict[str, Any]]:
def _rpc_trade_status(self, trade_ids: Optional[List[int]] = None) -> List[Dict[str, Any]]:
"""
Below follows the RPC backend it is prefixed with rpc_ to raise awareness that it is
a remotely exposed function
@@ -301,13 +301,13 @@ class RPC:
for oo in trade.open_orders
]
# exemple: '*.**.**' trying to enter, exit and exit with 3 different orders
# example: '*.**.**' trying to enter, exit and exit with 3 different orders
active_attempt_side_symbols_str = '.'.join(active_attempt_side_symbols)
detail_trade = [
f'{trade.id} {direction_str}',
trade.pair + active_attempt_side_symbols_str,
shorten_date(dt_humanize(trade.open_date, only_distance=True)),
shorten_date(dt_humanize_delta(trade.open_date_utc)),
profit_str
]
@@ -460,8 +460,11 @@ class RPC:
def _rpc_trade_statistics(
self, stake_currency: str, fiat_display_currency: str,
start_date: datetime = datetime.fromtimestamp(0)) -> Dict[str, Any]:
start_date: Optional[datetime] = None) -> Dict[str, Any]:
""" Returns cumulative profit statistics """
start_date = datetime.fromtimestamp(0) if start_date is None else start_date
trade_filter = ((Trade.is_open.is_(False) & (Trade.close_date >= start_date)) |
Trade.is_open.is_(True))
trades: Sequence[Trade] = Trade.session.scalars(Trade.get_trades_query(
@@ -596,10 +599,10 @@ class RPC:
'trade_count': len(trades),
'closed_trade_count': closed_trade_count,
'first_trade_date': format_date(first_date),
'first_trade_humanized': dt_humanize(first_date) if first_date else '',
'first_trade_humanized': dt_humanize_delta(first_date) if first_date else '',
'first_trade_timestamp': dt_ts_def(first_date, 0),
'latest_trade_date': format_date(last_date),
'latest_trade_humanized': dt_humanize(last_date) if last_date else '',
'latest_trade_humanized': dt_humanize_delta(last_date) if last_date else '',
'latest_trade_timestamp': dt_ts_def(last_date, 0),
'avg_duration': str(timedelta(seconds=sum(durations) / num)).split('.')[0],
'best_pair': best_pair[0] if best_pair else '',
@@ -1104,6 +1107,16 @@ class RPC:
return self._rpc_locks()
def _rpc_add_lock(
self, pair: str, until: datetime, reason: Optional[str], side: str) -> PairLock:
lock = PairLocks.lock_pair(
pair=pair,
until=until,
reason=reason,
side=side,
)
return lock
def _rpc_whitelist(self) -> Dict:
""" Returns the currently active whitelist"""
res = {'method': self._freqtrade.pairlists.name_list,
@@ -1177,9 +1190,11 @@ class RPC:
return self._freqtrade.edge.accepted_pairs()
@staticmethod
def _convert_dataframe_to_dict(strategy: str, pair: str, timeframe: str, dataframe: DataFrame,
last_analyzed: datetime) -> Dict[str, Any]:
def _convert_dataframe_to_dict(
strategy: str, pair: str, timeframe: str, dataframe: DataFrame,
last_analyzed: datetime, selected_cols: Optional[List[str]]) -> Dict[str, Any]:
has_content = len(dataframe) != 0
dataframe_columns = list(dataframe.columns)
signals = {
'enter_long': 0,
'exit_long': 0,
@@ -1187,6 +1202,11 @@ class RPC:
'exit_short': 0,
}
if has_content:
if selected_cols is not None:
# Ensure OHLCV columns are always present
cols_set = set(DEFAULT_DATAFRAME_COLUMNS + list(signals.keys()) + selected_cols)
df_cols = [col for col in dataframe_columns if col in cols_set]
dataframe = dataframe.loc[:, df_cols]
dataframe.loc[:, '__date_ts'] = dataframe.loc[:, 'date'].astype(int64) // 1000 // 1000
# Move signal close to separate column when signal for easy plotting
@@ -1211,6 +1231,7 @@ class RPC:
'timeframe': timeframe,
'timeframe_ms': timeframe_to_msecs(timeframe),
'strategy': strategy,
'all_columns': dataframe_columns,
'columns': list(dataframe.columns),
'data': dataframe.values.tolist(),
'length': len(dataframe),
@@ -1236,13 +1257,16 @@ class RPC:
})
return res
def _rpc_analysed_dataframe(self, pair: str, timeframe: str,
limit: Optional[int]) -> Dict[str, Any]:
def _rpc_analysed_dataframe(
self, pair: str, timeframe: str, limit: Optional[int],
selected_cols: Optional[List[str]]) -> Dict[str, Any]:
""" Analyzed dataframe in Dict form """
_data, last_analyzed = self.__rpc_analysed_dataframe_raw(pair, timeframe, limit)
return RPC._convert_dataframe_to_dict(self._freqtrade.config['strategy'],
pair, timeframe, _data, last_analyzed)
return RPC._convert_dataframe_to_dict(
self._freqtrade.config['strategy'], pair, timeframe, _data, last_analyzed,
selected_cols
)
def __rpc_analysed_dataframe_raw(
self,
@@ -1309,7 +1333,7 @@ class RPC:
@staticmethod
def _rpc_analysed_history_full(config: Config, pair: str, timeframe: str,
exchange) -> Dict[str, Any]:
exchange, selected_cols: Optional[List[str]]) -> Dict[str, Any]:
timerange_parsed = TimeRange.parse_timerange(config.get('timerange'))
from freqtrade.data.converter import trim_dataframe
@@ -1339,7 +1363,8 @@ class RPC:
df_analyzed = trim_dataframe(df_analyzed, timerange_parsed, startup_candles=startup_candles)
return RPC._convert_dataframe_to_dict(strategy.get_strategy_name(), pair, timeframe,
df_analyzed.copy(), dt_now())
df_analyzed.copy(), dt_now(),
selected_cols)
def _rpc_plot_config(self) -> Dict[str, Any]:
if (self._freqtrade.strategy.plot_config and
+7 -8
View File
@@ -33,7 +33,7 @@ from freqtrade.misc import chunks, plural
from freqtrade.persistence import Trade
from freqtrade.rpc import RPC, RPCException, RPCHandler
from freqtrade.rpc.rpc_types import RPCEntryMsg, RPCExitMsg, RPCOrderMsg, RPCSendMsg
from freqtrade.util import dt_humanize, fmt_coin, format_date, round_value
from freqtrade.util import dt_from_ts, dt_humanize_delta, fmt_coin, format_date, round_value
MAX_MESSAGE_LENGTH = MessageLimit.MAX_TEXT_LENGTH
@@ -488,7 +488,7 @@ class Telegram(RPCHandler):
elif msg['type'] == RPCMessageType.WARNING:
message = f"\N{WARNING SIGN} *Warning:* `{msg['status']}`"
elif msg['type'] == RPCMessageType.EXCEPTION:
# Errors will contain exceptions, which are wrapped in tripple ticks.
# Errors will contain exceptions, which are wrapped in triple ticks.
message = f"\N{WARNING SIGN} *ERROR:* \n {msg['status']}"
elif msg['type'] == RPCMessageType.STARTUP:
@@ -573,8 +573,7 @@ class Telegram(RPCHandler):
# TODO: This calculation ignores fees.
price_to_1st_entry = ((cur_entry_average - first_avg) / first_avg)
if is_open:
lines.append("({})".format(dt_humanize(order["order_filled_date"],
granularity=["day", "hour", "minute"])))
lines.append("({})".format(dt_humanize_delta(order["order_filled_date"])))
lines.append(f"*Amount:* {round_value(cur_entry_amount, 8)} "
f"({fmt_coin(order['cost'], quote_currency)})")
lines.append(f"*Average {wording} Price:* {round_value(cur_entry_average, 8)} "
@@ -657,7 +656,7 @@ class Telegram(RPCHandler):
position_adjust = self._config.get('position_adjustment_enable', False)
max_entries = self._config.get('max_entry_position_adjustment', -1)
for r in results:
r['open_date_hum'] = dt_humanize(r['open_date'])
r['open_date_hum'] = dt_humanize_delta(r['open_date'])
r['num_entries'] = len([o for o in r['orders'] if o['ft_is_entry']])
r['num_exits'] = len([o for o in r['orders'] if not o['ft_is_entry']
and not o['ft_order_side'] == 'stoploss'])
@@ -1174,7 +1173,7 @@ class Telegram(RPCHandler):
text='Cancel', callback_data='force_exit__cancel')])
await self._send_msg(msg="Which trade?", keyboard=buttons_aligned)
async def _force_exit_action(self, trade_id):
async def _force_exit_action(self, trade_id: str):
if trade_id != 'cancel':
try:
loop = asyncio.get_running_loop()
@@ -1289,7 +1288,7 @@ class Telegram(RPCHandler):
nrecent
)
trades_tab = tabulate(
[[dt_humanize(trade['close_date']),
[[dt_humanize_delta(dt_from_ts(trade['close_timestamp'])),
trade['pair'] + " (#" + str(trade['trade_id']) + ")",
f"{(trade['close_profit']):.2%} ({trade['close_profit_abs']})"]
for trade in trades['trades']],
@@ -1549,7 +1548,7 @@ class Telegram(RPCHandler):
async def send_blacklist_msg(self, blacklist: Dict):
errmsgs = []
for pair, error in blacklist['errors'].items():
for _, error in blacklist['errors'].items():
errmsgs.append(f"Error: {error['error_msg']}")
if errmsgs:
await self._send_msg('\n'.join(errmsgs))
+1 -1
View File
@@ -64,7 +64,7 @@ def informative(timeframe: str, asset: str = '',
def decorator(fn: PopulateIndicators):
informative_pairs = getattr(fn, '_ft_informative', [])
informative_pairs.append(InformativeData(_asset, _timeframe, _fmt, _ffill, _candle_type))
setattr(fn, '_ft_informative', informative_pairs)
setattr(fn, '_ft_informative', informative_pairs) # noqa: B010
return fn
return decorator
+1 -1
View File
@@ -78,7 +78,7 @@ def merge_informative_pair(dataframe: pd.DataFrame, informative: pd.DataFrame,
# all indicators on the informative sample MUST be calculated before this point
if ffill:
# https://pandas.pydata.org/docs/user_guide/merging.html#timeseries-friendly-merging
# merge_ordered - ffill method is 2.5x faster than seperate ffill()
# merge_ordered - ffill method is 2.5x faster than separate ffill()
dataframe = pd.merge_ordered(dataframe, informative, fill_method="ffill", left_on='date',
right_on=date_merge, how='left')
else:
@@ -3,7 +3,7 @@ def bot_loop_start(self, current_time: datetime, **kwargs) -> None:
"""
Called at the start of the bot iteration (one loop).
Might be used to perform pair-independent tasks
(e.g. gather some remote ressource for comparison)
(e.g. gather some remote resource for comparison)
For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/
+5 -3
View File
@@ -1,8 +1,9 @@
from freqtrade.util.datetime_helpers import (dt_floor_day, dt_from_ts, dt_humanize, dt_now, dt_ts,
dt_ts_def, dt_ts_none, dt_utc, format_date,
from freqtrade.util.datetime_helpers import (dt_floor_day, dt_from_ts, dt_humanize_delta, dt_now,
dt_ts, dt_ts_def, dt_ts_none, dt_utc, format_date,
format_ms_time, shorten_date)
from freqtrade.util.formatters import decimals_per_coin, fmt_coin, round_value
from freqtrade.util.ft_precise import FtPrecise
from freqtrade.util.measure_time import MeasureTime
from freqtrade.util.periodic_cache import PeriodicCache
from freqtrade.util.template_renderer import render_template, render_template_with_fallback # noqa
@@ -10,7 +11,7 @@ from freqtrade.util.template_renderer import render_template, render_template_wi
__all__ = [
'dt_floor_day',
'dt_from_ts',
'dt_humanize',
'dt_humanize_delta',
'dt_now',
'dt_ts',
'dt_ts_def',
@@ -24,4 +25,5 @@ __all__ = [
'decimals_per_coin',
'round_value',
'fmt_coin',
'MeasureTime',
]
+9 -10
View File
@@ -1,8 +1,9 @@
import re
from datetime import datetime, timezone
from typing import Optional
from time import time
from typing import Optional, Union
import arrow
import humanize
from freqtrade.constants import DATETIME_PRINT_FORMAT
@@ -25,7 +26,7 @@ def dt_ts(dt: Optional[datetime] = None) -> int:
"""
if dt:
return int(dt.timestamp() * 1000)
return int(dt_now().timestamp() * 1000)
return int(time() * 1000)
def dt_ts_def(dt: Optional[datetime], default: int = 0) -> int:
@@ -76,13 +77,11 @@ def shorten_date(_date: str) -> str:
return new_date
def dt_humanize(dt: datetime, **kwargs) -> str:
def dt_humanize_delta(dt: datetime):
"""
Return a humanized string for the given datetime.
:param dt: datetime to humanize
:param kwargs: kwargs to pass to arrow's humanize()
Return a humanized string for the given timedelta.
"""
return arrow.get(dt).humanize(**kwargs)
return humanize.naturaltime(dt)
def format_date(date: Optional[datetime]) -> str:
@@ -96,9 +95,9 @@ def format_date(date: Optional[datetime]) -> str:
return ''
def format_ms_time(date: int) -> str:
def format_ms_time(date: Union[int, float]) -> str:
"""
convert MS date to readable format.
: epoch-string in ms
"""
return datetime.fromtimestamp(date / 1000.0).strftime('%Y-%m-%dT%H:%M:%S')
return dt_from_ts(date).strftime('%Y-%m-%dT%H:%M:%S')
+43
View File
@@ -0,0 +1,43 @@
import logging
import time
from typing import Callable
from cachetools import TTLCache
logger = logging.getLogger(__name__)
class MeasureTime:
"""
Measure the time of a block of code and call a callback if the time limit is exceeded.
"""
def __init__(
self, callback: Callable[[float, float], None], time_limit: float, ttl: int = 3600 * 4):
"""
:param callback: The callback to call if the time limit is exceeded.
This callback will be called once every "ttl" seconds,
with the parameters "duration" (in seconds) and
"time limit" - representing the passed in time limit.
:param time_limit: The time limit in seconds.
:param ttl: The time to live of the cache in seconds.
defaults to 4 hours.
"""
self._callback = callback
self._time_limit = time_limit
self.__cache: TTLCache = TTLCache(maxsize=1, ttl=ttl)
def __enter__(self):
self._start = time.time()
def __exit__(self, *args):
end = time.time()
if self.__cache.get('value'):
return
duration = end - self._start
if duration < self._time_limit:
return
self._callback(duration, self._time_limit)
self.__cache['value'] = True
+7 -2
View File
@@ -3,7 +3,10 @@ Jinja2 rendering utils, used to generate new strategy and configurations.
"""
def render_template(templatefile: str, arguments: dict = {}) -> str:
from typing import Dict, Optional
def render_template(templatefile: str, arguments: Dict) -> str:
from jinja2 import Environment, PackageLoader, select_autoescape
@@ -16,11 +19,13 @@ def render_template(templatefile: str, arguments: dict = {}) -> str:
def render_template_with_fallback(templatefile: str, templatefallbackfile: str,
arguments: dict = {}) -> str:
arguments: Optional[Dict] = None) -> str:
"""
Use templatefile if possible, otherwise fall back to templatefallbackfile
"""
from jinja2.exceptions import TemplateNotFound
if arguments is None:
arguments = {}
try:
return render_template(templatefile, arguments)
except TemplateNotFound:
+2 -2
View File
@@ -70,7 +70,7 @@ class Wallets:
def _update_dry(self) -> None:
"""
Update from database in dry-run mode
- Apply apply profits of closed trades on top of stake amount
- Apply profits of closed trades on top of stake amount
- Subtract currently tied up stake_amount in open trades
- update balances for currencies currently in trades
"""
@@ -306,7 +306,7 @@ class Wallets:
:raise: DependencyException if the available stake amount is too low
"""
stake_amount: float
# Ensure wallets are uptodate.
# Ensure wallets are up-to-date.
if update:
self.update()
val_tied_up = Trade.total_open_trades_stakes()
+1 -1
View File
@@ -137,7 +137,7 @@ class Worker:
Throttles the given callable that it
takes at least `min_secs` to finish execution.
:param func: Any callable
:param throttle_secs: throttling interation execution time limit in seconds
:param throttle_secs: throttling iteration execution time limit in seconds
:param timeframe: ensure iteration is executed at the beginning of the next candle.
:param timeframe_offset: offset in seconds to apply to the next candle time.
:return: Any (result of execution of func)
+1 -1
View File
@@ -1,7 +1,7 @@
from freqtrade_client.ft_rest_client import FtRestClient
__version__ = '2024.3'
__version__ = '2024.4'
if 'dev' in __version__:
from pathlib import Path
+5 -2
View File
@@ -20,7 +20,10 @@ logger = logging.getLogger("ft_rest_client")
def add_arguments(args: Any = None):
parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(
prog="freqtrade-client",
description="Client for the freqtrade REST API",
)
parser.add_argument("command",
help="Positional argument defining the command to execute.",
nargs="?"
@@ -67,7 +70,7 @@ def print_commands():
# Print dynamic help for the different commands using the commands doc-strings
client = FtRestClient(None)
print("Possible commands:\n")
for x, y in inspect.getmembers(client):
for x, _ in inspect.getmembers(client):
if not x.startswith('_'):
doc = re.sub(':return:.*', '', getattr(client, x).__doc__, flags=re.MULTILINE).rstrip()
print(f"{x}\n\t{doc}\n")

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