Compare commits

...

605 Commits

Author SHA1 Message Date
Matthias ab42f84daa Merge pull request #11011 from freqtrade/new_release
New release 2024.11
2024-12-01 20:11:57 +01:00
Matthias f83a57ae5c chore: version bump to 2024.11 2024-12-01 19:35:02 +01:00
Matthias 56d747818d Merge branch 'stable' into new_release 2024-12-01 19:34:50 +01:00
Matthias 64b50d13fa chore: Add typehint for consolidated method 2024-12-01 16:25:00 +01:00
Matthias 0ada6fbd46 test: Add test case for kraken balance rewards 2024-12-01 15:34:36 +01:00
Matthias 2c8840b7e0 fix(kraken): consolidate balances from "rewards"
These currencies show up as <currency>.F -
but can be traded normally - hence we should count them as "tradable" balance.

closes #11007
closes #10925
2024-12-01 15:33:53 +01:00
Matthias 55247b4e12 chore: bump ccxt to 4.4.35 2024-12-01 15:19:45 +01:00
Matthias f31b26b1ab tests: Improve sample_order online testing 2024-11-30 18:12:32 +01:00
Matthias 2b350eb872 tests: improve assert accuracy 2024-11-30 13:50:12 +01:00
Matthias 88d77658f8 tests: extract strategy_safe_wrapper tests 2024-11-30 13:49:04 +01:00
Matthias cbbc386170 docs: update Hyperliquid docs to guide users to use API Wallets 2024-11-30 07:05:19 +01:00
Matthias 7f9226861a docs: slight formatting improvement 2024-11-30 07:04:19 +01:00
Matthias 9ff4298475 docs: update documentation for expanding pairs
This needs quoting otherwise zsh tries to expand the command,
causing failures.

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

closes #11003
2024-11-28 18:15:55 +01:00
Matthias cf9c9fe278 Merge pull request #11001 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-11-28 06:53:38 +01:00
xmatthias 7571ae19a0 chore: update pre-commit hooks 2024-11-28 03:14:33 +00:00
Matthias 58cde3253d docs: Fix rendering of $ signs 2024-11-27 06:41:45 +01:00
Matthias 47262b9350 docs: align character used for lists 2024-11-27 06:41:39 +01:00
Matthias 183909f474 chore: kwonly for cached arguments 2024-11-26 18:29:33 +01:00
Matthias 06bb43b13e chore: add missing docstring 2024-11-26 18:29:28 +01:00
Matthias 2ed8e1639f chore: move comment to where it belongs 2024-11-26 18:29:21 +01:00
Matthias e78d5b7787 chore: improved typing of rpc class 2024-11-26 18:29:17 +01:00
Matthias e0c6cc1563 feat: add log_responses to /balance endpoint
part of #10994
2024-11-26 10:23:03 +01:00
Matthias 472132c236 tests: update test for renamed prop 2024-11-26 07:08:55 +01:00
Matthias 1262aa19ec chore: make start_cap in wallets private 2024-11-26 06:37:35 +01:00
Matthias e9e3f7ef16 Merge pull request #10992 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-11-26 06:01:23 +01:00
xmatthias d9d4c478cc chore: update pre-commit hooks 2024-11-26 03:13:21 +00:00
Matthias db56fc158f test: update test market 2024-11-25 19:35:25 +01:00
Matthias af8d2490e2 feat: improve is_futures market check 2024-11-25 19:35:25 +01:00
Matthias fdc77bfd5b Merge pull request #10986 from freqtrade/dependabot/pip/develop/ruff-0.8.0
chore(deps-dev): bump ruff from 0.7.4 to 0.8.0
2024-11-25 07:55:15 +01:00
Matthias 5572d37c7f Merge pull request #10985 from freqtrade/dependabot/pip/develop/stable-baselines3-2.4.0
chore(deps): bump stable-baselines3 from 2.3.2 to 2.4.0
2024-11-25 07:39:01 +01:00
Matthias 57f2ab2f67 Merge pull request #10983 from freqtrade/dependabot/pip/develop/pydantic-2.10.1
chore(deps): bump pydantic from 2.9.2 to 2.10.1
2024-11-25 07:22:20 +01:00
Matthias 1cab52f555 Merge pull request #10988 from freqtrade/dependabot/pip/develop/numexpr-2.10.2
chore(deps): bump numexpr from 2.10.1 to 2.10.2
2024-11-25 07:07:41 +01:00
Matthias 748233ca70 chore: update imports to prevent shadowing internal exceptions 2024-11-25 06:39:18 +01:00
dependabot[bot] f76108c5b5 chore(deps): bump pydantic from 2.9.2 to 2.10.1
Bumps [pydantic](https://github.com/pydantic/pydantic) from 2.9.2 to 2.10.1.
- [Release notes](https://github.com/pydantic/pydantic/releases)
- [Changelog](https://github.com/pydantic/pydantic/blob/main/HISTORY.md)
- [Commits](https://github.com/pydantic/pydantic/compare/v2.9.2...v2.10.1)

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-25 03:46:25 +00:00
dependabot[bot] 8c6283a687 chore(deps-dev): bump ruff from 0.7.4 to 0.8.0
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.7.4 to 0.8.0.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.7.4...0.8.0)

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-25 03:45:25 +00:00
dependabot[bot] fc63a713fd chore(deps): bump orjson from 3.10.11 to 3.10.12
Bumps [orjson](https://github.com/ijl/orjson) from 3.10.11 to 3.10.12.
- [Release notes](https://github.com/ijl/orjson/releases)
- [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ijl/orjson/compare/3.10.11...3.10.12)

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-25 03:45:01 +00:00
dependabot[bot] 52f24af701 chore(deps): bump mkdocs-material in the mkdocs group
Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


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

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

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

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

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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 03:57:52 +00:00
dependabot[bot] 8f9d0019e6 chore(deps-dev): bump ruff from 0.7.3 to 0.7.4
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.7.3 to 0.7.4.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.7.3...0.7.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-18 03:57:42 +00:00
dependabot[bot] 9e3fae1991 chore(deps): bump pyjwt from 2.9.0 to 2.10.0
Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.9.0 to 2.10.0.
- [Release notes](https://github.com/jpadilla/pyjwt/releases)
- [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/jpadilla/pyjwt/compare/2.9.0...2.10.0)

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

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

closes #10941
2024-11-16 10:02:22 +01:00
Matthias c61a71a36e fix: percent_change from candle must provide percentage, not ratio
closes #10937
2024-11-16 08:50:41 +01:00
Matthias 46f2e693bc chore: bump ccxt to 4.4.31 2024-11-15 21:57:58 +01:00
Matthias cbc4bb5420 chore: use __all__ for hyperopt main export 2024-11-15 07:24:46 +01:00
Matthias 49e85e0680 docs: update migration docs to new syntax
only update the "after" parts.
No changs to highlighting, as the actual syntax used is not relevant for the strategy.
2024-11-15 07:21:22 +01:00
Matthias e8d724680a chore: improved log msg wording 2024-11-15 07:19:05 +01:00
Matthias 6d9ab5e9ff docs: update more typing wordings 2024-11-15 07:18:41 +01:00
Matthias a6d513a0e6 docs: update callback docs to new syntax 2024-11-15 07:15:31 +01:00
Matthias d565c40f4b docs: Updated syntax 2024-11-15 07:11:54 +01:00
Matthias 52b4ae5eae Docs: Align to 3.10 syntax 2024-11-15 07:10:31 +01:00
Matthias 5c4b9e59d1 chore: add Hyperliquid as supported exchange to docs 2024-11-14 07:08:27 +01:00
Matthias a02f63dcc3 chore: add hyperliquid to supported exchange list 2024-11-14 07:06:43 +01:00
Matthias a3a94e6ea4 chore: reorder hyperliquid docs, add note about order types 2024-11-14 07:06:09 +01:00
Matthias c039a45def fix: hyperliquid market orders don't exist and always require price 2024-11-14 07:04:39 +01:00
Matthias 3276dd6b94 Merge pull request #10931 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-11-14 06:34:32 +01:00
xmatthias de306de78b chore: update pre-commit hooks 2024-11-14 03:06:23 +00:00
Matthias 9360c109a9 docs: fix spelling 2024-11-12 19:32:42 +01:00
Matthias 5d9af5a7da test: update tests with new formatting 2024-11-12 19:27:17 +01:00
Matthias 88e0fed4c4 fix: Consistently format pair rate between entry and exit messages
closes #10922
2024-11-12 19:27:17 +01:00
Matthias 98dcc08c6d feat: add fmt_coin2 2024-11-12 19:27:17 +01:00
gaardiolor 702b50e7e9 Update exchanges.md - less details on the private key conversion 2024-11-12 18:22:19 +01:00
gaardiolor 8f946ea1b2 Update exchanges.md - further clarify private key best practices 2024-11-12 18:13:31 +01:00
Matthias 4e5ae0af84 Merge pull request #10919 from freqtrade/refactor/hyperopt
Restore hyperopt logging, refactor hyperopt layout
2024-11-12 18:13:02 +01:00
Matthias ca00871362 chore: adopt hyperopt class to new ruff formatting 2024-11-12 18:05:51 +01:00
Matthias a1d8658297 Merge branch 'develop' into pr/gaardiolor/10839 2024-11-12 18:05:25 +01:00
gaardiolor 31814e8bf9 Update docs/stoploss.md - clarify that stoploss_on_exchange is only supported for hyperliquid futures
Co-authored-by: Matthias <xmatthias@outlook.com>
2024-11-12 17:55:12 +01:00
Matthias 0028df2564 feat(hyperliquid): fix a few settings to make spot work properly 2024-11-12 06:58:21 +01:00
Matthias 9b3764212f chore: fix bad import 2024-11-12 06:30:55 +01:00
Matthias 8128b85706 Merge pull request #10921 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-11-12 06:24:15 +01:00
xmatthias d7c88a38fc chore: update pre-commit hooks 2024-11-12 03:04:16 +00:00
Matthias 0b0b221c02 chore: update advanced template to new formatting 2024-11-11 20:25:15 +01:00
Matthias d9b8f46282 docs: update doc samples to use lowercased tuple and dict 2024-11-11 20:24:01 +01:00
Matthias fbb64db3ae define log_queue globally 2024-11-11 20:14:37 +01:00
Matthias 33d8e67a87 docs: add note about logging 2024-11-11 20:12:31 +01:00
Matthias f05f173d23 chore: improved docstring for logging_mp_handle 2024-11-11 19:53:59 +01:00
Matthias 81a622a9fa chore: remove unnecessary log messages 2024-11-11 19:53:26 +01:00
Matthias 67415dd7e2 chore: improved module docstring 2024-11-11 19:53:04 +01:00
Matthias e8cd6b3be3 Merge pull request #10910 from freqtrade/dependabot/pip/develop/websockets-14.0
chore(deps): bump websockets from 13.1 to 14.0
2024-11-11 19:48:05 +01:00
Matthias 7e96e7af83 feat: add hyperopt log handlers to allow for multiprocessing logging 2024-11-11 19:43:37 +01:00
Matthias 119b73ead2 chore: improtve method sorting 2024-11-11 19:43:37 +01:00
Matthias 365c454da1 chore: Improve import comments 2024-11-11 19:43:37 +01:00
Matthias b6d1f9f754 test: update hyperopt tests for new structure 2024-11-11 19:43:37 +01:00
Matthias 6719d9670d feat: split hyperopt class
this ensures it's clear which parts are passed to workers
2024-11-11 19:43:37 +01:00
Matthias 62234878a1 test: update mocks for new layout 2024-11-11 19:43:37 +01:00
Matthias 851a9a7620 refactor: move hyperopt-auto 2024-11-11 19:43:37 +01:00
Matthias 61d9002cb1 refactor: move hyperopt-output 2024-11-11 19:43:37 +01:00
Matthias 84fc5dfcf7 refactor: move hyperopt-loss interface 2024-11-11 19:43:37 +01:00
Matthias e1b3ae208d chore: move hyperopt to it's own module 2024-11-11 19:43:37 +01:00
Matthias 60ec9739c2 chore: update websocket implementation to new asyncio api 2024-11-11 19:21:45 +01:00
Matthias c9fb3dfb1d chore: Update websocket imports to use "legacy" client
temporary - must be updated to the new implementation
2024-11-11 07:09:13 +01:00
Matthias 8230d1b515 Merge pull request #10913 from freqtrade/dependabot/pip/develop/ccxt-4.4.29
chore(deps): bump ccxt from 4.4.26 to 4.4.29
2024-11-11 06:29:59 +01:00
dependabot[bot] 838b4f4e52 chore(deps): bump websockets from 13.1 to 14.0
Bumps [websockets](https://github.com/python-websockets/websockets) from 13.1 to 14.0.
- [Release notes](https://github.com/python-websockets/websockets/releases)
- [Commits](https://github.com/python-websockets/websockets/compare/13.1...14.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-11 05:29:17 +00:00
Matthias eae5b154c9 Merge pull request #10908 from freqtrade/dependabot/pip/develop/mkdocs-54fce57b69
chore(deps): bump mkdocs-material from 9.5.43 to 9.5.44 in the mkdocs group
2024-11-11 06:28:51 +01:00
Matthias ebc870d2d4 Merge pull request #10911 from freqtrade/dependabot/pip/develop/packaging-24.2
chore(deps): bump packaging from 24.1 to 24.2
2024-11-11 06:28:15 +01:00
Matthias 986b1aadbf Merge pull request #10912 from freqtrade/dependabot/pip/develop/tqdm-4.67.0
chore(deps): bump tqdm from 4.66.6 to 4.67.0
2024-11-11 06:27:16 +01:00
Matthias 9ced73bf19 Merge pull request #10914 from freqtrade/dependabot/pip/develop/ruff-0.7.3
chore(deps-dev): bump ruff from 0.7.2 to 0.7.3
2024-11-11 06:26:38 +01:00
Matthias f0428d5c99 Merge pull request #10915 from freqtrade/dependabot/pip/develop/python-telegram-bot-21.7
chore(deps): bump python-telegram-bot from 21.6 to 21.7
2024-11-11 06:26:11 +01:00
Matthias 486a0d2c6e Merge pull request #10916 from freqtrade/dependabot/github_actions/develop/pypa/gh-action-pypi-publish-1.12.2
chore(deps): bump pypa/gh-action-pypi-publish from 1.11.0 to 1.12.2
2024-11-11 06:25:47 +01:00
dependabot[bot] 13cf6a1007 chore(deps): bump pypa/gh-action-pypi-publish from 1.11.0 to 1.12.2
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.11.0 to 1.12.2.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.11.0...v1.12.2)

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-11 03:11:15 +00:00
dependabot[bot] 057bb31d94 chore(deps-dev): bump ruff from 0.7.2 to 0.7.3
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.7.2 to 0.7.3.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.7.2...0.7.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-11 03:11:00 +00:00
dependabot[bot] 6258c0671b chore(deps): bump ccxt from 4.4.26 to 4.4.29
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.26 to 4.4.29.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.4.26...4.4.29)

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-11 03:10:35 +00:00
dependabot[bot] 6411dcbf72 chore(deps): bump mkdocs-material in the mkdocs group
Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


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

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 05:27:37 +00:00
Matthias 34588b7dc0 Merge pull request #10875 from freqtrade/dependabot/github_actions/develop/pypa/gh-action-pypi-publish-1.11.0
chore(deps): bump pypa/gh-action-pypi-publish from 1.10.3 to 1.11.0
2024-11-04 06:26:39 +01:00
Matthias f6999898d5 Merge pull request #10877 from freqtrade/dependabot/pip/develop/mkdocs-0610082a93
chore(deps): bump mkdocs-material from 9.5.42 to 9.5.43 in the mkdocs group
2024-11-04 06:26:25 +01:00
dependabot[bot] 821243ce7c chore(deps): bump ccxt from 4.4.24 to 4.4.26
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.24 to 4.4.26.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.4.24...4.4.26)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 03:35:00 +00:00
dependabot[bot] 44d0abb111 chore(deps): bump torch from 2.2.2 to 2.5.1
Bumps [torch](https://github.com/pytorch/pytorch) from 2.2.2 to 2.5.1.
- [Release notes](https://github.com/pytorch/pytorch/releases)
- [Changelog](https://github.com/pytorch/pytorch/blob/main/RELEASE.md)
- [Commits](https://github.com/pytorch/pytorch/compare/v2.2.2...v2.5.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 03:34:44 +00:00
dependabot[bot] 10fd2b291e chore(deps): bump orjson from 3.10.10 to 3.10.11
Bumps [orjson](https://github.com/ijl/orjson) from 3.10.10 to 3.10.11.
- [Release notes](https://github.com/ijl/orjson/releases)
- [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ijl/orjson/compare/3.10.10...3.10.11)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 03:34:35 +00:00
dependabot[bot] 5034c7c312 chore(deps-dev): bump ruff from 0.7.1 to 0.7.2
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.7.1 to 0.7.2.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.7.1...0.7.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 03:34:25 +00:00
dependabot[bot] ff36ba7742 chore(deps): bump janus from 1.0.0 to 1.1.0
Bumps [janus](https://github.com/aio-libs/janus) from 1.0.0 to 1.1.0.
- [Release notes](https://github.com/aio-libs/janus/releases)
- [Changelog](https://github.com/aio-libs/janus/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/janus/compare/v1.0.0...v1.1.0)

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

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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 03:33:44 +00:00
dependabot[bot] e224ee8277 chore(deps): bump mkdocs-material in the mkdocs group
Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 03:33:34 +00:00
dependabot[bot] d236f97605 chore(deps-dev): bump pytest-cov from 5.0.0 to 6.0.0 in the pytest group
Bumps the pytest group with 1 update: [pytest-cov](https://github.com/pytest-dev/pytest-cov).


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

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

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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-11-04 03:04:18 +00:00
Matthias 63dfa52ea8 chore: Fix missing space in log message 2024-11-03 13:43:30 +01:00
Matthias 74507e9981 docs: remove more --dmmp docs 2024-11-02 17:24:31 +01:00
Matthias d24bef7883 tests: remove --dmmp from tests 2024-11-02 16:54:54 +01:00
Matthias 59bf0c080a docs: update docs about removed --dmmp option 2024-11-02 16:54:45 +01:00
Matthias e8553cfac0 docs: Update documentation about dmmp 2024-11-02 16:50:20 +01:00
Matthias baadf62fc3 chore: remove --dmmp option 2024-11-02 16:49:26 +01:00
gaardiolor c4312fbb31 _lev_prep: fixed typo. dry_run_liquidation_price: use wallet_balance instead of stake_amount 2024-11-01 19:14:37 +01:00
Matthias 1bf71b47df docs: add gate documentation about new permissions 2024-11-01 17:49:00 +01:00
Matthias 67f26fa1ac Merge pull request #10782 from freqtrade/feat/bt_reverse
Enable future positions to reverse their position
2024-11-01 09:29:35 +01:00
Matthias dafabd5b99 Merge pull request #10860 from xzmeng/fix-test-download-data-timerange
tests: fix failure when local timezone is not utc
2024-11-01 09:19:39 +01:00
Matthias 878adfb0c0 tests: expand tests to cover several times and timezones 2024-11-01 08:57:03 +01:00
Matthias 3da9f6d0e3 Merge pull request #10857 from freqtrade/ci_mypy_cache
Improved caching
2024-11-01 08:44:13 +01:00
Matthias f5fdd8a6ab Merge pull request #10866 from xzmeng/fix-strategy101-link
docs: fix strategy 101 link
2024-10-31 15:47:02 +01:00
Meng Xiangzhuo 47e4599fe2 docs: fix strategy 101 link 2024-10-31 22:20:45 +08:00
Matthias 81b432dc0e chore: bump dev-version to 2024.11-dev 2024-10-31 08:08:02 +01:00
Matthias 87c5668b14 Merge pull request #10863 from freqtrade/new_release
New release 2024.10
2024-10-31 08:04:45 +01:00
Matthias 84da133e95 chore: bump version to 2024.10 2024-10-31 07:14:12 +01:00
Matthias 80e254d46b Merge branch 'stable' into new_release 2024-10-31 07:13:50 +01:00
Matthias 7bce16f811 Merge pull request #10861 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-10-31 06:43:24 +01:00
xmatthias 0d430be1fa chore: update pre-commit hooks 2024-10-31 03:08:41 +00:00
Meng Xiangzhuo a8516ff0f5 tests: fix failure when local timezone is not utc 2024-10-31 01:37:24 +08:00
Matthias e99adc339e chore: re-use 3.12 cache for online tests 2024-10-30 15:55:50 +01:00
Matthias 1f237db2b5 Merge pull request #10856 from freqtrade/fix/bt_parallel
Fix backtest parallelism with timeframe-detail
2024-10-29 20:27:14 +01:00
Matthias 04e8c3ceea chore: try improve caching 2024-10-29 19:31:08 +01:00
Matthias 592848ad03 test: re-add excluded assert 2024-10-29 19:27:03 +01:00
Matthias 22318eb80e chore: fix bad usage of variables 2024-10-29 18:14:53 +01:00
Matthias c03f8afca1 docs: document new behavior 2024-10-29 07:15:47 +01:00
Matthias 88f8e0b8b1 test: improve test for long/short switch 2024-10-29 07:15:47 +01:00
Matthias 8f6b831512 tests: adjust test_locked_pairs for new auto-lock 2024-10-29 07:15:47 +01:00
Matthias 85a2c12768 feat: Lock pairs in direction of the trade only 2024-10-29 07:15:47 +01:00
Matthias 1b8fd63853 tests: BT Detail tests should cover position switch 2024-10-29 07:15:47 +01:00
Matthias ae325ff124 tests: fix test based on changed behavior 2024-10-29 07:15:47 +01:00
Matthias 1698d96883 feat: Allow Backtesting to reverse position 2024-10-29 07:15:47 +01:00
Matthias e35f5c9fde tests: update test to account for newly added prop 2024-10-29 07:13:36 +01:00
Matthias 26a59e2cc5 fix: Don't allow backtesting to exceed max_open_trades in odd edge-cases
closes #10853
2024-10-29 07:13:02 +01:00
Matthias 56df7ad9fc chore: remove unnecessary commented log statement 2024-10-29 06:57:52 +01:00
Matthias 52ae7c3615 Merge pull request #10854 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-10-29 06:39:42 +01:00
xmatthias c9e56ab04b chore: update pre-commit hooks 2024-10-29 03:08:45 +00:00
gaardiolor cfa527e9af added tests for hyperliquid 2024-10-28 20:34:24 +01:00
Matthias 6cb17caf91 tests: Add test showing behavior from #10853 2024-10-28 20:28:26 +01:00
gaardiolor 67a0040732 added hyperliquid to conftest 2024-10-28 19:32:43 +01:00
gaardiolor 3913b450b6 check for tickers_have_bid_ask 2024-10-28 19:30:59 +01:00
Matthias 13d61e15f4 Merge pull request #10846 from freqtrade/dependabot/pip/develop/fastapi-0.115.4
chore(deps): bump fastapi from 0.115.2 to 0.115.4
2024-10-28 07:25:53 +01:00
Matthias 7d46236900 Merge pull request #10849 from freqtrade/dependabot/pip/develop/ruff-0.7.1
chore(deps-dev): bump ruff from 0.7.0 to 0.7.1
2024-10-28 07:15:44 +01:00
Matthias a7e54967c3 Merge pull request #10847 from freqtrade/dependabot/pip/develop/rich-13.9.3
chore(deps): bump rich from 13.9.2 to 13.9.3
2024-10-28 06:41:13 +01:00
Matthias a440e4ef79 Merge pull request #10843 from freqtrade/dependabot/pip/develop/ccxt-4.4.24
chore(deps): bump ccxt from 4.4.20 to 4.4.24
2024-10-28 06:39:15 +01:00
dependabot[bot] b5a52129fb chore(deps-dev): bump ruff from 0.7.0 to 0.7.1
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.7.0 to 0.7.1.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.7.0...0.7.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 05:39:02 +00:00
Matthias 8e72e0ec07 Merge pull request #10842 from freqtrade/dependabot/pip/develop/orjson-3.10.10
chore(deps): bump orjson from 3.10.9 to 3.10.10
2024-10-28 06:38:08 +01:00
Matthias 8204ecc131 Merge pull request #10844 from freqtrade/dependabot/pip/develop/mypy-1.13.0
chore(deps-dev): bump mypy from 1.12.1 to 1.13.0
2024-10-28 06:37:28 +01:00
dependabot[bot] beaf5215b7 chore(deps): bump rich from 13.9.2 to 13.9.3
Bumps [rich](https://github.com/Textualize/rich) from 13.9.2 to 13.9.3.
- [Release notes](https://github.com/Textualize/rich/releases)
- [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Textualize/rich/compare/v13.9.2...v13.9.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 03:33:21 +00:00
dependabot[bot] b3fa997bea chore(deps): bump fastapi from 0.115.2 to 0.115.4
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.115.2 to 0.115.4.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.115.2...0.115.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 03:32:56 +00:00
dependabot[bot] eab5bfb475 chore(deps-dev): bump mypy from 1.12.1 to 1.13.0
Bumps [mypy](https://github.com/python/mypy) from 1.12.1 to 1.13.0.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.12.1...v1.13.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-10-28 03:32:38 +00:00
dependabot[bot] 9f5d4dd67f chore(deps): bump ccxt from 4.4.20 to 4.4.24
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.20 to 4.4.24.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.4.20...4.4.24)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 03:32:33 +00:00
dependabot[bot] fc757f6cf7 chore(deps): bump orjson from 3.10.9 to 3.10.10
Bumps [orjson](https://github.com/ijl/orjson) from 3.10.9 to 3.10.10.
- [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.9...3.10.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-28 03:32:23 +00:00
gaardiolor fd619de1d3 typo 2024-10-27 22:00:52 +01:00
Matthias 4c0341b232 Merge pull request #10827 from freqtrade/fix/freqai-zeros
fix: guarantee crash resiliency, as long as users reload bot gracefully
2024-10-27 21:51:07 +01:00
Matthias a3bcc9d91e chore: schema.json updated 2024-10-27 21:28:36 +01:00
Robert Caulk d215e3ca68 chore: add documentation and add wait_for_training to config schema 2024-10-27 19:20:19 +01:00
gaardiolor 493fa8541b fixes related to ruff and mypy 2024-10-27 17:06:56 +01:00
Matthias 930b5e07f8 tests: adapt tests for improved createMarketBuyOrderRequiresPrice validation 2024-10-27 13:56:46 +01:00
Matthias 5174717ca9 fix: ensure createMarketBuyOrderRequiresPrice is only applied to buys
closes #10833
2024-10-27 13:56:38 +01:00
gaardiolor a7681fc712 revert ccxt version bump 2024-10-27 12:27:54 +01:00
gaardiolor 7d1976ad05 fix ohlcv_candle_limit funding_rate candles
Co-authored-by: Matthias <xmatthias@outlook.com>
2024-10-27 12:09:20 +01:00
Matthias d317f33f4e chore: improve potentially erroneous comparison 2024-10-27 09:53:51 +01:00
Matthias 9e53b0742c docs: update documentation code to align with comment
closes #10838
2024-10-27 07:57:52 +01:00
gaardiolor 4c78b8c6d1 updated dry_run_liquidation_price to match new signature
fixed comment
2024-10-26 14:13:57 +02:00
gaardiolor b88db55db3 Initial implementation of hyperliquid.
- Spot
- Swap (long, short, leverage, stoploss_on_exchange)
- dry_run_liquidation_price()
2024-10-25 17:47:53 +02:00
Matthias b8f8d1d4b1 Merge pull request #10831 from xzmeng/fix-log
feat: auto-create logs dir if it's absent
2024-10-25 07:38:53 +02:00
Matthias c4cbf6de3b tests: reset permissions on tmp-path
to facilitate cleanup
2024-10-25 07:22:11 +02:00
Matthias e7b0e3293d feat: Exit with exception, not with exit1
this aligns to how other parts of the code work - leaving "exit" to the outermost caller.
2024-10-25 06:34:46 +02:00
Meng Xiangzhuo 87c8e85068 feat: add user friendly message on permission error 2024-10-25 00:01:41 +08:00
Matthias c9ae5e1e9f Merge pull request #10834 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-10-24 09:29:03 +02:00
xmatthias 3d9514d266 chore: update pre-commit hooks 2024-10-24 06:07:00 +02:00
Matthias 3d9cbf52b9 chore: improve import sorting 2024-10-24 06:06:31 +02:00
Matthias 5b4043db85 chore: use Class for parse_timerange 2024-10-24 06:06:31 +02:00
Matthias 661d36614f Merge pull request #10824 from freqtrade/dependabot/pip/develop/torch-2.5.0
chore(deps): bump torch from 2.2.2 to 2.5.0
2024-10-23 18:06:09 +02:00
Matthias 11cef55c03 test: update mock to new pairlist location 2024-10-23 17:59:34 +02:00
Matthias 667d08d003 refactor: extract pairlist_api from background_tasks file 2024-10-23 17:57:36 +02:00
Meng Xiangzhuo ba780276a2 feat: auto-create logs dir if it's absent 2024-10-23 00:22:23 +08:00
Matthias 46db0bc08c feat: rename endpoint to be better aligned 2024-10-22 06:40:01 +02:00
Matthias 0de3c6945b test: add test for api/hyperopt-loss 2024-10-22 06:39:55 +02:00
Matthias 0bf30aaa6b feat: add hyperopt-loss api endpoint 2024-10-22 06:39:43 +02:00
Matthias faac205464 tests: add test for list-hyperoptloss 2024-10-22 06:39:30 +02:00
Matthias 1d5d7048d6 chore: improve test imports 2024-10-22 06:38:20 +02:00
Matthias 033643c4ca docs: add documentation for list-hyperoptloss 2024-10-22 06:38:20 +02:00
Matthias 8b8b5cfac4 feat: add list-hyperoptloss subcommand 2024-10-22 06:38:20 +02:00
Matthias 9ad32fd846 fix: improved defaultHyperoptLoss alias
this gives the class it's proper name.
2024-10-22 06:38:20 +02:00
Matthias d9766bfd7e Merge pull request #10829 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-10-22 06:35:18 +02:00
xmatthias 186f2a8392 chore: update pre-commit hooks 2024-10-22 03:08:05 +00:00
Matthias c6a43b0d0c Merge pull request #10820 from freqtrade/dependabot/pip/develop/mypy-1.12.1
chore(deps-dev): bump mypy from 1.11.2 to 1.12.1
2024-10-21 11:46:41 +02:00
Robert Caulk a1513b7c7c fix: allow user to break training 2024-10-21 11:40:46 +02:00
Robert Caulk 6571ef4e45 fix: guarantee crash resiliency, as long as users reload bot gracefully 2024-10-21 11:14:28 +02:00
Matthias a3f8a4f45e Merge pull request #10817 from freqtrade/dependabot/pip/develop/sqlalchemy-2.0.36
chore(deps): bump sqlalchemy from 2.0.35 to 2.0.36
2024-10-21 10:46:07 +02:00
Matthias 336111fbd4 chore: update sqlalchemy pre-commit 2024-10-21 10:19:27 +02:00
Matthias f100ad12e0 chore: fix whitespace error after merge 2024-10-21 10:18:01 +02:00
Matthias 7adc505a00 Merge branch 'develop' into dependabot/pip/develop/mypy-1.12.1 2024-10-21 10:17:19 +02:00
Matthias d781ee0596 Merge pull request #10814 from freqtrade/dependabot/pip/develop/types-9987b1e203
chore(deps-dev): bump types-requests from 2.32.0.20240914 to 2.32.0.20241016 in the types group
2024-10-21 09:44:14 +02:00
Matthias 0ea9240226 Merge pull request #10822 from freqtrade/dependabot/pip/develop/ccxt-4.4.20
chore(deps): bump ccxt from 4.4.17 to 4.4.20
2024-10-21 09:24:59 +02:00
Matthias dbbdb7cd7e Merge pull request #10818 from freqtrade/dependabot/pip/develop/psutil-6.1.0
chore(deps): bump psutil from 6.0.0 to 6.1.0
2024-10-21 08:56:25 +02:00
Matthias 2f98d2a781 Merge pull request #10825 from freqtrade/dependabot/pip/develop/orjson-3.10.9
chore(deps): bump orjson from 3.10.7 to 3.10.9
2024-10-21 08:32:40 +02:00
dependabot[bot] d9ea89857c chore(deps): bump sqlalchemy from 2.0.35 to 2.0.36
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.35 to 2.0.36.
- [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases)
- [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/main/CHANGES.rst)
- [Commits](https://github.com/sqlalchemy/sqlalchemy/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-21 06:05:10 +00:00
dependabot[bot] a29a697012 chore(deps): bump ccxt from 4.4.17 to 4.4.20
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.17 to 4.4.20.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.4.17...4.4.20)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-21 06:05:02 +00:00
Matthias 430706529a Merge pull request #10823 from freqtrade/dependabot/pip/develop/cryptography-43.0.3
chore(deps): bump cryptography from 42.0.8 to 43.0.3
2024-10-21 08:04:08 +02:00
Matthias 9b1999bc02 Merge pull request #10821 from freqtrade/dependabot/pip/develop/ruff-0.7.0
chore(deps-dev): bump ruff from 0.6.9 to 0.7.0
2024-10-21 07:53:50 +02:00
Matthias 196e82d3ab Merge pull request #10819 from freqtrade/dependabot/pip/develop/bottleneck-1.4.2
chore(deps): bump bottleneck from 1.4.1 to 1.4.2
2024-10-21 07:53:40 +02:00
dependabot[bot] 1761d0e65c chore(deps): bump psutil from 6.0.0 to 6.1.0
Bumps [psutil](https://github.com/giampaolo/psutil) from 6.0.0 to 6.1.0.
- [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst)
- [Commits](https://github.com/giampaolo/psutil/compare/release-6.0.0...release-6.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-21 05:33:28 +00:00
Matthias 9465723026 Merge pull request #10816 from freqtrade/dependabot/pip/develop/uvicorn-0.32.0
chore(deps): bump uvicorn from 0.31.1 to 0.32.0
2024-10-21 07:32:37 +02:00
Matthias c9246bf946 Merge pull request #10815 from freqtrade/dependabot/pip/develop/mkdocs-d0ead33a76
chore(deps): bump mkdocs-material from 9.5.40 to 9.5.42 in the mkdocs group
2024-10-21 07:32:10 +02:00
Matthias 4f0380bb2c Merge pull request #10779 from froggleston/frog-strat101-docs-1
Improve strategy development documentation
2024-10-21 06:41:59 +02:00
Matthias 41a4c7c39f chore: improve type safety 2024-10-21 06:39:55 +02:00
Matthias e3bdf89cab chore: bump pre-commit types-requests 2024-10-21 06:33:55 +02:00
dependabot[bot] 0843f09c86 chore(deps): bump orjson from 3.10.7 to 3.10.9
Bumps [orjson](https://github.com/ijl/orjson) from 3.10.7 to 3.10.9.
- [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.7...3.10.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-21 03:18:32 +00:00
dependabot[bot] 93afe00205 chore(deps): bump torch from 2.2.2 to 2.5.0
Bumps [torch](https://github.com/pytorch/pytorch) from 2.2.2 to 2.5.0.
- [Release notes](https://github.com/pytorch/pytorch/releases)
- [Changelog](https://github.com/pytorch/pytorch/blob/main/RELEASE.md)
- [Commits](https://github.com/pytorch/pytorch/compare/v2.2.2...v2.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-21 03:18:20 +00:00
dependabot[bot] 0f978bbcc7 chore(deps): bump cryptography from 42.0.8 to 43.0.3
Bumps [cryptography](https://github.com/pyca/cryptography) from 42.0.8 to 43.0.3.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/42.0.8...43.0.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-21 03:18:10 +00:00
dependabot[bot] 88c49a9f7c chore(deps-dev): bump ruff from 0.6.9 to 0.7.0
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.9 to 0.7.0.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.9...0.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-21 03:17:52 +00:00
dependabot[bot] 83f285c913 chore(deps-dev): bump mypy from 1.11.2 to 1.12.1
Bumps [mypy](https://github.com/python/mypy) from 1.11.2 to 1.12.1.
- [Changelog](https://github.com/python/mypy/blob/master/CHANGELOG.md)
- [Commits](https://github.com/python/mypy/compare/v1.11.2...v1.12.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-21 03:17:35 +00:00
dependabot[bot] 24499c5ea9 chore(deps): bump bottleneck from 1.4.1 to 1.4.2
Bumps [bottleneck](https://github.com/pydata/bottleneck) from 1.4.1 to 1.4.2.
- [Release notes](https://github.com/pydata/bottleneck/releases)
- [Changelog](https://github.com/pydata/bottleneck/blob/master/RELEASE.rst)
- [Commits](https://github.com/pydata/bottleneck/compare/v1.4.1...v1.4.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-21 03:17:23 +00:00
dependabot[bot] d02b1f04e2 chore(deps): bump uvicorn from 0.31.1 to 0.32.0
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.31.1 to 0.32.0.
- [Release notes](https://github.com/encode/uvicorn/releases)
- [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/uvicorn/compare/0.31.1...0.32.0)

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

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


Updates `mkdocs-material` from 9.5.40 to 9.5.42
- [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.40...9.5.42)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-21 03:16:38 +00:00
dependabot[bot] 306db6d026 chore(deps-dev): bump types-requests in the types group
Bumps the types group with 1 update: [types-requests](https://github.com/python/typeshed).


Updates `types-requests` from 2.32.0.20240914 to 2.32.0.20241016
- [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-10-21 03:15:50 +00:00
froggleston b75ff4bdf5 Adjust docs based on review 2024-10-20 21:53:12 +01:00
Matthias 60439c7d8e chore: enhanced pyright configuration
this should get rid of most errors in recent pylance versions
by disabling them all.
2024-10-20 15:29:37 +02:00
Matthias 3518a4167c chore: minor typing fix 2024-10-20 15:24:45 +02:00
Matthias 51dd7fa58e chore: ensure empty leverage tiers files don't cause an exception 2024-10-20 15:22:50 +02:00
Matthias 0a5549ebc9 docs: add canonical_version 2024-10-20 12:35:26 +02:00
Matthias 607167ebe7 fix: improve resilience of ft_rest_client 2024-10-20 11:52:29 +02:00
Matthias 1183517c3a chore: remove non-needed type_checking import 2024-10-20 11:45:39 +02:00
Matthias 6d169784ed chore: remove mypy override for telegram module
it's no longer necessary.
2024-10-20 10:41:19 +02:00
Matthias 9b16cc6266 Merge pull request #10810 from xzmeng/delete-market-change
fix: delete market change file when deleting backtest result
2024-10-19 13:37:43 +02:00
Matthias d56f46e1b1 Merge pull request #10805 from bearvar/ComplexHyperOptLoss
Add new hyperopt loss function
2024-10-19 13:32:46 +02:00
Matthias 6a5feacd38 tests: Skip binanceus websocket tests
binance.us volume is currently too low for this test to run reliably.
2024-10-19 13:32:27 +02:00
Matthias 0a91189e6a chore: remove unused kwargs 2024-10-19 13:10:40 +02:00
Matthias e9d9ebf156 docs: Update documentation for MultiMetricHyperOptLoss 2024-10-19 13:05:59 +02:00
Matthias 1a85dc9b99 chore: Update to new code standard 2024-10-19 13:02:45 +02:00
Matthias 660af7ec70 chore: Fix test failure, sort imports 2024-10-19 13:02:27 +02:00
Matthias 102d44a7b8 chore: improve lbank comment message 2024-10-19 12:35:23 +02:00
Matthias 549b0f8ef4 feat: delete all files when deleting a backtest result 2024-10-19 12:33:42 +02:00
Matthias c6fe8ae70c feat: add lbank candle limit parameters 2024-10-19 11:51:25 +02:00
Meng Xiangzhuo 9e1af0db63 delete market change when deleting backtest result 2024-10-19 10:55:24 +08:00
Matthias 43b6cb9bff docs: Update 101 docs 2024-10-18 18:21:27 +02:00
Matthias 9b7acf33c6 docs: rename file to strategy-101 2024-10-18 18:16:29 +02:00
Matthias 3914abda20 Merge pull request #10808 from freqtrade/update_macos_images
ci: update macos version to new images
2024-10-18 07:29:20 +02:00
Matthias e47042d1cb Merge pull request #10773 from freqtrade/fix/freqai_m3
fix: Support mps device where available
2024-10-18 07:07:23 +02:00
Matthias a40a2f96b5 chore: attempt to speed up CI
Coveralls is only needed by one matrix run ...
2024-10-18 06:26:36 +02:00
Matthias efc689a937 chore: update macos version to new images 2024-10-17 20:40:13 +02:00
bearvar 78bf40eaa9 Edit EXPECTANCY_CONST to show it can be decimal. 2024-10-17 22:09:00 +05:00
bearvar 578604619a Edit PF_CONST to show it can be decimal. 2024-10-17 22:07:32 +05:00
bearvar 6eac798d91 Rename ComplexHyperOptLoss to MultiMetricHyperOptLoss. Add it to test_loss_functions_better_profits. 2024-10-17 21:54:25 +05:00
Alex 121bdbad84 Update and rename hyperopt_loss_complex.py to hyperopt_loss_multi_metric.py 2024-10-17 21:42:34 +05:00
Matthias aaeaa02406 Merge pull request #10807 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-10-17 06:24:20 +02:00
xmatthias 5b2b2d1a5c chore: update pre-commit hooks 2024-10-17 03:06:56 +00:00
bearvar 9209d11084 Add new hyperopt loss function 2024-10-16 21:52:42 +05:00
froggleston 60f54f1bc9 Improve 101 docs 2024-10-15 12:37:55 +01:00
Robert Davey 8b1de5d9b5 Update docs/freqtrade-101.md
Co-authored-by: Matthias <xmatthias@outlook.com>
2024-10-15 11:54:26 +01:00
Matthias b2095481b3 Merge pull request #10783 from xzmeng/remove-setuppy
Remove setup.py
2024-10-15 06:39:29 +02:00
froggleston a8e8517364 Minor typo 2024-10-15 00:11:16 +01:00
Matthias c9b31c62cd docs: improved wording for developer docs 2024-10-14 19:10:49 +02:00
froggleston c72cf64c8a Americanize *sigh* 2024-10-14 17:36:43 +01:00
froggleston 8c2f841972 Update 101 and customisation docs 2024-10-14 17:33:55 +01:00
Meng Xiangzhuo 0c1c750005 Add and dependency groups 2024-10-14 19:51:04 +08:00
Matthias 33d98dfe8c Merge pull request #10791 from freqtrade/dependabot/pip/develop/ccxt-4.4.17
chore(deps): bump ccxt from 4.4.14 to 4.4.17
2024-10-14 08:55:13 +02:00
Matthias 256815e148 Merge pull request #10793 from freqtrade/dependabot/pip/develop/fastapi-0.115.2
chore(deps): bump fastapi from 0.115.0 to 0.115.2
2024-10-14 08:32:01 +02:00
dependabot[bot] cdbfbb5504 chore(deps): bump ccxt from 4.4.14 to 4.4.17
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.14 to 4.4.17.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.4.14...4.4.17)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 06:13:50 +00:00
Matthias 753a88c409 Merge pull request #10797 from freqtrade/dependabot/pip/develop/aiohttp-3.10.10
chore(deps): bump aiohttp from 3.10.9 to 3.10.10
2024-10-14 08:13:00 +02:00
Matthias eef6966a85 Merge pull request #10796 from freqtrade/dependabot/pip/develop/bottleneck-1.4.1
chore(deps): bump bottleneck from 1.4.0 to 1.4.1
2024-10-14 07:58:00 +02:00
Matthias d1d6a30884 Merge pull request #10795 from freqtrade/dependabot/pip/develop/pre-commit-4.0.1
chore(deps-dev): bump pre-commit from 4.0.0 to 4.0.1
2024-10-14 07:39:14 +02:00
dependabot[bot] 3a190a1c37 chore(deps): bump fastapi from 0.115.0 to 0.115.2
Bumps [fastapi](https://github.com/fastapi/fastapi) from 0.115.0 to 0.115.2.
- [Release notes](https://github.com/fastapi/fastapi/releases)
- [Commits](https://github.com/fastapi/fastapi/compare/0.115.0...0.115.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-10-14 05:15:21 +00:00
Matthias ae70902921 Merge pull request #10792 from freqtrade/dependabot/pip/develop/time-machine-2.16.0
chore(deps-dev): bump time-machine from 2.15.0 to 2.16.0
2024-10-14 07:14:59 +02:00
Matthias 5418237919 Merge pull request #10790 from freqtrade/dependabot/pip/develop/uvicorn-0.31.1
chore(deps): bump uvicorn from 0.31.0 to 0.31.1
2024-10-14 07:14:31 +02:00
Matthias 42fcdb33a1 Merge pull request #10789 from freqtrade/dependabot/pip/develop/mkdocs-2fa875aea2
chore(deps): bump mkdocs-material from 9.5.39 to 9.5.40 in the mkdocs group
2024-10-14 07:12:14 +02:00
dependabot[bot] 2098e69fb4 chore(deps): bump aiohttp from 3.10.9 to 3.10.10
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.9 to 3.10.10.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.9...v3.10.10)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 03:48:48 +00:00
dependabot[bot] 2415275e90 chore(deps): bump bottleneck from 1.4.0 to 1.4.1
Bumps [bottleneck](https://github.com/pydata/bottleneck) from 1.4.0 to 1.4.1.
- [Release notes](https://github.com/pydata/bottleneck/releases)
- [Changelog](https://github.com/pydata/bottleneck/blob/master/RELEASE.rst)
- [Commits](https://github.com/pydata/bottleneck/compare/v1.4.0...v1.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 03:48:29 +00:00
dependabot[bot] 83fcc92f75 chore(deps-dev): bump pre-commit from 4.0.0 to 4.0.1
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 4.0.0 to 4.0.1.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v4.0.0...v4.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 03:48:21 +00:00
dependabot[bot] 64a7ab6e2e chore(deps-dev): bump time-machine from 2.15.0 to 2.16.0
Bumps [time-machine](https://github.com/adamchainz/time-machine) from 2.15.0 to 2.16.0.
- [Changelog](https://github.com/adamchainz/time-machine/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/adamchainz/time-machine/compare/2.15.0...2.16.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 03:48:01 +00:00
dependabot[bot] a772e1f9ca chore(deps): bump uvicorn from 0.31.0 to 0.31.1
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.31.0 to 0.31.1.
- [Release notes](https://github.com/encode/uvicorn/releases)
- [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/uvicorn/compare/0.31.0...0.31.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 03:47:30 +00:00
dependabot[bot] 7bbed1705c chore(deps): bump mkdocs-material in the mkdocs group
Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `mkdocs-material` from 9.5.39 to 9.5.40
- [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.39...9.5.40)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-14 03:47:26 +00:00
Meng Xiangzhuo 4cd4660357 Remove setup.py 2024-10-13 23:22:10 +08:00
Matthias d503724bda chore: improve backtest comment 2024-10-12 13:04:09 +02:00
Matthias 2756a1f1a0 chore: improve statement sequence for improved short circuiting 2024-10-12 11:44:29 +02:00
Matthias 883122a98b refactor: Simplify backtest code slightly
This should also slighly improve performance for detail backtests
as it removes one comparison
2024-10-12 11:34:13 +02:00
Matthias bd7f0383d1 chore: improve clarity of argument name 2024-10-12 11:25:15 +02:00
Matthias aa8cefc110 fix: minor missing statement in detail backtest fallback 2024-10-12 11:06:13 +02:00
Matthias 48740f5032 docs: add new document to index 2024-10-12 08:41:03 +02:00
froggleston 35b81a2f38 Initial work on strategy dev docs 2024-10-11 23:00:09 +01:00
Matthias 4b582b9d7e fix(bybit): Override fetchOrder to false for spot markets
closes #10777
2024-10-10 19:48:30 +02:00
Matthias d72f45a94b Merge pull request #10776 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-10-10 06:28:32 +02:00
xmatthias a71ed3ec5a chore: update pre-commit hooks 2024-10-10 03:06:38 +00:00
Matthias 92b2a6fa24 fix: Support mps device where available 2024-10-08 07:20:49 +02:00
Matthias ddeb64964d Merge pull request #10770 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-10-08 06:36:12 +02:00
xmatthias b2d6c5b6a7 chore: update pre-commit hooks 2024-10-08 03:09:02 +00:00
Matthias 271375d171 Merge pull request #10758 from stash86/recursive-str
add is_number check to make sure we skip non-number columns
2024-10-07 21:09:58 +02:00
Matthias 5693039f0d tests: Add test-column to ensure string columns won't regress 2024-10-07 20:33:12 +02:00
Matthias 52304b37a2 feat: Allow strategies to not define enter_long 2024-10-07 20:28:29 +02:00
Matthias fe8eabda37 Merge pull request #10759 from freqtrade/dependabot/pip/develop/types-9d35b667ff
chore(deps-dev): bump types-python-dateutil from 2.9.0.20240906 to 2.9.0.20241003 in the types group
2024-10-07 08:27:26 +02:00
Matthias a00032de53 Merge pull request #10768 from freqtrade/dependabot/github_actions/develop/pypa/gh-action-pypi-publish-1.10.3
chore(deps): bump pypa/gh-action-pypi-publish from 1.10.2 to 1.10.3
2024-10-07 08:01:39 +02:00
Matthias b320358dc4 Merge pull request #10766 from freqtrade/dependabot/pip/develop/pymdown-extensions-10.11.2
chore(deps): bump pymdown-extensions from 10.11.1 to 10.11.2
2024-10-07 07:50:40 +02:00
Matthias f81ab8e4d4 Merge pull request #10765 from freqtrade/dependabot/pip/develop/rich-13.9.2
chore(deps): bump rich from 13.8.1 to 13.9.2
2024-10-07 07:24:49 +02:00
Matthias 9a537248a4 Merge pull request #10764 from freqtrade/dependabot/pip/develop/humanize-4.11.0
chore(deps): bump humanize from 4.10.0 to 4.11.0
2024-10-07 07:07:49 +02:00
Matthias eeed65b354 chore: Bump pre-commit types-python-dateutil 2024-10-07 07:07:25 +02:00
Matthias 3c6711c590 Merge pull request #10763 from freqtrade/dependabot/pip/develop/aiohttp-3.10.9
chore(deps): bump aiohttp from 3.10.8 to 3.10.9
2024-10-07 06:53:44 +02:00
Matthias 90d8dfcf12 Merge pull request #10762 from freqtrade/dependabot/pip/develop/pre-commit-4.0.0
chore(deps-dev): bump pre-commit from 3.8.0 to 4.0.0
2024-10-07 06:53:18 +02:00
Matthias 03b76f3c8a Merge pull request #10761 from freqtrade/dependabot/pip/develop/ccxt-4.4.14
chore(deps): bump ccxt from 4.4.9 to 4.4.14
2024-10-07 06:53:04 +02:00
Matthias b9caed4392 Merge pull request #10760 from freqtrade/dependabot/pip/develop/ruff-0.6.9
chore(deps-dev): bump ruff from 0.6.8 to 0.6.9
2024-10-07 06:52:56 +02:00
dependabot[bot] 6993a650b0 chore(deps): bump pypa/gh-action-pypi-publish from 1.10.2 to 1.10.3
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.10.2 to 1.10.3.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.10.2...v1.10.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 03:45:40 +00:00
dependabot[bot] 4000794af6 chore(deps): bump pymdown-extensions from 10.11.1 to 10.11.2
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.11.1 to 10.11.2.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.11.1...10.11.2)

---
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-10-07 03:17:55 +00:00
dependabot[bot] 7c9e6779c6 chore(deps): bump rich from 13.8.1 to 13.9.2
Bumps [rich](https://github.com/Textualize/rich) from 13.8.1 to 13.9.2.
- [Release notes](https://github.com/Textualize/rich/releases)
- [Changelog](https://github.com/Textualize/rich/blob/master/CHANGELOG.md)
- [Commits](https://github.com/Textualize/rich/compare/v13.8.1...v13.9.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 03:17:51 +00:00
dependabot[bot] ec6d9ec7de chore(deps): bump humanize from 4.10.0 to 4.11.0
Bumps [humanize](https://github.com/python-humanize/humanize) from 4.10.0 to 4.11.0.
- [Release notes](https://github.com/python-humanize/humanize/releases)
- [Commits](https://github.com/python-humanize/humanize/compare/4.10.0...4.11.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 03:17:48 +00:00
dependabot[bot] 58fccadf08 chore(deps): bump aiohttp from 3.10.8 to 3.10.9
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.8 to 3.10.9.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.8...v3.10.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 03:17:45 +00:00
dependabot[bot] 34e7362132 chore(deps-dev): bump pre-commit from 3.8.0 to 4.0.0
Bumps [pre-commit](https://github.com/pre-commit/pre-commit) from 3.8.0 to 4.0.0.
- [Release notes](https://github.com/pre-commit/pre-commit/releases)
- [Changelog](https://github.com/pre-commit/pre-commit/blob/main/CHANGELOG.md)
- [Commits](https://github.com/pre-commit/pre-commit/compare/v3.8.0...v4.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 03:17:30 +00:00
dependabot[bot] fca39a7809 chore(deps): bump ccxt from 4.4.9 to 4.4.14
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.9 to 4.4.14.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.4.9...4.4.14)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 03:17:24 +00:00
dependabot[bot] 278404f47c chore(deps-dev): bump ruff from 0.6.8 to 0.6.9
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.8 to 0.6.9.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.8...0.6.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 03:17:11 +00:00
dependabot[bot] 64612712a5 chore(deps-dev): bump types-python-dateutil in the types group
Bumps the types group with 1 update: [types-python-dateutil](https://github.com/python/typeshed).


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

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-10-07 03:15:59 +00:00
Stefano Ariestasia 9ffd0ad982 add is_number check 2024-10-07 11:17:15 +09:00
Matthias e703fec3af Merge pull request #10748 from freqtrade/maint/bump_ruff_minpython
Bump ruff target version to 3.9
2024-10-06 08:56:50 +02:00
Matthias 78e9eac64a chore: Fix remaining violations after merge 2024-10-06 08:28:55 +02:00
Matthias 2b1fc8725e Merge branch 'develop' into maint/bump_ruff_minpython 2024-10-06 08:28:09 +02:00
Matthias b885c3dda3 Merge pull request #10746 from xzmeng/startup-time
Postpone imports on demand
2024-10-06 08:21:54 +02:00
Matthias aa2c1501da chore: fix missed dependencies for deploy_ui 2024-10-05 13:20:43 +02:00
Matthias ed5c929b23 chore: remove rapidjson from "default" imports 2024-10-05 13:17:35 +02:00
Matthias 672a23dbcd chore: revert changes to btanalysis - they have no impact 2024-10-05 13:09:30 +02:00
Matthias 24c09d0319 refactor: move ui deployment to it's own file 2024-10-05 13:06:19 +02:00
Matthias 092275a981 refactor: move config deployment to it's own file 2024-10-05 13:00:11 +02:00
Matthias 3fe97e1709 chore: Remove __futures__ import and corresponding ruff skips 2024-10-05 11:43:28 +02:00
Matthias 1ad4f0c9b5 chore: re-export from system module 2024-10-05 11:30:17 +02:00
Matthias 27a327402b chore: improve behavior by validating earlier
(and with exception handling)
2024-10-05 11:09:39 +02:00
Matthias e2adfe9eab Merge pull request #10749 from xzmeng/fix-setuppy
Fix setup.py
2024-10-04 13:00:21 +02:00
Meng Xiangzhuo 67690c9e8e Fix setup.py 2024-10-04 18:11:36 +08:00
Matthias 4c3c2eaa3b chore: update ruff target version to 3.9 2024-10-04 07:11:06 +02:00
Matthias cf6c41f9db chore: remove no longer required imports 2024-10-04 07:10:49 +02:00
Matthias 2e0a597ee4 chore: update tests to modern typing syntax 2024-10-04 07:09:51 +02:00
Matthias 628983d123 chore: update ft_client to modern typing syntax 2024-10-04 07:08:56 +02:00
Matthias 8ec5dd6def chore: update to modern typing syntax 2024-10-04 07:08:30 +02:00
Matthias acc40c73f3 chore: update strategy to modern typing syntax 2024-10-04 07:07:32 +02:00
Matthias 1d4658e978 chore: update rpc to modern typing syntax 2024-10-04 07:06:27 +02:00
Matthias b8bbf3b69e chore: update resolvers to modern typing syntax 2024-10-04 07:02:57 +02:00
Matthias d8e41fa8b1 chore: update plugins to modern typing syntax 2024-10-04 07:02:20 +02:00
Matthias e9a6ba03f9 chore: update persistence to modern typing syntax 2024-10-04 06:55:05 +02:00
Matthias 2e69e38adb chore: update optimize to modern typing syntax 2024-10-04 06:53:50 +02:00
Matthias c5ed876c09 chore: update types config to modern typing syntax 2024-10-04 06:50:53 +02:00
Matthias 96f737f13e chore: update freqai to modern typing syntax 2024-10-04 06:50:34 +02:00
Matthias f369151e8e chore: update exchange config to modern typing syntax 2024-10-04 06:46:45 +02:00
Matthias d1b9990e4e chore: update edge config to modern typing syntax 2024-10-04 06:42:29 +02:00
Matthias ed7eb01d1b chore: update data to modern typing syntax 2024-10-04 06:42:04 +02:00
Matthias 6601127693 chore: update config to modern typing syntax 2024-10-04 06:39:58 +02:00
Matthias 43236c1cc4 chore: update config to modern typing syntax 2024-10-04 06:39:20 +02:00
Matthias 65bbf7b2a2 chore: update commands to modern typing syntax 2024-10-04 06:38:06 +02:00
Matthias 74b8dca63e chore: update to modern typing syntax 2024-10-04 06:36:00 +02:00
Matthias 7db0e3ac3f chore: remove obsolete version check 2024-10-04 06:31:19 +02:00
Meng Xiangzhuo f889c24497 Fix test 2024-10-04 11:56:38 +08:00
Meng Xiangzhuo 852a1900b4 Fix test 2024-10-04 11:52:07 +08:00
Meng Xiangzhuo 30a273c946 Revert lazy import requests 2024-10-04 11:36:51 +08:00
Meng Xiangzhuo 903614140a Eliminate unnecessary lazy import 2024-10-04 11:22:47 +08:00
Meng Xiangzhuo 0fe772efa8 Eliminate unnecessary TYPE_CHECKING 2024-10-04 11:10:39 +08:00
Meng Xiangzhuo e6dedee56f Split freqtrade.system into a package 2024-10-04 11:05:49 +08:00
Meng Xiangzhuo 66605e91aa Merge branch 'develop' into startup-time 2024-10-04 08:38:55 +08:00
Matthias 137db2c86c Merge pull request #10745 from freqtrade/update/binance-leverage-tiers
Update Binance Leverage Tiers
2024-10-03 06:45:16 +02:00
Meng Xiangzhuo 9c28a6ff4a Add test 2024-10-03 12:07:03 +08:00
Meng Xiangzhuo 48a8d7de1c Postpone imports on demand 2024-10-03 11:33:52 +08:00
xmatthias 5fa96c944d chore: update pre-commit hooks 2024-10-03 03:07:14 +00:00
Matthias fe270bd9ae Merge pull request #10741 from freqtrade/feat/improve_liquidation_logic
Improved liquidation price logic
2024-10-02 19:49:03 +02:00
Matthias a0912ad6b4 tests: update ccxt compat test 2024-10-02 18:10:28 +02:00
Matthias 9ba0c54295 chore: cleanup test code 2024-10-02 07:05:00 +02:00
Matthias 86721b88ce chore: improve import logic 2024-10-02 07:05:00 +02:00
Matthias 9bdee1b82d feat: improve typing of fetch_funding_rates 2024-10-02 07:05:00 +02:00
Matthias abe01f8f48 feat: implement liquidation price update on all order fills 2024-10-02 07:05:00 +02:00
Matthias c5525d356e feat: support backtesting with cross configuration 2024-10-02 07:05:00 +02:00
Matthias 36ae564d26 feat: update liquidation price on startup 2024-10-02 07:05:00 +02:00
Matthias cba6bd6ef5 fix: use t.leverage, not trade.leverage for cross liq calculations 2024-10-02 07:05:00 +02:00
Matthias 4d40ffedff fix: allow setting 0 as liquidation price 2024-10-02 07:05:00 +02:00
Matthias 319e8d746f feat: use proper trade objects for liquidation calc 2024-10-02 07:05:00 +02:00
Matthias fe7a88362b feat: add method to fetch binance funding fees
which is necessary to calculate accurate liquidation prices
2024-10-02 07:05:00 +02:00
Matthias 8bf314202f chore: simplify call to liquidation price for cross futures 2024-10-02 07:05:00 +02:00
Matthias ac8bc7dec2 fix: use "other trades" logic for binance cross calc 2024-10-02 07:05:00 +02:00
Matthias 0d5919392e test: update binance test 2024-10-02 07:05:00 +02:00
Matthias 1473abf19a refactor: rename dry-liquidation parameter
passing all open trades will be more flexible for the future.
2024-10-02 07:05:00 +02:00
Matthias 45e75f3d09 chore: improve arguments to get_liquidation_price 2024-10-02 07:05:00 +02:00
Matthias 5358f2fb9e feat: allow liquidation-price update without trades for cross mode 2024-10-02 07:05:00 +02:00
Matthias c316d27444 refactor: move exception handler into helper function 2024-10-02 07:05:00 +02:00
Matthias 0c0bb29f83 chore: add other_trades param to liquidation_price calls 2024-10-02 07:05:00 +02:00
Matthias 82bc3270e7 test: Update binance test for new approach 2024-10-02 07:05:00 +02:00
Matthias ec79b0b17b feat: update dry-run calculation params to be more generic 2024-10-02 07:05:00 +02:00
Matthias 0560567058 test: add test for liquidation_price update function 2024-10-02 07:05:00 +02:00
Matthias 3de740b35f feat: create shared method for liquidation price update 2024-10-02 07:05:00 +02:00
Matthias b69f598e51 refactor: move more code into cross conditional 2024-10-02 07:05:00 +02:00
Matthias 1ad177fca7 feat: add liquidation_price update support for cross mode 2024-10-02 07:05:00 +02:00
Matthias c3ad3ff348 Merge pull request #10737 from xzmeng/fix-doc
Correct admonition indent
2024-10-02 07:03:23 +02:00
Matthias ad578bb171 Merge pull request #10739 from freqtrade/dependabot/docker/python-3.12.7-slim-bookworm
chore(deps): bump python from 3.12.6-slim-bookworm to 3.12.7-slim-bookworm
2024-10-02 06:23:44 +02:00
dependabot[bot] 9538c49ac8 chore(deps): bump python
Bumps python from 3.12.6-slim-bookworm to 3.12.7-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-10-02 03:28:37 +00:00
Meng Xiangzhuo 84d43db500 Correct admonition indent 2024-10-02 05:12:24 +08:00
Matthias bf2d0468f9 tests: fix test-data setup for btc test trades 2024-10-01 20:48:43 +02:00
Matthias d2bc47cfe8 tests: fix test-data setup for usdt trades 2024-10-01 20:31:02 +02:00
Matthias d8c6c766b9 Merge pull request #10735 from freqtrade/maint/3.9_removal
Remove support for python 3.9
2024-10-01 19:46:31 +02:00
Matthias 98f4bf2f65 chore: remove Ta-lib binaries for 3.9 2024-10-01 18:18:46 +02:00
Matthias f9c8b7f4ab chore: fix ps syntax error 2024-10-01 08:16:13 +02:00
Matthias a92532c0c9 chore: Remove now pointless helper function 2024-10-01 07:11:10 +02:00
Matthias bede81632d chore: remove conditional requirements 2024-10-01 07:04:25 +02:00
Matthias e975062e0e chore: don't run CI on 3.9 2024-10-01 07:03:48 +02:00
Matthias 0cd051cee2 chore: update conditional check for python version 2024-10-01 06:44:49 +02:00
Matthias 682980be49 chore: drop 3.9 support from setup.ps1 2024-10-01 06:44:27 +02:00
Matthias 878043ea3d chore: drop 3.9 support from setup.sh 2024-10-01 06:44:19 +02:00
Matthias 757ae65189 chore: remove pointless conditional (we can't hit this point) 2024-10-01 06:41:58 +02:00
Matthias cd63abba5b chore: update documentation to mention 3.10+ compat 2024-10-01 06:41:05 +02:00
Matthias a0f00d0c83 Merge pull request #10734 from freqtrade/update/pre-commit-hooks
Update pre-commit hooks
2024-10-01 06:28:22 +02:00
xmatthias 915591c6d6 chore: update pre-commit hooks 2024-10-01 03:13:31 +00:00
Matthias cdcb21b841 Merge pull request #10730 from freqtrade/dependabot/pip/develop/aiohttp-3.10.8
chore(deps): bump aiohttp from 3.10.5 to 3.10.8
2024-09-30 09:26:31 +02:00
Matthias 2bf79a5298 Merge pull request #10729 from freqtrade/dependabot/pip/develop/pymdown-extensions-10.11.1
chore(deps): bump pymdown-extensions from 10.10.1 to 10.11.1
2024-09-30 09:05:30 +02:00
Matthias ebefefa0eb Merge pull request #10733 from freqtrade/dependabot/pip/develop/ruff-0.6.8
chore(deps-dev): bump ruff from 0.6.7 to 0.6.8
2024-09-30 08:49:46 +02:00
dependabot[bot] a740883c8d chore(deps): bump aiohttp from 3.10.5 to 3.10.8
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.10.5 to 3.10.8.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.10.5...v3.10.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 06:21:31 +00:00
Matthias da760075d2 Merge pull request #10732 from freqtrade/dependabot/pip/develop/ccxt-4.4.9
chore(deps): bump ccxt from 4.4.6 to 4.4.9
2024-09-30 08:20:09 +02:00
Matthias b8833772a3 Merge pull request #10731 from freqtrade/dependabot/pip/develop/uvicorn-0.31.0
chore(deps): bump uvicorn from 0.30.6 to 0.31.0
2024-09-30 08:06:09 +02:00
Matthias 131c6e761e Merge pull request #10727 from freqtrade/dependabot/pip/develop/tensorboard-2.18.0
chore(deps): bump tensorboard from 2.17.1 to 2.18.0
2024-09-30 07:30:54 +02:00
dependabot[bot] 1fb0f2f048 chore(deps): bump pymdown-extensions from 10.10.1 to 10.11.1
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.10.1 to 10.11.1.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.10.1...10.11.1)

---
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-09-30 05:25:20 +00:00
Matthias 2a25ac9d1f Merge pull request #10726 from freqtrade/dependabot/pip/develop/mkdocs-5d708111c2
chore(deps): bump mkdocs-material from 9.5.36 to 9.5.39 in the mkdocs group
2024-09-30 07:24:31 +02:00
Matthias 773bf765ad feat(hyperliquid): Remove precision override
part of #10377
2024-09-30 07:06:16 +02:00
Matthias b0b866eec5 Merge pull request #10723 from freqtrade/remove/deprecated_protection-setting
Remove long deprecated protections from config setting
2024-09-30 06:41:22 +02:00
dependabot[bot] 8cc928c841 chore(deps-dev): bump ruff from 0.6.7 to 0.6.8
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.6.7 to 0.6.8.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.6.7...0.6.8)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 03:42:21 +00:00
dependabot[bot] 4180420765 chore(deps): bump ccxt from 4.4.6 to 4.4.9
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.4.6 to 4.4.9.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/4.4.6...4.4.9)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 03:42:05 +00:00
dependabot[bot] 30945dc240 chore(deps): bump uvicorn from 0.30.6 to 0.31.0
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.30.6 to 0.31.0.
- [Release notes](https://github.com/encode/uvicorn/releases)
- [Changelog](https://github.com/encode/uvicorn/blob/master/CHANGELOG.md)
- [Commits](https://github.com/encode/uvicorn/compare/0.30.6...0.31.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 03:41:58 +00:00
dependabot[bot] 4ef87e3419 chore(deps): bump tensorboard from 2.17.1 to 2.18.0
Bumps [tensorboard](https://github.com/tensorflow/tensorboard) from 2.17.1 to 2.18.0.
- [Release notes](https://github.com/tensorflow/tensorboard/releases)
- [Changelog](https://github.com/tensorflow/tensorboard/blob/master/RELEASE.md)
- [Commits](https://github.com/tensorflow/tensorboard/compare/2.17.1...2.18.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 03:41:22 +00:00
dependabot[bot] 4785194527 chore(deps): bump mkdocs-material in the mkdocs group
Bumps the mkdocs group with 1 update: [mkdocs-material](https://github.com/squidfunk/mkdocs-material).


Updates `mkdocs-material` from 9.5.36 to 9.5.39
- [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.36...9.5.39)

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

Signed-off-by: dependabot[bot] <support@github.com>
2024-09-30 03:41:18 +00:00
Matthias 8a742ce3ec chore: bump version to 2024.10-dev 2024-09-29 20:01:11 +02:00
Matthias f77fedbea4 chore: move available_protections constant to test file (it's only used there) 2024-09-29 15:08:57 +02:00
Matthias 39c582dac2 tests: move protection-validation test to protection test file 2024-09-29 15:06:43 +02:00
Matthias 8736728478 chore: remove unused import 2024-09-29 15:06:22 +02:00
Matthias 23cf9f47b0 chore: move protection validation to protectionManager 2024-09-29 15:06:15 +02:00
Matthias b8feefc541 tests: update protection tests 2024-09-29 09:44:07 +02:00
Matthias e3a6c71087 chore: Remove protections from config logic 2024-09-29 09:41:02 +02:00
Matthias d6cc88fa99 chore: remove schema syntax highlighting for protections 2024-09-29 09:27:40 +02:00
Matthias 428d451e55 chore: remove long-deprecated setting 2024-09-29 09:07:43 +02:00
Matthias aa67abad94 docs: simplify protections intro
this had way too many consecutive "boxes"
2024-09-29 08:38:07 +02:00
318 changed files with 22901 additions and 10286 deletions
+18 -15
View File
@@ -25,7 +25,7 @@ jobs:
strategy:
matrix:
os: [ "ubuntu-20.04", "ubuntu-22.04", "ubuntu-24.04" ]
python-version: ["3.9", "3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
@@ -46,7 +46,7 @@ jobs:
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip
key: pip-${{ matrix.python-version }}-ubuntu
- name: TA binary *nix
if: steps.cache.outputs.cache-hit != 'true'
@@ -68,11 +68,17 @@ jobs:
python build_helpers/freqtrade_client_version_align.py
- name: Tests
if: (!(runner.os == 'Linux' && matrix.python-version == '3.12' && matrix.os == 'ubuntu-22.04'))
run: |
pytest --random-order
- name: Tests with Coveralls
if: (runner.os == 'Linux' && matrix.python-version == '3.12' && matrix.os == 'ubuntu-22.04')
run: |
pytest --random-order --cov=freqtrade --cov=freqtrade_client --cov-config=.coveragerc
- name: Coveralls
if: (runner.os == 'Linux' && matrix.python-version == '3.10' && matrix.os == 'ubuntu-22.04')
if: (runner.os == 'Linux' && matrix.python-version == '3.12' && matrix.os == 'ubuntu-22.04')
env:
# Coveralls token. Not used as secret due to github not providing secrets to forked repositories
COVERALLS_REPO_TOKEN: 6D1m0xupS3FgutfuGao8keFf9Hc0FpIXu
@@ -138,11 +144,8 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ "macos-12", "macos-13", "macos-14" ]
python-version: ["3.9", "3.10", "3.11", "3.12"]
exclude:
- os: "macos-14"
python-version: "3.9"
os: [ "macos-13", "macos-14", "macos-15" ]
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
@@ -164,7 +167,7 @@ jobs:
uses: actions/cache@v4
with:
path: ~/Library/Caches/pip
key: ${{ matrix.os }}-${{ matrix.python-version }}-pip
key: pip-${{ matrix.os }}-${{ matrix.python-version }}
- name: TA binary *nix
if: steps.cache.outputs.cache-hit != 'true'
@@ -193,7 +196,7 @@ jobs:
rm /usr/local/bin/python3.11-config || true
rm /usr/local/bin/python3.12-config || true
brew install hdf5 c-blosc libomp
brew install libomp
- name: Installation (python)
run: |
@@ -263,7 +266,7 @@ jobs:
strategy:
matrix:
os: [ windows-latest ]
python-version: ["3.9", "3.10", "3.11", "3.12"]
python-version: ["3.10", "3.11", "3.12"]
steps:
- uses: actions/checkout@v4
@@ -277,7 +280,7 @@ jobs:
uses: actions/cache@v4
with:
path: ~\AppData\Local\pip\Cache
key: ${{ matrix.os }}-${{ matrix.python-version }}-pip
key: pip-${{ matrix.os }}-${{ matrix.python-version }}
- name: Installation
run: |
@@ -417,7 +420,7 @@ jobs:
uses: actions/cache@v4
with:
path: ~/.cache/pip
key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip
key: pip-3.12-ubuntu
- name: TA binary *nix
if: steps.cache.outputs.cache-hit != 'true'
@@ -537,12 +540,12 @@ jobs:
- name: Publish to PyPI (Test)
uses: pypa/gh-action-pypi-publish@v1.10.2
uses: pypa/gh-action-pypi-publish@v1.12.2
with:
repository-url: https://test.pypi.org/legacy/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@v1.10.2
uses: pypa/gh-action-pypi-publish@v1.12.2
deploy-docker:
+6 -6
View File
@@ -9,17 +9,17 @@ repos:
# stages: [push]
- repo: https://github.com/pre-commit/mirrors-mypy
rev: "v1.11.2"
rev: "v1.13.0"
hooks:
- id: mypy
exclude: build_helpers
additional_dependencies:
- types-cachetools==5.5.0.20240820
- types-filelock==3.2.7
- types-requests==2.32.0.20240914
- types-requests==2.32.0.20241016
- types-tabulate==0.9.0.20240106
- types-python-dateutil==2.9.0.20240906
- SQLAlchemy==2.0.35
- types-python-dateutil==2.9.0.20241003
- SQLAlchemy==2.0.36
# stages: [push]
- repo: https://github.com/pycqa/isort
@@ -31,13 +31,13 @@ repos:
- repo: https://github.com/charliermarsh/ruff-pre-commit
# Ruff version.
rev: 'v0.6.7'
rev: 'v0.8.0'
hooks:
- id: ruff
- id: ruff-format
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v4.6.0
rev: v5.0.0
hooks:
- id: end-of-file-fixer
exclude: |
+1 -1
View File
@@ -1,4 +1,4 @@
FROM python:3.12.6-slim-bookworm as base
FROM python:3.12.7-slim-bookworm as base
# Setup env
ENV LANG C.UTF-8
+4 -2
View File
@@ -33,6 +33,7 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even
- [X] [Bybit](https://bybit.com/)
- [X] [Gate.io](https://www.gate.io/ref/6266643)
- [X] [HTX](https://www.htx.com/) (Former Huobi)
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
- [X] [Kraken](https://kraken.com/)
- [X] [OKX](https://okx.com/) (Former OKEX)
- [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
@@ -41,6 +42,7 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even
- [X] [Binance](https://www.binance.com/)
- [X] [Gate.io](https://www.gate.io/ref/6266643)
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
- [X] [OKX](https://okx.com/)
- [X] [Bybit](https://bybit.com/)
@@ -61,7 +63,7 @@ Please find the complete documentation on the [freqtrade website](https://www.fr
## Features
- [x] **Based on Python 3.9+**: For botting on any operating system - Windows, macOS and Linux.
- [x] **Based on Python 3.10+**: For botting on any operating system - Windows, macOS and Linux.
- [x] **Persistence**: Persistence is achieved through sqlite.
- [x] **Dry-run**: Run the bot without paying money.
- [x] **Backtesting**: Run a simulation of your buy/sell strategy.
@@ -218,7 +220,7 @@ To run this bot we recommend you a cloud instance with a minimum of:
### Software requirements
- [Python >= 3.9](http://docs.python-guide.org/en/latest/starting/installation/)
- [Python >= 3.10](http://docs.python-guide.org/en/latest/starting/installation/)
- [pip](https://pip.pypa.io/en/stable/installing/)
- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
- [TA-Lib](https://ta-lib.github.io/ta-lib-python/)
Binary file not shown.
+16 -56
View File
@@ -579,57 +579,6 @@
]
}
},
"protections": {
"description": "Configuration for various protections.",
"type": "array",
"items": {
"type": "object",
"properties": {
"method": {
"description": "Method used for the protection.",
"type": "string",
"enum": [
"CooldownPeriod",
"LowProfitPairs",
"MaxDrawdown",
"StoplossGuard"
]
},
"stop_duration": {
"description": "Duration to lock the pair after a protection is triggered, in minutes.",
"type": "number",
"minimum": 0.0
},
"stop_duration_candles": {
"description": "Duration to lock the pair after a protection is triggered, in number of candles.",
"type": "number",
"minimum": 0
},
"unlock_at": {
"description": "Time when trading will be unlocked regularly. Format: HH:MM",
"type": "string"
},
"trade_limit": {
"description": "Minimum number of trades required during lookback period.",
"type": "number",
"minimum": 1
},
"lookback_period": {
"description": "Period to look back for protection checks, in minutes.",
"type": "number",
"minimum": 1
},
"lookback_period_candles": {
"description": "Period to look back for protection checks, in number of candles.",
"type": "number",
"minimum": 1
}
},
"required": [
"method"
]
}
},
"telegram": {
"description": "Telegram settings.",
"type": "object",
@@ -733,12 +682,18 @@
},
"exit_fill": {
"description": "Telegram setting for exit fill signals.",
"type": "string",
"enum": [
"on",
"off",
"silent"
"type": [
"string",
"object"
],
"additionalProperties": {
"type": "string",
"enum": [
"on",
"off",
"silent"
]
},
"default": "on"
},
"exit_cancel": {
@@ -1434,6 +1389,11 @@
"type": "string",
"default": "example"
},
"wait_for_training_iteration_on_reload": {
"description": "Wait for the next training iteration to complete after /reload or ctrl+c.",
"type": "boolean",
"default": true
},
"feature_parameters": {
"description": "The parameters used to engineer the feature set",
"type": "object",
+3 -3
View File
@@ -37,8 +37,8 @@ class SuperDuperHyperOptLoss(IHyperOptLoss):
min_date: datetime,
max_date: datetime,
config: Config,
processed: Dict[str, DataFrame],
backtest_stats: Dict[str, Any],
processed: dict[str, DataFrame],
backtest_stats: dict[str, Any],
**kwargs,
) -> float:
"""
@@ -103,7 +103,7 @@ class MyAwesomeStrategy(IStrategy):
SKDecimal(0.01, 0.20, decimals=3, name='roi_p3'),
]
def generate_roi_table(params: Dict) -> Dict[int, float]:
def generate_roi_table(params: Dict) -> dict[int, float]:
roi_table = {}
roi_table[0] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
+21 -12
View File
@@ -10,12 +10,14 @@ To learn how to get data for the pairs and exchange you're interested in, head o
```
usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [-s NAME]
[--strategy-path PATH] [-i TIMEFRAME]
[--timerange TIMERANGE]
[--data-format-ohlcv {json,jsongz,hdf5}]
[--strategy-path PATH]
[--recursive-strategy-search]
[--freqaimodel NAME] [--freqaimodel-path PATH]
[-i TIMEFRAME] [--timerange TIMERANGE]
[--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}]
[--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[-p PAIRS [PAIRS ...]] [--eps] [--dmmp]
[-p PAIRS [PAIRS ...]] [--eps]
[--enable-protections]
[--dry-run-wallet DRY_RUN_WALLET]
[--timeframe-detail TIMEFRAME_DETAIL]
@@ -24,8 +26,9 @@ usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[--export-filename PATH]
[--breakdown {day,week,month} [{day,week,month} ...]]
[--cache {none,day,week,month}]
[--freqai-backtest-live-models]
optional arguments:
options:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
@@ -48,10 +51,6 @@ optional arguments:
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking).
--dmmp, --disable-max-market-positions
Disable applying `max_open_trades` during backtest
(same as setting `max_open_trades` to a very high
number).
--enable-protections, --enableprotections
Enable protections for backtesting.Will slow
backtesting down by a considerable amount, but will
@@ -80,10 +79,13 @@ optional arguments:
--cache {none,day,week,month}
Load a cached backtest result no older than specified
age (default: day).
--freqai-backtest-live-models
Run backtest with ready models.
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
--logfile FILE, --log-file FILE
Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
@@ -92,7 +94,7 @@ Common arguments:
`userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
-d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
@@ -102,6 +104,12 @@ Strategy arguments:
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
--recursive-strategy-search
Recursively search for a strategy in the strategies
folder.
--freqaimodel NAME Specify a custom freqaimodels.
--freqaimodel-path PATH
Specify additional lookup path for freqaimodels.
```
@@ -558,6 +566,7 @@ Since backtesting lacks some detailed information about what happens within a ca
- Stoploss
- ROI
- Trailing stoploss
- Position reversals (futures only) happen if an entry signal in the other direction than the closing trade triggers at the candle the existing trade closes.
Taking these assumptions, backtesting tries to mirror real trading as closely as possible. However, backtesting will **never** replace running a strategy in dry-run mode.
Also, keep in mind that past results don't guarantee future success.
@@ -572,7 +581,7 @@ These limits are usually listed in the exchange documentation as "trading rules"
Backtesting (as well as live and dry-run) does honor these limits, and will ensure that a stoploss can be placed below this value - so the value will be slightly higher than what the exchange specifies.
Freqtrade has however no information about historic limits.
This can lead to situations where trading-limits are inflated by using a historic price, resulting in minimum amounts > 50$.
This can lead to situations where trading-limits are inflated by using a historic price, resulting in minimum amounts > 50\$.
For example:
+15 -16
View File
@@ -146,10 +146,10 @@ Freqtrade can also load many options via command line (CLI) arguments (check out
The prevalence for all Options is as follows:
- CLI arguments override any other option
- [Environment Variables](#environment-variables)
- Configuration files are used in sequence (the last file wins) and override Strategy configurations.
- Strategy configurations are only used if they are not set via configuration or command-line arguments. These options are marked with [Strategy Override](#parameters-in-the-strategy) in the below table.
* CLI arguments override any other option
* [Environment Variables](#environment-variables)
* Configuration files are used in sequence (the last file wins) and override Strategy configurations.
* Strategy configurations are only used if they are not set via configuration or command-line arguments. These options are marked with [Strategy Override](#parameters-in-the-strategy) in the below table.
### Parameters table
@@ -183,7 +183,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `margin_mode` | When trading with leverage, this determines if the collateral owned by the trader will be shared or isolated to each trading pair [leverage documentation](leverage.md). <br> **Datatype:** String
| `liquidation_buffer` | A ratio specifying how large of a safety net to place between the liquidation price and the stoploss to prevent a position from reaching the liquidation price [leverage documentation](leverage.md). <br>*Defaults to `0.05`.* <br> **Datatype:** Float
| | **Unfilled timeout**
| `unfilledtimeout.entry` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled entry order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
| `unfilledtimeout.entry` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled entry order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
| `unfilledtimeout.exit` | **Required.** How long (in minutes or seconds) the bot will wait for an unfilled exit order to complete, after which the order will be cancelled and repeated at current (new) price, as long as there is a signal. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
| `unfilledtimeout.unit` | Unit to use in unfilledtimeout setting. Note: If you set unfilledtimeout.unit to "seconds", "internals.process_throttle_secs" must be inferior or equal to timeout [Strategy Override](#parameters-in-the-strategy). <br> *Defaults to `"minutes"`.* <br> **Datatype:** String
| `unfilledtimeout.exit_timeout_count` | How many times can exit orders time out. Once this number of timeouts is reached, an emergency exit is triggered. 0 to disable and allow unlimited order cancels. [Strategy Override](#parameters-in-the-strategy).<br>*Defaults to `0`.* <br> **Datatype:** Integer
@@ -229,7 +229,6 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| | **Plugins**
| `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation of all possible configuration options.
| `pairlists` | Define one or more pairlists to be used. [More information](plugins.md#pairlists-and-pairlist-handlers). <br>*Defaults to `StaticPairList`.* <br> **Datatype:** List of Dicts
| `protections` | Define one or more protections to be used. [More information](plugins.md#protections). <br> **Datatype:** List of Dicts
| | **Telegram**
| `telegram.enabled` | Enable the usage of Telegram. <br> **Datatype:** Boolean
| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
@@ -296,10 +295,10 @@ Values set in the configuration file always overwrite values set in the strategy
* `order_time_in_force`
* `unfilledtimeout`
* `disable_dataframe_checks`
- `use_exit_signal`
* `use_exit_signal`
* `exit_profit_only`
- `exit_profit_offset`
- `ignore_roi_if_entry_signal`
* `exit_profit_offset`
* `ignore_roi_if_entry_signal`
* `ignore_buying_expired_candle_after`
* `position_adjustment_enable`
* `max_entry_position_adjustment`
@@ -312,12 +311,12 @@ There are several methods to configure how much of the stake currency the bot wi
The minimum stake amount will depend on exchange and pair and is usually listed in the exchange support pages.
Assuming the minimum tradable amount for XRP/USD is 20 XRP (given by the exchange), and the price is 0.6$, the minimum stake amount to buy this pair is `20 * 0.6 ~= 12`.
This exchange has also a limit on USD - where all orders must be > 10$ - which however does not apply in this case.
Assuming the minimum tradable amount for XRP/USD is 20 XRP (given by the exchange), and the price is 0.6\$, the minimum stake amount to buy this pair is `20 * 0.6 ~= 12`.
This exchange has also a limit on USD - where all orders must be > 10\$ - which however does not apply in this case.
To guarantee safe execution, freqtrade will not allow buying with a stake-amount of 10.1$, instead, it'll make sure that there's enough space to place a stoploss below the pair (+ an offset, defined by `amount_reserve_percent`, which defaults to 5%).
To guarantee safe execution, freqtrade will not allow buying with a stake-amount of 10.1\$, instead, it'll make sure that there's enough space to place a stoploss below the pair (+ an offset, defined by `amount_reserve_percent`, which defaults to 5%).
With a reserve of 5%, the minimum stake amount would be ~12.6$ (`12 * (1 + 0.05)`). If we take into account a stoploss of 10% on top of that - we'd end up with a value of ~14$ (`12.6 / (1 - 0.1)`).
With a reserve of 5%, the minimum stake amount would be ~12.6\$ (`12 * (1 + 0.05)`). If we take into account a stoploss of 10% on top of that - we'd end up with a value of ~14\$ (`12.6 / (1 - 0.1)`).
To limit this calculation in case of large stoploss values, the calculated minimum stake-limit will never be more than 50% above the real limit.
@@ -363,9 +362,9 @@ To overcome this, the option `amend_last_stake_amount` can be set to `True`, whi
In the example above this would mean:
- Trade1: 400 USDT
- Trade2: 400 USDT
- Trade3: 200 USDT
* Trade1: 400 USDT
* Trade2: 400 USDT
* Trade3: 200 USDT
!!! Note
This option only applies with [Static stake amount](#static-stake-amount) - since [Dynamic stake amount](#dynamic-stake-amount) divides the balances evenly.
+6 -6
View File
@@ -11,9 +11,8 @@ Without provided configuration, `--exchange` becomes mandatory.
You can use a relative timerange (`--days 20`) or an absolute starting point (`--timerange 20200101-`). For incremental downloads, the relative approach should be used.
!!! Tip "Tip: Updating existing data"
If you already have backtesting data available in your data-directory and would like to refresh this data up to today, freqtrade will automatically calculate the data missing for the existing pairs and the download will occur from the latest available point until "now", neither --days or --timerange parameters are required. Freqtrade will keep the available data and only download the missing data.
If you are updating existing data after inserting new pairs that you have no data for, use `--new-pairs-days xx` parameter. Specified number of days will be downloaded for new pairs while old pairs will be updated with missing data only.
If you use `--days xx` parameter alone - data for specified number of days will be downloaded for _all_ pairs. Be careful, if specified number of days is smaller than gap between now and last downloaded candle - freqtrade will delete all existing data to avoid gaps in candle data.
If you already have backtesting data available in your data-directory and would like to refresh this data up to today, freqtrade will automatically calculate the missing timerange for the existing pairs and the download will occur from the latest available point until "now", neither `--days` or `--timerange` parameters are required. Freqtrade will keep the available data and only download the missing data.
If you are updating existing data after inserting new pairs that you have no data for, use the `--new-pairs-days xx` parameter. Specified number of days will be downloaded for new pairs while old pairs will be updated with missing data only.
### Usage
@@ -90,7 +89,7 @@ Common arguments:
!!! Tip "Downloading all data for one quote currency"
Often, you'll want to download data for all pairs of a specific quote-currency. In such cases, you can use the following shorthand:
`freqtrade download-data --exchange binance --pairs .*/USDT <...>`. The provided "pairs" string will be expanded to contain all active pairs on the exchange.
`freqtrade download-data --exchange binance --pairs ".*/USDT" <...>`. The provided "pairs" string will be expanded to contain all active pairs on the exchange.
To also download data for inactive (delisted) pairs, add `--include-inactive-pairs` to the command.
!!! Note "Startup period"
@@ -117,16 +116,17 @@ freqtrade download-data --exchange binance --pairs ETH/USDT XRP/USDT BTC/USDT
or as regex (in this case, to download all active USDT pairs)
```bash
freqtrade download-data --exchange binance --pairs .*/USDT
freqtrade download-data --exchange binance --pairs ".*/USDT"
```
### Other Notes
* To use a different directory than the exchange specific default, use `--datadir user_data/data/some_directory`.
* To change the exchange used to download the historical data from, please use a different configuration file (you'll probably need to adjust rate limits etc.)
* To change the exchange used to download the historical data from, either use `--exchange <exchange>` - or specify a different configuration file.
* To use `pairs.json` from some other directory, use `--pairs-file some_other_dir/pairs.json`.
* To download historical candle (OHLCV) data for only 10 days, use `--days 10` (defaults to 30 days).
* To download historical candle (OHLCV) data from a fixed starting point, use `--timerange 20200101-` - which will download all data from January 1st, 2020.
* Given starting points are ignored if data is already available, downloading only missing data up to today.
* Use `--timeframes` to specify what timeframe download the historical candle (OHLCV) data for. Default is `--timeframes 1m 5m` which will download 1-minute and 5-minute data.
* To use exchange, timeframe and list of pairs as defined in your configuration file, use the `-c/--config` option. With this, the script uses the whitelist defined in the config as the list of currency pairs to download data for and does not require the pairs.json file. You can combine `-c/--config` with most other options.
+4 -1
View File
@@ -75,7 +75,10 @@ Webhook terminology changed from "sell" to "exit", and from "buy" to "entry", re
* `webhooksellfill`, `webhookexitfill` -> `exit_fill`
* `webhooksellcancel`, `webhookexitcancel` -> `exit_cancel`
## Removal of `populate_any_indicators`
version 2023.3 saw the removal of `populate_any_indicators` in favor of split methods for feature engineering and targets. Please read the [migration document](strategy_migration.md#freqai-strategy) for full details.
## Removal of `protections` from configuration
Setting protections from the configuration via `"protections": [],` has been removed in 2024.10, after having raised deprecation warnings for over 3 years.
+3 -4
View File
@@ -116,7 +116,7 @@ A similar setup can also be taken for Pycharm - using `freqtrade` as module name
![Pycharm debug configuration](assets/pycharm_debug.png)
!!! Note "Startup directory"
This assumes that you have the repository checked out, and the editor is started at the repository root level (so setup.py is at the top level of your repository).
This assumes that you have the repository checked out, and the editor is started at the repository root level (so pyproject.toml is at the top level of your repository).
## ErrorHandling
@@ -162,7 +162,7 @@ Hopefully you also want to contribute this back upstream.
Whatever your motivations are - This should get you off the ground in trying to develop a new Pairlist Handler.
First of all, have a look at the [VolumePairList](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/pairlist/VolumePairList.py) Handler, and best copy this file with a name of your new Pairlist Handler.
First of all, have a look at the [VolumePairList](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/plugins/pairlist/VolumePairList.py) Handler, and best copy this file with a name of your new Pairlist Handler.
This is a simple Handler, which however serves as a good example on how to start developing.
@@ -226,7 +226,7 @@ In `VolumePairList`, this implements different methods of sorting, does early va
##### sample
``` python
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
def filter_pairlist(self, pairlist: list[str], tickers: dict) -> List[str]:
# Generate dynamic whitelist
pairs = self._calculate_pairlist(pairlist, tickers)
return pairs
@@ -241,7 +241,6 @@ No protection should use datetime directly, but use the provided `date_now` vari
!!! Tip "Writing a new Protection"
Best copy one of the existing Protections to have a good example.
Don't forget to register your protection in `constants.py` under the variable `AVAILABLE_PROTECTIONS` - otherwise it will not be selectable.
#### Implementation of a new protection
+45 -1
View File
@@ -252,6 +252,14 @@ OKX requires a passphrase for each api key, you will therefore need to add this
Gate.io allows the use of `POINT` to pay for fees. As this is not a tradable currency (no regular market available), automatic fee calculations will fail (and default to a fee of 0).
The configuration parameter `exchange.unknown_fee_rate` can be used to specify the exchange rate between Point and the stake currency. Obviously, changing the stake-currency will also require changes to this value.
Gate API keys require the following permissions on top of the market type you want to trade:
* "Spot Trade" _or_ "Perpetual Futures" (Read and Write) (either select both, or the one matching the market you want to trade)
* "Wallet" (read only)
* "Account" (read only)
Without these permissions, the bot will not start correctly and show errors like "permission missing".
## Bybit
Futures trading on bybit is currently supported for USDT markets, and will use isolated futures mode.
@@ -261,6 +269,7 @@ On startup, freqtrade will set the position mode to "One-way Mode" for the whole
As bybit doesn't provide funding rate history, the dry-run calculation is used for live trades as well.
API Keys for live futures trading must have the following permissions:
* Read-write
* Contract - Orders
* Contract - Positions
@@ -295,6 +304,41 @@ It's therefore required to pass the UID as well.
!!! Warning "Necessary Verification"
Bitmart requires Verification Lvl2 to successfully trade on the spot market through the API - even though trading via UI works just fine with just Lvl1 verification.
## Hyperliquid
!!! Tip "Stoploss on Exchange"
Hyperliquid supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it.
Hyperliquid is a Decentralized Exchange (DEX). Decentralized exchanges work a bit different compared to normal exchanges. Instead of authenticating private API calls using an API key, private API calls need to be signed with the private key of your wallet (We recommend using an api Wallet for this, generated either on Hyperliquid or in your wallet of choice).
This needs to be configured like this:
```json
"exchange": {
"name": "hyperliquid",
"walletAddress": "your_eth_wallet_address",
"privateKey": "your_api_private_key",
// ...
}
```
* walletAddress in hex format: `0x<40 hex characters>` - Can be easily copied from your wallet - and should be your wallet address, not your API Wallet Address.
* privateKey in hex format: `0x<64 hex characters>` - Use the key the API Wallet shows on creation.
Hyperliquid handles deposits and withdrawals on the Arbitrum One chain, a Layer 2 scaling solution built on top of Ethereum. Hyperliquid uses USDC as quote / collateral. The process of depositing USDC on Hyperliquid requires a couple of steps, see [how to start trading](https://hyperliquid.gitbook.io/hyperliquid-docs/onboarding/how-to-start-trading) for details on what steps are needed.
!!! Note "Hyperliquid general usage Notes"
Hyperliquid does not support market orders, however ccxt will simulate market orders by placing limit orders with a maximum slippage of 5%.
Unfortunately, hyperliquid only offers 5000 historic candles, so backtesting will either need to build candles historically (by waiting and downloading the data incrementally over time) - or will be limited to the last 5000 candles.
!!! Info "Some general best practices (non exhaustive)"
* Beware of supply chain attacks, like pip package poisoning etcetera. Whenever you use your private key, make sure your environment is safe.
* Don't use your actual wallet private key for trading. Use the Hyperliquid [API generator](https://app.hyperliquid.xyz/API) to create a separate API wallet.
* Don't store your actual wallet private key on the server you use for freqtrade. Use the API wallet private key instead. This key won't allow withdrawals, only trading.
* Always keep your mnemonic phrase and private key private.
* Don't use the same mnemonic as the one you had to backup when initializing a hardware wallet, using the same mnemonic basically deletes the security of your hardware wallet.
* Create a different software wallet, only transfer the funds you want to trade with to that wallet, and use that wallet to trade on Hyperliquid.
* If you have funds you don't want to use for trading (after making a profit for example), transfer them back to your hardware wallet.
## All exchanges
Should you experience constant errors with Nonce (like `InvalidNonce`), it is best to regenerate the API keys. Resetting Nonce is difficult and it's usually easier to regenerate the API keys.
@@ -304,7 +348,7 @@ Should you experience constant errors with Nonce (like `InvalidNonce`), it is be
* The Ocean (exchange id: `theocean`) exchange uses Web3 functionality and requires `web3` python package to be installed:
```shell
$ pip3 install web3
pip3 install web3
```
### Getting latest price / Incomplete candles
+16 -3
View File
@@ -100,6 +100,19 @@ You can use the `/stopentry` command in Telegram to prevent future trade entry,
Please look at the [advanced setup documentation Page](advanced-setup.md#running-multiple-instances-of-freqtrade).
### I'm getting "Impossible to load Strategy" when starting the bot
This error message is shown when the bot cannot load the strategy.
Usually, you can use `freqtrade list-strategies` to list all available strategies.
The output of this command will also include a status column, showing if the strategy can be loaded.
Please check the following:
* Are you using the correct strategy name? The strategy name is case-sensitive and must correspond to the Strategy class name (not the filename!).
* Is the strategy in the `user_data/strategies` directory, and has the file-ending `.py`?
* Does the bot show other warnings before this error? Maybe you're missing some dependencies for the strategy - which would be highlighted in the log.
* In case of docker - is the strategy directory mounted correctly (check the volumes part of the docker-compose file)?
### I'm getting "Missing data fillup" messages in the log
This message is just a warning that the latest candles had missing candles in them.
@@ -146,9 +159,9 @@ The same fix should be applied in the configuration file, if order types are def
### I'm trying to start the bot live, but get an API permission error
Errors like `Invalid API-key, IP, or permissions for action` mean exactly what they actually say.
Your API key is either invalid (copy/paste error? check for leading/trailing spaces in the config), expired, or the IP you're running the bot from is not enabled in the Exchange's API console.
Usually, the permission "Spot Trading" (or the equivalent in the exchange you use) will be necessary.
Errors like `Invalid API-key, IP, or permissions for action` mean exactly what they actually say.
Your API key is either invalid (copy/paste error? check for leading/trailing spaces in the config), expired, or the IP you're running the bot from is not enabled in the Exchange's API console.
Usually, the permission "Spot Trading" (or the equivalent in the exchange you use) will be necessary.
Futures will usually have to be enabled specifically.
### How do I search the bot logs for something?
+7 -7
View File
@@ -293,10 +293,10 @@ class MyCoolPyTorchClassifier(BasePyTorchClassifier):
super().__init__(**kwargs)
config = self.freqai_info.get("model_training_parameters", {})
self.learning_rate: float = config.get("learning_rate", 3e-4)
self.model_kwargs: Dict[str, Any] = config.get("model_kwargs", {})
self.trainer_kwargs: Dict[str, Any] = config.get("trainer_kwargs", {})
self.model_kwargs: dict[str, Any] = config.get("model_kwargs", {})
self.trainer_kwargs: dict[str, Any] = config.get("trainer_kwargs", {})
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
def fit(self, data_dictionary: dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
"""
User sets up the training and test data to fit their desired model here
:param data_dictionary: the dictionary holding all data for train, test,
@@ -359,10 +359,10 @@ class PyTorchMLPRegressor(BasePyTorchRegressor):
super().__init__(**kwargs)
config = self.freqai_info.get("model_training_parameters", {})
self.learning_rate: float = config.get("learning_rate", 3e-4)
self.model_kwargs: Dict[str, Any] = config.get("model_kwargs", {})
self.trainer_kwargs: Dict[str, Any] = config.get("trainer_kwargs", {})
self.model_kwargs: dict[str, Any] = config.get("model_kwargs", {})
self.trainer_kwargs: dict[str, Any] = config.get("trainer_kwargs", {})
def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
def fit(self, data_dictionary: dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
n_features = data_dictionary["train_features"].shape[-1]
model = PyTorchMLPModel(
input_dim=n_features,
@@ -393,7 +393,7 @@ Here we create a `PyTorchMLPRegressor` class that implements the `fit` method. T
For example, if you are using a binary classifier to predict price movements as up or down, you can set the class names as follows:
```python
def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
def set_freqai_targets(self, dataframe: DataFrame, metadata: dict, **kwargs) -> DataFrame:
self.freqai.class_names = ["down", "up"]
dataframe['&s-up_or_down'] = np.where(dataframe["close"].shift(-100) >
dataframe["close"], 'up', 'down')
+1
View File
@@ -22,6 +22,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
| `write_metrics_to_disk` | Collect train timings, inference timings and cpu usage in json file. <br> **Datatype:** Boolean. <br> Default: `False`
| `data_kitchen_thread_count` | <br> Designate the number of threads you want to use for data processing (outlier methods, normalization, etc.). This has no impact on the number of threads used for training. If user does not set it (default), FreqAI will use max number of threads - 2 (leaving 1 physical core available for Freqtrade bot and FreqUI) <br> **Datatype:** Positive integer.
| `activate_tensorboard` | <br> Indicate whether or not to activate tensorboard for the tensorboard enabled modules (currently Reinforcment Learning, XGBoost, Catboost, and PyTorch). Tensorboard needs Torch installed, which means you will need the torch/RL docker image or you need to answer "yes" to the install question about whether or not you wish to install Torch. <br> **Datatype:** Boolean. <br> Default: `True`.
| `wait_for_training_iteration_on_reload` | <br> When using /reload or ctrl-c, wait for the current training iteration to finish before completing graceful shutdown. If set to `False`, FreqAI will break the current training iteration, allowing you to shutdown gracefully more quickly, but you will lose your current training iteration. <br> **Datatype:** Boolean. <br> Default: `True`.
### Feature parameters
+44 -24
View File
@@ -42,11 +42,11 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--recursive-strategy-search] [--freqaimodel NAME]
[--freqaimodel-path PATH] [-i TIMEFRAME]
[--timerange TIMERANGE]
[--data-format-ohlcv {json,jsongz,hdf5}]
[--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}]
[--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[-p PAIRS [PAIRS ...]] [--hyperopt-path PATH]
[--eps] [--dmmp] [--enable-protections]
[--eps] [--enable-protections]
[--dry-run-wallet DRY_RUN_WALLET]
[--timeframe-detail TIMEFRAME_DETAIL] [-e INT]
[--spaces {all,buy,sell,roi,stoploss,trailing,protection,trades,default} [{all,buy,sell,roi,stoploss,trailing,protection,trades,default} ...]]
@@ -55,15 +55,15 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--hyperopt-loss NAME] [--disable-param-export]
[--ignore-missing-spaces] [--analyze-per-epoch]
optional arguments:
options:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME
Specify timeframe (`1m`, `5m`, `30m`, `1h`, `1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--data-format-ohlcv {json,jsongz,hdf5}
--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}
Storage format for downloaded candle (OHLCV) data.
(default: `json`).
(default: `feather`).
--max-open-trades INT
Override the value of the `max_open_trades`
configuration setting.
@@ -80,10 +80,6 @@ optional arguments:
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking).
--dmmp, --disable-max-market-positions
Disable applying `max_open_trades` during backtest
(same as setting `max_open_trades` to a very high
number).
--enable-protections, --enableprotections
Enable protections for backtesting.Will slow
backtesting down by a considerable amount, but will
@@ -133,7 +129,8 @@ optional arguments:
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
--logfile FILE, --log-file FILE
Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
@@ -142,7 +139,7 @@ Common arguments:
`userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
-d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
@@ -445,7 +442,6 @@ While this strategy is most likely too simple to provide consistent profit, it s
Whether you are using `.range` functionality or the alternatives above, you should try to use space ranges as small as possible since this will improve CPU/RAM usage.
## Optimizing protections
Freqtrade can also optimize protections. How you optimize protections is up to you, and the following should be considered as example only.
@@ -589,14 +585,15 @@ Currently, the following loss functions are builtin:
* `ShortTradeDurHyperOptLoss` - (default legacy Freqtrade hyperoptimization loss function) - Mostly for short trade duration and avoiding losses.
* `OnlyProfitHyperOptLoss` - takes only amount of profit into consideration.
* `SharpeHyperOptLoss` - optimizes Sharpe Ratio calculated on trade returns relative to standard deviation.
* `SharpeHyperOptLossDaily` - optimizes Sharpe Ratio calculated on **daily** trade returns relative to standard deviation.
* `SortinoHyperOptLoss` - optimizes Sortino Ratio calculated on trade returns relative to **downside** standard deviation.
* `SharpeHyperOptLoss` - Optimizes Sharpe Ratio calculated on trade returns relative to standard deviation.
* `SharpeHyperOptLossDaily` - Optimizes Sharpe Ratio calculated on **daily** trade returns relative to standard deviation.
* `SortinoHyperOptLoss` - Optimizes Sortino Ratio calculated on trade returns relative to **downside** standard deviation.
* `SortinoHyperOptLossDaily` - optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation.
* `MaxDrawDownHyperOptLoss` - Optimizes Maximum absolute drawdown.
* `MaxDrawDownRelativeHyperOptLoss` - Optimizes both maximum absolute drawdown while also adjusting for maximum relative drawdown.
* `CalmarHyperOptLoss` - Optimizes Calmar Ratio calculated on trade returns relative to max drawdown.
* `ProfitDrawDownHyperOptLoss` - Optimizes by max Profit & min Drawdown objective. `DRAWDOWN_MULT` variable within the hyperoptloss file can be adjusted to be stricter or more flexible on drawdown purposes.
* `MultiMetricHyperOptLoss` - Optimizes by several key metrics to achieve balanced performance. The primary focus is on maximizing Profit and minimizing Drawdown, while also considering additional metrics such as Profit Factor, Expectancy Ratio and Winrate. Moreover, it applies a penalty for epochs with a low number of trades, encouraging strategies with adequate trade frequency.
Creation of a custom loss function is covered in the [Advanced Hyperopt](advanced-hyperopt.md) part of the documentation.
@@ -867,18 +864,15 @@ You can use the `--print-all` command line option if you would like to see all r
## Position stacking and disabling max market positions
In some situations, you may need to run Hyperopt (and Backtesting) with the
`--eps`/`--enable-position-staking` and `--dmmp`/`--disable-max-market-positions` arguments.
In some situations, you may need to run Hyperopt (and Backtesting) with the `--eps`/`--enable-position-staking` argument, or you may need to set `max_open_trades` to a very high number to disable the limit on the number of open trades.
By default, hyperopt emulates the behavior of the Freqtrade Live Run/Dry Run, where only one
open trade is allowed for every traded pair. The total number of trades open for all pairs
open trade per pair is allowed. The total number of trades open for all pairs
is also limited by the `max_open_trades` setting. During Hyperopt/Backtesting this may lead to
some potential trades to be hidden (or masked) by previously open trades.
potential trades being hidden (or masked) by already open trades.
The `--eps`/`--enable-position-stacking` argument allows emulation of buying the same pair multiple times,
while `--dmmp`/`--disable-max-market-positions` disables applying `max_open_trades`
during Hyperopt/Backtesting (which is equal to setting `max_open_trades` to a very high
number).
The `--eps`/`--enable-position-stacking` argument allows emulation of buying the same pair multiple times.
Using `--max-open-trades` with a very high number will disable the limit on the number of open trades.
!!! Note
Dry/live runs will **NOT** use position stacking - therefore it does make sense to also validate the strategy without this as it's closer to reality.
@@ -919,13 +913,39 @@ Your epochs should therefore be aligned to the possible values - or you should b
After you run Hyperopt for the desired amount of epochs, you can later list all results for analysis, select only best or profitable once, and show the details for any of the epochs previously evaluated. This can be done with the `hyperopt-list` and `hyperopt-show` sub-commands. The usage of these sub-commands is described in the [Utils](utils.md#list-hyperopt-results) chapter.
## Output debug messages from your strategy
If you want to output debug messages from your strategy, you can use the `logging` module. By default, Freqtrade will output all messages with a level of `INFO` or higher.
``` python
import logging
logger = logging.getLogger(__name__)
class MyAwesomeStrategy(IStrategy):
...
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
logger.info("This is a debug message")
...
```
!!! Note "using print"
Messages printed via `print()` will not be shown in the hyperopt output unless parallelism is disabled (`-j 1`).
It is recommended to use the `logging` module instead.
## Validate backtesting results
Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected.
To achieve same the results (number of trades, their durations, profit, etc.) as during Hyperopt, please use the same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
To achieve same the results (number of trades, their durations, profit, etc.) as during Hyperopt, please use the same configuration and parameters (timerange, timeframe, ...) used for hyperopt for Backtesting.
### Why do my backtest results not match my hyperopt results?
Should results not match, check the following factors:
* You may have added parameters to hyperopt in `populate_indicators()` where they will be calculated only once **for all epochs**. If you are, for example, trying to optimise multiple SMA timeperiod values, the hyperoptable timeperiod parameter should be placed in `populate_entry_trend()` which is calculated every epoch. See [Optimizing an indicator parameter](https://www.freqtrade.io/en/stable/hyperopt/#optimizing-an-indicator-parameter).
+3 -2
View File
@@ -352,7 +352,7 @@ The optional `bearer_token` will be included in the requests Authorization Heade
#### MarketCapPairList
`MarketCapPairList` employs sorting/filtering of pairs by their marketcap rank based of CoinGecko. It will only recognize coins up to the coin placed at rank 250. The returned pairlist will be sorted based of their marketcap ranks.
`MarketCapPairList` employs sorting/filtering of pairs by their marketcap rank based of CoinGecko. The returned pairlist will be sorted based of their marketcap ranks.
```json
"pairlists": [
@@ -366,7 +366,8 @@ The optional `bearer_token` will be included in the requests Authorization Heade
]
```
`number_assets` defines the maximum number of pairs returned by the pairlist. `max_rank` will determine the maximum rank used in creating/filtering the pairlist. It's expected that some coins within the top `max_rank` marketcap will not be included in the resulting pairlist since not all pairs will have active trading pairs in your preferred market/stake/exchange combination.
`number_assets` defines the maximum number of pairs returned by the pairlist. `max_rank` will determine the maximum rank used in creating/filtering the pairlist. It's expected that some coins within the top `max_rank` marketcap will not be included in the resulting pairlist since not all pairs will have active trading pairs in your preferred market/stake/exchange combination.
While using a `max_rank` bigger than 250 is supported, it's not recommended, as it'll cause multiple API calls to CoinGecko, which can lead to rate limit issues.
The `refresh_period` setting defines the interval (in seconds) at which the marketcap rank data will be refreshed. The default is 86,400 seconds (1 day). The pairlist cache (`refresh_period`) applies to both generating pairlists (when in the first position in the list) and filtering instances (when not in the first position in the list).
+1 -9
View File
@@ -1,24 +1,16 @@
## Protections
!!! Warning "Beta feature"
This feature is still in it's testing phase. Should you notice something you think is wrong please let us know via Discord or via Github Issue.
Protections will protect your strategy from unexpected events and market conditions by temporarily stop trading for either one pair, or for all pairs.
All protection end times are rounded up to the next candle to avoid sudden, unexpected intra-candle buys.
!!! Note
!!! Tip "Usage tips"
Not all Protections will work for all strategies, and parameters will need to be tuned for your strategy to improve performance.
!!! Tip
Each Protection can be configured multiple times with different parameters, to allow different levels of protection (short-term / long-term).
!!! Note "Backtesting"
Protections are supported by backtesting and hyperopt, but must be explicitly enabled by using the `--enable-protections` flag.
!!! Warning "Setting protections from the configuration"
Setting protections from the configuration via `"protections": [],` key should be considered deprecated and will be removed in a future version.
It is also no longer guaranteed that your protections apply to the strategy in cases where the strategy defines [protections as property](hyperopt.md#optimizing-protections).
### Available Protections
* [`StoplossGuard`](#stoploss-guard) Stop trading if a certain amount of stoploss occurred within a certain time window.
+7 -5
View File
@@ -28,7 +28,7 @@ Freqtrade is a free and open source crypto trading bot written in Python. It is
- Develop your Strategy: Write your strategy in python, using [pandas](https://pandas.pydata.org/). Example strategies to inspire you are available in the [strategy repository](https://github.com/freqtrade/freqtrade-strategies).
- Download market data: Download historical data of the exchange and the markets your may want to trade with.
- Backtest: Test your strategy on downloaded historical data.
- Optimize: Find the best parameters for your strategy using hyperoptimization which employs machining learning methods. You can optimize buy, sell, take profit (ROI), stop-loss and trailing stop-loss parameters for your strategy.
- Optimize: Find the best parameters for your strategy using hyperoptimization which employs machine learning methods. You can optimize buy, sell, take profit (ROI), stop-loss and trailing stop-loss parameters for your strategy.
- Select markets: Create your static list or use an automatic one based on top traded volumes and/or prices (not available during backtesting). You can also explicitly blacklist markets you don't want to trade.
- Run: Test your strategy with simulated money (Dry-Run mode) or deploy it with real money (Live-Trade mode).
- Run using Edge (optional module): The concept is to find the best historical [trade expectancy](edge.md#expectancy) by markets based on variation of the stop-loss and then allow/reject markets to trade. The sizing of the trade is based on a risk of a percentage of your capital.
@@ -40,11 +40,12 @@ Freqtrade is a free and open source crypto trading bot written in Python. It is
Please read the [exchange specific notes](exchanges.md) to learn about eventual, special configurations needed for each exchange.
- [X] [Binance](https://www.binance.com/)
- [X] [Bitmart](https://bitmart.com/)
- [X] [BingX](https://bingx.com/invite/0EM9RX)
- [X] [Bitmart](https://bitmart.com/)
- [X] [Bybit](https://bybit.com/)
- [X] [Gate.io](https://www.gate.io/ref/6266643)
- [X] [HTX](https://www.htx.com/) (Former Huobi)
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
- [X] [Kraken](https://kraken.com/)
- [X] [OKX](https://okx.com/) (Former OKEX)
- [ ] [potentially many others through <img alt="ccxt" width="30px" src="assets/ccxt-logo.svg" />](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
@@ -52,9 +53,10 @@ Please read the [exchange specific notes](exchanges.md) to learn about eventual,
### Supported Futures Exchanges (experimental)
- [X] [Binance](https://www.binance.com/)
- [X] [Gate.io](https://www.gate.io/ref/6266643)
- [X] [OKX](https://okx.com/)
- [X] [Bybit](https://bybit.com/)
- [X] [Gate.io](https://www.gate.io/ref/6266643)
- [X] [Hyperliquid](https://hyperliquid.xyz/) (A decentralized exchange, or DEX)
- [X] [OKX](https://okx.com/)
Please make sure to read the [exchange specific notes](exchanges.md), as well as the [trading with leverage](leverage.md) documentation before diving in.
@@ -85,7 +87,7 @@ To run this bot we recommend you a linux cloud instance with a minimum of:
Alternatively
- Python 3.9+
- Python 3.10+
- pip (pip3)
- git
- TA-Lib
+25 -18
View File
@@ -24,7 +24,7 @@ The easiest way to install and run Freqtrade is to clone the bot Github reposito
The `stable` branch contains the code of the last release (done usually once per month on an approximately one week old snapshot of the `develop` branch to prevent packaging bugs, so potentially it's more stable).
!!! Note
Python3.9 or higher and the corresponding `pip` are assumed to be available. The install-script will warn you and stop if that's not the case. `git` is also needed to clone the Freqtrade repository.
Python3.10 or higher and the corresponding `pip` are assumed to be available. The install-script will warn you and stop if that's not the case. `git` is also needed to clone the Freqtrade repository.
Also, python headers (`python<yourversion>-dev` / `python<yourversion>-devel`) must be available for the installation to complete successfully.
!!! Warning "Up-to-date clock"
@@ -42,7 +42,7 @@ These requirements apply to both [Script Installation](#script-installation) and
### Install guide
* [Python >= 3.9](http://docs.python-guide.org/en/latest/starting/installation/)
* [Python >= 3.10](http://docs.python-guide.org/en/latest/starting/installation/)
* [pip](https://pip.pypa.io/en/stable/installing/)
* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended)
@@ -54,7 +54,7 @@ We've included/collected install instructions for Ubuntu, MacOS, and Windows. Th
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.
Python3.10 or higher and the corresponding pip are assumed to be available.
=== "Debian/Ubuntu"
#### Install necessary dependencies
@@ -67,16 +67,28 @@ OS Specific steps are listed first, the common section below is necessary for al
sudo apt install -y python3-pip python3-venv python3-dev python3-pandas git curl
```
=== "MacOS"
#### Install necessary dependencies
Install [Homebrew](https://brew.sh/) if you don't have it already.
```bash
# install packages
brew install gettext libomp
```
!!! Note
The `setup.sh` script will install these dependencies for you - assuming brew is installed on your system.
=== "RaspberryPi/Raspbian"
The following assumes the latest [Raspbian Buster lite image](https://www.raspberrypi.org/downloads/raspbian/).
This image comes with python3.9 preinstalled, making it easy to get freqtrade up and running.
This image comes with python3.11 preinstalled, making it easy to get freqtrade up and running.
Tested using a Raspberry Pi 3 with the Raspbian Buster lite image, all updates applied.
```bash
sudo apt-get install python3-venv libatlas-base-dev cmake curl
# Use pywheels.org to speed up installation
# Use piwheels.org to speed up installation
sudo echo "[global]\nextra-index-url=https://www.piwheels.org/simple" > tee /etc/pip.conf
git clone https://github.com/freqtrade/freqtrade.git
@@ -150,9 +162,7 @@ Each time you open a new terminal, you must run `source .venv/bin/activate` to a
source ./.venv/bin/activate
```
### Congratulations
[You are ready](#you-are-ready), and run the bot
[You are now ready](#you-are-ready) to run the bot.
### Other options of /setup.sh script
@@ -169,7 +179,7 @@ You can as well update, configure and reset the codebase of your bot with `./scr
** --install **
With this option, the script will install the bot and most dependencies:
You will need to have git and python3.9+ installed beforehand for this to work.
You will need to have git and python3.10+ installed beforehand for this to work.
* Mandatory software as: `ta-lib`
* Setup your virtualenv under `.venv/`
@@ -220,7 +230,7 @@ cd ..
rm -rf ./ta-lib*
```
#### Setup Python virtual environment (virtualenv)
### Setup Python virtual environment (virtualenv)
You will run freqtrade in separated `virtual environment`
@@ -232,19 +242,18 @@ python3 -m venv .venv
source .venv/bin/activate
```
#### Install python dependencies
### Install python dependencies
```bash
python3 -m pip install --upgrade pip
python3 -m pip install -r requirements.txt
# install freqtrade
python3 -m pip install -e .
```
### Congratulations
[You are now ready](#you-are-ready) to run the bot.
[You are ready](#you-are-ready), and run the bot
#### (Optional) Post-installation Tasks
### (Optional) Post-installation Tasks
!!! Note
If you run the bot on a server, you should consider using [Docker](docker_quickstart.md) or a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout.
@@ -333,9 +342,7 @@ cd build_helpers
bash install_ta-lib.sh ${CONDA_PREFIX} nosudo
```
### Congratulations
[You are ready](#you-are-ready), and run the bot
[You are now ready](#you-are-ready) to run the bot.
### Important shortcuts
+2 -2
View File
@@ -1,7 +1,7 @@
markdown==3.7
mkdocs==1.6.1
mkdocs-material==9.5.36
mkdocs-material==9.5.45
mdx_truly_sane_lists==1.3
pymdown-extensions==10.10.1
pymdown-extensions==10.12
jinja2==3.1.4
mike==2.1.3
+1
View File
@@ -36,6 +36,7 @@ The Order-type will be ignored if only one mode is available.
| Gate | limit |
| Okx | limit |
| Kucoin | stop-limit, stop-market|
| Hyperliquid (futures only) | limit |
!!! Note "Tight stoploss"
<ins>Do not set too low/tight stoploss value when using stop loss on exchange!</ins>
+196
View File
@@ -0,0 +1,196 @@
# Freqtrade Strategies 101: A Quick Start for Strategy Development
For the purposes of this quick start, we are assuming you are familiar with the basics of trading, and have read the
[Freqtrade basics](bot-basics.md) page.
## Required Knowledge
A strategy in Freqtrade is a Python class that defines the logic for buying and selling cryptocurrency `assets`.
Assets are defined as `pairs`, which represent the `coin` and the `stake`. The coin is the asset you are trading using another currency as the stake.
Data is supplied by the exchange in the form of `candles`, which are made up of a six values: `date`, `open`, `high`, `low`, `close` and `volume`.
`Technical analysis` functions analyse the candle data using various computational and statistical formulae, and produce secondary values called `indicators`.
Indicators are analysed on the asset pair candles to generate `signals`.
Signals are turned into `orders` on a cryptocurrency `exchange`, i.e. `trades`.
We use the terms `entry` and `exit` instead of `buying` and `selling` because Freqtrade supports both `long` and `short` trades.
- **long**: You buy the coin based on a stake, e.g. buying the coin BTC using USDT as your stake, and you make a profit by selling the coin at a higher rate than you paid for. In long trades, profits are made by the coin value going up versus the stake.
- **short**: You borrow capital from the exchange in the form of the coin, and you pay back the stake value of the coin later. In short trades profits are made by the coin value going down versus the stake (you pay the loan off at a lower rate).
Whilst Freqtrade supports spot and futures markets for certain exchanges, for simplicity we will focus on spot (long) trades only.
## Structure of a Basic Strategy
### Main dataframe
Freqtrade strategies use a tabular data structure with rows and columns known as a `dataframe` to generate signals to enter and exit trades.
Each pair in your configured pairlist has its own dataframe. Dataframes are indexed by the `date` column, e.g. `2024-06-31 12:00`.
The next 5 columns represent the `open`, `high`, `low`, `close` and `volume` (OHLCV) data.
### Populate indicator values
The `populate_indicators` function adds columns to the dataframe that represent the technical analysis indicator values.
Examples of common indicators include Relative Strength Index, Bollinger Bands, Money Flow Index, Moving Average, and Average True Range.
Columns are added to the dataframe by calling technical analysis functions, e.g. ta-lib's RSI function `ta.RSI()`, and assigning them to a column name, e.g. `rsi`
```python
dataframe['rsi'] = ta.RSI(dataframe)
```
??? Hint "Technical Analysis libraries"
Different libraries work in different ways to generate indicator values. Please check the documentation of each library to understand
how to integrate it into your strategy. You can also check the [Freqtrade example strategies](https://github.com/freqtrade/freqtrade-strategies) to give you ideas.
### Populate entry signals
The `populate_entry_trend` function defines conditions for an entry signal.
The dataframe column `enter_long` is added to the dataframe, and when a value of `1` is in this column, Freqtrade sees an entry signal.
??? Hint "Shorting"
To enter short trades, use the `enter_short` column.
### Populate exit signals
The `populate_exit_trend` function defines conditions for an exit signal.
The dataframe column `exit_long` is added to the dataframe, and when a value of `1` is in this column, Freqtrade sees an exit signal.
??? Hint "Shorting"
To exit short trades, use the `exit_short` column.
## A simple strategy
Here is a minimal example of a Freqtrade strategy:
```python
from freqtrade.strategy import IStrategy
from pandas import DataFrame
import talib.abstract as ta
class MyStrategy(IStrategy):
# set the initial stoploss to -10%
stoploss = -0.10
# exit profitable positions at any time when the profit is greater than 1%
minimal_roi = {"0": 0.01}
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# generate values for technical analysis indicators
dataframe['rsi'] = ta.RSI(dataframe, timeperiod=14)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# generate entry signals based on indicator values
dataframe.loc[
(dataframe['rsi'] < 30),
'enter_long'] = 1
return dataframe
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# generate exit signals based on indicator values
dataframe.loc[
(dataframe['rsi'] > 70),
'exit_long'] = 1
return dataframe
```
## Making trades
When a signal is found (a `1` in an entry or exit column), Freqtrade will attempt to make an order, i.e. a `trade` or `position`.
Each new trade position takes up a `slot`. Slots represent the maximum number of concurrent new trades that can be opened.
The number of slots is defined by the `max_open_trades` [configuration](configuration.md) option.
However, there can be a range of scenarios where generating a signal does not always create a trade order. These include:
- not enough remaining stake to buy an asset, or funds in your wallet to sell an asset (including any fees)
- not enough remaining free slots for a new trade to be opened (the number of positions you have open equals the `max_open_trades` option)
- there is already an open trade for a pair (Freqtrade cannot stack positions - however it can [adjust existing positions](strategy-callbacks.md#adjust-trade-position))
- if an entry and exit signal is present on the same candle, they are considered as [colliding](strategy-customization.md#colliding-signals), and no order will be raised
- the strategy actively rejects the trade order due to logic you specify by using one of the relevant [entry](strategy-callbacks.md#trade-entry-buy-order-confirmation) or [exit](strategy-callbacks.md#trade-exit-sell-order-confirmation) callbacks
Read through the [strategy customization](strategy-customization.md) documentation for more details.
## Backtesting and forward testing
Strategy development can be a long and frustrating process, as turning our human "gut instincts" into a working computer-controlled
("algo") strategy is not always straightforward.
Therefore a strategy should be tested to verify that it is going to work as intended.
Freqtrade has two testing modes:
- **backtesting**: using historical data that you [download from an exchange](data-download.md), backtesting is a quick way to assess performance of a strategy. However, it can be very easy to distort results so a strategy will look a lot more profitable than it really is. Check the [backtesting documentation](backtesting.md) for more information.
- **dry run**: often referred to as _forward testing_, dry runs use real time data from the exchange. However, any signals that would result in trades are tracked as normal by Freqtrade, but do not have any trades opened on the exchange itself. Forward testing runs in real time, so whilst it takes longer to get results it is a much more reliable indicator of **potential** performance than backtesting.
Dry runs are enabled by setting `dry_run` to true in your [configuration](configuration.md#using-dry-run-mode).
!!! Warning "Backtests can be very inaccurate"
There are many reasons why backtest results may not match reality. Please check the [backtesting assumptions](backtesting.md#assumptions-made-by-backtesting) and [common strategy mistakes](strategy-customization.md#common-mistakes-when-developing-strategies) documentation.
Some websites that list and rank Freqtrade strategies show impressive backtest results. Do not assume these results are achieveable or realistic.
??? Hint "Useful commands"
Freqtrade includes two useful commands to check for basic flaws in strategies: [lookahead-analysis](lookahead-analysis.md) and [recursive-analysis](recursive-analysis.md).
### Assessing backtesting and dry run results
Always dry run your strategy after backtesting it to see if backtesting and dry run results are sufficiently similar.
If there is any significant difference, verify that your entry and exit signals are consistent and appear on the same candles between the two modes. However, there will always be differences between dry runs and backtests:
- Backtesting assumes all orders fill. In dry runs this might not be the case if using limit orders or there is no volume on the exchange.
- Following an entry signal on candle close, backtesting assumes trades enter at the next candle's open price (unless you have custom pricing callbacks in your strategy). In dry runs, there is often a delay between signals and trades opening.
This is because when new candles come in on your main timeframe, e.g. every 5 minutes, it takes time for Freqtrade to analyse all pair dataframes. Therefore, Freqtrade will attempt to open trades a few seconds (ideally a small a delay as possible)
after candle open.
- As entry rates in dry runs might not match backtesting, this means profit calculations will also differ. Therefore, it is normal if ROI, stoploss, trailing stoploss and callback exits are not identical.
- The more computational "lag" you have between new candles coming in and your signals being raised and trades being opened will result in greater price unpredictability. Make sure your computer is powerful enough to process the data for the number
of pairs you have in your pairlist within a reasonable time. Freqtrade will warn you in the logs if there are significant data processing delays.
## Controlling or monitoring a running bot
Once your bot is running in dry or live mode, Freqtrade has five mechanisms to control or monitor a running bot:
- **[FreqUI](freq-ui.md)**: The easiest to get started with, FreqUI is a web interface to see and control current activity of your bot.
- **[Telegram](telegram-usage.md)**: On mobile devices, Telegram integration is available to get alerts about your bot activity and to control certain aspects.
- **[FTUI](https://github.com/freqtrade/ftui)**: FTUI is a terminal (command line) interface to Freqtrade, and allows monitoring of a running bot only.
- **[REST API](rest-api.md)**: The REST API allows programmers to develop their own tools to interact with a Freqtrade bot.
- **[Webhooks](webhook-config.md)**: Freqtrade can send information to other services, e.g. discord, by webhooks.
### Logs
Freqtrade generates extensive debugging logs to help you understand what's happening. Please familiarise yourself with the information and error messages you might see in your bot logs.
## Final Thoughts
Algo trading is difficult, and most public strategies are not good performers due to the time and effort to make a strategy work profitably in multiple scenarios.
Therefore, taking public strategies and using backtests as a way to assess performance is often problematic. However, Freqtrade provides useful ways to help you make decisions and do your due diligence.
There are many different ways to achieve profitability, and there is no one single tip, trick or config option that will fix a poorly performing strategy.
Freqtrade is an open source platform with a large and helpful community - make sure to visit our [discord channel](https://discord.gg/p7nuUNVfP7) to discuss your strategy with others!
As always, only invest what you are willing to lose.
## Conclusion
Developing a strategy in Freqtrade involves defining entry and exit signals based on technical indicators. By following the structure and methods outlined above, you can create and test your own trading strategies.
Common questions and answers are available on our [FAQ](faq.md).
To continue, refer to the more in-depth [Freqtrade strategy customization documentation](strategy-customization.md).
+2 -2
View File
@@ -38,9 +38,9 @@ class AwesomeStrategy(IStrategy):
trade.set_custom_data(key='entry_type', value=trade_entry_type)
return super().bot_loop_start(**kwargs)
def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str,
def adjust_entry_price(self, trade: Trade, order: Order | None, pair: str,
current_time: datetime, proposed_rate: float, current_order_rate: float,
entry_tag: Optional[str], side: str, **kwargs) -> float:
entry_tag: str | None, side: str, **kwargs) -> float:
# Limit orders to use and follow SMA200 as price target for the first 10 minutes since entry trigger for BTC/USDT pair.
if (
pair == 'BTC/USDT'
+26 -25
View File
@@ -90,8 +90,8 @@ Called before entering a trade, makes it possible to manage your position size w
class AwesomeStrategy(IStrategy):
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: Optional[float], max_stake: float,
leverage: float, entry_tag: Optional[str], side: str,
proposed_stake: float, min_stake: float | None, max_stake: float,
leverage: float, entry_tag: str | None, side: str,
**kwargs) -> float:
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
@@ -165,7 +165,8 @@ Called for open trade every iteration (roughly every 5 seconds) until a trade is
The usage of the custom stoploss method must be enabled by setting `use_custom_stoploss=True` on the strategy object.
The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss (before this method is called for the first time for a trade), and is still mandatory.
The stoploss price can only ever move upwards - if the stoploss value returned from `custom_stoploss` would result in a lower stoploss price than was previously set, it will be ignored. The traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss (before this method is called for the first time for a trade), and is still mandatory.
As custom stoploss acts as regular, changing stoploss, it will behave similar to `trailing_stop` - and trades exiting due to this will have the exit_reason of `"trailing_stop_loss"`.
The method must return a stoploss value (float / number) as a percentage of the current price.
E.g. If the `current_rate` is 200 USD, then returning `0.02` will set the stoploss price 2% lower, at 196 USD.
@@ -212,7 +213,7 @@ class AwesomeStrategy(IStrategy):
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
"""
Custom stoploss logic, returning the new distance relative to current_rate (as ratio).
e.g. returning -0.05 would create a stoploss 5% below current_rate.
@@ -250,7 +251,7 @@ class AwesomeStrategy(IStrategy):
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
# Make sure you have the longest interval first - these conditions are evaluated from top to bottom.
if current_time - timedelta(minutes=120) > trade.open_date_utc:
@@ -276,7 +277,7 @@ class AwesomeStrategy(IStrategy):
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
if after_fill:
# After an additional order, start with a stoploss of 10% below the new open rate
@@ -305,7 +306,7 @@ class AwesomeStrategy(IStrategy):
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
if pair in ("ETH/BTC", "XRP/BTC"):
return -0.10
@@ -331,7 +332,7 @@ class AwesomeStrategy(IStrategy):
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
if current_profit < 0.04:
return None # return None to keep using the initial stoploss
@@ -363,7 +364,7 @@ class AwesomeStrategy(IStrategy):
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
# evaluate highest to lowest, so that highest possible stop is used
if current_profit > 0.40:
@@ -394,7 +395,7 @@ class AwesomeStrategy(IStrategy):
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
last_candle = dataframe.iloc[-1].squeeze()
@@ -439,7 +440,7 @@ Stoploss values returned from `custom_stoploss()` must specify a percentage rela
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
# once the profit has risen above 10%, keep the stoploss at 7% above the open price
if current_profit > 0.10:
@@ -482,7 +483,7 @@ The helper function `stoploss_from_absolute()` can be used to convert from an ab
def custom_stoploss(self, pair: str, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
trade_date = timeframe_to_prev_date(self.timeframe, trade.open_date_utc)
candle = dataframe.iloc[-1].squeeze()
@@ -519,8 +520,8 @@ class AwesomeStrategy(IStrategy):
# ... populate_* methods
def custom_entry_price(self, pair: str, trade: Optional[Trade], current_time: datetime, proposed_rate: float,
entry_tag: Optional[str], side: str, **kwargs) -> float:
def custom_entry_price(self, pair: str, trade: Trade | None, current_time: datetime, proposed_rate: float,
entry_tag: str | None, side: str, **kwargs) -> float:
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
timeframe=self.timeframe)
@@ -530,7 +531,7 @@ class AwesomeStrategy(IStrategy):
def custom_exit_price(self, pair: str, trade: Trade,
current_time: datetime, proposed_rate: float,
current_profit: float, exit_tag: Optional[str], **kwargs) -> float:
current_profit: float, exit_tag: str | None, **kwargs) -> float:
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=pair,
timeframe=self.timeframe)
@@ -662,7 +663,7 @@ class AwesomeStrategy(IStrategy):
# ... populate_* methods
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
time_in_force: str, current_time: datetime, entry_tag: str | None,
side: str, **kwargs) -> bool:
"""
Called right before placing a entry order.
@@ -820,8 +821,8 @@ class DigDeeperStrategy(IStrategy):
# This is called when placing the initial order (opening trade)
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: Optional[float], max_stake: float,
leverage: float, entry_tag: Optional[str], side: str,
proposed_stake: float, min_stake: float | None, max_stake: float,
leverage: float, entry_tag: str | None, side: str,
**kwargs) -> float:
# We need to leave most of the funds for possible further DCA orders
@@ -830,11 +831,11 @@ class DigDeeperStrategy(IStrategy):
def adjust_trade_position(self, trade: Trade, current_time: datetime,
current_rate: float, current_profit: float,
min_stake: Optional[float], max_stake: float,
min_stake: float | None, max_stake: float,
current_entry_rate: float, current_exit_rate: float,
current_entry_profit: float, current_exit_profit: float,
**kwargs
) -> Union[Optional[float], Tuple[Optional[float], Optional[str]]]:
) -> float | None | tuple[float | None, str | None]:
"""
Custom trade adjustment logic, returning the stake amount that a trade should be
increased or decreased.
@@ -890,7 +891,7 @@ class DigDeeperStrategy(IStrategy):
# Hope you have a deep wallet!
try:
# This returns first order stake size
stake_amount = filled_entries[0].stake_amount
stake_amount = filled_entries[0].stake_amount_filled
# This then calculates current safety order size
stake_amount = stake_amount * (1 + (count_of_entries * 0.25))
return stake_amount, "1/3rd_increase"
@@ -946,9 +947,9 @@ class AwesomeStrategy(IStrategy):
# ... populate_* methods
def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str,
def adjust_entry_price(self, trade: Trade, order: Order | None, pair: str,
current_time: datetime, proposed_rate: float, current_order_rate: float,
entry_tag: Optional[str], side: str, **kwargs) -> float:
entry_tag: str | None, side: str, **kwargs) -> float:
"""
Entry price re-adjustment logic, returning the user desired limit price.
This only executes when a order was already placed, still open (unfilled fully or partially)
@@ -975,7 +976,7 @@ class AwesomeStrategy(IStrategy):
pair == "BTC/USDT"
and entry_tag == "long_sma200"
and side == "long"
and (current_time - timedelta(minutes=10)) > trade.open_date_utc
and (current_time - timedelta(minutes=10)) <= trade.open_date_utc
):
# just cancel the order if it has been filled more than half of the amount
if order.filled > order.remaining:
@@ -1003,7 +1004,7 @@ For markets / exchanges that don't support leverage, this method is ignored.
class AwesomeStrategy(IStrategy):
def leverage(self, pair: str, current_time: datetime, current_rate: float,
proposed_leverage: float, max_leverage: float, entry_tag: Optional[str], side: str,
proposed_leverage: float, max_leverage: float, entry_tag: str | None, side: str,
**kwargs) -> float:
"""
Customize leverage for each new trade. This method is only called in futures mode.
+237 -131
View File
@@ -2,52 +2,93 @@
This page explains how to customize your strategies, add new indicators and set up trading rules.
Please familiarize yourself with [Freqtrade basics](bot-basics.md) first, which provides overall info on how the bot operates.
If you haven't already, please familiarize yourself with:
- the [Freqtrade strategy 101](strategy-101.md), which provides a quick start to strategy development
- the [Freqtrade bot basics](bot-basics.md), which provides overall info on how the bot operates
## Develop your own strategy
The bot includes a default strategy file.
Also, several other strategies are available in the [strategy repository](https://github.com/freqtrade/freqtrade-strategies).
You will however most likely have your own idea for a strategy.
This document intends to help you convert your strategy idea into your own strategy.
To get started, use `freqtrade new-strategy --strategy AwesomeStrategy` (you can obviously use your own naming for your strategy).
This will create a new strategy file from a template, which will be located under `user_data/strategies/AwesomeStrategy.py`.
This document intends to help you convert your ideas into a working strategy.
### Generating a strategy template
To get started, you can use the command:
```bash
freqtrade new-strategy --strategy AwesomeStrategy
```
This will create a new strategy called `AwesomeStrategy` from a template, which will be located using the filename `user_data/strategies/AwesomeStrategy.py`.
!!! Note
This is just a template file, which will most likely not be profitable out of the box.
There is a difference between the *name* of the strategy and the filename. In most commands, Freqtrade uses the *name* of the strategy, *not the filename*.
!!! Note
The `new-strategy` command generates starting examples which will not be profitable out of the box.
??? Hint "Different template levels"
`freqtrade new-strategy` has an additional parameter, `--template`, which controls the amount of pre-build information you get in the created strategy. Use `--template minimal` to get an empty strategy without any indicator examples, or `--template advanced` to get a template with most callbacks defined.
`freqtrade new-strategy` has an additional parameter, `--template`, which controls the amount of pre-build information you get in the created strategy. Use `--template minimal` to get an empty strategy without any indicator examples, or `--template advanced` to get a template with more complicated features defined.
### Anatomy of a strategy
A strategy file contains all the information needed to build a good strategy:
A strategy file contains all the information needed to build the strategy logic:
- Candle data in OHLCV format
- Indicators
- Entry strategy rules
- Exit strategy rules
- Minimal ROI recommended
- Stoploss strongly recommended
- Entry logic
- Signals
- Exit logic
- Signals
- Minimal ROI
- Callbacks ("custom functions")
- Stoploss
- Fixed/absolute
- Trailing
- Callbacks ("custom functions")
- Pricing [optional]
- Position adjustment [optional]
The bot also include a sample strategy called `SampleStrategy` you can update: `user_data/strategies/sample_strategy.py`.
You can test it with the parameter: `--strategy SampleStrategy`
The bot includes a sample strategy called `SampleStrategy` that you can use as a basis: `user_data/strategies/sample_strategy.py`.
You can test it with the parameter: `--strategy SampleStrategy`. Remember that you use the strategy class name, not the filename.
Additionally, there is an attribute called `INTERFACE_VERSION`, which defines the version of the strategy interface the bot should use.
The current version is 3 - which is also the default when it's not set explicitly in the strategy.
Future versions will require this to be set.
You may see older strategies set to interface version 2, and these will need to be updated to v3 terminology as future versions will require this to be set.
Starting the bot in dry or live mode is accomplished using the `trade` command:
```bash
freqtrade trade --strategy AwesomeStrategy
```
### Bot modes
Freqtrade strategies can be processed by the Freqtrade bot in 5 main modes:
- backtesting
- hyperopting
- dry ("forward testing")
- live
- FreqAI (not covered here)
Check the [configuration documentation](configuration.md) about how to set the bot to dry or live mode.
**Always use dry mode when testing as this gives you an idea of how your strategy will work in reality without risking capital.**
## Diving in deeper
**For the following section we will use the [user_data/strategies/sample_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_strategy.py)
file as reference.**
!!! Note "Strategies and Backtesting"
To avoid problems and unexpected differences between Backtesting and dry/live modes, please be aware
To avoid problems and unexpected differences between backtesting and dry/live modes, please be aware
that during backtesting the full time range is passed to the `populate_*()` methods at once.
It is therefore best to use vectorized operations (across the whole dataframe, not loops) and
avoid index referencing (`df.iloc[-1]`), but instead use `df.shift()` to get to the previous candle.
@@ -57,14 +98,22 @@ file as reference.**
needs to take care to avoid having the strategy utilize data from the future.
Some common patterns for this are listed in the [Common Mistakes](#common-mistakes-when-developing-strategies) section of this document.
??? Hint "Lookahead and recursive analysis"
Freqtrade includes two helpful commands to help assess common lookahead (using future data) and
recursive bias (variance in indicator values) issues. Before running a strategy in dry or live more,
you should always use these commands first. Please check the relevant documentation for
[lookahead](lookahead-analysis.md) and [recursive](recursive-analysis.md) analysis.
### Dataframe
Freqtrade uses [pandas](https://pandas.pydata.org/) to store/provide the candlestick (OHLCV) data.
Pandas is a great library developed for processing large amounts of data.
Pandas is a great library developed for processing large amounts of data in tabular format.
Each row in a dataframe corresponds to one candle on a chart, with the latest candle always being the last in the dataframe (sorted by date).
Each row in a dataframe corresponds to one candle on a chart, with the latest complete candle always being the last in the dataframe (sorted by date).
``` output
If we were to look at the first few rows of the main dataframe using the pandas `head()` function, we would see:
```output
> dataframe.head()
date open high low close volume
0 2021-11-09 23:25:00+00:00 67279.67 67321.84 67255.01 67300.97 44.62253
@@ -74,20 +123,16 @@ Each row in a dataframe corresponds to one candle on a chart, with the latest ca
4 2021-11-09 23:45:00+00:00 67160.48 67160.48 66901.26 66943.37 111.39292
```
Pandas provides fast ways to calculate metrics. To benefit from this speed, it's advised to not use loops, but use vectorized methods instead.
Vectorized operations perform calculations across the whole range of data and are therefore, compared to looping through each row, a lot faster when calculating indicators.
As a dataframe is a table, simple python comparisons like the following will not work
A dataframe is a table where columns are not single values, but a series of data values. As such, simple python comparisons like the following will not work:
``` python
if dataframe['rsi'] > 30:
dataframe['enter_long'] = 1
```
The above section will fail with `The truth value of a Series is ambiguous. [...]`.
The above section will fail with `The truth value of a Series is ambiguous [...]`.
This must instead be written in a pandas-compatible way, so the operation is performed across the whole dataframe.
This must instead be written in a pandas-compatible way, so the operation is performed across the whole dataframe, i.e. `vectorisation`.
``` python
dataframe.loc[
@@ -97,13 +142,38 @@ This must instead be written in a pandas-compatible way, so the operation is per
With this section, you have a new column in your dataframe, which has `1` assigned whenever RSI is above 30.
Freqtrade uses this new column as an entry signal, where it is assumed that a trade will subsequently open on the next open candle.
Pandas provides fast ways to calculate metrics, i.e. "vectorisation". To benefit from this speed, it is advised to not use loops, but use vectorized methods instead.
Vectorized operations perform calculations across the whole range of data and are therefore, compared to looping through each row, a lot faster when calculating indicators.
??? Hint "Signals vs Trades"
- Signals are generated from indicators at candle close, and are intentions to enter a trade.
- Trades are orders that are executed (on the exchange in live mode) where a trade will then open as close to next candle open as possible.
!!! Warning "Trade order assumptions"
In backtesting, signals are generated on candle close. Trades are then initiated immeditely on next candle open.
In dry and live, this may be delayed due to all pair dataframes needing to be analysed first, then trade processing
for each of those pairs happens. This means that in dry/live you need to be mindful of having as low a computation
delay as possible, usually by running a low number of pairs and having a CPU with a good clock speed.
#### Why can't I see "real time" candle data?
Freqtrade does not store incomplete/unfinished candles in the dataframe.
The use of incomplete data for making strategy decisions is called "repainting" and you might see other platforms allow this.
Freqtrade does not. Only complete/finished candle data is available in the dataframe.
### Customize Indicators
Buy and sell signals need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file.
Entry and exit signals need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file.
You should only add the indicators used in either `populate_entry_trend()`, `populate_exit_trend()`, or to populate another indicator, otherwise performance may suffer.
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected.
It's important to always return the dataframe from these three functions without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected.
Sample:
@@ -124,7 +194,7 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame
stoch = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch['fastd']
dataframe['fastk'] = stoch['fastk']
dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband']
dataframe['bb_lower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband']
dataframe['sma'] = ta.SMA(dataframe, timeperiod=40)
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
dataframe['mfi'] = ta.MFI(dataframe)
@@ -145,6 +215,8 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame
dataframe['plus_di'] = ta.PLUS_DI(dataframe)
dataframe['minus_dm'] = ta.MINUS_DM(dataframe)
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# remember to always return the dataframe
return dataframe
```
@@ -164,11 +236,13 @@ Additional technical libraries can be installed as necessary, or custom indicato
### Strategy startup period
Most indicators have an instable startup period, in which they are either not available (NaN), or the calculation is incorrect. This can lead to inconsistencies, since Freqtrade does not know how long this instable period should be.
Some indicators have an unstable startup period in which there isn't enough candle data to calculate any values (NaN), or the calculation is incorrect. This can lead to inconsistencies, since Freqtrade does not know how long this unstable period is and uses whatever indicator values are in the dataframe.
To account for this, the strategy can be assigned the `startup_candle_count` attribute.
This should be set to the maximum number of candles that the strategy requires to calculate stable indicators. In the case where a user includes higher timeframes with informative pairs, the `startup_candle_count` does not necessarily change. The value is the maximum period (in candles) that any of the informatives timeframes need to compute stable indicators.
You can use [recursive-analysis](recursive-analysis.md) to check and find the correct `startup_candle_count` to be used.
You can use [recursive-analysis](recursive-analysis.md) to check and find the correct `startup_candle_count` to be used. When recursive analysis shows a variance of 0%, then you can be sure that you have enough startup candle data.
In this example strategy, this should be set to 400 (`startup_candle_count = 400`), since the minimum needed history for ema100 calculation to make sure the value is correct is 400 candles.
@@ -195,19 +269,22 @@ Let's try to backtest 1 month (January 2019) of 5m candles using an example stra
freqtrade backtesting --timerange 20190101-20190201 --timeframe 5m
```
Assuming `startup_candle_count` is set to 400, backtesting knows it needs 400 candles to generate valid buy signals. It will load data from `20190101 - (400 * 5m)` - which is ~2018-12-30 11:40:00.
If this data is available, indicators will be calculated with this extended timerange. The instable startup period (up to 2019-01-01 00:00:00) will then be removed before starting backtesting.
Assuming `startup_candle_count` is set to 400, backtesting knows it needs 400 candles to generate valid entry signals. It will load data from `20190101 - (400 * 5m)` - which is ~2018-12-30 11:40:00.
!!! Note
If data for the startup period is not available, then the timerange will be adjusted to account for this startup period - so Backtesting would start at 2019-01-02 09:20:00.
If this data is available, indicators will be calculated with this extended timerange. The unstable startup period (up to 2019-01-01 00:00:00) will then be removed before backtesting is carried out.
!!! Note "Unavailable startup candle data"
If data for the startup period is not available, then the timerange will be adjusted to account for this startup period. In our example, backtesting would then start from 2019-01-02 09:20:00.
### Entry signal rules
Edit the method `populate_entry_trend()` in your strategy file to update your entry strategy.
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected.
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected. The strategy may then produce invalid values, or cease to work entirely.
This method will also define a new column, `"enter_long"` (`"enter_short"` for shorts), which needs to contain 1 for entries, and 0 for "no action". `enter_long` is a mandatory column that must be set even if the strategy is shorting only.
This method will also define a new column, `"enter_long"` (`"enter_short"` for shorts), which needs to contain `1` for entries, and `0` for "no action". `enter_long` is a mandatory column that must be set even if the strategy is shorting only.
You can name your entry signals by using the `"enter_tag"` column, which can help debug and assess your strategy later.
Sample from `user_data/strategies/sample_strategy.py`:
@@ -232,12 +309,15 @@ def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFram
```
??? Note "Enter short trades"
Short-entries can be created by setting `enter_short` (corresponds to `enter_long` for long trades).
Short entries can be created by setting `enter_short` (corresponds to `enter_long` for long trades).
The `enter_tag` column remains identical.
Short-trades need to be supported by your exchange and market configuration!
Please make sure to set [`can_short`]() appropriately on your strategy if you intend to short.
Shorting needs to be supported by your exchange and market configuration!
Also, make sure you set [`can_short`](#can-short) appropriately on your strategy if you intend to short.
```python
# allow both long and short trades
can_short = True
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
@@ -261,17 +341,21 @@ def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFram
```
!!! Note
Buying requires sellers to buy from - therefore volume needs to be > 0 (`dataframe['volume'] > 0`) to make sure that the bot does not buy/sell in no-activity periods.
Buying requires sellers to buy from. Therefore volume needs to be > 0 (`dataframe['volume'] > 0`) to make sure that the bot does not buy/sell in no-activity periods.
### Exit signal rules
Edit the method `populate_exit_trend()` into your strategy file to update your exit strategy.
The exit-signal can be suppressed by setting `use_exit_signal` to false in the configuration or strategy.
`use_exit_signal` will not influence [signal collision rules](#colliding-signals) - which will still apply and can prevent entries.
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected.
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected. The strategy may then produce invalid values, or cease to work entirely.
This method will also define a new column, `"exit_long"` (`"exit_short"` for shorts), which needs to contain 1 for exits, and 0 for "no action".
This method will also define a new column, `"exit_long"` (`"exit_short"` for shorts), which needs to contain `1` for exits, and `0` for "no action".
You can name your exit signals by using the `"exit_tag"` column, which can help debug and assess your strategy later.
Sample from `user_data/strategies/sample_strategy.py`:
@@ -295,11 +379,15 @@ def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame
```
??? Note "Exit short trades"
Short-exits can be created by setting `exit_short` (corresponds to `exit_long`).
Short exits can be created by setting `exit_short` (corresponds to `exit_long`).
The `exit_tag` column remains identical.
Short-trades need to be supported by your exchange and market configuration!
Shorting needs to be supported by your exchange and market configuration!
Also, make sure you set [`can_short`](#can-short) appropriately on your strategy if you intend to short.
```python
# allow both long and short trades
can_short = True
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
@@ -322,9 +410,9 @@ def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame
### Minimal ROI
This dict defines the minimal Return On Investment (ROI) a trade should reach before exiting, independent from the exit signal.
The `minimal_roi` strategy variable defines the minimal Return On Investment (ROI) a trade should reach before exiting, independent from the exit signal.
It is of the following format, with the dict key (left side of the colon) being the minutes passed since the trade opened, and the value (right side of the colon) being the percentage.
It is of the following format, i.e. a python `dict`, with the dict key (left side of the colon) being the minutes passed since the trade opened, and the value (right side of the colon) being the percentage.
```python
minimal_roi = {
@@ -344,14 +432,19 @@ The above configuration would therefore mean:
The calculation does include fees.
#### Disabling minimal ROI
To disable ROI completely, set it to an empty dictionary:
```python
minimal_roi = {}
```
#### Using calculations in minimal ROI
To use times based on candle duration (timeframe), the following snippet can be handy.
This will allow you to change the timeframe for the strategy, and ROI times will still be set as candles (e.g. after 3 candles ...)
This will allow you to change the timeframe for the strategy, but the minimal ROI times will still be set as candles, e.g. after 3 candles.
``` python
from freqtrade.exchange import timeframe_to_minutes
@@ -368,9 +461,9 @@ class AwesomeStrategy(IStrategy):
```
??? info "Orders that don't fill immediately"
`minimal_roi` will take the `trade.open_date` as reference, which is the time the trade was initialized / the first order for this trade was placed.
This will also hold true for limit orders that don't fill immediately (usually in combination with "off-spot" prices through `custom_entry_price()`), as well as for cases where the initial order is replaced through `adjust_entry_price()`.
The time used will still be from the initial `trade.open_date` (when the initial order was first placed), not from the newly placed order date.
`minimal_roi` will take the `trade.open_date` as reference, which is the time the trade was initialized, i.e. when the first order for this trade was placed.
This will also hold true for limit orders that don't fill immediately (usually in combination with "off-spot" prices through `custom_entry_price()`), as well as for cases where the initial order price is replaced through `adjust_entry_price()`.
The time used will still be from the initial `trade.open_date` (when the initial order was first placed), not from the newly placed or adjusted order date.
### Stoploss
@@ -386,35 +479,44 @@ For the full documentation on stoploss features, look at the dedicated [stoploss
### Timeframe
This is the set of candles the bot should download and use for the analysis.
This is the periodicity of candles the bot should use in the strategy.
Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported by your exchange should work.
Please note that the same entry/exit signals may work well with one timeframe, but not with the others.
Please note that the same entry/exit signals may work well with one timeframe, but not with others.
This setting is accessible within the strategy methods as the `self.timeframe` attribute.
### Can short
To use short signals in futures markets, you will have to let us know to do so by setting `can_short=True`.
To use short signals in futures markets, you will have to set `can_short = True`.
Strategies which enable this will fail to load on spot markets.
Disabling of this will have short signals ignored (also in futures markets).
If you have `1` values in the `enter_short` column to raise short signals, setting `can_short = False` (which is the default) will mean that these short signals are ignored, even if you have specified futures markets in your configuration.
### Metadata dict
The metadata-dict (available for `populate_entry_trend`, `populate_exit_trend`, `populate_indicators`) contains additional information.
Currently this is `pair`, which can be accessed using `metadata['pair']` - and will return a pair in the format `XRP/BTC`.
The `metadata` dict (available for `populate_entry_trend`, `populate_exit_trend`, `populate_indicators`) contains additional information.
Currently this is `pair`, which can be accessed using `metadata['pair']`, and will return a pair in the format `XRP/BTC` (or `XRP/BTC:BTC` for futures markets).
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-persistent) section.
The metadata dict should not be modified and does not persist information across multiple functions in your strategy.
Instead, please check the [Storing information](strategy-advanced.md#storing-information-persistent) section.
--8<-- "includes/strategy-imports.md"
## Strategy file loading
By default, freqtrade will attempt to load strategies from all `.py` files within `user_data/strategies`.
By default, freqtrade will attempt to load strategies from all `.py` files within the `userdir` (default `user_data/strategies`).
Assuming your strategy is called `AwesomeStrategy`, stored in the file `user_data/strategies/AwesomeStrategy.py`, then you can start freqtrade with `freqtrade trade --strategy AwesomeStrategy`.
Note that we're using the class-name, not the file name.
Assuming your strategy is called `AwesomeStrategy`, stored in the file `user_data/strategies/AwesomeStrategy.py`, then you can start freqtrade in dry (or live, depending on your configuration) mode with:
```bash
freqtrade trade --strategy AwesomeStrategy`
```
Note that we're using the class name, not the file name.
You can use `freqtrade list-strategies` to see a list of all strategies Freqtrade is able to load (all strategies in the correct folder).
It will also include a "status" field, highlighting potential problems.
@@ -426,9 +528,11 @@ It will also include a "status" field, highlighting potential problems.
### Get data for non-tradeable pairs
Data for additional, informative pairs (reference pairs) can be beneficial for some strategies.
Data for additional, informative pairs (reference pairs) can be beneficial for some strategies to see data on a wider timeframe.
OHLCV data for these pairs will be downloaded as part of the regular whitelist refresh process and is available via `DataProvider` just as other pairs (see below).
These parts will **not** be traded unless they are also specified in the pair whitelist, or have been selected by Dynamic Whitelisting.
These pairs will **not** be traded unless they are also specified in the pair whitelist, or have been selected by Dynamic Whitelisting, e.g. `VolumePairlist`.
The pairs need to be specified as tuples in the format `("pair", "timeframe")`, with pair as the first and timeframe as the second argument.
@@ -468,18 +572,24 @@ A full sample can be found [in the DataProvider section](#complete-data-provider
### Informative pairs decorator (`@informative()`)
In most common case it is possible to easily define informative pairs by using a decorator. All decorated `populate_indicators_*` methods run in isolation,
not having access to data from other informative pairs, in the end all informative dataframes are merged and passed to main `populate_indicators()` method.
When hyperopting, use of hyperoptable parameter `.value` attribute is not supported. Please use `.range` attribute. See [optimizing an indicator parameter](hyperopt.md#optimizing-an-indicator-parameter)
for more information.
To easily define informative pairs, use the `@informative` decorator. All decorated `populate_indicators_*` methods run in isolation,
and do not have access to data from other informative pairs. However, all informative dataframes for each pair are merged and passed to main `populate_indicators()` method.
!!! Note
Do not use the `@informative` decorator if you need to use data from one informative pair when generating another informative pair. Instead, define informative pairs manually as described [in the DataProvider section](#complete-data-provider-sample).
When hyperopting, use of the hyperoptable parameter `.value` attribute is not supported. Please use the `.range` attribute. See [optimizing an indicator parameter](hyperopt.md#optimizing-an-indicator-parameter) for more information.
??? info "Full documentation"
``` python
def informative(timeframe: str, asset: str = '',
fmt: Optional[Union[str, Callable[[KwArg(str)], str]]] = None,
*,
candle_type: Optional[CandleType] = None,
ffill: bool = True) -> Callable[[PopulateIndicators], PopulateIndicators]:
def informative(
timeframe: str,
asset: str = "",
fmt: str | Callable[[Any], str] | None = None,
*,
candle_type: CandleType | str | None = None,
ffill: bool = True,
) -> Callable[[PopulateIndicators], PopulateIndicators]:
"""
A decorator for populate_indicators_Nn(self, dataframe, metadata), allowing these functions to
define informative indicators.
@@ -568,10 +678,6 @@ for more information.
```
!!! Note
Do not use `@informative` decorator if you need to use data of one informative pair when generating another informative pair. Instead, define informative pairs
manually as described [in the DataProvider section](#complete-data-provider-sample).
!!! Note
Use string formatting when accessing informative dataframes of other pairs. This will allow easily changing stake currency in config without having to adjust strategy code.
@@ -592,18 +698,15 @@ for more information.
Alternatively column renaming may be used to remove stake currency from column names: `@informative('1h', 'BTC/{stake}', fmt='{base}_{column}_{timeframe}')`.
!!! Warning "Duplicate method names"
Methods tagged with `@informative()` decorator must always have unique names! Re-using same name (for example when copy-pasting already defined informative method)
will overwrite previously defined method and not produce any errors due to limitations of Python programming language. In such cases you will find that indicators
created in earlier-defined methods are not available in the dataframe. Carefully review method names and make sure they are unique!
Methods tagged with the `@informative()` decorator must always have unique names! Reusing the same name (for example when copy-pasting already defined informative methods) will overwrite previously defined methods and not produce any errors due to limitations of Python programming language. In such cases you will find that indicators created in methods higher up in the strategy file are not available in the dataframe. Carefully review method names and make sure they are unique!
### *merge_informative_pair()*
This method helps you merge an informative pair to a regular dataframe without lookahead bias.
It's there to help you merge the dataframe in a safe and consistent way.
This method helps you merge an informative pair to the regular main dataframe safely and consistently, without lookahead bias.
Options:
- Rename the columns for you to create unique columns
- Rename the columns to create unique columns
- Merge the dataframe without lookahead bias
- Forward-fill (optional)
@@ -654,20 +757,20 @@ All columns of the informative dataframe will be available on the returning data
```
!!! Warning "Informative timeframe < timeframe"
Using informative timeframes smaller than the dataframe timeframe is not recommended with this method, as it will not use any of the additional information this would provide.
To use the more detailed information properly, more advanced methods should be applied (which are out of scope for freqtrade documentation, as it'll depend on the respective need).
Using informative timeframes smaller than the main dataframe timeframe is not recommended with this method, as it will not use any of the additional information this would provide.
To use the more detailed information properly, more advanced methods should be applied (which are out of scope for this documentation).
## Additional data (DataProvider)
The strategy provides access to the `DataProvider`. This allows you to get additional data to use in your strategy.
All methods return `None` in case of failure (do not raise an exception).
All methods return `None` in case of failure, i.e. failures do not raise an exception.
Please always check the mode of operation to select the correct method to get data (samples see below).
Please always check the mode of operation to select the correct method to get data (see below for examples).
!!! Warning "Hyperopt"
Dataprovider is available during hyperopt, however it can only be used in `populate_indicators()` within a strategy.
It is not available in `populate_buy()` and `populate_sell()` methods, nor in `populate_indicators()`, if this method located in the hyperopt file.
!!! Warning "Hyperopt Limitations"
The DataProvider is available during hyperopt, however it can only be used in `populate_indicators()` **within a strategy**, not within a hyperopt class file.
It is also not available in `populate_entry_trend()` and `populate_exit_trend()` methods.
### Possible options for DataProvider
@@ -693,31 +796,31 @@ for pair, timeframe in self.dp.available_pairs:
### *current_whitelist()*
Imagine you've developed a strategy that trades the `5m` timeframe using signals generated from a `1d` timeframe on the top 10 volume pairs by volume.
Imagine you've developed a strategy that trades the `5m` timeframe using signals generated from a `1d` timeframe on the top 10 exchange pairs by volume.
The strategy might look something like this:
The strategy logic might look something like this:
*Scan through the top 10 pairs by volume using the `VolumePairList` every 5 minutes and use a 14 day RSI to buy and sell.*
*Scan through the top 10 pairs by volume using the `VolumePairList` every 5 minutes and use a 14 day RSI to enter and exit.*
Due to the limited available data, it's very difficult to resample `5m` candles into daily candles for use in a 14 day RSI. Most exchanges limit us to just 500-1000 candles which effectively gives us around 1.74 daily candles. We need 14 days at least!
Due to the limited available data, it's very difficult to resample `5m` candles into daily candles for use in a 14 day RSI. Most exchanges limit users to just 500-1000 candles which effectively gives us around 1.74 daily candles. We need 14 days at least!
Since we can't resample the data we will have to use an informative pair; and since the whitelist will be dynamic we don't know which pair(s) to use.
Since we can't resample the data we will have to use an informative pair, and since the whitelist will be dynamic we don't know which pair(s) to use! We have a problem!
This is where calling `self.dp.current_whitelist()` comes in handy.
This is where calling `self.dp.current_whitelist()` comes in handy to retrieve only those pairs in the whitelist.
```python
def informative_pairs(self):
# get access to all pairs available in whitelist.
pairs = self.dp.current_whitelist()
# Assign tf to each pair so they can be downloaded and cached for strategy.
# Assign timeframe to each pair so they can be downloaded and cached for strategy.
informative_pairs = [(pair, '1d') for pair in pairs]
return informative_pairs
```
??? Note "Plotting with current_whitelist"
Current whitelist is not supported for `plot-dataframe`, as this command is usually used by providing an explicit pairlist - and would therefore make the return values of this method misleading.
It's also not supported for freqUI visualization in [webserver mode](utils.md#webserver-mode) - as the configuration for webserver mode doesn't require a pairlist to be set.
Current whitelist is not supported for `plot-dataframe`, as this command is usually used by providing an explicit pairlist and would therefore make the return values of this method misleading.
It's also not supported for FreqUI visualization in [webserver mode](utils.md#webserver-mode), as the configuration for webserver mode doesn't require a pairlist to be set.
### *get_pair_dataframe(pair, timeframe)*
@@ -758,7 +861,7 @@ if self.dp.runmode.value in ('live', 'dry_run'):
dataframe['best_ask'] = ob['asks'][0][0]
```
The orderbook structure is aligned with the order structure from [ccxt](https://github.com/ccxt/ccxt/wiki/Manual#order-book-structure), so the result will look as follows:
The orderbook structure is aligned with the order structure from [ccxt](https://github.com/ccxt/ccxt/wiki/Manual#order-book-structure), so the result will be formatted as follows:
``` js
{
@@ -776,7 +879,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.
Therefore, using `ob['bids'][0][0]` as demonstrated above will use 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 up-to-date values.
@@ -793,12 +896,12 @@ if self.dp.runmode.value in ('live', 'dry_run'):
!!! Warning
Although the ticker data structure is a part of the ccxt Unified Interface, the values returned by this method can
vary for different exchanges. For instance, many exchanges do not return `vwap` values, some exchanges
does not always fills in the `last` field (so it can be None), etc. So you need to carefully verify the ticker
vary for different exchanges. For instance, many exchanges do not return `vwap` values, and some exchanges
do not always fill in the `last` field (so it can be None), etc. So you need to carefully verify the ticker
data returned from the exchange and add appropriate error handling / defaults.
!!! Warning "Warning about backtesting"
This method will always return up-to-date values - so usage during backtesting / hyperopt without runmode checks will lead to wrong results.
This method will always return up-to-date / real-time values. As such, usage during backtesting / hyperopt without runmode checks will lead to wrong results, e.g. your whole dataframe will contain the same single value in all rows.
### Send Notification
@@ -817,7 +920,7 @@ Notifications will only be sent in trading modes (Live/Dry-run) - so this method
!!! Warning "Spamming"
You can spam yourself pretty good by setting `always_send=True` in this method. Use this with great care and only in conditions you know will not happen throughout a candle to avoid a message every 5 seconds.
### Complete Data-provider sample
### Complete DataProvider sample
```python
from freqtrade.strategy import IStrategy, merge_informative_pair
@@ -884,14 +987,14 @@ class SampleStrategy(IStrategy):
## Additional data (Wallets)
The strategy provides access to the `wallets` object. This contains the current balances on the exchange.
The strategy provides access to the `wallets` object. This contains the current balances of your wallets/accounts on the exchange.
!!! Note "Backtesting / Hyperopt"
Wallets behaves differently depending on the function it's called.
Wallets behaves differently depending on the function from which it is called.
Within `populate_*()` methods, it'll return the full wallet as configured.
Within [callbacks](strategy-callbacks.md), you'll get the wallet state corresponding to the actual simulated wallet at that point in the simulation process.
Please always check if `wallets` is available to avoid failures during backtesting.
Always check if `wallets` is available to avoid failures during backtesting.
``` python
if self.wallets:
@@ -910,15 +1013,15 @@ if self.wallets:
## Additional data (Trades)
A history of Trades can be retrieved in the strategy by querying the database.
A history of trades can be retrieved in the strategy by querying the database.
At the top of the file, import Trade.
At the top of the file, import the required object:
```python
from freqtrade.persistence import Trade
```
The following example queries for the current pair and trades from today, however other filters can easily be added.
The following example queries trades from today for the current pair (`metadata['pair']`). Other filters can easily be added.
``` python
trades = Trade.get_trades_proxy(pair=metadata['pair'],
@@ -936,7 +1039,9 @@ For a full list of available methods, please consult the [Trade object](trade-ob
## Prevent trades from happening for a specific pair
Freqtrade locks pairs automatically for the current candle (until that candle is over) when a pair is sold, preventing an immediate re-buy of that pair.
Freqtrade locks pairs automatically for the current candle (until that candle is over) when a pair exits, preventing an immediate re-entry of that pair.
This is to prevent "waterfalls" of many and frequent trades within a single candle.
Locked pairs will show the message `Pair <pair> is currently locked.`.
@@ -947,7 +1052,7 @@ Sometimes it may be desired to lock a pair after certain events happen (e.g. mul
Freqtrade has an easy method to do this from within the strategy, by calling `self.lock_pair(pair, until, [reason])`.
`until` must be a datetime object in the future, after which trading will be re-enabled for that pair, while `reason` is an optional string detailing why the pair was locked.
Locks can also be lifted manually, by calling `self.unlock_pair(pair)` or `self.unlock_reason(<reason>)` - providing reason the pair was locked with.
Locks can also be lifted manually, by calling `self.unlock_pair(pair)` or `self.unlock_reason(<reason>)`, providing the reason the pair was unlocked.
`self.unlock_reason(<reason>)` will unlock all pairs currently locked with the provided reason.
To verify if a pair is currently locked, use `self.is_pair_locked(pair)`.
@@ -956,7 +1061,7 @@ To verify if a pair is currently locked, use `self.is_pair_locked(pair)`.
Locked pairs will always be rounded up to the next candle. So assuming a `5m` timeframe, a lock with `until` set to 10:18 will lock the pair until the candle from 10:15-10:20 will be finished.
!!! Warning
Manually locking pairs is not available during backtesting, only locks via Protections are allowed.
Manually locking pairs is not available during backtesting. Only locks via Protections are allowed.
#### Pair locking example
@@ -966,7 +1071,7 @@ from datetime import timedelta, datetime, timezone
# Put the above lines a the top of the strategy file, next to all the other imports
# --------
# Within populate indicators (or populate_buy):
# Within populate indicators (or populate_entry_trend):
if self.config['runmode'].value in ('live', 'dry_run'):
# fetch closed trades for the last 2 days
trades = Trade.get_trades_proxy(
@@ -979,9 +1084,9 @@ if self.config['runmode'].value in ('live', 'dry_run'):
self.lock_pair(metadata['pair'], until=datetime.now(timezone.utc) + timedelta(hours=12))
```
## Print created dataframe
## Print the main dataframe
To inspect the created dataframe, you can issue a print-statement in either `populate_entry_trend()` or `populate_exit_trend()`.
To inspect the current main dataframe, you can issue a print-statement in either `populate_entry_trend()` or `populate_exit_trend()`.
You may also want to print the pair so it's clear what data is currently shown.
``` python
@@ -1001,29 +1106,30 @@ def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFram
return dataframe
```
Printing more than a few rows is also possible (simply use `print(dataframe)` instead of `print(dataframe.tail())`), however not recommended, as that will be very verbose (~500 lines per pair every 5 seconds).
Printing more than a few rows is also possible by using `print(dataframe)` instead of `print(dataframe.tail())`. However this is not recommended, as can results in a lot of output (~500 lines per pair every 5 seconds).
## Common mistakes when developing strategies
### Peeking into the future while backtesting
### Looking into the future while backtesting
Backtesting analyzes the whole time-range at once for performance reasons. Because of this, strategy authors need to make sure that strategies do not look-ahead into the future.
This is a common pain-point, which can cause huge differences between backtesting and dry/live run methods, since they all use data which is not available during dry/live runs, so these strategies will perform well during backtesting, but will fail / perform badly in real conditions.
Backtesting analyzes the whole dataframe timerange at once for performance reasons. Because of this, strategy authors need to make sure that strategies do not lookahead into the future, i.e. using data that would not be available in dry or live mode.
The following lists some common patterns which should be avoided to prevent frustration:
This is a common pain-point, which can cause huge differences between backtesting and dry/live run methods. Strategies that look into the future will perform well during backtesting, often with incredible profits or winrates, but will fail or perform badly in real conditions.
The following list contains some common patterns which should be avoided to prevent frustration:
- don't use `shift(-1)` or other negative values. This uses data from the future in backtesting, which is not available in dry or live modes.
- don't use `.iloc[-1]` or any other absolute position in the dataframe within `populate_` functions, as this will be different between dry-run and backtesting. Absolute `iloc` indexing is safe to use in callbacks however - see [Strategy Callbacks](strategy-callbacks.md).
- don't use `dataframe['volume'].mean()`. This uses the full DataFrame for backtesting, including data from the future. Use `dataframe['volume'].rolling(<window>).mean()` instead
- don't use `.resample('1h')`. This uses the left border of the interval, so moves data from an hour to the start of the hour. Use `.resample('1h', label='right')` instead.
- don't use functions that use all dataframe or column values, e.g. `dataframe['mean_volume'] = dataframe['volume'].mean()`. As backtesting uses the full dataframe, at any point in the dataframe, the `'mean_volume'` series would include data from the future. Use rolling() calculations instead, e.g. `dataframe['volume'].rolling(<window>).mean()`.
- don't use `.resample('1h')`. This uses the left border of the period interval, so moves data from an hour boundary to the start of the hour. Use `.resample('1h', label='right')` instead.
!!! Tip "Identifying problems"
You may also want to check the 2 helper commands [lookahead-analysis](lookahead-analysis.md) and [recursive-analysis](recursive-analysis.md), which can each help you figure out problems with your strategy in different ways.
Please treat them as what they are - helpers to identify most common problems. A negative result of each does not guarantee that there's none of the above errors included.
You should always use the two helper commands [lookahead-analysis](lookahead-analysis.md) and [recursive-analysis](recursive-analysis.md), which can each help you figure out problems with your strategy in different ways.
Please treat them as what they are - helpers to identify most common problems. A negative result of each does not guarantee that there are none of the above errors included.
### Colliding signals
When conflicting signals collide (e.g. both `'enter_long'` and `'exit_long'` are 1), freqtrade will do nothing and ignore the entry signal. This will avoid trades that enter, and exit immediately. Obviously, this can potentially lead to missed entries.
When conflicting signals collide (e.g. both `'enter_long'` and `'exit_long'` are set to `1`), freqtrade will do nothing and ignore the entry signal. This will avoid trades that enter, and exit immediately. Obviously, this can potentially lead to missed entries.
The following rules apply, and entry signals will be ignored if more than one of the 3 signals is set:
@@ -1032,11 +1138,11 @@ The following rules apply, and entry signals will be ignored if more than one of
## Further strategy ideas
To get additional Ideas for strategies, head over to the [strategy repository](https://github.com/freqtrade/freqtrade-strategies). Feel free to use them as they are - but results will depend on the current market situation, pairs used etc. - therefore please backtest the strategy for your exchange/desired pairs first, evaluate carefully, use at your own risk.
Feel free to use any of them as inspiration for your own strategies.
We're happy to accept Pull Requests containing new Strategies to that repo.
To get additional ideas for strategies, head over to the [strategy repository](https://github.com/freqtrade/freqtrade-strategies). Feel free to use them as examples, but results will depend on the current market situation, pairs used, etc. Therefore, these strategies should be considered only for learning purposes, not real world trading. Please backtest the strategy for your exchange/desired pairs first, then dry run to evaluate carefully, and use at your own risk.
## Next step
Feel free to use any of them as inspiration for your own strategies. We're happy to accept Pull Requests containing new strategies to the repository.
## Next steps
Now you have a perfect strategy you probably want to backtest it.
Your next step is to learn [How to use the Backtesting](backtesting.md).
Your next step is to learn [how to use backtesting](backtesting.md).
+1 -1
View File
@@ -215,7 +215,7 @@ trades.groupby("pair")["exit_reason"].value_counts()
```
## Analyze the loaded trades for trade parallelism
This can be useful to find the best `max_open_trades` parameter, when used with backtesting in conjunction with `--disable-max-market-positions`.
This can be useful to find the best `max_open_trades` parameter, when used with backtesting in conjunction with a very high `max_open_trades` setting.
`analyze_trade_parallelism()` returns a timeseries dataframe with an "open_trades" column, specifying the number of open trades for each candle.
+9 -9
View File
@@ -214,8 +214,8 @@ class AwesomeStrategy(IStrategy):
``` python hl_lines="4"
class AwesomeStrategy(IStrategy):
def custom_stake_amount(self, pair: str, current_time: datetime, current_rate: float,
proposed_stake: float, min_stake: Optional[float], max_stake: float,
entry_tag: Optional[str], side: str, **kwargs) -> float:
proposed_stake: float, min_stake: float | None, max_stake: float,
entry_tag: str | None, side: str, **kwargs) -> float:
# ...
return proposed_stake
```
@@ -237,7 +237,7 @@ After:
``` python hl_lines="4"
class AwesomeStrategy(IStrategy):
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: Optional[str],
time_in_force: str, current_time: datetime, entry_tag: str | None,
side: str, **kwargs) -> bool:
return True
```
@@ -280,8 +280,8 @@ After:
``` python hl_lines="3"
class AwesomeStrategy(IStrategy):
def custom_entry_price(self, pair: str, trade: Optional[Trade], current_time: datetime, proposed_rate: float,
entry_tag: Optional[str], side: str, **kwargs) -> float:
def custom_entry_price(self, pair: str, trade: Trade | None, current_time: datetime, proposed_rate: float,
entry_tag: str | None, side: str, **kwargs) -> float:
return proposed_rate
```
@@ -312,7 +312,7 @@ After:
``` python hl_lines="5 7"
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, after_fill: bool,
**kwargs) -> Optional[float]:
**kwargs) -> float | None:
# once the profit has risen above 10%, keep the stoploss at 7% above the open price
if current_profit > 0.10:
return stoploss_from_open(0.07, current_profit, is_short=trade.is_short)
@@ -329,7 +329,7 @@ After:
`order_time_in_force` attributes changed from `"buy"` to `"entry"` and `"sell"` to `"exit"`.
``` python
order_time_in_force: Dict = {
order_time_in_force: dict = {
"buy": "gtc",
"sell": "gtc",
}
@@ -338,7 +338,7 @@ After:
After:
``` python hl_lines="2 3"
order_time_in_force: Dict = {
order_time_in_force: dict = {
"entry": "GTC",
"exit": "GTC",
}
@@ -780,7 +780,7 @@ class MyCoolFreqaiModel(BaseRegressionModel):
def predict(
self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs
) -> Tuple[DataFrame, npt.NDArray[np.int_]]:
) -> tuple[DataFrame, npt.NDArray[np.int_]]:
# ... your custom stuff
+76 -70
View File
@@ -58,6 +58,7 @@ For the Freqtrade configuration, you can then use the full value (including `-`
```json
"chat_id": "-1001332619709"
```
!!! Warning "Using telegram groups"
When using telegram groups, you're giving every member of the telegram group access to your freqtrade bot and to all commands possible via telegram. Please make sure that you can trust everyone in the telegram group to avoid unpleasant surprises.
@@ -93,9 +94,12 @@ Example configuration showing the different settings:
"trailing_stop_loss": "on",
"stop_loss": "on",
"stoploss_on_exchange": "on",
"custom_exit": "silent",
"partial_exit": "on"
"custom_exit": "silent", // custom_exit without specifying an exit reason
"partial_exit": "on",
// "custom_exit_message": "silent", // Disable individual custom exit reasons
"*": "off" // Disable all other exit reasons
},
// "exit": "off", // Simplistic configuration to disable all exit messages
"exit_cancel": "on",
"exit_fill": "off",
"protection_trigger": "off",
@@ -108,16 +112,16 @@ Example configuration showing the different settings:
},
```
`entry` notifications are sent when the order is placed, while `entry_fill` notifications are sent when the order is filled on the exchange.
`exit` notifications are sent when the order is placed, while `exit_fill` notifications are sent when the order is filled on the exchange.
`*_fill` notifications are off by default and must be explicitly enabled.
`protection_trigger` notifications are sent when a protection triggers and `protection_trigger_global` notifications trigger when global protections are triggered.
`strategy_msg` - Receive notifications from the strategy, sent via `self.dp.send_msg()` from the strategy [more details](strategy-customization.md#send-notification).
`show_candle` - show candle values as part of entry/exit messages. Only possible values are `"ohlc"` or `"off"`.
`balance_dust_level` will define what the `/balance` command takes as "dust" - Currencies with a balance below this will be shown.
`allow_custom_messages` completely disable strategy messages.
`reload` allows you to disable reload-buttons on selected messages.
* `entry` notifications are sent when the order is placed, while `entry_fill` notifications are sent when the order is filled on the exchange.
* `exit` notifications are sent when the order is placed, while `exit_fill` notifications are sent when the order is filled on the exchange.
Exit messages (`exit` and `exit_fill`) can be further controlled at individual exit reasons level, with the specific exit reason as the key. the default for all exit reasons is `on` - but can be configured via special `*` key - which will act as a wildcard for all exit reasons that are not explicitly defined.
* `*_fill` notifications are off by default and must be explicitly enabled.
* `protection_trigger` notifications are sent when a protection triggers and `protection_trigger_global` notifications trigger when global protections are triggered.
* `strategy_msg` - Receive notifications from the strategy, sent via `self.dp.send_msg()` from the strategy [more details](strategy-customization.md#send-notification).
* `show_candle` - show candle values as part of entry/exit messages. Only possible values are `"ohlc"` or `"off"`.
* `balance_dust_level` will define what the `/balance` command takes as "dust" - Currencies with a balance below this will be shown.
* `allow_custom_messages` completely disable strategy messages.
* `reload` allows you to disable reload-buttons on selected messages.
## Create a custom keyboard (command shortcut buttons)
@@ -231,23 +235,23 @@ Once all positions are sold, run `/stop` to completely stop the bot.
`/reload_config` resets "max_open_trades" to the value set in the configuration and resets this command.
!!! Warning
The stop-buy signal is ONLY active while the bot is running, and is not persisted anyway, so restarting the bot will cause this to reset.
The stop-buy signal is ONLY active while the bot is running, and is not persisted anyway, so restarting the bot will cause this to reset.
### /status
For each open trade, the bot will send you the following message.
Enter Tag is configurable via Strategy.
> **Trade ID:** `123` `(since 1 days ago)`
> **Current Pair:** CVC/BTC
> **Direction:** Long
> **Leverage:** 1.0
> **Amount:** `26.64180098`
> **Enter Tag:** Awesome Long Signal
> **Open Rate:** `0.00007489`
> **Current Rate:** `0.00007489`
> **Unrealized Profit:** `12.95%`
> **Stoploss:** `0.00007389 (-0.02%)`
> **Trade ID:** `123` `(since 1 days ago)`
> **Current Pair:** CVC/BTC
> **Direction:** Long
> **Leverage:** 1.0
> **Amount:** `26.64180098`
> **Enter Tag:** Awesome Long Signal
> **Open Rate:** `0.00007489`
> **Current Rate:** `0.00007489`
> **Unrealized Profit:** `12.95%`
> **Stoploss:** `0.00007389 (-0.02%)`
### /status table
@@ -274,34 +278,34 @@ current max
Return a summary of your profit/loss and performance.
> **ROI:** Close trades
> ∙ `0.00485701 BTC (2.2%) (15.2 Σ%)`
> ∙ `62.968 USD`
> **ROI:** All trades
> ∙ `0.00255280 BTC (1.5%) (6.43 Σ%)`
> ∙ `33.095 EUR`
>
> **Total Trade Count:** `138`
> **Bot started:** `2022-07-11 18:40:44`
> **First Trade opened:** `3 days ago`
> **Latest Trade opened:** `2 minutes ago`
> **Avg. Duration:** `2:33:45`
> **Best Performing:** `PAY/BTC: 50.23%`
> **Trading volume:** `0.5 BTC`
> **Profit factor:** `1.04`
> **Win / Loss:** `102 / 36`
> **Winrate:** `73.91%`
> **Expectancy (Ratio):** `4.87 (1.66)`
> **Max Drawdown:** `9.23% (0.01255 BTC)`
> **ROI:** Close trades
> ∙ `0.00485701 BTC (2.2%) (15.2 Σ%)`
> ∙ `62.968 USD`
> **ROI:** All trades
> ∙ `0.00255280 BTC (1.5%) (6.43 Σ%)`
> ∙ `33.095 EUR`
>
> **Total Trade Count:** `138`
> **Bot started:** `2022-07-11 18:40:44`
> **First Trade opened:** `3 days ago`
> **Latest Trade opened:** `2 minutes ago`
> **Avg. Duration:** `2:33:45`
> **Best Performing:** `PAY/BTC: 50.23%`
> **Trading volume:** `0.5 BTC`
> **Profit factor:** `1.04`
> **Win / Loss:** `102 / 36`
> **Winrate:** `73.91%`
> **Expectancy (Ratio):** `4.87 (1.66)`
> **Max Drawdown:** `9.23% (0.01255 BTC)`
The relative profit of `1.2%` is the average profit per trade.
The relative profit of `15.2 Σ%` is be based on the starting capital - so in this case, the starting capital was `0.00485701 * 1.152 = 0.00738 BTC`.
Starting capital is either taken from the `available_capital` setting, or calculated by using current wallet size - profits.
Profit Factor is calculated as gross profits / gross losses - and should serve as an overall metric for the strategy.
Expectancy corresponds to the average return per currency unit at risk, i.e. the winrate and the risk-reward ratio (the average gain of winning trades compared to the average loss of losing trades).
Expectancy Ratio is expected profit or loss of a subsequent trade based on the performance of all past trades.
Max drawdown corresponds to the backtesting metric `Absolute Drawdown (Account)` - calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`.
Bot started date will refer to the date the bot was first started. For older bots, this will default to the first trade's open date.
The relative profit of `1.2%` is the average profit per trade.
The relative profit of `15.2 Σ%` is be based on the starting capital - so in this case, the starting capital was `0.00485701 * 1.152 = 0.00738 BTC`.
**Starting capital(**) is either taken from the `available_capital` setting, or calculated by using current wallet size - profits.
**Profit Factor** is calculated as gross profits / gross losses - and should serve as an overall metric for the strategy.
**Expectancy** corresponds to the average return per currency unit at risk, i.e. the winrate and the risk-reward ratio (the average gain of winning trades compared to the average loss of losing trades).
**Expectancy Ratio** is expected profit or loss of a subsequent trade based on the performance of all past trades.
**Max drawdown** corresponds to the backtesting metric `Absolute Drawdown (Account)` - calculated as `(Absolute Drawdown) / (DrawdownHigh + startingBalance)`.
**Bot started date** will refer to the date the bot was first started. For older bots, this will default to the first trade's open date.
### /forceexit <trade_id>
@@ -329,33 +333,34 @@ Note that for this to work, `force_entry_enable` needs to be set to true.
### /performance
Return the performance of each crypto-currency the bot has sold.
> Performance:
> 1. `RCN/BTC 0.003 BTC (57.77%) (1)`
> 2. `PAY/BTC 0.0012 BTC (56.91%) (1)`
> 3. `VIB/BTC 0.0011 BTC (47.07%) (1)`
> 4. `SALT/BTC 0.0010 BTC (30.24%) (1)`
> 5. `STORJ/BTC 0.0009 BTC (27.24%) (1)`
> ...
> Performance:
> 1. `RCN/BTC 0.003 BTC (57.77%) (1)`
> 2. `PAY/BTC 0.0012 BTC (56.91%) (1)`
> 3. `VIB/BTC 0.0011 BTC (47.07%) (1)`
> 4. `SALT/BTC 0.0010 BTC (30.24%) (1)`
> 5. `STORJ/BTC 0.0009 BTC (27.24%) (1)`
> ...
### /balance
Return the balance of all crypto-currency your have on the exchange.
> **Currency:** BTC
> **Available:** 3.05890234
> **Balance:** 3.05890234
> **Pending:** 0.0
> **Currency:** CVC
> **Available:** 86.64180098
> **Balance:** 86.64180098
> **Pending:** 0.0
> **Currency:** BTC
> **Available:** 3.05890234
> **Balance:** 3.05890234
> **Pending:** 0.0
>
> **Currency:** CVC
> **Available:** 86.64180098
> **Balance:** 86.64180098
> **Pending:** 0.0
### /daily <n>
Per default `/daily` will return the 7 last days. The example below if for `/daily 3`:
> **Daily Profit over the last 3 days:**
```
Day (count) USDT USD Profit %
-------------- ------------ ---------- ----------
@@ -370,6 +375,7 @@ Per default `/weekly` will return the 8 last weeks, including the current week.
from Monday. The example below if for `/weekly 3`:
> **Weekly Profit over the last 3 weeks (starting from Monday):**
```
Monday (count) Profit BTC Profit USD Profit %
------------- -------------- ------------ ----------
@@ -396,18 +402,18 @@ Month (count) Profit BTC Profit USD Profit %
Shows the current whitelist
> Using whitelist `StaticPairList` with 22 pairs
> Using whitelist `StaticPairList` with 22 pairs
> `IOTA/BTC, NEO/BTC, TRX/BTC, VET/BTC, ADA/BTC, ETC/BTC, NCASH/BTC, DASH/BTC, XRP/BTC, XVG/BTC, EOS/BTC, LTC/BTC, OMG/BTC, BTG/BTC, LSK/BTC, ZEC/BTC, HOT/BTC, IOTX/BTC, XMR/BTC, AST/BTC, XLM/BTC, NANO/BTC`
### /blacklist [pair]
Shows the current blacklist.
If Pair is set, then this pair will be added to the pairlist.
Also supports multiple pairs, separated by a space.
Also supports multiple pairs, separated by a space.
Use `/reload_config` to reset the blacklist.
> Using blacklist `StaticPairList` with 2 pairs
>`DODGE/BTC`, `HOT/BTC`.
> Using blacklist `StaticPairList` with 2 pairs
>`DODGE/BTC`, `HOT/BTC`.
### /edge
+1
View File
@@ -143,6 +143,7 @@ Most properties here can be None as they are dependent on the exchange response.
| `remaining` | float | Remaining amount |
| `cost` | float | Cost of the order - usually average * filled (*Exchange dependent on futures, may contain the cost with or without leverage and may be in contracts.*) |
| `stake_amount` | float | Stake amount used for this order. *Added in 2023.7.* |
| `stake_amount_filled` | float | Filled Stake amount used for this order. *Added in 2024.11.* |
| `order_date` | datetime | Order creation date **use `order_date_utc` instead** |
| `order_date_utc` | datetime | Order creation date (in UTC) |
| `order_fill_date` | datetime | Order fill date **use `order_fill_utc` instead** |
+39
View File
@@ -216,6 +216,45 @@ Example: Search dedicated strategy path.
freqtrade list-strategies --strategy-path ~/.freqtrade/strategies/
```
## List Hyperopt-Loss functions
Use the `list-hyperoptloss` subcommand to see all hyperopt loss functions available.
It provides a quick list of all available loss functions in your environment.
This subcommand can be useful for finding problems in your environment with loading loss functions: modules with Hyperopt-Loss functions that contain errors and failed to load are printed in red (LOAD FAILED), while hyperopt-Loss functions with duplicate names are printed in yellow (DUPLICATE NAME).
```
usage: freqtrade list-hyperoptloss [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH]
[--hyperopt-path PATH] [-1] [--no-color]
options:
-h, --help show this help message and exit
--hyperopt-path PATH Specify additional lookup path for Hyperopt Loss
functions.
-1, --one-column Print output in one column.
--no-color Disable colorization of hyperopt results. May be
useful if you are redirecting output to a file.
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE, --log-file FILE
Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
```
## List freqAI models
Use the `list-freqaimodels` subcommand to see all freqAI models available.
+2 -2
View File
@@ -5,7 +5,7 @@ We **strongly** recommend that Windows users use [Docker](docker_quickstart.md)
If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work.
Otherwise, please follow the instructions below.
All instructions assume that python 3.9+ is installed and available.
All instructions assume that python 3.10+ is installed and available.
## Clone the git repository
@@ -42,7 +42,7 @@ cd freqtrade
Install ta-lib according to the [ta-lib documentation](https://github.com/TA-Lib/ta-lib-python#windows).
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), Freqtrade provides these dependencies (in the binary wheel format) for the latest 3 Python versions (3.9, 3.10, 3.11 and 3.12) and for 64bit Windows.
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), Freqtrade provides these dependencies (in the binary wheel format) for the latest 3 Python versions (3.10, 3.11 and 3.12) and for 64bit Windows.
These Wheels are also used by CI running on windows, and are therefore tested together with freqtrade.
Other versions must be downloaded from the above link.
+1 -1
View File
@@ -1,6 +1,6 @@
"""Freqtrade bot"""
__version__ = "2024.9"
__version__ = "2024.11"
if "dev" in __version__:
from pathlib import Path
+1 -1
View File
@@ -3,7 +3,7 @@
__main__.py for Freqtrade
To launch Freqtrade as a module
> python -m freqtrade (with Python >= 3.9)
> python -m freqtrade (with Python >= 3.10)
"""
from freqtrade import main
+1
View File
@@ -27,6 +27,7 @@ from freqtrade.commands.hyperopt_commands import start_hyperopt_list, start_hype
from freqtrade.commands.list_commands import (
start_list_exchanges,
start_list_freqAI_models,
start_list_hyperopt_loss_functions,
start_list_markets,
start_list_strategies,
start_list_timeframes,
+5 -4
View File
@@ -1,8 +1,7 @@
import logging
from pathlib import Path
from typing import Any, Dict
from typing import Any
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode
from freqtrade.exceptions import ConfigurationError, OperationalException
@@ -10,13 +9,15 @@ from freqtrade.exceptions import ConfigurationError, OperationalException
logger = logging.getLogger(__name__)
def setup_analyze_configuration(args: Dict[str, Any], method: RunMode) -> Dict[str, Any]:
def setup_analyze_configuration(args: dict[str, Any], method: RunMode) -> dict[str, Any]:
"""
Prepare the configuration for the entry/exit reason analysis module
:param args: Cli args from Arguments()
:param method: Bot running mode
:return: Configuration
"""
from freqtrade.configuration import setup_utils_configuration
config = setup_utils_configuration(args, method)
no_unlimited_runmodes = {
@@ -47,7 +48,7 @@ def setup_analyze_configuration(args: Dict[str, Any], method: RunMode) -> Dict[s
return config
def start_analysis_entries_exits(args: Dict[str, Any]) -> None:
def start_analysis_entries_exits(args: dict[str, Any]) -> None:
"""
Start analysis script
:param args: Cli args from Arguments()
+19 -13
View File
@@ -5,7 +5,7 @@ This module contains the argument manager class
from argparse import ArgumentParser, Namespace, _ArgumentGroup
from functools import partial
from pathlib import Path
from typing import Any, Dict, List, Optional, Union
from typing import Any
from freqtrade.commands.cli_options import AVAILABLE_CLI_OPTIONS
from freqtrade.constants import DEFAULT_CONFIG
@@ -23,7 +23,7 @@ ARGS_STRATEGY = [
ARGS_TRADE = ["db_url", "sd_notify", "dry_run", "dry_run_wallet", "fee"]
ARGS_WEBSERVER: List[str] = []
ARGS_WEBSERVER: list[str] = []
ARGS_COMMON_OPTIMIZE = [
"timeframe",
@@ -37,7 +37,6 @@ ARGS_COMMON_OPTIMIZE = [
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + [
"position_stacking",
"use_max_market_positions",
"enable_protections",
"dry_run_wallet",
"timeframe_detail",
@@ -53,7 +52,6 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + [
"hyperopt",
"hyperopt_path",
"position_stacking",
"use_max_market_positions",
"enable_protections",
"dry_run_wallet",
"timeframe_detail",
@@ -117,7 +115,7 @@ ARGS_CREATE_USERDIR = ["user_data_dir", "reset"]
ARGS_BUILD_CONFIG = ["config"]
ARGS_SHOW_CONFIG = ["user_data_dir", "config", "show_sensitive"]
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"]
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "strategy_path", "template"]
ARGS_CONVERT_DATA_TRADES = ["pairs", "format_from_trades", "format_to", "erase", "exchange"]
ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase", "exchange"]
@@ -242,8 +240,7 @@ ARGS_STRATEGY_UPDATER = ["strategy_list", "strategy_path", "recursive_strategy_s
ARGS_LOOKAHEAD_ANALYSIS = [
a
for a in ARGS_BACKTEST
if a
not in ("position_stacking", "use_max_market_positions", "backtest_cache", "backtest_breakdown")
if a not in ("position_stacking", "backtest_cache", "backtest_breakdown")
] + ["minimum_trade_amount", "targeted_trade_amount", "lookahead_analysis_exportfilename"]
ARGS_RECURSIVE_ANALYSIS = ["timeframe", "timerange", "dataformat_ohlcv", "pairs", "startup_candle"]
@@ -258,6 +255,7 @@ NO_CONF_REQURIED = [
"list-pairs",
"list-strategies",
"list-freqaimodels",
"list-hyperoptloss",
"list-data",
"hyperopt-list",
"hyperopt-show",
@@ -277,11 +275,11 @@ class Arguments:
Arguments Class. Manage the arguments received by the cli
"""
def __init__(self, args: Optional[List[str]]) -> None:
def __init__(self, args: list[str] | None) -> None:
self.args = args
self._parsed_arg: Optional[Namespace] = None
self._parsed_arg: Namespace | None = None
def get_parsed_arg(self) -> Dict[str, Any]:
def get_parsed_arg(self) -> dict[str, Any]:
"""
Return the list of arguments
:return: List[str] List of arguments
@@ -321,9 +319,7 @@ class Arguments:
return parsed_arg
def _build_args(
self, optionlist: List[str], parser: Union[ArgumentParser, _ArgumentGroup]
) -> None:
def _build_args(self, optionlist: list[str], parser: ArgumentParser | _ArgumentGroup) -> None:
for val in optionlist:
opt = AVAILABLE_CLI_OPTIONS[val]
parser.add_argument(*opt.cli, dest=val, **opt.kwargs)
@@ -365,6 +361,7 @@ class Arguments:
start_list_data,
start_list_exchanges,
start_list_freqAI_models,
start_list_hyperopt_loss_functions,
start_list_markets,
start_list_strategies,
start_list_timeframes,
@@ -566,6 +563,15 @@ class Arguments:
list_strategies_cmd.set_defaults(func=start_list_strategies)
self._build_args(optionlist=ARGS_LIST_STRATEGIES, parser=list_strategies_cmd)
# Add list-Hyperopt loss subcommand
list_hyperopt_loss_cmd = subparsers.add_parser(
"list-hyperoptloss",
help="Print available hyperopt loss functions.",
parents=[_common_parser],
)
list_hyperopt_loss_cmd.set_defaults(func=start_list_hyperopt_loss_functions)
self._build_args(optionlist=ARGS_LIST_HYPEROPTS, parser=list_hyperopt_loss_cmd)
# Add list-freqAI Models subcommand
list_freqaimodels_cmd = subparsers.add_parser(
"list-freqaimodels",
+13 -244
View File
@@ -1,261 +1,27 @@
import logging
import secrets
from pathlib import Path
from typing import Any, Dict, List
from typing import Any
from questionary import Separator, prompt
from freqtrade.configuration import sanitize_config
from freqtrade.configuration.config_setup import setup_utils_configuration
from freqtrade.configuration.detect_environment import running_in_docker
from freqtrade.configuration.directory_operations import chown_user_directory
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import MAP_EXCHANGE_CHILDCLASS, available_exchanges
from freqtrade.util import render_template
logger = logging.getLogger(__name__)
def validate_is_int(val):
try:
_ = int(val)
return True
except Exception:
return False
def validate_is_float(val):
try:
_ = float(val)
return True
except Exception:
return False
def ask_user_overwrite(config_path: Path) -> bool:
questions = [
{
"type": "confirm",
"name": "overwrite",
"message": f"File {config_path} already exists. Overwrite?",
"default": False,
},
]
answers = prompt(questions)
return answers["overwrite"]
def ask_user_config() -> Dict[str, Any]:
"""
Ask user a few questions to build the configuration.
Interactive questions built using https://github.com/tmbo/questionary
:returns: Dict with keys to put into template
"""
questions: List[Dict[str, Any]] = [
{
"type": "confirm",
"name": "dry_run",
"message": "Do you want to enable Dry-run (simulated trades)?",
"default": True,
},
{
"type": "text",
"name": "stake_currency",
"message": "Please insert your stake currency:",
"default": "USDT",
},
{
"type": "text",
"name": "stake_amount",
"message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):",
"default": "unlimited",
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val),
"filter": lambda val: (
'"' + UNLIMITED_STAKE_AMOUNT + '"' if val == UNLIMITED_STAKE_AMOUNT else val
),
},
{
"type": "text",
"name": "max_open_trades",
"message": "Please insert max_open_trades (Integer or -1 for unlimited open trades):",
"default": "3",
"validate": lambda val: validate_is_int(val),
},
{
"type": "select",
"name": "timeframe_in_config",
"message": "Time",
"choices": ["Have the strategy define timeframe.", "Override in configuration."],
},
{
"type": "text",
"name": "timeframe",
"message": "Please insert your desired timeframe (e.g. 5m):",
"default": "5m",
"when": lambda x: x["timeframe_in_config"] == "Override in configuration.",
},
{
"type": "text",
"name": "fiat_display_currency",
"message": (
"Please insert your display Currency for reporting "
"(leave empty to disable FIAT conversion):"
),
"default": "USD",
},
{
"type": "select",
"name": "exchange_name",
"message": "Select exchange",
"choices": [
"binance",
"binanceus",
"bingx",
"gate",
"htx",
"kraken",
"kucoin",
"okx",
Separator("------------------"),
"other",
],
},
{
"type": "confirm",
"name": "trading_mode",
"message": "Do you want to trade Perpetual Swaps (perpetual futures)?",
"default": False,
"filter": lambda val: "futures" if val else "spot",
"when": lambda x: x["exchange_name"] in ["binance", "gate", "okx", "bybit"],
},
{
"type": "autocomplete",
"name": "exchange_name",
"message": "Type your exchange name (Must be supported by ccxt)",
"choices": available_exchanges(),
"when": lambda x: x["exchange_name"] == "other",
},
{
"type": "password",
"name": "exchange_key",
"message": "Insert Exchange Key",
"when": lambda x: not x["dry_run"],
},
{
"type": "password",
"name": "exchange_secret",
"message": "Insert Exchange Secret",
"when": lambda x: not x["dry_run"],
},
{
"type": "password",
"name": "exchange_key_password",
"message": "Insert Exchange API Key password",
"when": lambda x: not x["dry_run"] and x["exchange_name"] in ("kucoin", "okx"),
},
{
"type": "confirm",
"name": "telegram",
"message": "Do you want to enable Telegram?",
"default": False,
},
{
"type": "password",
"name": "telegram_token",
"message": "Insert Telegram token",
"when": lambda x: x["telegram"],
},
{
"type": "password",
"name": "telegram_chat_id",
"message": "Insert Telegram chat id",
"when": lambda x: x["telegram"],
},
{
"type": "confirm",
"name": "api_server",
"message": "Do you want to enable the Rest API (includes FreqUI)?",
"default": False,
},
{
"type": "text",
"name": "api_server_listen_addr",
"message": (
"Insert Api server Listen Address (0.0.0.0 for docker, "
"otherwise best left untouched)"
),
"default": "127.0.0.1" if not running_in_docker() else "0.0.0.0", # noqa: S104
"when": lambda x: x["api_server"],
},
{
"type": "text",
"name": "api_server_username",
"message": "Insert api-server username",
"default": "freqtrader",
"when": lambda x: x["api_server"],
},
{
"type": "password",
"name": "api_server_password",
"message": "Insert api-server password",
"when": lambda x: x["api_server"],
},
]
answers = prompt(questions)
if not answers:
# Interrupted questionary sessions return an empty dict.
raise OperationalException("User interrupted interactive questions.")
# Ensure default is set for non-futures exchanges
answers["trading_mode"] = answers.get("trading_mode", "spot")
answers["margin_mode"] = "isolated" if answers.get("trading_mode") == "futures" else ""
# Force JWT token to be a random string
answers["api_server_jwt_key"] = secrets.token_hex()
answers["api_server_ws_token"] = secrets.token_urlsafe(25)
return answers
def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None:
"""
Applies selections to the template and writes the result to config_path
:param config_path: Path object for new config file. Should not exist yet
:param selections: Dict containing selections taken by the user.
"""
from jinja2.exceptions import TemplateNotFound
try:
exchange_template = MAP_EXCHANGE_CHILDCLASS.get(
selections["exchange_name"], selections["exchange_name"]
)
selections["exchange"] = render_template(
templatefile=f"subtemplates/exchange_{exchange_template}.j2", arguments=selections
)
except TemplateNotFound:
selections["exchange"] = render_template(
templatefile="subtemplates/exchange_generic.j2", arguments=selections
)
config_text = render_template(templatefile="base_config.json.j2", arguments=selections)
logger.info(f"Writing config to `{config_path}`.")
logger.info(
"Please make sure to check the configuration contents and adjust settings to your needs."
)
config_path.write_text(config_text)
def start_new_config(args: Dict[str, Any]) -> None:
def start_new_config(args: dict[str, Any]) -> None:
"""
Create a new strategy from a template
Asking the user questions to fill out the template accordingly.
"""
from freqtrade.configuration.deploy_config import (
ask_user_config,
ask_user_overwrite,
deploy_new_config,
)
from freqtrade.configuration.directory_operations import chown_user_directory
config_path = Path(args["config"][0])
chown_user_directory(config_path.parent)
if config_path.exists():
@@ -271,7 +37,10 @@ def start_new_config(args: Dict[str, Any]) -> None:
deploy_new_config(config_path, selections)
def start_show_config(args: Dict[str, Any]) -> None:
def start_show_config(args: dict[str, Any]) -> None:
from freqtrade.configuration import sanitize_config
from freqtrade.configuration.config_setup import setup_utils_configuration
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE, set_dry=False)
print("Your combined configuration is:")
-8
View File
@@ -168,14 +168,6 @@ AVAILABLE_CLI_OPTIONS = {
action="store_true",
default=False,
),
"use_max_market_positions": Arg(
"--dmmp",
"--disable-max-market-positions",
help="Disable applying `max_open_trades` during backtest "
"(same as setting `max_open_trades` to a very high number).",
action="store_false",
default=True,
),
"backtest_show_pair_list": Arg(
"--show-pair-list",
help="Show backtesting pairlist sorted by profit.",
+23 -18
View File
@@ -1,24 +1,12 @@
import logging
import sys
from collections import defaultdict
from typing import Any, Dict
from typing import Any
from freqtrade.configuration import TimeRange, setup_utils_configuration
from freqtrade.constants import DATETIME_PRINT_FORMAT, DL_DATA_TIMEFRAMES, Config
from freqtrade.data.converter import (
convert_ohlcv_format,
convert_trades_format,
convert_trades_to_ohlcv,
)
from freqtrade.data.history import download_data_main
from freqtrade.enums import CandleType, RunMode, TradingMode
from freqtrade.exceptions import ConfigurationError
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.misc import plural
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
from freqtrade.resolvers import ExchangeResolver
from freqtrade.util import print_rich_table
from freqtrade.util.migrations import migrate_data
logger = logging.getLogger(__name__)
@@ -38,10 +26,13 @@ def _check_data_config_download_sanity(config: Config) -> None:
)
def start_download_data(args: Dict[str, Any]) -> None:
def start_download_data(args: dict[str, Any]) -> None:
"""
Download data (former download_backtest_data.py script)
"""
from freqtrade.configuration import setup_utils_configuration
from freqtrade.data.history import download_data_main
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
_check_data_config_download_sanity(config)
@@ -53,7 +44,11 @@ def start_download_data(args: Dict[str, Any]) -> None:
sys.exit("SIGINT received, aborting ...")
def start_convert_trades(args: Dict[str, Any]) -> None:
def start_convert_trades(args: dict[str, Any]) -> None:
from freqtrade.configuration import TimeRange, setup_utils_configuration
from freqtrade.data.converter import convert_trades_to_ohlcv
from freqtrade.resolvers import ExchangeResolver
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
timerange = TimeRange()
@@ -92,10 +87,14 @@ def start_convert_trades(args: Dict[str, Any]) -> None:
)
def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None:
def start_convert_data(args: dict[str, Any], ohlcv: bool = True) -> None:
"""
Convert data from one format to another
"""
from freqtrade.configuration import setup_utils_configuration
from freqtrade.data.converter import convert_ohlcv_format, convert_trades_format
from freqtrade.util.migrations import migrate_data
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
if ohlcv:
migrate_data(config)
@@ -114,10 +113,13 @@ def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None:
)
def start_list_data(args: Dict[str, Any]) -> None:
def start_list_data(args: dict[str, Any]) -> None:
"""
List available OHLCV data
"""
from freqtrade.configuration import setup_utils_configuration
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.util import print_rich_table
if args["trades"]:
start_list_trades_data(args)
@@ -177,10 +179,13 @@ def start_list_data(args: Dict[str, Any]) -> None:
)
def start_list_trades_data(args: Dict[str, Any]) -> None:
def start_list_trades_data(args: dict[str, Any]) -> None:
"""
List available Trades data
"""
from freqtrade.configuration import setup_utils_configuration
from freqtrade.misc import plural
from freqtrade.util import print_rich_table
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
+4 -5
View File
@@ -1,18 +1,17 @@
import logging
from typing import Any, Dict
from typing import Any
from sqlalchemy import func, select
from freqtrade.configuration.config_setup import setup_utils_configuration
from freqtrade.enums import RunMode
logger = logging.getLogger(__name__)
def start_convert_db(args: Dict[str, Any]) -> None:
def start_convert_db(args: dict[str, Any]) -> None:
from sqlalchemy import func, select
from sqlalchemy.orm import make_transient
from freqtrade.configuration.config_setup import setup_utils_configuration
from freqtrade.persistence import Order, Trade, init_db
from freqtrade.persistence.migrations import set_sequence_ids
from freqtrade.persistence.pairlock import PairLock
+24 -82
View File
@@ -1,16 +1,11 @@
import logging
import sys
from pathlib import Path
from typing import Any, Dict, Optional, Tuple
from typing import Any
import requests
from freqtrade.configuration import setup_utils_configuration
from freqtrade.configuration.directory_operations import copy_sample_files, create_userdata_dir
from freqtrade.constants import USERPATH_STRATEGIES
from freqtrade.enums import RunMode
from freqtrade.exceptions import ConfigurationError, OperationalException
from freqtrade.util import render_template, render_template_with_fallback
logger = logging.getLogger(__name__)
@@ -20,12 +15,14 @@ logger = logging.getLogger(__name__)
req_timeout = 30
def start_create_userdir(args: Dict[str, Any]) -> None:
def start_create_userdir(args: dict[str, Any]) -> None:
"""
Create "user_data" directory to contain user data strategies, hyperopt, ...)
:param args: Cli args from Arguments()
:return: None
"""
from freqtrade.configuration.directory_operations import copy_sample_files, create_userdata_dir
if "user_data_dir" in args and args["user_data_dir"]:
userdir = create_userdata_dir(args["user_data_dir"], create_dir=True)
copy_sample_files(userdir, overwrite=args["reset"])
@@ -38,6 +35,8 @@ def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: st
"""
Deploy new strategy from template to strategy_path
"""
from freqtrade.util import render_template, render_template_with_fallback
fallback = "full"
attributes = render_template_with_fallback(
templatefile=f"strategy_subtemplates/strategy_attributes_{subtemplate}.j2",
@@ -81,11 +80,20 @@ def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: st
strategy_path.write_text(strategy_text)
def start_new_strategy(args: Dict[str, Any]) -> None:
def start_new_strategy(args: dict[str, Any]) -> None:
from freqtrade.configuration import setup_utils_configuration
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
if "strategy" in args and args["strategy"]:
new_path = config["user_data_dir"] / USERPATH_STRATEGIES / (args["strategy"] + ".py")
if "strategy_path" in args and args["strategy_path"]:
strategy_dir = Path(args["strategy_path"])
else:
strategy_dir = config["user_data_dir"] / USERPATH_STRATEGIES
if not strategy_dir.is_dir():
logger.info(f"Creating strategy directory {strategy_dir}")
strategy_dir.mkdir(parents=True)
new_path = strategy_dir / (args["strategy"] + ".py")
if new_path.exists():
raise OperationalException(
@@ -98,80 +106,14 @@ def start_new_strategy(args: Dict[str, Any]) -> None:
raise ConfigurationError("`new-strategy` requires --strategy to be set.")
def clean_ui_subdir(directory: Path):
if directory.is_dir():
logger.info("Removing UI directory content.")
def start_install_ui(args: dict[str, Any]) -> None:
from freqtrade.commands.deploy_ui import (
clean_ui_subdir,
download_and_install_ui,
get_ui_download_url,
read_ui_version,
)
for p in reversed(list(directory.glob("**/*"))): # iterate contents from leaves to root
if p.name in (".gitkeep", "fallback_file.html"):
continue
if p.is_file():
p.unlink()
elif p.is_dir():
p.rmdir()
def read_ui_version(dest_folder: Path) -> Optional[str]:
file = dest_folder / ".uiversion"
if not file.is_file():
return None
with file.open("r") as f:
return f.read()
def download_and_install_ui(dest_folder: Path, dl_url: str, version: str):
from io import BytesIO
from zipfile import ZipFile
logger.info(f"Downloading {dl_url}")
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:
with zf.open(fn) as x:
destfile = dest_folder / fn.filename
if fn.is_dir():
destfile.mkdir(exist_ok=True)
else:
destfile.write_bytes(x.read())
with (dest_folder / ".uiversion").open("w") as f:
f.write(version)
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", timeout=req_timeout)
resp.raise_for_status()
r = resp.json()
if version:
tmp = [x for x in r if x["name"] == version]
if tmp:
latest_version = tmp[0]["name"]
assets = tmp[0].get("assets", [])
else:
raise ValueError("UI-Version not found.")
else:
latest_version = r[0]["name"]
assets = r[0].get("assets", [])
dl_url = ""
if assets and len(assets) > 0:
dl_url = assets[0]["browser_download_url"]
# URL not found - try assets url
if not dl_url:
assets = r[0]["assets_url"]
resp = requests.get(assets, timeout=req_timeout)
r = resp.json()
dl_url = r[0]["browser_download_url"]
return dl_url, latest_version
def start_install_ui(args: Dict[str, Any]) -> None:
dest_folder = Path(__file__).parents[1] / "rpc/api_server/ui/installed/"
# First make sure the assets are removed.
dl_url, latest_version = get_ui_download_url(args.get("ui_version"))
+83
View File
@@ -0,0 +1,83 @@
import logging
from pathlib import Path
import requests
logger = logging.getLogger(__name__)
# Timeout for requests
req_timeout = 30
def clean_ui_subdir(directory: Path):
if directory.is_dir():
logger.info("Removing UI directory content.")
for p in reversed(list(directory.glob("**/*"))): # iterate contents from leaves to root
if p.name in (".gitkeep", "fallback_file.html"):
continue
if p.is_file():
p.unlink()
elif p.is_dir():
p.rmdir()
def read_ui_version(dest_folder: Path) -> str | None:
file = dest_folder / ".uiversion"
if not file.is_file():
return None
with file.open("r") as f:
return f.read()
def download_and_install_ui(dest_folder: Path, dl_url: str, version: str):
from io import BytesIO
from zipfile import ZipFile
logger.info(f"Downloading {dl_url}")
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:
with zf.open(fn) as x:
destfile = dest_folder / fn.filename
if fn.is_dir():
destfile.mkdir(exist_ok=True)
else:
destfile.write_bytes(x.read())
with (dest_folder / ".uiversion").open("w") as f:
f.write(version)
def get_ui_download_url(version: str | None = None) -> tuple[str, str]:
base_url = "https://api.github.com/repos/freqtrade/frequi/"
# Get base UI Repo path
resp = requests.get(f"{base_url}releases", timeout=req_timeout)
resp.raise_for_status()
r = resp.json()
if version:
tmp = [x for x in r if x["name"] == version]
if tmp:
latest_version = tmp[0]["name"]
assets = tmp[0].get("assets", [])
else:
raise ValueError("UI-Version not found.")
else:
latest_version = r[0]["name"]
assets = r[0].get("assets", [])
dl_url = ""
if assets and len(assets) > 0:
dl_url = assets[0]["browser_download_url"]
# URL not found - try assets url
if not dl_url:
assets = r[0]["assets_url"]
resp = requests.get(assets, timeout=req_timeout)
r = resp.json()
dl_url = r[0]["browser_download_url"]
return dl_url, latest_version
+9 -7
View File
@@ -1,22 +1,21 @@
import logging
from operator import itemgetter
from typing import Any, Dict
from typing import Any
from freqtrade.configuration import setup_utils_configuration
from freqtrade.data.btanalysis import get_latest_hyperopt_file
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
from freqtrade.optimize.optimize_reports import show_backtest_result
logger = logging.getLogger(__name__)
def start_hyperopt_list(args: Dict[str, Any]) -> None:
def start_hyperopt_list(args: dict[str, Any]) -> None:
"""
List hyperopt epochs previously evaluated
"""
from freqtrade.optimize.hyperopt_output import HyperoptOutput
from freqtrade.configuration import setup_utils_configuration
from freqtrade.data.btanalysis import get_latest_hyperopt_file
from freqtrade.optimize.hyperopt.hyperopt_output import HyperoptOutput
from freqtrade.optimize.hyperopt_tools import HyperoptTools
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
@@ -57,11 +56,14 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
HyperoptTools.export_csv_file(config, epochs, export_csv)
def start_hyperopt_show(args: Dict[str, Any]) -> None:
def start_hyperopt_show(args: dict[str, Any]) -> None:
"""
Show details of a hyperopt epoch previously evaluated
"""
from freqtrade.configuration import setup_utils_configuration
from freqtrade.data.btanalysis import get_latest_hyperopt_file
from freqtrade.optimize.hyperopt_tools import HyperoptTools
from freqtrade.optimize.optimize_reports import show_backtest_result
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
+57 -21
View File
@@ -1,33 +1,29 @@
import csv
import logging
import sys
from typing import Any, Dict, List, Union
from typing import Any
import rapidjson
from rich.console import Console
from rich.table import Table
from rich.text import Text
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode
from freqtrade.exceptions import ConfigurationError, OperationalException
from freqtrade.exchange import list_available_exchanges, market_is_active
from freqtrade.ft_types import ValidExchangesType
from freqtrade.misc import parse_db_uri_for_logging, plural
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.util import print_rich_table
logger = logging.getLogger(__name__)
def start_list_exchanges(args: Dict[str, Any]) -> None:
def start_list_exchanges(args: dict[str, Any]) -> None:
"""
Print available exchanges
:param args: Cli args from Arguments()
:return: None
"""
available_exchanges: List[ValidExchangesType] = list_available_exchanges(
from rich.console import Console
from rich.table import Table
from rich.text import Text
from freqtrade.exchange import list_available_exchanges
available_exchanges: list[ValidExchangesType] = list_available_exchanges(
args["list_exchanges_all"]
)
@@ -85,9 +81,13 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
console.print(table)
def _print_objs_tabular(objs: List, print_colorized: bool) -> None:
def _print_objs_tabular(objs: list, print_colorized: bool) -> None:
from rich.console import Console
from rich.table import Table
from rich.text import Text
names = [s["name"] for s in objs]
objs_to_print: List[Dict[str, Union[Text, str]]] = [
objs_to_print: list[dict[str, Text | str]] = [
{
"name": Text(s["name"] if s["name"] else "--"),
"location": s["location_rel"],
@@ -125,10 +125,13 @@ def _print_objs_tabular(objs: List, print_colorized: bool) -> None:
console.print(table)
def start_list_strategies(args: Dict[str, Any]) -> None:
def start_list_strategies(args: dict[str, Any]) -> None:
"""
Print files with Strategy custom classes available in the directory
"""
from freqtrade.configuration import setup_utils_configuration
from freqtrade.resolvers import StrategyResolver
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
strategy_objs = StrategyResolver.search_all_objects(
@@ -148,13 +151,15 @@ def start_list_strategies(args: Dict[str, Any]) -> None:
_print_objs_tabular(strategy_objs, config.get("print_colorized", False))
def start_list_freqAI_models(args: Dict[str, Any]) -> None:
def start_list_freqAI_models(args: dict[str, Any]) -> None:
"""
Print files with FreqAI models custom classes available in the directory
"""
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
from freqtrade.configuration import setup_utils_configuration
from freqtrade.resolvers.freqaimodel_resolver import FreqaiModelResolver
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
model_objs = FreqaiModelResolver.search_all_objects(config, not args["print_one_column"])
# Sort alphabetically
model_objs = sorted(model_objs, key=lambda x: x["name"])
@@ -164,10 +169,31 @@ def start_list_freqAI_models(args: Dict[str, Any]) -> None:
_print_objs_tabular(model_objs, config.get("print_colorized", False))
def start_list_timeframes(args: Dict[str, Any]) -> None:
def start_list_hyperopt_loss_functions(args: dict[str, Any]) -> None:
"""
Print files with FreqAI models custom classes available in the directory
"""
from freqtrade.configuration import setup_utils_configuration
from freqtrade.resolvers.hyperopt_resolver import HyperOptLossResolver
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
model_objs = HyperOptLossResolver.search_all_objects(config, not args["print_one_column"])
# Sort alphabetically
model_objs = sorted(model_objs, key=lambda x: x["name"])
if args["print_one_column"]:
print("\n".join([s["name"] for s in model_objs]))
else:
_print_objs_tabular(model_objs, config.get("print_colorized", False))
def start_list_timeframes(args: dict[str, Any]) -> None:
"""
Print timeframes available on Exchange
"""
from freqtrade.configuration import setup_utils_configuration
from freqtrade.resolvers import ExchangeResolver
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
# Do not use timeframe set in the config
config["timeframe"] = None
@@ -184,13 +210,19 @@ def start_list_timeframes(args: Dict[str, Any]) -> None:
)
def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
def start_list_markets(args: dict[str, Any], pairs_only: bool = False) -> None:
"""
Print pairs/markets on the exchange
:param args: Cli args from Arguments()
:param pairs_only: if True print only pairs, otherwise print all instruments (markets)
:return: None
"""
from freqtrade.configuration import setup_utils_configuration
from freqtrade.exchange import market_is_active
from freqtrade.misc import plural
from freqtrade.resolvers import ExchangeResolver
from freqtrade.util import print_rich_table
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
# Init exchange
@@ -281,6 +313,8 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
elif args.get("print_one_column", False):
print("\n".join(pairs.keys()))
elif args.get("list_pairs_print_json", False):
import rapidjson
print(rapidjson.dumps(list(pairs.keys()), default=str))
elif args.get("print_csv", False):
writer = csv.DictWriter(sys.stdout, fieldnames=headers)
@@ -296,12 +330,14 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
print(f"{summary_str}.")
def start_show_trades(args: Dict[str, Any]) -> None:
def start_show_trades(args: dict[str, Any]) -> None:
"""
Show trades
"""
import json
from freqtrade.configuration import setup_utils_configuration
from freqtrade.misc import parse_db_uri_for_logging
from freqtrade.persistence import Trade, init_db
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
+14 -10
View File
@@ -1,23 +1,24 @@
import logging
from typing import Any, Dict
from typing import Any
from freqtrade import constants
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode
from freqtrade.exceptions import ConfigurationError, OperationalException
from freqtrade.util import fmt_coin
logger = logging.getLogger(__name__)
def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[str, Any]:
def setup_optimize_configuration(args: dict[str, Any], method: RunMode) -> dict[str, Any]:
"""
Prepare the configuration for the Hyperopt module
:param args: Cli args from Arguments()
:param method: Bot running mode
:return: Configuration
"""
from freqtrade.configuration import setup_utils_configuration
from freqtrade.util import fmt_coin
config = setup_utils_configuration(args, method)
no_unlimited_runmodes = {
@@ -41,7 +42,7 @@ def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[
return config
def start_backtesting(args: Dict[str, Any]) -> None:
def start_backtesting(args: dict[str, Any]) -> None:
"""
Start Backtesting script
:param args: Cli args from Arguments()
@@ -60,10 +61,11 @@ def start_backtesting(args: Dict[str, Any]) -> None:
backtesting.start()
def start_backtesting_show(args: Dict[str, Any]) -> None:
def start_backtesting_show(args: dict[str, Any]) -> None:
"""
Show previous backtest result
"""
from freqtrade.configuration import setup_utils_configuration
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
@@ -76,7 +78,7 @@ def start_backtesting_show(args: Dict[str, Any]) -> None:
show_sorted_pairlist(config, results)
def start_hyperopt(args: Dict[str, Any]) -> None:
def start_hyperopt(args: dict[str, Any]) -> None:
"""
Start hyperopt script
:param args: Cli args from Arguments()
@@ -121,7 +123,7 @@ def start_hyperopt(args: Dict[str, Any]) -> None:
# Same in Edge and Backtesting start() functions.
def start_edge(args: Dict[str, Any]) -> None:
def start_edge(args: dict[str, Any]) -> None:
"""
Start Edge script
:param args: Cli args from Arguments()
@@ -138,24 +140,26 @@ def start_edge(args: Dict[str, Any]) -> None:
edge_cli.start()
def start_lookahead_analysis(args: Dict[str, Any]) -> None:
def start_lookahead_analysis(args: dict[str, Any]) -> None:
"""
Start the backtest bias tester script
:param args: Cli args from Arguments()
:return: None
"""
from freqtrade.configuration import setup_utils_configuration
from freqtrade.optimize.analysis.lookahead_helpers import LookaheadAnalysisSubFunctions
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
LookaheadAnalysisSubFunctions.start(config)
def start_recursive_analysis(args: Dict[str, Any]) -> None:
def start_recursive_analysis(args: dict[str, Any]) -> None:
"""
Start the backtest recursive tester script
:param args: Cli args from Arguments()
:return: None
"""
from freqtrade.configuration import setup_utils_configuration
from freqtrade.optimize.analysis.recursive_helpers import RecursiveAnalysisSubFunctions
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
+4 -4
View File
@@ -1,22 +1,22 @@
import logging
from typing import Any, Dict
from typing import Any
import rapidjson
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode
from freqtrade.resolvers import ExchangeResolver
logger = logging.getLogger(__name__)
def start_test_pairlist(args: Dict[str, Any]) -> None:
def start_test_pairlist(args: dict[str, Any]) -> None:
"""
Test Pairlist configuration
"""
from freqtrade.configuration import setup_utils_configuration
from freqtrade.persistence import FtNoDBContext
from freqtrade.plugins.pairlistmanager import PairListManager
from freqtrade.resolvers import ExchangeResolver
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
+6 -5
View File
@@ -1,11 +1,10 @@
from typing import Any, Dict
from typing import Any
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode
from freqtrade.exceptions import ConfigurationError
def validate_plot_args(args: Dict[str, Any]) -> None:
def validate_plot_args(args: dict[str, Any]) -> None:
if not args.get("datadir") and not args.get("config"):
raise ConfigurationError(
"You need to specify either `--datadir` or `--config` "
@@ -13,11 +12,12 @@ def validate_plot_args(args: Dict[str, Any]) -> None:
)
def start_plot_dataframe(args: Dict[str, Any]) -> None:
def start_plot_dataframe(args: dict[str, Any]) -> None:
"""
Entrypoint for dataframe plotting
"""
# Import here to avoid errors if plot-dependencies are not installed.
from freqtrade.configuration import setup_utils_configuration
from freqtrade.plot.plotting import load_and_plot_trades
validate_plot_args(args)
@@ -26,11 +26,12 @@ def start_plot_dataframe(args: Dict[str, Any]) -> None:
load_and_plot_trades(config)
def start_plot_profit(args: Dict[str, Any]) -> None:
def start_plot_profit(args: dict[str, Any]) -> None:
"""
Entrypoint for plot_profit
"""
# Import here to avoid errors if plot-dependencies are not installed.
from freqtrade.configuration import setup_utils_configuration
from freqtrade.plot.plotting import plot_profit
validate_plot_args(args)
@@ -1,27 +1,22 @@
import logging
import sys
import time
from pathlib import Path
from typing import Any, Dict
from typing import Any
from freqtrade.configuration import setup_utils_configuration
from freqtrade.enums import RunMode
from freqtrade.resolvers import StrategyResolver
from freqtrade.strategy.strategyupdater import StrategyUpdater
logger = logging.getLogger(__name__)
def start_strategy_update(args: Dict[str, Any]) -> None:
def start_strategy_update(args: dict[str, Any]) -> None:
"""
Start the strategy updating script
:param args: Cli args from Arguments()
:return: None
"""
if sys.version_info == (3, 8): # pragma: no cover
sys.exit("Freqtrade strategy updater requires Python version >= 3.9")
from freqtrade.configuration import setup_utils_configuration
from freqtrade.resolvers import StrategyResolver
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
@@ -49,6 +44,8 @@ def start_strategy_update(args: Dict[str, Any]) -> None:
def start_conversion(strategy_obj, config):
from freqtrade.strategy.strategyupdater import StrategyUpdater
print(f"Conversion of {Path(strategy_obj['location']).name} started.")
instance_strategy_updater = StrategyUpdater()
start = time.perf_counter()
+2 -2
View File
@@ -1,12 +1,12 @@
import logging
import signal
from typing import Any, Dict
from typing import Any
logger = logging.getLogger(__name__)
def start_trading(args: Dict[str, Any]) -> int:
def start_trading(args: dict[str, Any]) -> int:
"""
Main entry point for trading mode
"""
+2 -2
View File
@@ -1,9 +1,9 @@
from typing import Any, Dict
from typing import Any
from freqtrade.enums import RunMode
def start_webserver(args: Dict[str, Any]) -> None:
def start_webserver(args: dict[str, Any]) -> None:
"""
Main entry point for webserver mode
"""
-1
View File
@@ -1,6 +1,5 @@
# flake8: noqa: F401
from freqtrade.configuration.asyncio_config import asyncio_setup
from freqtrade.configuration.config_secrets import sanitize_config
from freqtrade.configuration.config_setup import setup_utils_configuration
from freqtrade.configuration.config_validation import validate_config_consistency
+13 -59
View File
@@ -1,10 +1,8 @@
# Required json-schema for user specified config
from typing import Dict
from freqtrade.constants import (
AVAILABLE_DATAHANDLERS,
AVAILABLE_PAIRLISTS,
AVAILABLE_PROTECTIONS,
BACKTEST_BREAKDOWNS,
DRY_RUN_WALLET,
EXPORT_OPTIONS,
@@ -24,7 +22,7 @@ from freqtrade.constants import (
from freqtrade.enums import RPCMessageType
__MESSAGE_TYPE_DICT: Dict[str, Dict[str, str]] = {x: {"type": "object"} for x in RPCMessageType}
__MESSAGE_TYPE_DICT: dict[str, dict[str, str]] = {x: {"type": "object"} for x in RPCMessageType}
__IN_STRATEGY = "\nUsually specified in the strategy and missing in the configuration."
@@ -449,60 +447,6 @@ CONF_SCHEMA = {
"required": ["method"],
},
},
"protections": {
"description": "Configuration for various protections.",
"type": "array",
"items": {
"type": "object",
"properties": {
"method": {
"description": "Method used for the protection.",
"type": "string",
"enum": AVAILABLE_PROTECTIONS,
},
"stop_duration": {
"description": (
"Duration to lock the pair after a protection is triggered, "
"in minutes."
),
"type": "number",
"minimum": 0.0,
},
"stop_duration_candles": {
"description": (
"Duration to lock the pair after a protection is triggered, in "
"number of candles."
),
"type": "number",
"minimum": 0,
},
"unlock_at": {
"description": (
"Time when trading will be unlocked regularly. Format: HH:MM"
),
"type": "string",
},
"trade_limit": {
"description": "Minimum number of trades required during lookback period.",
"type": "number",
"minimum": 1,
},
"lookback_period": {
"description": "Period to look back for protection checks, in minutes.",
"type": "number",
"minimum": 1,
},
"lookback_period_candles": {
"description": (
"Period to look back for protection checks, in number " "of candles."
),
"type": "number",
"minimum": 1,
},
},
"required": ["method"],
},
},
# RPC section
"telegram": {
"description": "Telegram settings.",
@@ -573,8 +517,11 @@ CONF_SCHEMA = {
},
"exit_fill": {
"description": "Telegram setting for exit fill signals.",
"type": "string",
"enum": TELEGRAM_SETTING_OPTIONS,
"type": ["string", "object"],
"additionalProperties": {
"type": "string",
"enum": TELEGRAM_SETTING_OPTIONS,
},
"default": "on",
},
"exit_cancel": {
@@ -1051,6 +998,13 @@ CONF_SCHEMA = {
"type": "string",
"default": "example",
},
"wait_for_training_iteration_on_reload": {
"description": (
"Wait for the next training iteration to complete after /reload or ctrl+c."
),
"type": "boolean",
"default": True,
},
"feature_parameters": {
"description": "The parameters used to engineer the feature set",
"type": "object",
+3 -3
View File
@@ -1,5 +1,5 @@
import logging
from typing import Any, Dict
from typing import Any
from freqtrade.enums import RunMode
@@ -11,8 +11,8 @@ logger = logging.getLogger(__name__)
def setup_utils_configuration(
args: Dict[str, Any], method: RunMode, *, set_dry: bool = True
) -> Dict[str, Any]:
args: dict[str, Any], method: RunMode, *, set_dry: bool = True
) -> dict[str, Any]:
"""
Prepare the configuration for utils subcommands
:param args: Cli args from Arguments()
+20 -57
View File
@@ -1,8 +1,7 @@
import logging
from collections import Counter
from copy import deepcopy
from datetime import datetime
from typing import Any, Dict
from typing import Any
from jsonschema import Draft4Validator, validators
from jsonschema.exceptions import ValidationError, best_match
@@ -44,7 +43,7 @@ def _extend_validator(validator_class):
FreqtradeValidator = _extend_validator(Draft4Validator)
def validate_config_schema(conf: Dict[str, Any], preliminary: bool = False) -> Dict[str, Any]:
def validate_config_schema(conf: dict[str, Any], preliminary: bool = False) -> dict[str, Any]:
"""
Validate the configuration follow the Config Schema
:param conf: Config in JSON format
@@ -70,7 +69,7 @@ def validate_config_schema(conf: Dict[str, Any], preliminary: bool = False) -> D
raise ValidationError(best_match(Draft4Validator(conf_schema).iter_errors(conf)).message)
def validate_config_consistency(conf: Dict[str, Any], *, preliminary: bool = False) -> None:
def validate_config_consistency(conf: dict[str, Any], *, preliminary: bool = False) -> None:
"""
Validate the configuration consistency.
Should be ran after loading both configuration and strategy,
@@ -84,7 +83,6 @@ def validate_config_consistency(conf: Dict[str, Any], *, preliminary: bool = Fal
_validate_price_config(conf)
_validate_edge(conf)
_validate_whitelist(conf)
_validate_protections(conf)
_validate_unlimited_amount(conf)
_validate_ask_orderbook(conf)
_validate_freqai_hyperopt(conf)
@@ -99,7 +97,7 @@ def validate_config_consistency(conf: Dict[str, Any], *, preliminary: bool = Fal
validate_config_schema(conf, preliminary=preliminary)
def _validate_unlimited_amount(conf: Dict[str, Any]) -> None:
def _validate_unlimited_amount(conf: dict[str, Any]) -> None:
"""
If edge is disabled, either max_open_trades or stake_amount need to be set.
:raise: ConfigurationError if config validation failed
@@ -112,7 +110,7 @@ def _validate_unlimited_amount(conf: Dict[str, Any]) -> None:
raise ConfigurationError("`max_open_trades` and `stake_amount` cannot both be unlimited.")
def _validate_price_config(conf: Dict[str, Any]) -> None:
def _validate_price_config(conf: dict[str, Any]) -> None:
"""
When using market orders, price sides must be using the "other" side of the price
"""
@@ -128,7 +126,7 @@ def _validate_price_config(conf: Dict[str, Any]) -> None:
raise ConfigurationError('Market exit orders require exit_pricing.price_side = "other".')
def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:
def _validate_trailing_stoploss(conf: dict[str, Any]) -> None:
if conf.get("stoploss") == 0.0:
raise ConfigurationError(
"The config stoploss needs to be different from 0 to avoid problems with sell orders."
@@ -161,7 +159,7 @@ def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:
)
def _validate_edge(conf: Dict[str, Any]) -> None:
def _validate_edge(conf: dict[str, Any]) -> None:
"""
Edge and Dynamic whitelist should not both be enabled, since edge overrides dynamic whitelists.
"""
@@ -175,7 +173,7 @@ def _validate_edge(conf: Dict[str, Any]) -> None:
)
def _validate_whitelist(conf: Dict[str, Any]) -> None:
def _validate_whitelist(conf: dict[str, Any]) -> None:
"""
Dynamic whitelist does not require pair_whitelist to be set - however StaticWhitelist does.
"""
@@ -196,42 +194,7 @@ def _validate_whitelist(conf: Dict[str, Any]) -> None:
raise ConfigurationError("StaticPairList requires pair_whitelist to be set.")
def _validate_protections(conf: Dict[str, Any]) -> None:
"""
Validate protection configuration validity
"""
for prot in conf.get("protections", []):
parsed_unlock_at = None
if (config_unlock_at := prot.get("unlock_at")) is not None:
try:
parsed_unlock_at = datetime.strptime(config_unlock_at, "%H:%M")
except ValueError:
raise ConfigurationError(f"Invalid date format for unlock_at: {config_unlock_at}.")
if "stop_duration" in prot and "stop_duration_candles" in prot:
raise ConfigurationError(
"Protections must specify either `stop_duration` or `stop_duration_candles`.\n"
f"Please fix the protection {prot.get('method')}."
)
if "lookback_period" in prot and "lookback_period_candles" in prot:
raise ConfigurationError(
"Protections must specify either `lookback_period` or `lookback_period_candles`.\n"
f"Please fix the protection {prot.get('method')}."
)
if parsed_unlock_at is not None and (
"stop_duration" in prot or "stop_duration_candles" in prot
):
raise ConfigurationError(
"Protections must specify either `unlock_at`, `stop_duration` or "
"`stop_duration_candles`.\n"
f"Please fix the protection {prot.get('method')}."
)
def _validate_ask_orderbook(conf: Dict[str, Any]) -> None:
def _validate_ask_orderbook(conf: dict[str, Any]) -> None:
ask_strategy = conf.get("exit_pricing", {})
ob_min = ask_strategy.get("order_book_min")
ob_max = ask_strategy.get("order_book_max")
@@ -251,7 +214,7 @@ def _validate_ask_orderbook(conf: Dict[str, Any]) -> None:
)
def validate_migrated_strategy_settings(conf: Dict[str, Any]) -> None:
def validate_migrated_strategy_settings(conf: dict[str, Any]) -> None:
_validate_time_in_force(conf)
_validate_order_types(conf)
_validate_unfilledtimeout(conf)
@@ -259,7 +222,7 @@ def validate_migrated_strategy_settings(conf: Dict[str, Any]) -> None:
_strategy_settings(conf)
def _validate_time_in_force(conf: Dict[str, Any]) -> None:
def _validate_time_in_force(conf: dict[str, Any]) -> None:
time_in_force = conf.get("order_time_in_force", {})
if "buy" in time_in_force or "sell" in time_in_force:
if conf.get("trading_mode", TradingMode.SPOT) != TradingMode.SPOT:
@@ -280,7 +243,7 @@ def _validate_time_in_force(conf: Dict[str, Any]) -> None:
)
def _validate_order_types(conf: Dict[str, Any]) -> None:
def _validate_order_types(conf: dict[str, Any]) -> None:
order_types = conf.get("order_types", {})
old_order_types = [
"buy",
@@ -315,7 +278,7 @@ def _validate_order_types(conf: Dict[str, Any]) -> None:
process_deprecated_setting(conf, "order_types", o, "order_types", n)
def _validate_unfilledtimeout(conf: Dict[str, Any]) -> None:
def _validate_unfilledtimeout(conf: dict[str, Any]) -> None:
unfilledtimeout = conf.get("unfilledtimeout", {})
if any(x in unfilledtimeout for x in ["buy", "sell"]):
if conf.get("trading_mode", TradingMode.SPOT) != TradingMode.SPOT:
@@ -334,7 +297,7 @@ def _validate_unfilledtimeout(conf: Dict[str, Any]) -> None:
process_deprecated_setting(conf, "unfilledtimeout", o, "unfilledtimeout", n)
def _validate_pricing_rules(conf: Dict[str, Any]) -> None:
def _validate_pricing_rules(conf: dict[str, Any]) -> None:
if conf.get("ask_strategy") or conf.get("bid_strategy"):
if conf.get("trading_mode", TradingMode.SPOT) != TradingMode.SPOT:
raise ConfigurationError("Please migrate your pricing settings to use the new wording.")
@@ -364,7 +327,7 @@ def _validate_pricing_rules(conf: Dict[str, Any]) -> None:
del conf["ask_strategy"]
def _validate_freqai_hyperopt(conf: Dict[str, Any]) -> None:
def _validate_freqai_hyperopt(conf: dict[str, Any]) -> None:
freqai_enabled = conf.get("freqai", {}).get("enabled", False)
analyze_per_epoch = conf.get("analyze_per_epoch", False)
if analyze_per_epoch and freqai_enabled:
@@ -373,7 +336,7 @@ def _validate_freqai_hyperopt(conf: Dict[str, Any]) -> None:
)
def _validate_freqai_include_timeframes(conf: Dict[str, Any], preliminary: bool) -> None:
def _validate_freqai_include_timeframes(conf: dict[str, Any], preliminary: bool) -> None:
freqai_enabled = conf.get("freqai", {}).get("enabled", False)
if freqai_enabled:
main_tf = conf.get("timeframe", "5m")
@@ -404,7 +367,7 @@ def _validate_freqai_include_timeframes(conf: Dict[str, Any], preliminary: bool)
)
def _validate_freqai_backtest(conf: Dict[str, Any]) -> None:
def _validate_freqai_backtest(conf: dict[str, Any]) -> None:
if conf.get("runmode", RunMode.OTHER) == RunMode.BACKTEST:
freqai_enabled = conf.get("freqai", {}).get("enabled", False)
timerange = conf.get("timerange")
@@ -427,7 +390,7 @@ def _validate_freqai_backtest(conf: Dict[str, Any]) -> None:
)
def _validate_consumers(conf: Dict[str, Any]) -> None:
def _validate_consumers(conf: dict[str, Any]) -> None:
emc_conf = conf.get("external_message_consumer", {})
if emc_conf.get("enabled", False):
if len(emc_conf.get("producers", [])) < 1:
@@ -447,7 +410,7 @@ def _validate_consumers(conf: Dict[str, Any]) -> None:
)
def _validate_orderflow(conf: Dict[str, Any]) -> None:
def _validate_orderflow(conf: dict[str, Any]) -> None:
if conf.get("exchange", {}).get("use_public_trades"):
if "orderflow" not in conf:
raise ConfigurationError(
@@ -455,7 +418,7 @@ def _validate_orderflow(conf: Dict[str, Any]) -> None:
)
def _strategy_settings(conf: Dict[str, Any]) -> None:
def _strategy_settings(conf: dict[str, Any]) -> None:
process_deprecated_setting(conf, None, "use_sell_signal", None, "use_exit_signal")
process_deprecated_setting(conf, None, "sell_profit_only", None, "exit_profit_only")
process_deprecated_setting(conf, None, "sell_profit_offset", None, "exit_profit_offset")
+10 -13
View File
@@ -5,9 +5,10 @@ This module contains the configuration class
import ast
import logging
import warnings
from collections.abc import Callable
from copy import deepcopy
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional, Tuple
from typing import Any
from freqtrade import constants
from freqtrade.configuration.deprecated_settings import process_temporary_deprecated_settings
@@ -37,9 +38,9 @@ class Configuration:
Reuse this class for the bot, backtesting, hyperopt and every script that required configuration
"""
def __init__(self, args: Dict[str, Any], runmode: Optional[RunMode] = None) -> None:
def __init__(self, args: dict[str, Any], runmode: RunMode | None = None) -> None:
self.args = args
self.config: Optional[Config] = None
self.config: Config | None = None
self.runmode = runmode
def get_config(self) -> Config:
@@ -53,7 +54,7 @@ class Configuration:
return self.config
@staticmethod
def from_files(files: List[str]) -> Dict[str, Any]:
def from_files(files: list[str]) -> dict[str, Any]:
"""
Iterate through the config files passed in, loading all of them
and merging their contents.
@@ -68,7 +69,7 @@ class Configuration:
c = Configuration({"config": files}, RunMode.OTHER)
return c.get_config()
def load_config(self) -> Dict[str, Any]:
def load_config(self) -> dict[str, Any]:
"""
Extract information for sys.argv and load the bot configuration
:return: Configuration dictionary
@@ -241,11 +242,7 @@ class Configuration:
logstring="Parameter --enable-protections detected, enabling Protections. ...",
)
if "use_max_market_positions" in self.args and not self.args["use_max_market_positions"]:
config.update({"use_max_market_positions": False})
logger.info("Parameter --disable-max-market-positions detected ...")
logger.info("max_open_trades set to unlimited ...")
elif "max_open_trades" in self.args and self.args["max_open_trades"]:
if "max_open_trades" in self.args and self.args["max_open_trades"]:
config.update({"max_open_trades": self.args["max_open_trades"]})
logger.info(
"Parameter --max-open-trades detected, overriding max_open_trades to: %s ...",
@@ -421,7 +418,7 @@ class Configuration:
]
self._args_to_config_loop(config, configurations)
def _args_to_config_loop(self, config, configurations: List[Tuple[str, str]]) -> None:
def _args_to_config_loop(self, config, configurations: list[tuple[str, str]]) -> None:
for argname, logstring in configurations:
self._args_to_config(config, argname=argname, logstring=logstring)
@@ -455,8 +452,8 @@ class Configuration:
config: Config,
argname: str,
logstring: str,
logfun: Optional[Callable] = None,
deprecated_msg: Optional[str] = None,
logfun: Callable | None = None,
deprecated_msg: str | None = None,
) -> None:
"""
:param config: Configuration dictionary
+250
View File
@@ -0,0 +1,250 @@
import logging
import secrets
from pathlib import Path
from typing import Any
from questionary import Separator, prompt
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__)
def validate_is_int(val):
try:
_ = int(val)
return True
except Exception:
return False
def validate_is_float(val):
try:
_ = float(val)
return True
except Exception:
return False
def ask_user_overwrite(config_path: Path) -> bool:
questions = [
{
"type": "confirm",
"name": "overwrite",
"message": f"File {config_path} already exists. Overwrite?",
"default": False,
},
]
answers = prompt(questions)
return answers["overwrite"]
def ask_user_config() -> dict[str, Any]:
"""
Ask user a few questions to build the configuration.
Interactive questions built using https://github.com/tmbo/questionary
:returns: Dict with keys to put into template
"""
from freqtrade.configuration.detect_environment import running_in_docker
from freqtrade.exchange import available_exchanges
questions: list[dict[str, Any]] = [
{
"type": "confirm",
"name": "dry_run",
"message": "Do you want to enable Dry-run (simulated trades)?",
"default": True,
},
{
"type": "text",
"name": "stake_currency",
"message": "Please insert your stake currency:",
"default": "USDT",
},
{
"type": "text",
"name": "stake_amount",
"message": f"Please insert your stake amount (Number or '{UNLIMITED_STAKE_AMOUNT}'):",
"default": "unlimited",
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val),
"filter": lambda val: (
'"' + UNLIMITED_STAKE_AMOUNT + '"' if val == UNLIMITED_STAKE_AMOUNT else val
),
},
{
"type": "text",
"name": "max_open_trades",
"message": "Please insert max_open_trades (Integer or -1 for unlimited open trades):",
"default": "3",
"validate": lambda val: validate_is_int(val),
},
{
"type": "select",
"name": "timeframe_in_config",
"message": "Time",
"choices": ["Have the strategy define timeframe.", "Override in configuration."],
},
{
"type": "text",
"name": "timeframe",
"message": "Please insert your desired timeframe (e.g. 5m):",
"default": "5m",
"when": lambda x: x["timeframe_in_config"] == "Override in configuration.",
},
{
"type": "text",
"name": "fiat_display_currency",
"message": (
"Please insert your display Currency for reporting "
"(leave empty to disable FIAT conversion):"
),
"default": "USD",
},
{
"type": "select",
"name": "exchange_name",
"message": "Select exchange",
"choices": [
"binance",
"binanceus",
"bingx",
"gate",
"htx",
"kraken",
"kucoin",
"okx",
Separator("------------------"),
"other",
],
},
{
"type": "confirm",
"name": "trading_mode",
"message": "Do you want to trade Perpetual Swaps (perpetual futures)?",
"default": False,
"filter": lambda val: "futures" if val else "spot",
"when": lambda x: x["exchange_name"] in ["binance", "gate", "okx", "bybit"],
},
{
"type": "autocomplete",
"name": "exchange_name",
"message": "Type your exchange name (Must be supported by ccxt)",
"choices": available_exchanges(),
"when": lambda x: x["exchange_name"] == "other",
},
{
"type": "password",
"name": "exchange_key",
"message": "Insert Exchange Key",
"when": lambda x: not x["dry_run"],
},
{
"type": "password",
"name": "exchange_secret",
"message": "Insert Exchange Secret",
"when": lambda x: not x["dry_run"],
},
{
"type": "password",
"name": "exchange_key_password",
"message": "Insert Exchange API Key password",
"when": lambda x: not x["dry_run"] and x["exchange_name"] in ("kucoin", "okx"),
},
{
"type": "confirm",
"name": "telegram",
"message": "Do you want to enable Telegram?",
"default": False,
},
{
"type": "password",
"name": "telegram_token",
"message": "Insert Telegram token",
"when": lambda x: x["telegram"],
},
{
"type": "password",
"name": "telegram_chat_id",
"message": "Insert Telegram chat id",
"when": lambda x: x["telegram"],
},
{
"type": "confirm",
"name": "api_server",
"message": "Do you want to enable the Rest API (includes FreqUI)?",
"default": False,
},
{
"type": "text",
"name": "api_server_listen_addr",
"message": (
"Insert Api server Listen Address (0.0.0.0 for docker, "
"otherwise best left untouched)"
),
"default": "127.0.0.1" if not running_in_docker() else "0.0.0.0", # noqa: S104
"when": lambda x: x["api_server"],
},
{
"type": "text",
"name": "api_server_username",
"message": "Insert api-server username",
"default": "freqtrader",
"when": lambda x: x["api_server"],
},
{
"type": "password",
"name": "api_server_password",
"message": "Insert api-server password",
"when": lambda x: x["api_server"],
},
]
answers = prompt(questions)
if not answers:
# Interrupted questionary sessions return an empty dict.
raise OperationalException("User interrupted interactive questions.")
# Ensure default is set for non-futures exchanges
answers["trading_mode"] = answers.get("trading_mode", "spot")
answers["margin_mode"] = "isolated" if answers.get("trading_mode") == "futures" else ""
# Force JWT token to be a random string
answers["api_server_jwt_key"] = secrets.token_hex()
answers["api_server_ws_token"] = secrets.token_urlsafe(25)
return answers
def deploy_new_config(config_path: Path, selections: dict[str, Any]) -> None:
"""
Applies selections to the template and writes the result to config_path
:param config_path: Path object for new config file. Should not exist yet
:param selections: Dict containing selections taken by the user.
"""
from jinja2.exceptions import TemplateNotFound
from freqtrade.exchange import MAP_EXCHANGE_CHILDCLASS
from freqtrade.util import render_template
try:
exchange_template = MAP_EXCHANGE_CHILDCLASS.get(
selections["exchange_name"], selections["exchange_name"]
)
selections["exchange"] = render_template(
templatefile=f"subtemplates/exchange_{exchange_template}.j2", arguments=selections
)
except TemplateNotFound:
selections["exchange"] = render_template(
templatefile="subtemplates/exchange_generic.j2", arguments=selections
)
config_text = render_template(templatefile="base_config.json.j2", arguments=selections)
logger.info(f"Writing config to `{config_path}`.")
logger.info(
"Please make sure to check the configuration contents and adjust settings to your needs."
)
config_path.write_text(config_text)
@@ -3,7 +3,6 @@ Functions to handle deprecated settings
"""
import logging
from typing import Optional
from freqtrade.constants import Config
from freqtrade.exceptions import ConfigurationError, OperationalException
@@ -14,9 +13,9 @@ logger = logging.getLogger(__name__)
def check_conflicting_settings(
config: Config,
section_old: Optional[str],
section_old: str | None,
name_old: str,
section_new: Optional[str],
section_new: str | None,
name_new: str,
) -> None:
section_new_config = config.get(section_new, {}) if section_new else config
@@ -34,7 +33,7 @@ def check_conflicting_settings(
def process_removed_setting(
config: Config, section1: str, name1: str, section2: Optional[str], name2: str
config: Config, section1: str, name1: str, section2: str | None, name2: str
) -> None:
"""
:param section1: Removed section
@@ -54,9 +53,9 @@ def process_removed_setting(
def process_deprecated_setting(
config: Config,
section_old: Optional[str],
section_old: str | None,
name_old: str,
section_new: Optional[str],
section_new: str | None,
name_new: str,
) -> None:
check_conflicting_settings(config, section_old, name_old, section_new, name_new)
@@ -177,4 +176,6 @@ def process_temporary_deprecated_settings(config: Config) -> None:
)
if "protections" in config:
logger.warning("DEPRECATED: Setting 'protections' in the configuration is deprecated.")
raise ConfigurationError(
"DEPRECATED: Setting 'protections' in the configuration is deprecated."
)
@@ -1,7 +1,6 @@
import logging
import shutil
from pathlib import Path
from typing import Optional
from freqtrade.configuration.detect_environment import running_in_docker
from freqtrade.constants import (
@@ -18,7 +17,7 @@ from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__)
def create_datadir(config: Config, datadir: Optional[str] = None) -> Path:
def create_datadir(config: Config, datadir: str | None = None) -> Path:
folder = Path(datadir) if datadir else Path(f"{config['user_data_dir']}/data")
if not datadir:
# set datadir
+4 -4
View File
@@ -1,6 +1,6 @@
import logging
import os
from typing import Any, Dict
from typing import Any
from freqtrade.constants import ENV_VAR_PREFIX
from freqtrade.misc import deep_merge_dicts
@@ -24,7 +24,7 @@ def _get_var_typed(val):
return val
def _flat_vars_to_nested_dict(env_dict: Dict[str, Any], prefix: str) -> Dict[str, Any]:
def _flat_vars_to_nested_dict(env_dict: dict[str, Any], prefix: str) -> dict[str, Any]:
"""
Environment variables must be prefixed with FREQTRADE.
FREQTRADE__{section}__{key}
@@ -33,7 +33,7 @@ def _flat_vars_to_nested_dict(env_dict: Dict[str, Any], prefix: str) -> Dict[str
:return: Nested dict based on available and relevant variables.
"""
no_convert = ["CHAT_ID", "PASSWORD"]
relevant_vars: Dict[str, Any] = {}
relevant_vars: dict[str, Any] = {}
for env_var, val in sorted(env_dict.items()):
if env_var.startswith(prefix):
@@ -51,7 +51,7 @@ def _flat_vars_to_nested_dict(env_dict: Dict[str, Any], prefix: str) -> Dict[str
return relevant_vars
def enironment_vars_to_dict() -> Dict[str, Any]:
def enironment_vars_to_dict() -> dict[str, Any]:
"""
Read environment variables and return a nested dict for relevant variables
Relevant variables must follow the FREQTRADE__{section}__{key} pattern
+5 -5
View File
@@ -7,7 +7,7 @@ import re
import sys
from copy import deepcopy
from pathlib import Path
from typing import Any, Dict, List, Optional
from typing import Any
import rapidjson
@@ -42,7 +42,7 @@ def log_config_error_range(path: str, errmsg: str) -> str:
return ""
def load_file(path: Path) -> Dict[str, Any]:
def load_file(path: Path) -> dict[str, Any]:
try:
with path.open("r") as file:
config = rapidjson.load(file, parse_mode=CONFIG_PARSE_MODE)
@@ -51,7 +51,7 @@ def load_file(path: Path) -> Dict[str, Any]:
return config
def load_config_file(path: str) -> Dict[str, Any]:
def load_config_file(path: str) -> dict[str, Any]:
"""
Loads a config file from the given path
:param path: path as str
@@ -78,8 +78,8 @@ def load_config_file(path: str) -> Dict[str, Any]:
def load_from_files(
files: List[str], base_path: Optional[Path] = None, level: int = 0
) -> Dict[str, Any]:
files: list[str], base_path: Path | None = None, level: int = 0
) -> dict[str, Any]:
"""
Recursively load configuration files if specified.
Sub-files are assumed to be relative to the initial config.
+7 -8
View File
@@ -5,7 +5,6 @@ This module contains the argument manager class
import logging
import re
from datetime import datetime, timezone
from typing import Optional
from typing_extensions import Self
@@ -25,24 +24,24 @@ class TimeRange:
def __init__(
self,
starttype: Optional[str] = None,
stoptype: Optional[str] = None,
starttype: str | None = None,
stoptype: str | None = None,
startts: int = 0,
stopts: int = 0,
):
self.starttype: Optional[str] = starttype
self.stoptype: Optional[str] = stoptype
self.starttype: str | None = starttype
self.stoptype: str | None = stoptype
self.startts: int = startts
self.stopts: int = stopts
@property
def startdt(self) -> Optional[datetime]:
def startdt(self) -> datetime | None:
if self.startts:
return datetime.fromtimestamp(self.startts, tz=timezone.utc)
return None
@property
def stopdt(self) -> Optional[datetime]:
def stopdt(self) -> datetime | None:
if self.stopts:
return datetime.fromtimestamp(self.stopts, tz=timezone.utc)
return None
@@ -120,7 +119,7 @@ class TimeRange:
self.starttype = "date"
@classmethod
def parse_timerange(cls, text: Optional[str]) -> Self:
def parse_timerange(cls, text: str | None) -> Self:
"""
Parse the value of the argument --timerange to determine what is the range desired
:param text: value from --timerange
+10 -10
View File
@@ -4,7 +4,7 @@
bot constants
"""
from typing import Any, Dict, List, Literal, Optional, Tuple
from typing import Any, Literal
from freqtrade.enums import CandleType, PriceType
@@ -38,6 +38,7 @@ HYPEROPT_LOSS_BUILTIN = [
"MaxDrawDownHyperOptLoss",
"MaxDrawDownRelativeHyperOptLoss",
"ProfitDrawDownHyperOptLoss",
"MultiMetricHyperOptLoss",
]
AVAILABLE_PAIRLISTS = [
"StaticPairList",
@@ -57,7 +58,6 @@ AVAILABLE_PAIRLISTS = [
"SpreadFilter",
"VolatilityFilter",
]
AVAILABLE_PROTECTIONS = ["CooldownPeriod", "LowProfitPairs", "MaxDrawdown", "StoplossGuard"]
AVAILABLE_DATAHANDLERS = ["json", "jsongz", "hdf5", "feather", "parquet"]
BACKTEST_BREAKDOWNS = ["day", "week", "month"]
BACKTEST_CACHE_AGE = ["none", "day", "week", "month"]
@@ -98,7 +98,7 @@ DL_DATA_TIMEFRAMES = ["1m", "5m"]
ENV_VAR_PREFIX = "FREQTRADE__"
CANCELED_EXCHANGE_STATES = ("cancelled", "canceled", "expired")
CANCELED_EXCHANGE_STATES = ("cancelled", "canceled", "expired", "rejected")
NON_OPEN_EXCHANGE_STATES = CANCELED_EXCHANGE_STATES + ("closed",)
# Define decimals per coin for outputs
@@ -188,14 +188,14 @@ CANCEL_REASON = {
}
# List of pairs with their timeframes
PairWithTimeframe = Tuple[str, str, CandleType]
ListPairsWithTimeframes = List[PairWithTimeframe]
PairWithTimeframe = tuple[str, str, CandleType]
ListPairsWithTimeframes = list[PairWithTimeframe]
# Type for trades list
TradeList = List[List]
TradeList = list[list]
# ticks, pair, timeframe, CandleType
TickWithTimeframe = Tuple[str, str, CandleType, Optional[int], Optional[int]]
ListTicksWithTimeframes = List[TickWithTimeframe]
TickWithTimeframe = tuple[str, str, CandleType, int | None, int | None]
ListTicksWithTimeframes = list[TickWithTimeframe]
LongShort = Literal["long", "short"]
EntryExit = Literal["entry", "exit"]
@@ -204,9 +204,9 @@ MakerTaker = Literal["maker", "taker"]
BidAsk = Literal["bid", "ask"]
OBLiteral = Literal["asks", "bids"]
Config = Dict[str, Any]
Config = dict[str, Any]
# Exchange part of the configuration.
ExchangeConfig = Dict[str, Any]
ExchangeConfig = dict[str, Any]
IntOrInf = float
+24 -25
View File
@@ -6,7 +6,7 @@ import logging
from copy import copy
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Literal, Optional, Union
from typing import Any, Literal
import numpy as np
import pandas as pd
@@ -53,7 +53,7 @@ BT_DATA_COLUMNS = [
]
def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str:
def get_latest_optimize_filename(directory: Path | str, variant: str) -> str:
"""
Get latest backtest export based on '.last_result.json'.
:param directory: Directory to search for last result
@@ -84,7 +84,7 @@ def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> s
return data[f"latest_{variant}"]
def get_latest_backtest_filename(directory: Union[Path, str]) -> str:
def get_latest_backtest_filename(directory: Path | str) -> str:
"""
Get latest backtest export based on '.last_result.json'.
:param directory: Directory to search for last result
@@ -97,7 +97,7 @@ def get_latest_backtest_filename(directory: Union[Path, str]) -> str:
return get_latest_optimize_filename(directory, "backtest")
def get_latest_hyperopt_filename(directory: Union[Path, str]) -> str:
def get_latest_hyperopt_filename(directory: Path | str) -> str:
"""
Get latest hyperopt export based on '.last_result.json'.
:param directory: Directory to search for last result
@@ -114,9 +114,7 @@ def get_latest_hyperopt_filename(directory: Union[Path, str]) -> str:
return "hyperopt_results.pickle"
def get_latest_hyperopt_file(
directory: Union[Path, str], predef_filename: Optional[str] = None
) -> Path:
def get_latest_hyperopt_file(directory: Path | str, predef_filename: str | None = None) -> Path:
"""
Get latest hyperopt export based on '.last_result.json'.
:param directory: Directory to search for last result
@@ -137,7 +135,7 @@ def get_latest_hyperopt_file(
return directory / get_latest_hyperopt_filename(directory)
def load_backtest_metadata(filename: Union[Path, str]) -> Dict[str, Any]:
def load_backtest_metadata(filename: Path | str) -> dict[str, Any]:
"""
Read metadata dictionary from backtest results file without reading and deserializing entire
file.
@@ -154,7 +152,7 @@ def load_backtest_metadata(filename: Union[Path, str]) -> Dict[str, Any]:
raise OperationalException("Unexpected error while loading backtest metadata.") from e
def load_backtest_stats(filename: Union[Path, str]) -> BacktestResultType:
def load_backtest_stats(filename: Path | str) -> BacktestResultType:
"""
Load backtest statistics file.
:param filename: pathlib.Path object, or string pointing to the file.
@@ -176,7 +174,7 @@ def load_backtest_stats(filename: Union[Path, str]) -> BacktestResultType:
return data
def load_and_merge_backtest_result(strategy_name: str, filename: Path, results: Dict[str, Any]):
def load_and_merge_backtest_result(strategy_name: str, filename: Path, results: dict[str, Any]):
"""
Load one strategy from multi-strategy result and merge it with results
:param strategy_name: Name of the strategy contained in the result
@@ -195,12 +193,12 @@ def load_and_merge_backtest_result(strategy_name: str, filename: Path, results:
break
def _get_backtest_files(dirname: Path) -> List[Path]:
def _get_backtest_files(dirname: Path) -> list[Path]:
# Weird glob expression here avoids including .meta.json files.
return list(reversed(sorted(dirname.glob("backtest-result-*-[0-9][0-9].json"))))
def _extract_backtest_result(filename: Path) -> List[BacktestHistoryEntryType]:
def _extract_backtest_result(filename: Path) -> list[BacktestHistoryEntryType]:
metadata = load_backtest_metadata(filename)
return [
{
@@ -220,14 +218,14 @@ def _extract_backtest_result(filename: Path) -> List[BacktestHistoryEntryType]:
]
def get_backtest_result(filename: Path) -> List[BacktestHistoryEntryType]:
def get_backtest_result(filename: Path) -> list[BacktestHistoryEntryType]:
"""
Get backtest result read from metadata file
"""
return _extract_backtest_result(filename)
def get_backtest_resultlist(dirname: Path) -> List[BacktestHistoryEntryType]:
def get_backtest_resultlist(dirname: Path) -> list[BacktestHistoryEntryType]:
"""
Get list of backtest results read from metadata files
"""
@@ -244,12 +242,13 @@ def delete_backtest_result(file_abs: Path):
"""
# *.meta.json
logger.info(f"Deleting backtest result file: {file_abs.name}")
file_abs_meta = file_abs.with_suffix(".meta.json")
file_abs.unlink()
file_abs_meta.unlink()
for file in file_abs.parent.glob(f"{file_abs.stem}*"):
logger.info(f"Deleting file: {file}")
file.unlink()
def update_backtest_metadata(filename: Path, strategy: str, content: Dict[str, Any]):
def update_backtest_metadata(filename: Path, strategy: str, content: dict[str, Any]):
"""
Updates backtest metadata file with new content.
:raises: ValueError if metadata file does not exist, or strategy is not in this file.
@@ -275,8 +274,8 @@ def get_backtest_market_change(filename: Path, include_ts: bool = True) -> pd.Da
def find_existing_backtest_stats(
dirname: Union[Path, str], run_ids: Dict[str, str], min_backtest_date: Optional[datetime] = None
) -> Dict[str, Any]:
dirname: Path | str, run_ids: dict[str, str], min_backtest_date: datetime | None = None
) -> dict[str, Any]:
"""
Find existing backtest stats that match specified run IDs and load them.
:param dirname: pathlib.Path object, or string pointing to the file.
@@ -287,7 +286,7 @@ def find_existing_backtest_stats(
# Copy so we can modify this dict without affecting parent scope.
run_ids = copy(run_ids)
dirname = Path(dirname)
results: Dict[str, Any] = {
results: dict[str, Any] = {
"metadata": {},
"strategy": {},
"strategy_comparison": [],
@@ -344,7 +343,7 @@ def _load_backtest_data_df_compatibility(df: pd.DataFrame) -> pd.DataFrame:
return df
def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = None) -> pd.DataFrame:
def load_backtest_data(filename: Path | str, strategy: str | None = None) -> pd.DataFrame:
"""
Load backtest data file.
:param filename: pathlib.Path object, or string pointing to a file or directory
@@ -438,7 +437,7 @@ def evaluate_result_multi(
return df_final[df_final["open_trades"] > max_open_trades]
def trade_list_to_dataframe(trades: Union[List[Trade], List[LocalTrade]]) -> pd.DataFrame:
def trade_list_to_dataframe(trades: list[Trade] | list[LocalTrade]) -> pd.DataFrame:
"""
Convert list of Trade objects to pandas Dataframe
:param trades: List of trade objects
@@ -452,7 +451,7 @@ def trade_list_to_dataframe(trades: Union[List[Trade], List[LocalTrade]]) -> pd.
return df
def load_trades_from_db(db_url: str, strategy: Optional[str] = None) -> pd.DataFrame:
def load_trades_from_db(db_url: str, strategy: str | None = None) -> pd.DataFrame:
"""
Load trades from a DB (using dburl)
:param db_url: Sqlite url (default format sqlite:///tradesv3.dry-run.sqlite)
@@ -475,7 +474,7 @@ def load_trades(
db_url: str,
exportfilename: Path,
no_trades: bool = False,
strategy: Optional[str] = None,
strategy: str | None = None,
) -> pd.DataFrame:
"""
Based on configuration option 'trade_source':
+3 -4
View File
@@ -3,7 +3,6 @@ Functions to convert data from one format to another
"""
import logging
from typing import Dict
import numpy as np
import pandas as pd
@@ -158,8 +157,8 @@ def trim_dataframe(
def trim_dataframes(
preprocessed: Dict[str, DataFrame], timerange, startup_candles: int
) -> Dict[str, DataFrame]:
preprocessed: dict[str, DataFrame], timerange, startup_candles: int
) -> dict[str, DataFrame]:
"""
Trim startup period from analyzed dataframes
:param preprocessed: Dict of pair: dataframe
@@ -167,7 +166,7 @@ def trim_dataframes(
:param startup_candles: Startup-candles that should be removed
:return: Dict of trimmed dataframes
"""
processed: Dict[str, DataFrame] = {}
processed: dict[str, DataFrame] = {}
for pair, df in preprocessed.items():
trimed_df = trim_dataframe(df, timerange, startup_candles=startup_candles)
+2 -3
View File
@@ -7,7 +7,6 @@ import time
import typing
from collections import OrderedDict
from datetime import datetime
from typing import Tuple
import numpy as np
import pandas as pd
@@ -62,11 +61,11 @@ def _calculate_ohlcv_candle_start_and_end(df: pd.DataFrame, timeframe: str):
def populate_dataframe_with_trades(
cached_grouped_trades: OrderedDict[Tuple[datetime, datetime], pd.DataFrame],
cached_grouped_trades: OrderedDict[tuple[datetime, datetime], pd.DataFrame],
config: Config,
dataframe: pd.DataFrame,
trades: pd.DataFrame,
) -> Tuple[pd.DataFrame, OrderedDict[Tuple[datetime, datetime], pd.DataFrame]]:
) -> tuple[pd.DataFrame, OrderedDict[tuple[datetime, datetime], pd.DataFrame]]:
"""
Populates a dataframe with trades
:param dataframe: Dataframe to populate
+3 -4
View File
@@ -4,7 +4,6 @@ Functions to convert data from one format to another
import logging
from pathlib import Path
from typing import Dict, List
import pandas as pd
from pandas import DataFrame, to_datetime
@@ -34,7 +33,7 @@ def trades_df_remove_duplicates(trades: pd.DataFrame) -> pd.DataFrame:
return trades.drop_duplicates(subset=["timestamp", "id"])
def trades_dict_to_list(trades: List[Dict]) -> TradeList:
def trades_dict_to_list(trades: list[dict]) -> TradeList:
"""
Convert fetch_trades result into a List (to be more memory efficient).
:param trades: List of trades, as returned by ccxt.fetch_trades.
@@ -91,8 +90,8 @@ def trades_to_ohlcv(trades: DataFrame, timeframe: str) -> DataFrame:
def convert_trades_to_ohlcv(
pairs: List[str],
timeframes: List[str],
pairs: list[str],
timeframes: list[str],
datadir: Path,
timerange: TimeRange,
erase: bool,
+23 -23
View File
@@ -8,7 +8,7 @@ Common Interface for bot and strategy to access data.
import logging
from collections import deque
from datetime import datetime, timezone
from typing import Any, Dict, List, Optional, Tuple
from typing import Any
from pandas import DataFrame, Timedelta, Timestamp, to_timedelta
@@ -40,23 +40,23 @@ class DataProvider:
def __init__(
self,
config: Config,
exchange: Optional[Exchange],
exchange: Exchange | None,
pairlists=None,
rpc: Optional[RPCManager] = None,
rpc: RPCManager | None = None,
) -> None:
self._config = config
self._exchange = exchange
self._pairlists = pairlists
self.__rpc = rpc
self.__cached_pairs: Dict[PairWithTimeframe, Tuple[DataFrame, datetime]] = {}
self.__slice_index: Optional[int] = None
self.__slice_date: Optional[datetime] = None
self.__cached_pairs: dict[PairWithTimeframe, tuple[DataFrame, datetime]] = {}
self.__slice_index: int | None = None
self.__slice_date: datetime | None = None
self.__cached_pairs_backtesting: Dict[PairWithTimeframe, DataFrame] = {}
self.__producer_pairs_df: Dict[
str, Dict[PairWithTimeframe, Tuple[DataFrame, datetime]]
self.__cached_pairs_backtesting: dict[PairWithTimeframe, DataFrame] = {}
self.__producer_pairs_df: dict[
str, dict[PairWithTimeframe, tuple[DataFrame, datetime]]
] = {}
self.__producer_pairs: Dict[str, List[str]] = {}
self.__producer_pairs: dict[str, list[str]] = {}
self._msg_queue: deque = deque()
self._default_candle_type = self._config.get("candle_type_def", CandleType.SPOT)
@@ -101,7 +101,7 @@ class DataProvider:
self.__cached_pairs[pair_key] = (dataframe, datetime.now(timezone.utc))
# For multiple producers we will want to merge the pairlists instead of overwriting
def _set_producer_pairs(self, pairlist: List[str], producer_name: str = "default"):
def _set_producer_pairs(self, pairlist: list[str], producer_name: str = "default"):
"""
Set the pairs received to later be used.
@@ -109,7 +109,7 @@ class DataProvider:
"""
self.__producer_pairs[producer_name] = pairlist
def get_producer_pairs(self, producer_name: str = "default") -> List[str]:
def get_producer_pairs(self, producer_name: str = "default") -> list[str]:
"""
Get the pairs cached from the producer
@@ -177,7 +177,7 @@ class DataProvider:
timeframe: str,
candle_type: CandleType,
producer_name: str = "default",
) -> Tuple[bool, int]:
) -> tuple[bool, int]:
"""
Append a candle to the existing external dataframe. The incoming dataframe
must have at least 1 candle.
@@ -255,10 +255,10 @@ class DataProvider:
def get_producer_df(
self,
pair: str,
timeframe: Optional[str] = None,
candle_type: Optional[CandleType] = None,
timeframe: str | None = None,
candle_type: CandleType | None = None,
producer_name: str = "default",
) -> Tuple[DataFrame, datetime]:
) -> tuple[DataFrame, datetime]:
"""
Get the pair data from producers.
@@ -349,7 +349,7 @@ class DataProvider:
return total_candles
def get_pair_dataframe(
self, pair: str, timeframe: Optional[str] = None, candle_type: str = ""
self, pair: str, timeframe: str | None = None, candle_type: str = ""
) -> DataFrame:
"""
Return pair candle (OHLCV) data, either live or cached historical -- depending
@@ -377,7 +377,7 @@ class DataProvider:
logger.warning(f"No data found for ({pair}, {timeframe}, {candle_type}).")
return data
def get_analyzed_dataframe(self, pair: str, timeframe: str) -> Tuple[DataFrame, datetime]:
def get_analyzed_dataframe(self, pair: str, timeframe: str) -> tuple[DataFrame, datetime]:
"""
Retrieve the analyzed dataframe. Returns the full dataframe in trade mode (live / dry),
and the last 1000 candles (up to the time evaluated at this moment) in all other modes.
@@ -408,7 +408,7 @@ class DataProvider:
"""
return RunMode(self._config.get("runmode", RunMode.OTHER))
def current_whitelist(self) -> List[str]:
def current_whitelist(self) -> list[str]:
"""
fetch latest available whitelist.
@@ -437,7 +437,7 @@ class DataProvider:
def refresh(
self,
pairlist: ListPairsWithTimeframes,
helping_pairs: Optional[ListPairsWithTimeframes] = None,
helping_pairs: ListPairsWithTimeframes | None = None,
) -> None:
"""
Refresh data, called with each cycle
@@ -471,7 +471,7 @@ class DataProvider:
return list(self._exchange._klines.keys())
def ohlcv(
self, pair: str, timeframe: Optional[str] = None, copy: bool = True, candle_type: str = ""
self, pair: str, timeframe: str | None = None, copy: bool = True, candle_type: str = ""
) -> DataFrame:
"""
Get candle (OHLCV) data for the given pair as DataFrame
@@ -497,7 +497,7 @@ class DataProvider:
return DataFrame()
def trades(
self, pair: str, timeframe: Optional[str] = None, copy: bool = True, candle_type: str = ""
self, pair: str, timeframe: str | None = None, copy: bool = True, candle_type: str = ""
) -> DataFrame:
"""
Get candle (TRADES) data for the given pair as DataFrame
@@ -529,7 +529,7 @@ class DataProvider:
)
return trades_df
def market(self, pair: str) -> Optional[Dict[str, Any]]:
def market(self, pair: str) -> dict[str, Any] | None:
"""
Return market data for the pair
:param pair: Pair to get the data for
+6 -7
View File
@@ -1,6 +1,5 @@
import logging
from pathlib import Path
from typing import Dict, List
import joblib
import pandas as pd
@@ -48,14 +47,14 @@ def _load_signal_candles(backtest_dir: Path):
return _load_backtest_analysis_data(backtest_dir, "signals")
def _load_exit_signal_candles(backtest_dir: Path) -> Dict[str, Dict[str, pd.DataFrame]]:
def _load_exit_signal_candles(backtest_dir: Path) -> dict[str, dict[str, pd.DataFrame]]:
return _load_backtest_analysis_data(backtest_dir, "exited")
def _process_candles_and_indicators(
pairlist, strategy_name, trades, signal_candles, date_col: str = "open_date"
):
analysed_trades_dict: Dict[str, Dict] = {strategy_name: {}}
analysed_trades_dict: dict[str, dict] = {strategy_name: {}}
try:
logger.info(f"Processing {strategy_name} : {len(pairlist)} pairs")
@@ -261,8 +260,8 @@ def prepare_results(
def print_results(
res_df: pd.DataFrame,
exit_df: pd.DataFrame,
analysis_groups: List[str],
indicator_list: List[str],
analysis_groups: list[str],
indicator_list: list[str],
entry_only: bool,
exit_only: bool,
csv_path: Path,
@@ -307,7 +306,7 @@ def print_results(
def _merge_dfs(
entry_df: pd.DataFrame,
exit_df: pd.DataFrame,
available_inds: List[str],
available_inds: list[str],
entry_only: bool,
exit_only: bool,
):
@@ -438,7 +437,7 @@ def _generate_dfs(
pairlist: list,
enter_reason_list: list,
exit_reason_list: list,
signal_candles: Dict,
signal_candles: dict,
strategy_name: str,
timerange: TimeRange,
trades: pd.DataFrame,
@@ -1,5 +1,4 @@
import logging
from typing import Optional
from pandas import DataFrame, read_feather, to_datetime
@@ -37,7 +36,7 @@ class FeatherDataHandler(IDataHandler):
)
def _ohlcv_load(
self, pair: str, timeframe: str, timerange: Optional[TimeRange], candle_type: CandleType
self, pair: str, timeframe: str, timerange: TimeRange | None, candle_type: CandleType
) -> DataFrame:
"""
Internal method used to load data for one pair from disk.
@@ -59,20 +58,25 @@ class FeatherDataHandler(IDataHandler):
)
if not filename.exists():
return DataFrame(columns=self._columns)
pairdata = read_feather(filename)
pairdata.columns = self._columns
pairdata = pairdata.astype(
dtype={
"open": "float",
"high": "float",
"low": "float",
"close": "float",
"volume": "float",
}
)
pairdata["date"] = to_datetime(pairdata["date"], unit="ms", utc=True)
return pairdata
try:
pairdata = read_feather(filename)
pairdata.columns = self._columns
pairdata = pairdata.astype(
dtype={
"open": "float",
"high": "float",
"low": "float",
"close": "float",
"volume": "float",
}
)
pairdata["date"] = to_datetime(pairdata["date"], unit="ms", utc=True)
return pairdata
except Exception as e:
logger.exception(
f"Error loading data from {filename}. Exception: {e}. Returning empty dataframe."
)
return DataFrame(columns=self._columns)
def ohlcv_append(
self, pair: str, timeframe: str, data: DataFrame, candle_type: CandleType
@@ -108,7 +112,7 @@ class FeatherDataHandler(IDataHandler):
raise NotImplementedError()
def _trades_load(
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> DataFrame:
"""
Load a pair from file, either .json.gz or .json
@@ -1,5 +1,4 @@
import logging
from typing import Optional
import numpy as np
import pandas as pd
@@ -45,7 +44,7 @@ class HDF5DataHandler(IDataHandler):
)
def _ohlcv_load(
self, pair: str, timeframe: str, timerange: Optional[TimeRange], candle_type: CandleType
self, pair: str, timeframe: str, timerange: TimeRange | None, candle_type: CandleType
) -> pd.DataFrame:
"""
Internal method used to load data for one pair from disk.
@@ -69,28 +68,36 @@ class HDF5DataHandler(IDataHandler):
)
if not filename.exists():
return pd.DataFrame(columns=self._columns)
where = []
if timerange:
if timerange.starttype == "date":
where.append(f"date >= Timestamp({timerange.startts * 1e9})")
if timerange.stoptype == "date":
where.append(f"date <= Timestamp({timerange.stopts * 1e9})")
try:
where = []
if timerange:
if timerange.starttype == "date":
where.append(f"date >= Timestamp({timerange.startts * 1e9})")
if timerange.stoptype == "date":
where.append(f"date <= Timestamp({timerange.stopts * 1e9})")
pairdata = pd.read_hdf(filename, key=key, mode="r", where=where)
pairdata = pd.read_hdf(filename, key=key, mode="r", where=where)
if list(pairdata.columns) != self._columns:
raise ValueError("Wrong dataframe format")
pairdata = pairdata.astype(
dtype={
"open": "float",
"high": "float",
"low": "float",
"close": "float",
"volume": "float",
}
)
pairdata = pairdata.reset_index(drop=True)
return pairdata
if list(pairdata.columns) != self._columns:
raise ValueError("Wrong dataframe format")
pairdata = pairdata.astype(
dtype={
"open": "float",
"high": "float",
"low": "float",
"close": "float",
"volume": "float",
}
)
pairdata = pairdata.reset_index(drop=True)
return pairdata
except ValueError:
raise
except Exception as e:
logger.exception(
f"Error loading data from {filename}. Exception: {e}. Returning empty dataframe."
)
return pd.DataFrame(columns=self._columns)
def ohlcv_append(
self, pair: str, timeframe: str, data: pd.DataFrame, candle_type: CandleType
@@ -134,7 +141,7 @@ class HDF5DataHandler(IDataHandler):
raise NotImplementedError()
def _trades_load(
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> pd.DataFrame:
"""
Load a pair from h5 file.
@@ -10,7 +10,6 @@ from abc import ABC, abstractmethod
from copy import deepcopy
from datetime import datetime, timezone
from pathlib import Path
from typing import List, Optional, Tuple, Type
from pandas import DataFrame, to_datetime
@@ -71,7 +70,7 @@ class IDataHandler(ABC):
]
@classmethod
def ohlcv_get_pairs(cls, datadir: Path, timeframe: str, candle_type: CandleType) -> List[str]:
def ohlcv_get_pairs(cls, datadir: Path, timeframe: str, candle_type: CandleType) -> list[str]:
"""
Returns a list of all pairs with ohlcv data available in this datadir
for the specified timeframe
@@ -107,7 +106,7 @@ class IDataHandler(ABC):
def ohlcv_data_min_max(
self, pair: str, timeframe: str, candle_type: CandleType
) -> Tuple[datetime, datetime, int]:
) -> tuple[datetime, datetime, int]:
"""
Returns the min and max timestamp for the given pair and timeframe.
:param pair: Pair to get min/max for
@@ -126,7 +125,7 @@ class IDataHandler(ABC):
@abstractmethod
def _ohlcv_load(
self, pair: str, timeframe: str, timerange: Optional[TimeRange], candle_type: CandleType
self, pair: str, timeframe: str, timerange: TimeRange | None, candle_type: CandleType
) -> DataFrame:
"""
Internal method used to load data for one pair from disk.
@@ -168,7 +167,7 @@ class IDataHandler(ABC):
"""
@classmethod
def trades_get_available_data(cls, datadir: Path, trading_mode: TradingMode) -> List[str]:
def trades_get_available_data(cls, datadir: Path, trading_mode: TradingMode) -> list[str]:
"""
Returns a list of all pairs with ohlcv data available in this datadir
:param datadir: Directory to search for ohlcv files
@@ -191,7 +190,7 @@ class IDataHandler(ABC):
self,
pair: str,
trading_mode: TradingMode,
) -> Tuple[datetime, datetime, int]:
) -> tuple[datetime, datetime, int]:
"""
Returns the min and max timestamp for the given pair's trades data.
:param pair: Pair to get min/max for
@@ -212,7 +211,7 @@ class IDataHandler(ABC):
)
@classmethod
def trades_get_pairs(cls, datadir: Path) -> List[str]:
def trades_get_pairs(cls, datadir: Path) -> list[str]:
"""
Returns a list of all pairs for which trade data is available in this
:param datadir: Directory to search for ohlcv files
@@ -247,7 +246,7 @@ class IDataHandler(ABC):
@abstractmethod
def _trades_load(
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> DataFrame:
"""
Load a pair from file, either .json.gz or .json
@@ -282,7 +281,7 @@ class IDataHandler(ABC):
return False
def trades_load(
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> DataFrame:
"""
Load a pair from file, either .json.gz or .json
@@ -370,7 +369,7 @@ class IDataHandler(ABC):
timeframe: str,
candle_type: CandleType,
*,
timerange: Optional[TimeRange] = None,
timerange: TimeRange | None = None,
fill_missing: bool = True,
drop_incomplete: bool = False,
startup_candles: int = 0,
@@ -532,7 +531,7 @@ class IDataHandler(ABC):
Path(old_name).rename(new_name)
def get_datahandlerclass(datatype: str) -> Type[IDataHandler]:
def get_datahandlerclass(datatype: str) -> type[IDataHandler]:
"""
Get datahandler class.
Could be done using Resolvers, but since this may be called often and resolvers
@@ -566,7 +565,7 @@ def get_datahandlerclass(datatype: str) -> Type[IDataHandler]:
def get_datahandler(
datadir: Path, data_format: Optional[str] = None, data_handler: Optional[IDataHandler] = None
datadir: Path, data_format: str | None = None, data_handler: IDataHandler | None = None
) -> IDataHandler:
"""
:param datadir: Folder to save data
@@ -1,5 +1,4 @@
import logging
from typing import Optional
import numpy as np
from pandas import DataFrame, read_json, to_datetime
@@ -45,7 +44,7 @@ class JsonDataHandler(IDataHandler):
)
def _ohlcv_load(
self, pair: str, timeframe: str, timerange: Optional[TimeRange], candle_type: CandleType
self, pair: str, timeframe: str, timerange: TimeRange | None, candle_type: CandleType
) -> DataFrame:
"""
Internal method used to load data for one pair from disk.
@@ -119,7 +118,7 @@ class JsonDataHandler(IDataHandler):
raise NotImplementedError()
def _trades_load(
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> DataFrame:
"""
Load a pair from file, either .json.gz or .json
@@ -1,5 +1,4 @@
import logging
from typing import Optional
from pandas import DataFrame, read_parquet, to_datetime
@@ -35,7 +34,7 @@ class ParquetDataHandler(IDataHandler):
data.reset_index(drop=True).loc[:, self._columns].to_parquet(filename)
def _ohlcv_load(
self, pair: str, timeframe: str, timerange: Optional[TimeRange], candle_type: CandleType
self, pair: str, timeframe: str, timerange: TimeRange | None, candle_type: CandleType
) -> DataFrame:
"""
Internal method used to load data for one pair from disk.
@@ -57,20 +56,25 @@ class ParquetDataHandler(IDataHandler):
)
if not filename.exists():
return DataFrame(columns=self._columns)
pairdata = read_parquet(filename)
pairdata.columns = self._columns
pairdata = pairdata.astype(
dtype={
"open": "float",
"high": "float",
"low": "float",
"close": "float",
"volume": "float",
}
)
pairdata["date"] = to_datetime(pairdata["date"], unit="ms", utc=True)
return pairdata
try:
pairdata = read_parquet(filename)
pairdata.columns = self._columns
pairdata = pairdata.astype(
dtype={
"open": "float",
"high": "float",
"low": "float",
"close": "float",
"volume": "float",
}
)
pairdata["date"] = to_datetime(pairdata["date"], unit="ms", utc=True)
return pairdata
except Exception as e:
logger.exception(
f"Error loading data from {filename}. Exception: {e}. Returning empty dataframe."
)
return DataFrame(columns=self._columns)
def ohlcv_append(
self, pair: str, timeframe: str, data: DataFrame, candle_type: CandleType
@@ -106,7 +110,7 @@ class ParquetDataHandler(IDataHandler):
raise NotImplementedError()
def _trades_load(
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
self, pair: str, trading_mode: TradingMode, timerange: TimeRange | None = None
) -> DataFrame:
"""
Load a pair from file, either .json.gz or .json
+156 -131
View File
@@ -2,18 +2,11 @@ import logging
import operator
from datetime import datetime, timedelta
from pathlib import Path
from typing import Dict, List, Optional, Tuple
from pandas import DataFrame, concat
from freqtrade.configuration import TimeRange
from freqtrade.constants import (
DATETIME_PRINT_FORMAT,
DEFAULT_DATAFRAME_COLUMNS,
DL_DATA_TIMEFRAMES,
DOCS_LINK,
Config,
)
from freqtrade.constants import DATETIME_PRINT_FORMAT, DL_DATA_TIMEFRAMES, DOCS_LINK, Config
from freqtrade.data.converter import (
clean_ohlcv_dataframe,
convert_trades_to_ohlcv,
@@ -25,8 +18,9 @@ from freqtrade.enums import CandleType, TradingMode
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import Exchange
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
from freqtrade.util import dt_now, dt_ts, format_ms_time, get_progress_tracker
from freqtrade.util import dt_now, dt_ts, format_ms_time
from freqtrade.util.migrations import migrate_data
from freqtrade.util.progress_tracker import CustomProgress, retrieve_progress_tracker
logger = logging.getLogger(__name__)
@@ -37,12 +31,12 @@ def load_pair_history(
timeframe: str,
datadir: Path,
*,
timerange: Optional[TimeRange] = None,
timerange: TimeRange | None = None,
fill_up_missing: bool = True,
drop_incomplete: bool = False,
startup_candles: int = 0,
data_format: Optional[str] = None,
data_handler: Optional[IDataHandler] = None,
data_format: str | None = None,
data_handler: IDataHandler | None = None,
candle_type: CandleType = CandleType.SPOT,
) -> DataFrame:
"""
@@ -77,16 +71,16 @@ def load_pair_history(
def load_data(
datadir: Path,
timeframe: str,
pairs: List[str],
pairs: list[str],
*,
timerange: Optional[TimeRange] = None,
timerange: TimeRange | None = None,
fill_up_missing: bool = True,
startup_candles: int = 0,
fail_without_data: bool = False,
data_format: str = "feather",
candle_type: CandleType = CandleType.SPOT,
user_futures_funding_rate: Optional[int] = None,
) -> Dict[str, DataFrame]:
user_futures_funding_rate: int | None = None,
) -> dict[str, DataFrame]:
"""
Load ohlcv history data for a list of pairs.
@@ -101,7 +95,7 @@ def load_data(
:param candle_type: Any of the enum CandleType (must match trading mode!)
:return: dict(<pair>:<Dataframe>)
"""
result: Dict[str, DataFrame] = {}
result: dict[str, DataFrame] = {}
if startup_candles > 0 and timerange:
logger.info(f"Using indicator startup period: {startup_candles} ...")
@@ -135,10 +129,10 @@ def refresh_data(
*,
datadir: Path,
timeframe: str,
pairs: List[str],
pairs: list[str],
exchange: Exchange,
data_format: Optional[str] = None,
timerange: Optional[TimeRange] = None,
data_format: str | None = None,
timerange: TimeRange | None = None,
candle_type: CandleType,
) -> None:
"""
@@ -168,11 +162,11 @@ def refresh_data(
def _load_cached_data_for_updating(
pair: str,
timeframe: str,
timerange: Optional[TimeRange],
timerange: TimeRange | None,
data_handler: IDataHandler,
candle_type: CandleType,
prepend: bool = False,
) -> Tuple[DataFrame, Optional[int], Optional[int]]:
) -> tuple[DataFrame, int | None, int | None]:
"""
Load cached data to download more data.
If timerange is passed in, checks whether data from an before the stored data will be
@@ -200,14 +194,21 @@ def _load_cached_data_for_updating(
candle_type=candle_type,
)
if not data.empty:
if not prepend and start and start < data.iloc[0]["date"]:
# Earlier data than existing data requested, redownload all
data = DataFrame(columns=DEFAULT_DATAFRAME_COLUMNS)
if prepend:
end = data.iloc[0]["date"]
else:
if prepend:
end = data.iloc[0]["date"]
else:
start = data.iloc[-1]["date"]
if start and start < data.iloc[0]["date"]:
# Earlier data than existing data requested, Update start date
logger.info(
f"{pair}, {timeframe}, {candle_type}: "
f"Requested start date {start:{DATETIME_PRINT_FORMAT}} earlier than local "
f"data start date {data.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}. "
f"Use `--prepend` to download data prior "
f"to {data.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}, or "
"`--erase` to redownload all data."
)
start = data.iloc[-1]["date"]
start_ms = int(start.timestamp() * 1000) if start else None
end_ms = int(end.timestamp() * 1000) if end else None
return data, start_ms, end_ms
@@ -220,8 +221,8 @@ def _download_pair_history(
exchange: Exchange,
timeframe: str = "5m",
new_pairs_days: int = 30,
data_handler: Optional[IDataHandler] = None,
timerange: Optional[TimeRange] = None,
data_handler: IDataHandler | None = None,
timerange: TimeRange | None = None,
candle_type: CandleType,
erase: bool = False,
prepend: bool = False,
@@ -318,25 +319,28 @@ def _download_pair_history(
def refresh_backtest_ohlcv_data(
exchange: Exchange,
pairs: List[str],
timeframes: List[str],
pairs: list[str],
timeframes: list[str],
datadir: Path,
trading_mode: str,
timerange: Optional[TimeRange] = None,
timerange: TimeRange | None = None,
new_pairs_days: int = 30,
erase: bool = False,
data_format: Optional[str] = None,
data_format: str | None = None,
prepend: bool = False,
) -> List[str]:
progress_tracker: CustomProgress | None = None,
) -> list[str]:
"""
Refresh stored ohlcv data for backtesting and hyperopt operations.
Used by freqtrade download-data subcommand.
:return: List of pairs that are not available.
"""
progress_tracker = retrieve_progress_tracker(progress_tracker)
pairs_not_available = []
data_handler = get_datahandler(datadir, data_format)
candle_type = CandleType.get_default(trading_mode)
with get_progress_tracker() as progress:
with progress_tracker as progress:
tf_length = len(timeframes) if trading_mode != "futures" else len(timeframes) + 2
timeframe_task = progress.add_task("Timeframe", total=tf_length)
pair_task = progress.add_task("Downloading data...", total=len(pairs))
@@ -346,7 +350,7 @@ def refresh_backtest_ohlcv_data(
progress.update(timeframe_task, completed=0)
if pair not in exchange.markets:
pairs_not_available.append(pair)
pairs_not_available.append(f"{pair}: Pair not available on exchange.")
logger.info(f"Skipping pair {pair}...")
continue
for timeframe in timeframes:
@@ -404,7 +408,7 @@ def _download_trades_history(
pair: str,
*,
new_pairs_days: int = 30,
timerange: Optional[TimeRange] = None,
timerange: TimeRange | None = None,
data_handler: IDataHandler,
trading_mode: TradingMode,
) -> bool:
@@ -412,104 +416,101 @@ def _download_trades_history(
Download trade history from the exchange.
Appends to previously downloaded trades data.
"""
try:
until = None
since = 0
if timerange:
if timerange.starttype == "date":
since = timerange.startts * 1000
if timerange.stoptype == "date":
until = timerange.stopts * 1000
until = None
since = 0
if timerange:
if timerange.starttype == "date":
since = timerange.startts * 1000
if timerange.stoptype == "date":
until = timerange.stopts * 1000
trades = data_handler.trades_load(pair, trading_mode)
trades = data_handler.trades_load(pair, trading_mode)
# TradesList columns are defined in constants.DEFAULT_TRADES_COLUMNS
# DEFAULT_TRADES_COLUMNS: 0 -> timestamp
# DEFAULT_TRADES_COLUMNS: 1 -> id
# TradesList columns are defined in constants.DEFAULT_TRADES_COLUMNS
# DEFAULT_TRADES_COLUMNS: 0 -> timestamp
# DEFAULT_TRADES_COLUMNS: 1 -> id
if not trades.empty and since > 0 and since < trades.iloc[0]["timestamp"]:
# since is before the first trade
logger.info(
f"Start ({trades.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}) earlier than "
f"available data. Redownloading trades for {pair}..."
)
trades = trades_list_to_df([])
from_id = trades.iloc[-1]["id"] if not trades.empty else None
if not trades.empty and since < trades.iloc[-1]["timestamp"]:
# Reset since to the last available point
# - 5 seconds (to ensure we're getting all trades)
since = trades.iloc[-1]["timestamp"] - (5 * 1000)
logger.info(
f"Using last trade date -5s - Downloading trades for {pair} "
f"since: {format_ms_time(since)}."
)
if not since:
since = dt_ts(dt_now() - timedelta(days=new_pairs_days))
logger.debug(
"Current Start: %s",
"None" if trades.empty else f"{trades.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}",
if not trades.empty and since > 0 and since < trades.iloc[0]["timestamp"]:
# since is before the first trade
raise ValueError(
f"Start {format_ms_time(since)} earlier than "
f"available data ({trades.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}). "
f"Please use `--erase` if you'd like to redownload {pair}."
)
logger.debug(
"Current End: %s",
"None" if trades.empty else f"{trades.iloc[-1]['date']:{DATETIME_PRINT_FORMAT}}",
)
logger.info(f"Current Amount of trades: {len(trades)}")
# Default since_ms to 30 days if nothing is given
new_trades = exchange.get_historic_trades(
pair=pair,
since=since,
until=until,
from_id=from_id,
from_id = trades.iloc[-1]["id"] if not trades.empty else None
if not trades.empty and since < trades.iloc[-1]["timestamp"]:
# Reset since to the last available point
# - 5 seconds (to ensure we're getting all trades)
since = trades.iloc[-1]["timestamp"] - (5 * 1000)
logger.info(
f"Using last trade date -5s - Downloading trades for {pair} "
f"since: {format_ms_time(since)}."
)
new_trades_df = trades_list_to_df(new_trades[1])
trades = concat([trades, new_trades_df], axis=0)
# Remove duplicates to make sure we're not storing data we don't need
trades = trades_df_remove_duplicates(trades)
data_handler.trades_store(pair, trades, trading_mode)
logger.debug(
"New Start: %s",
"None" if trades.empty else f"{trades.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}",
)
logger.debug(
"New End: %s",
"None" if trades.empty else f"{trades.iloc[-1]['date']:{DATETIME_PRINT_FORMAT}}",
)
logger.info(f"New Amount of trades: {len(trades)}")
return True
if not since:
since = dt_ts(dt_now() - timedelta(days=new_pairs_days))
except Exception:
logger.exception(f'Failed to download and store historic trades for pair: "{pair}". ')
return False
logger.debug(
"Current Start: %s",
"None" if trades.empty else f"{trades.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}",
)
logger.debug(
"Current End: %s",
"None" if trades.empty else f"{trades.iloc[-1]['date']:{DATETIME_PRINT_FORMAT}}",
)
logger.info(f"Current Amount of trades: {len(trades)}")
# Default since_ms to 30 days if nothing is given
new_trades = exchange.get_historic_trades(
pair=pair,
since=since,
until=until,
from_id=from_id,
)
new_trades_df = trades_list_to_df(new_trades[1])
trades = concat([trades, new_trades_df], axis=0)
# Remove duplicates to make sure we're not storing data we don't need
trades = trades_df_remove_duplicates(trades)
data_handler.trades_store(pair, trades, trading_mode)
logger.debug(
"New Start: %s",
"None" if trades.empty else f"{trades.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}",
)
logger.debug(
"New End: %s",
"None" if trades.empty else f"{trades.iloc[-1]['date']:{DATETIME_PRINT_FORMAT}}",
)
logger.info(f"New Amount of trades: {len(trades)}")
return True
def refresh_backtest_trades_data(
exchange: Exchange,
pairs: List[str],
pairs: list[str],
datadir: Path,
timerange: TimeRange,
trading_mode: TradingMode,
new_pairs_days: int = 30,
erase: bool = False,
data_format: str = "feather",
) -> List[str]:
progress_tracker: CustomProgress | None = None,
) -> list[str]:
"""
Refresh stored trades data for backtesting and hyperopt operations.
Used by freqtrade download-data subcommand.
:return: List of pairs that are not available.
"""
progress_tracker = retrieve_progress_tracker(progress_tracker)
pairs_not_available = []
data_handler = get_datahandler(datadir, data_format=data_format)
with get_progress_tracker() as progress:
with progress_tracker as progress:
pair_task = progress.add_task("Downloading data...", total=len(pairs))
for pair in pairs:
progress.update(pair_task, description=f"Downloading trades [{pair}]")
if pair not in exchange.markets:
pairs_not_available.append(pair)
pairs_not_available.append(f"{pair}: Pair not available on exchange.")
logger.info(f"Skipping pair {pair}...")
continue
@@ -518,20 +519,28 @@ def refresh_backtest_trades_data(
logger.info(f"Deleting existing data for pair {pair}.")
logger.info(f"Downloading trades for pair {pair}.")
_download_trades_history(
exchange=exchange,
pair=pair,
new_pairs_days=new_pairs_days,
timerange=timerange,
data_handler=data_handler,
trading_mode=trading_mode,
)
try:
_download_trades_history(
exchange=exchange,
pair=pair,
new_pairs_days=new_pairs_days,
timerange=timerange,
data_handler=data_handler,
trading_mode=trading_mode,
)
except ValueError as e:
pairs_not_available.append(f"{pair}: {str(e)}")
except Exception:
logger.exception(
f'Failed to download and store historic trades for pair: "{pair}". '
)
progress.update(pair_task, advance=1)
return pairs_not_available
def get_timerange(data: Dict[str, DataFrame]) -> Tuple[datetime, datetime]:
def get_timerange(data: dict[str, DataFrame]) -> tuple[datetime, datetime]:
"""
Get the maximum common timerange for the given backtest data.
@@ -577,23 +586,35 @@ def validate_backtest_data(
def download_data_main(config: Config) -> None:
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
exchange = ExchangeResolver.load_exchange(config, validate=False)
download_data(config, exchange)
def download_data(
config: Config,
exchange: Exchange,
*,
progress_tracker: CustomProgress | None = None,
) -> None:
"""
Download data function. Used from both cli and API.
"""
timerange = TimeRange()
if "days" in config:
time_since = (datetime.now() - timedelta(days=config["days"])).strftime("%Y%m%d")
timerange = TimeRange.parse_timerange(f"{time_since}-")
if "timerange" in config:
timerange = timerange.parse_timerange(config["timerange"])
timerange = TimeRange.parse_timerange(config["timerange"])
# Remove stake-currency to skip checks which are not relevant for datadownload
config["stake_currency"] = ""
pairs_not_available: List[str] = []
pairs_not_available: list[str] = []
# Init exchange
from freqtrade.resolvers.exchange_resolver import ExchangeResolver
exchange = ExchangeResolver.load_exchange(config, validate=False)
available_pairs = [
p
for p in exchange.get_markets(
@@ -605,17 +626,18 @@ def download_data_main(config: Config) -> None:
if "timeframes" not in config:
config["timeframes"] = DL_DATA_TIMEFRAMES
logger.info(
f"About to download pairs: {expanded_pairs}, "
f"intervals: {config['timeframes']} to {config['datadir']}"
)
if len(expanded_pairs) == 0:
logger.warning(
"No pairs available for download. "
"Please make sure you're using the correct Pair naming for your selected trade mode. \n"
f"More info: {DOCS_LINK}/bot-basics/#pair-naming"
)
return
logger.info(
f"About to download pairs: {expanded_pairs}, "
f"intervals: {config['timeframes']} to {config['datadir']}"
)
for timeframe in config["timeframes"]:
exchange.validate_timeframes(timeframe)
@@ -637,6 +659,7 @@ def download_data_main(config: Config) -> None:
erase=bool(config.get("erase")),
data_format=config["dataformat_trades"],
trading_mode=config.get("trading_mode", TradingMode.SPOT),
progress_tracker=progress_tracker,
)
if config.get("convert_trades") or not exchange.get_option("ohlcv_has_history", True):
@@ -672,10 +695,12 @@ def download_data_main(config: Config) -> None:
data_format=config["dataformat_ohlcv"],
trading_mode=config.get("trading_mode", "spot"),
prepend=config.get("prepend_data", False),
progress_tracker=progress_tracker,
)
finally:
if pairs_not_available:
logger.info(
f"Pairs [{','.join(pairs_not_available)}] not available "
f"on exchange {exchange.name}."
errors = "\n" + ("\n".join(pairs_not_available))
logger.warning(
f"Encountered a problem downloading the following pairs from {exchange.name}: "
f"{errors}"
)
+8 -9
View File
@@ -2,7 +2,6 @@ import logging
import math
from dataclasses import dataclass
from datetime import datetime
from typing import Dict, Tuple
import numpy as np
import pandas as pd
@@ -11,7 +10,7 @@ import pandas as pd
logger = logging.getLogger(__name__)
def calculate_market_change(data: Dict[str, pd.DataFrame], column: str = "close") -> float:
def calculate_market_change(data: dict[str, pd.DataFrame], column: str = "close") -> float:
"""
Calculate market change based on "column".
Calculation is done by taking the first non-null and the last non-null element of each column
@@ -32,7 +31,7 @@ def calculate_market_change(data: Dict[str, pd.DataFrame], column: str = "close"
def combine_dataframes_by_column(
data: Dict[str, pd.DataFrame], column: str = "close"
data: dict[str, pd.DataFrame], column: str = "close"
) -> pd.DataFrame:
"""
Combine multiple dataframes "column"
@@ -50,7 +49,7 @@ def combine_dataframes_by_column(
def combined_dataframes_with_rel_mean(
data: Dict[str, pd.DataFrame], fromdt: datetime, todt: datetime, column: str = "close"
data: dict[str, pd.DataFrame], fromdt: datetime, todt: datetime, column: str = "close"
) -> pd.DataFrame:
"""
Combine multiple dataframes "column"
@@ -70,7 +69,7 @@ def combined_dataframes_with_rel_mean(
def combine_dataframes_with_mean(
data: Dict[str, pd.DataFrame], column: str = "close"
data: dict[str, pd.DataFrame], column: str = "close"
) -> pd.DataFrame:
"""
Combine multiple dataframes "column"
@@ -222,7 +221,7 @@ def calculate_max_drawdown(
)
def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> Tuple[float, float]:
def calculate_csum(trades: pd.DataFrame, starting_balance: float = 0) -> tuple[float, float]:
"""
Calculate min/max cumsum of trades, to show if the wallet/stake amount ratio is sane
:param trades: DataFrame containing trades (requires columns close_date and profit_percent)
@@ -255,15 +254,15 @@ def calculate_cagr(days_passed: int, starting_balance: float, final_balance: flo
return (final_balance / starting_balance) ** (1 / (days_passed / 365)) - 1
def calculate_expectancy(trades: pd.DataFrame) -> Tuple[float, float]:
def calculate_expectancy(trades: pd.DataFrame) -> tuple[float, float]:
"""
Calculate expectancy
:param trades: DataFrame containing trades (requires columns close_date and profit_abs)
:return: expectancy, expectancy_ratio
"""
expectancy = 0
expectancy_ratio = 100
expectancy = 0.0
expectancy_ratio = 100.0
if len(trades) > 0:
winning_trades = trades.loc[trades["profit_abs"] > 0]
+8 -8
View File
@@ -5,7 +5,7 @@ import logging
from collections import defaultdict
from copy import deepcopy
from datetime import timedelta
from typing import Any, Dict, List, NamedTuple
from typing import Any, NamedTuple
import numpy as np
import utils_find_1st as utf1st
@@ -44,7 +44,7 @@ class Edge:
Author: https://github.com/mishaker
"""
_cached_pairs: Dict[str, Any] = {} # Keeps a list of pairs
_cached_pairs: dict[str, Any] = {} # Keeps a list of pairs
def __init__(self, config: Config, exchange, strategy) -> None:
self.config = config
@@ -52,7 +52,7 @@ class Edge:
self.strategy: IStrategy = strategy
self.edge_config = self.config.get("edge", {})
self._cached_pairs: Dict[str, Any] = {} # Keeps a list of pairs
self._cached_pairs: dict[str, Any] = {} # Keeps a list of pairs
self._final_pairs: list = []
# checking max_open_trades. it should be -1 as with Edge
@@ -93,7 +93,7 @@ class Edge:
except IndexError:
self.fee = None
def calculate(self, pairs: List[str]) -> bool:
def calculate(self, pairs: list[str]) -> bool:
if self.fee is None and pairs:
self.fee = self.exchange.get_fee(pairs[0])
@@ -104,7 +104,7 @@ class Edge:
):
return False
data: Dict[str, Any] = {}
data: dict[str, Any] = {}
logger.info("Using stake_currency: %s ...", self.config["stake_currency"])
logger.info("Using local backtesting data (using whitelist in given config) ...")
@@ -231,7 +231,7 @@ class Edge:
)
return self.strategy.stoploss
def adjust(self, pairs: List[str]) -> list:
def adjust(self, pairs: list[str]) -> list:
"""
Filters out and sorts "pairs" according to Edge calculated pairs
"""
@@ -260,7 +260,7 @@ class Edge:
return self._final_pairs
def accepted_pairs(self) -> List[Dict[str, Any]]:
def accepted_pairs(self) -> list[dict[str, Any]]:
"""
return a list of accepted pairs along with their winrate, expectancy and stoploss
"""
@@ -322,7 +322,7 @@ class Edge:
return result
def _process_expectancy(self, results: DataFrame) -> Dict[str, Any]:
def _process_expectancy(self, results: DataFrame) -> dict[str, Any]:
"""
This calculates WinRate, Required Risk Reward, Risk Reward and Expectancy of all pairs
The calculation will be done per pair and per strategy.
+1
View File
@@ -43,4 +43,5 @@ from freqtrade.exchange.hyperliquid import Hyperliquid
from freqtrade.exchange.idex import Idex
from freqtrade.exchange.kraken import Kraken
from freqtrade.exchange.kucoin import Kucoin
from freqtrade.exchange.lbank import Lbank
from freqtrade.exchange.okx import Okx
+74 -12
View File
@@ -3,7 +3,6 @@
import logging
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, List, Optional, Tuple
import ccxt
@@ -46,19 +45,19 @@ class Binance(Exchange):
"ws_enabled": False,
}
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
# TradingMode.SPOT always supported and not required in this list
# (TradingMode.MARGIN, MarginMode.CROSS),
# (TradingMode.FUTURES, MarginMode.CROSS),
(TradingMode.FUTURES, MarginMode.ISOLATED)
]
def get_tickers(self, symbols: Optional[List[str]] = None, cached: bool = False) -> Tickers:
def get_tickers(self, symbols: list[str] | None = None, *, cached: bool = False) -> Tickers:
tickers = super().get_tickers(symbols=symbols, cached=cached)
if self.trading_mode == TradingMode.FUTURES:
# Binance's future result has no bid/ask values.
# Therefore we must fetch that from fetch_bids_asks and combine the two results.
bidsasks = self.fetch_bids_asks(symbols, cached)
bidsasks = self.fetch_bids_asks(symbols, cached=cached)
tickers = deep_merge_dicts(bidsasks, tickers, allow_null_overrides=False)
return tickers
@@ -106,7 +105,7 @@ class Binance(Exchange):
candle_type: CandleType,
is_new_pair: bool = False,
raise_: bool = False,
until_ms: Optional[int] = None,
until_ms: int | None = None,
) -> OHLCVResponse:
"""
Overwrite to introduce "fast new pair" functionality by detecting the pair's listing date
@@ -144,6 +143,27 @@ class Binance(Exchange):
"""
return open_date.minute == 0 and open_date.second < 15
def fetch_funding_rates(self, symbols: list[str] | None = None) -> dict[str, dict[str, float]]:
"""
Fetch funding rates for the given symbols.
:param symbols: List of symbols to fetch funding rates for
:return: Dict of funding rates for the given symbols
"""
try:
if self.trading_mode == TradingMode.FUTURES:
rates = self._api.fetch_funding_rates(symbols)
return rates
return {}
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f"Error in additional_exchange_init due to {e.__class__.__name__}. Message: {e}"
) from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
def dry_run_liquidation_price(
self,
pair: str,
@@ -153,9 +173,8 @@ class Binance(Exchange):
stake_amount: float,
leverage: float,
wallet_balance: float, # Or margin balance
mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only
) -> Optional[float]:
open_trades: list,
) -> float | None:
"""
Important: Must be fetching data from cached values as this is used by backtesting!
MARGIN: https://www.binance.com/en/support/faq/f6b010588e55413aa58b7d63ee0125ed
@@ -172,6 +191,7 @@ class Binance(Exchange):
:param wallet_balance: Amount of margin_mode in the wallet being used to trade
Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance
:param open_trades: List of open trades in the same wallet
# * Only required for Cross
:param mm_ex_1: (TMM)
@@ -180,15 +200,41 @@ class Binance(Exchange):
:param upnl_ex_1: (UPNL)
Cross-Margin Mode: Unrealized PNL of all other contracts, excluding Contract 1.
Isolated-Margin Mode: 0
:param other
"""
side_1 = -1 if is_short else 1
cross_vars = upnl_ex_1 - mm_ex_1 if self.margin_mode == MarginMode.CROSS else 0.0
cross_vars: float = 0.0
# mm_ratio: Binance's formula specifies maintenance margin rate which is mm_ratio * 100%
# maintenance_amt: (CUM) Maintenance Amount of position
mm_ratio, maintenance_amt = self.get_maintenance_ratio_and_amt(pair, stake_amount)
if self.margin_mode == MarginMode.CROSS:
mm_ex_1: float = 0.0
upnl_ex_1: float = 0.0
pairs = [trade.pair for trade in open_trades]
if self._config["runmode"] in ("live", "dry_run"):
funding_rates = self.fetch_funding_rates(pairs)
for trade in open_trades:
if trade.pair == pair:
# Only "other" trades are considered
continue
if self._config["runmode"] in ("live", "dry_run"):
mark_price = funding_rates[trade.pair]["markPrice"]
else:
# Fall back to open rate for backtesting
mark_price = trade.open_rate
mm_ratio1, maint_amnt1 = self.get_maintenance_ratio_and_amt(
trade.pair, trade.stake_amount
)
maint_margin = trade.amount * mark_price * mm_ratio1 - maint_amnt1
mm_ex_1 += maint_margin
upnl_ex_1 += trade.amount * mark_price - trade.amount * trade.open_rate
cross_vars = upnl_ex_1 - mm_ex_1
side_1 = -1 if is_short else 1
if maintenance_amt is None:
raise OperationalException(
"Parameter maintenance_amt is required by Binance.liquidation_price"
@@ -204,7 +250,7 @@ class Binance(Exchange):
"Freqtrade only supports isolated futures for leverage trading"
)
def load_leverage_tiers(self) -> Dict[str, List[Dict]]:
def load_leverage_tiers(self) -> dict[str, list[dict]]:
if self.trading_mode == TradingMode.FUTURES:
if self._config["dry_run"]:
leverage_tiers_path = Path(__file__).parent / "binance_leverage_tiers.json"
@@ -214,3 +260,19 @@ class Binance(Exchange):
return self.get_leverage_tiers()
else:
return {}
async def _async_get_trade_history_id_startup(
self, pair: str, since: int | None
) -> tuple[list[list], str]:
"""
override for initial call
Binance only provides a limited set of historic trades data.
Using from_id=0, we can get the earliest available trades.
So if we don't get any data with the provided "since", we can assume to
download all available data.
"""
t, from_id = await self._async_fetch_trades(pair, since=since)
if not t:
return [], "0"
return t, from_id
File diff suppressed because it is too large Load Diff
+2 -3
View File
@@ -2,7 +2,6 @@
import logging
from datetime import datetime, timezone
from typing import Dict, List, Optional
from freqtrade.exchange import Exchange
@@ -17,8 +16,8 @@ class Bitpanda(Exchange):
"""
def get_trades_for_order(
self, order_id: str, pair: str, since: datetime, params: Optional[Dict] = None
) -> List:
self, order_id: str, pair: str, since: datetime, params: dict | None = None
) -> list:
"""
Fetch Orders using the "fetch_my_trades" endpoint and filter them by order-id.
The "since" argument passed in is coming from the database and is in UTC,
+36 -21
View File
@@ -2,16 +2,16 @@
import logging
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional, Tuple
from typing import Any
import ccxt
from freqtrade.constants import BuySell
from freqtrade.enums import CandleType, MarginMode, PriceType, TradingMode
from freqtrade.enums import MarginMode, PriceType, TradingMode
from freqtrade.exceptions import DDosProtection, ExchangeError, OperationalException, TemporaryError
from freqtrade.exchange import Exchange
from freqtrade.exchange.common import retrier
from freqtrade.exchange.exchange_types import FtHas
from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
from freqtrade.util.datetime_helpers import dt_now, dt_ts
@@ -36,11 +36,18 @@ class Bybit(Exchange):
"order_time_in_force": ["GTC", "FOK", "IOC", "PO"],
"ws_enabled": True,
"trades_has_history": False, # Endpoint doesn't support pagination
"exchange_has_overrides": {
# Bybit spot does not support fetch_order
# Unless the account is unified.
# TODO: Can be removed once bybit fully forces all accounts to unified mode.
"fetchOrder": False,
},
}
_ft_has_futures: FtHas = {
"ohlcv_has_history": True,
"mark_ohlcv_timeframe": "4h",
"funding_fee_timeframe": "8h",
"funding_fee_candle_limit": 200,
"stoploss_on_exchange": True,
"stoploss_order_types": {"limit": "limit", "market": "market"},
# bybit response parsing fails to populate stopLossPrice
@@ -51,16 +58,19 @@ class Bybit(Exchange):
PriceType.MARK: "MarkPrice",
PriceType.INDEX: "IndexPrice",
},
"exchange_has_overrides": {
"fetchOrder": True,
},
}
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
# TradingMode.SPOT always supported and not required in this list
# (TradingMode.FUTURES, MarginMode.CROSS),
(TradingMode.FUTURES, MarginMode.ISOLATED)
]
@property
def _ccxt_config(self) -> Dict:
def _ccxt_config(self) -> dict:
# Parameters to add directly to ccxt sync/async initialization.
# ccxt defaults to swap mode.
config = {}
@@ -69,7 +79,7 @@ class Bybit(Exchange):
config.update(super()._ccxt_config)
return config
def market_is_future(self, market: Dict[str, Any]) -> bool:
def market_is_future(self, market: dict[str, Any]) -> bool:
main = super().market_is_future(market)
# For ByBit, we'll only support USDT markets for now.
return main and market["settle"] == "USDT"
@@ -105,14 +115,6 @@ class Bybit(Exchange):
except ccxt.BaseError as e:
raise OperationalException(e) from e
def ohlcv_candle_limit(
self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = None
) -> int:
if candle_type in (CandleType.FUNDING_RATE):
return 200
return super().ohlcv_candle_limit(timeframe, candle_type, since_ms)
def _lev_prep(self, pair: str, leverage: float, side: BuySell, accept_fail: bool = False):
if self.trading_mode != TradingMode.SPOT:
params = {"leverage": leverage}
@@ -126,7 +128,7 @@ class Bybit(Exchange):
leverage: float,
reduceOnly: bool,
time_in_force: str = "GTC",
) -> Dict:
) -> dict:
params = super()._get_params(
side=side,
ordertype=ordertype,
@@ -138,6 +140,17 @@ class Bybit(Exchange):
params["position_idx"] = 0
return params
def _order_needs_price(self, side: BuySell, ordertype: str) -> bool:
# Bybit requires price for market orders - but only for classic accounts,
# and only in spot mode
return (
ordertype != "market"
or (
side == "buy" and not self.unified_account and self.trading_mode == TradingMode.SPOT
)
or self._ft_has.get("marketOrderRequiresPrice", False)
)
def dry_run_liquidation_price(
self,
pair: str,
@@ -147,9 +160,8 @@ class Bybit(Exchange):
stake_amount: float,
leverage: float,
wallet_balance: float, # Or margin balance
mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only
) -> Optional[float]:
open_trades: list,
) -> float | None:
"""
Important: Must be fetching data from cached values as this is used by backtesting!
PERPETUAL:
@@ -178,6 +190,7 @@ class Bybit(Exchange):
:param wallet_balance: Amount of margin_mode in the wallet being used to trade
Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance
:param open_trades: List of other open trades in the same wallet
"""
market = self.markets[pair]
@@ -220,7 +233,9 @@ class Bybit(Exchange):
logger.warning(f"Could not update funding fees for {pair}.")
return 0.0
def fetch_orders(self, pair: str, since: datetime, params: Optional[Dict] = None) -> List[Dict]:
def fetch_orders(
self, pair: str, since: datetime, params: dict | None = None
) -> list[CcxtOrder]:
"""
Fetch all orders for a pair "since"
:param pair: Pair for the query
@@ -237,7 +252,7 @@ class Bybit(Exchange):
return orders
def fetch_order(self, order_id: str, pair: str, params: Optional[Dict] = None) -> Dict:
def fetch_order(self, order_id: str, pair: str, params: dict | None = None) -> CcxtOrder:
if self.exchange_has("fetchOrder"):
# Set acknowledged to True to avoid ccxt exception
params = {"acknowledged": True}
@@ -255,7 +270,7 @@ class Bybit(Exchange):
return order
@retrier
def get_leverage_tiers(self) -> Dict[str, List[Dict]]:
def get_leverage_tiers(self) -> dict[str, list[dict]]:
"""
Cache leverage tiers for 1 day, since they are not expected to change often, and
bybit requires pagination to fetch all tiers.
+6 -4
View File
@@ -1,8 +1,9 @@
import asyncio
import logging
import time
from collections.abc import Callable
from functools import wraps
from typing import Any, Callable, Dict, List, Optional, TypeVar, cast, overload
from typing import Any, TypeVar, cast, overload
from freqtrade.constants import ExchangeConfig
from freqtrade.exceptions import DDosProtection, RetryableOrderError, TemporaryError
@@ -57,12 +58,13 @@ SUPPORTED_EXCHANGES = [
"bybit",
"gate",
"htx",
"hyperliquid",
"kraken",
"okx",
]
# either the main, or replacement methods (array) is required
EXCHANGE_HAS_REQUIRED: Dict[str, List[str]] = {
EXCHANGE_HAS_REQUIRED: dict[str, list[str]] = {
# Required / private
"fetchOrder": ["fetchOpenOrder", "fetchClosedOrder"],
"fetchL2OrderBook": ["fetchTicker"],
@@ -172,7 +174,7 @@ def retrier(_func: F, *, retries=API_RETRY_COUNT) -> F: ...
def retrier(*, retries=API_RETRY_COUNT) -> Callable[[F], F]: ...
def retrier(_func: Optional[F] = None, *, retries=API_RETRY_COUNT):
def retrier(_func: F | None = None, *, retries=API_RETRY_COUNT):
def decorator(f: F) -> F:
@wraps(f)
def wrapper(*args, **kwargs):
@@ -185,7 +187,7 @@ def retrier(_func: Optional[F] = None, *, retries=API_RETRY_COUNT):
logger.warning(msg + f"Retrying still for {count} times.")
count -= 1
kwargs.update({"count": count})
if isinstance(ex, (DDosProtection, RetryableOrderError)):
if isinstance(ex, DDosProtection | RetryableOrderError):
# increasing backoff
backoff_delay = calculate_backoff(count + 1, retries)
logger.info(f"Applying DDosProtection backoff delay: {backoff_delay}")
File diff suppressed because it is too large Load Diff
+32 -29
View File
@@ -1,28 +1,28 @@
from typing import Dict, List, Optional, Tuple, TypedDict
from typing import Any, Literal, TypedDict
from freqtrade.enums import CandleType
class FtHas(TypedDict, total=False):
order_time_in_force: List[str]
exchange_has_overrides: Dict[str, bool]
order_time_in_force: list[str]
exchange_has_overrides: dict[str, bool]
marketOrderRequiresPrice: bool
# Stoploss on exchange
stoploss_on_exchange: bool
stop_price_param: str
stop_price_prop: str
stop_price_prop: Literal["stopPrice", "stopLossPrice"]
stop_price_type_field: str
stop_price_type_value_mapping: Dict
stoploss_order_types: Dict[str, str]
stop_price_type_value_mapping: dict
stoploss_order_types: dict[str, str]
# ohlcv
ohlcv_params: Dict
ohlcv_params: dict
ohlcv_candle_limit: int
ohlcv_has_history: bool
ohlcv_partial_candle: bool
ohlcv_require_since: bool
ohlcv_volume_currency: str
ohlcv_candle_limit_per_timeframe: Dict[str, int]
ohlcv_candle_limit_per_timeframe: dict[str, int]
# Tickers
tickers_have_quoteVolume: bool
tickers_have_percentage: bool
@@ -35,16 +35,17 @@ class FtHas(TypedDict, total=False):
trades_has_history: bool
trades_pagination_overlap: bool
# Orderbook
l2_limit_range: Optional[List[int]]
l2_limit_range: list[int] | None
l2_limit_range_required: bool
# Futures
ccxt_futures_name: str # usually swap
mark_ohlcv_price: str
mark_ohlcv_timeframe: str
funding_fee_timeframe: str
funding_fee_candle_limit: int
floor_leverage: bool
needs_trading_fees: bool
order_props_in_contracts: List[str]
order_props_in_contracts: list[Literal["amount", "cost", "filled", "remaining"]]
# Websocket control
ws_enabled: bool
@@ -52,27 +53,27 @@ class FtHas(TypedDict, total=False):
class Ticker(TypedDict):
symbol: str
ask: Optional[float]
askVolume: Optional[float]
bid: Optional[float]
bidVolume: Optional[float]
last: Optional[float]
quoteVolume: Optional[float]
baseVolume: Optional[float]
percentage: Optional[float]
ask: float | None
askVolume: float | None
bid: float | None
bidVolume: float | None
last: float | None
quoteVolume: float | None
baseVolume: float | None
percentage: float | None
# Several more - only listing required.
Tickers = Dict[str, Ticker]
Tickers = dict[str, Ticker]
class OrderBook(TypedDict):
symbol: str
bids: List[Tuple[float, float]]
asks: List[Tuple[float, float]]
timestamp: Optional[int]
datetime: Optional[str]
nonce: Optional[int]
bids: list[tuple[float, float]]
asks: list[tuple[float, float]]
timestamp: int | None
datetime: str | None
nonce: int | None
class CcxtBalance(TypedDict):
@@ -81,7 +82,7 @@ class CcxtBalance(TypedDict):
total: float
CcxtBalances = Dict[str, CcxtBalance]
CcxtBalances = dict[str, CcxtBalance]
class CcxtPosition(TypedDict):
@@ -89,10 +90,12 @@ class CcxtPosition(TypedDict):
side: str
contracts: float
leverage: float
collateral: Optional[float]
initialMargin: Optional[float]
liquidationPrice: Optional[float]
collateral: float | None
initialMargin: float | None
liquidationPrice: float | None
CcxtOrder = dict[str, Any]
# pair, timeframe, candleType, OHLCV, drop last?,
OHLCVResponse = Tuple[str, str, CandleType, List, bool]
OHLCVResponse = tuple[str, str, CandleType, list, bool]
+18 -22
View File
@@ -5,7 +5,7 @@ Exchange support utils
import inspect
from datetime import datetime, timedelta, timezone
from math import ceil, floor
from typing import Any, Dict, List, Optional, Tuple
from typing import Any
import ccxt
from ccxt import (
@@ -33,20 +33,18 @@ from freqtrade.util import FtPrecise
CcxtModuleType = Any
def is_exchange_known_ccxt(
exchange_name: str, ccxt_module: Optional[CcxtModuleType] = None
) -> bool:
def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType | None = None) -> bool:
return exchange_name in ccxt_exchanges(ccxt_module)
def ccxt_exchanges(ccxt_module: Optional[CcxtModuleType] = None) -> List[str]:
def ccxt_exchanges(ccxt_module: CcxtModuleType | None = None) -> list[str]:
"""
Return the list of all exchanges known to ccxt
"""
return ccxt_module.exchanges if ccxt_module is not None else ccxt.exchanges
def available_exchanges(ccxt_module: Optional[CcxtModuleType] = None) -> List[str]:
def available_exchanges(ccxt_module: CcxtModuleType | None = None) -> list[str]:
"""
Return exchanges available to the bot, i.e. non-bad exchanges in the ccxt list
"""
@@ -54,7 +52,7 @@ def available_exchanges(ccxt_module: Optional[CcxtModuleType] = None) -> List[st
return [x for x in exchanges if validate_exchange(x)[0]]
def validate_exchange(exchange: str) -> Tuple[bool, str, Optional[ccxt.Exchange]]:
def validate_exchange(exchange: str) -> tuple[bool, str, ccxt.Exchange | None]:
"""
returns: can_use, reason, exchange_object
with Reason including both missing and missing_opt
@@ -91,7 +89,7 @@ def validate_exchange(exchange: str) -> Tuple[bool, str, Optional[ccxt.Exchange]
def _build_exchange_list_entry(
exchange_name: str, exchangeClasses: Dict[str, Any]
exchange_name: str, exchangeClasses: dict[str, Any]
) -> ValidExchangesType:
valid, comment, ex_mod = validate_exchange(exchange_name)
result: ValidExchangesType = {
@@ -121,7 +119,7 @@ def _build_exchange_list_entry(
return result
def list_available_exchanges(all_exchanges: bool) -> List[ValidExchangesType]:
def list_available_exchanges(all_exchanges: bool) -> list[ValidExchangesType]:
"""
:return: List of tuples with exchangename, valid, reason.
"""
@@ -130,16 +128,14 @@ def list_available_exchanges(all_exchanges: bool) -> List[ValidExchangesType]:
subclassed = {e["name"].lower(): e for e in ExchangeResolver.search_all_objects({}, False)}
exchanges_valid: List[ValidExchangesType] = [
exchanges_valid: list[ValidExchangesType] = [
_build_exchange_list_entry(e, subclassed) for e in exchanges
]
return exchanges_valid
def date_minus_candles(
timeframe: str, candle_count: int, date: Optional[datetime] = None
) -> datetime:
def date_minus_candles(timeframe: str, candle_count: int, date: datetime | None = None) -> datetime:
"""
subtract X candles from a date.
:param timeframe: timeframe in string format (e.g. "5m")
@@ -155,7 +151,7 @@ def date_minus_candles(
return new_date
def market_is_active(market: Dict) -> bool:
def market_is_active(market: dict) -> bool:
"""
Return True if the market is active.
"""
@@ -166,7 +162,7 @@ def market_is_active(market: Dict) -> bool:
return market.get("active", True) is not False
def amount_to_contracts(amount: float, contract_size: Optional[float]) -> float:
def amount_to_contracts(amount: float, contract_size: float | None) -> float:
"""
Convert amount to contracts.
:param amount: amount to convert
@@ -179,7 +175,7 @@ def amount_to_contracts(amount: float, contract_size: Optional[float]) -> float:
return amount
def contracts_to_amount(num_contracts: float, contract_size: Optional[float]) -> float:
def contracts_to_amount(num_contracts: float, contract_size: float | None) -> float:
"""
Takes num-contracts and converts it to contract size
:param num_contracts: number of contracts
@@ -194,7 +190,7 @@ def contracts_to_amount(num_contracts: float, contract_size: Optional[float]) ->
def amount_to_precision(
amount: float, amount_precision: Optional[float], precisionMode: Optional[int]
amount: float, amount_precision: float | None, precisionMode: int | None
) -> float:
"""
Returns the amount to buy or sell to a precision the Exchange accepts
@@ -224,9 +220,9 @@ def amount_to_precision(
def amount_to_contract_precision(
amount,
amount_precision: Optional[float],
precisionMode: Optional[int],
contract_size: Optional[float],
amount_precision: float | None,
precisionMode: int | None,
contract_size: float | None,
) -> float:
"""
Returns the amount to buy or sell to a precision the Exchange accepts
@@ -285,8 +281,8 @@ def __price_to_precision_significant_digits(
def price_to_precision(
price: float,
price_precision: Optional[float],
precisionMode: Optional[int],
price_precision: float | None,
precisionMode: int | None,
*,
rounding_mode: int = ROUND,
) -> float:
@@ -1,5 +1,4 @@
from datetime import datetime, timezone
from typing import Optional
import ccxt
from ccxt import ROUND_DOWN, ROUND_UP
@@ -51,7 +50,7 @@ def timeframe_to_resample_freq(timeframe: str) -> str:
return resample_interval
def timeframe_to_prev_date(timeframe: str, date: Optional[datetime] = None) -> datetime:
def timeframe_to_prev_date(timeframe: str, date: datetime | None = None) -> datetime:
"""
Use Timeframe and determine the candle start date for this date.
Does not round when given a candle start date.
@@ -66,7 +65,7 @@ def timeframe_to_prev_date(timeframe: str, date: Optional[datetime] = None) -> d
return dt_from_ts(new_timestamp)
def timeframe_to_next_date(timeframe: str, date: Optional[datetime] = None) -> datetime:
def timeframe_to_next_date(timeframe: str, date: datetime | None = None) -> datetime:
"""
Use Timeframe and determine next candle.
:param timeframe: timeframe in string format (e.g. "5m")
+5 -6
View File
@@ -4,7 +4,6 @@ import time
from copy import deepcopy
from functools import partial
from threading import Thread
from typing import Dict, Set
import ccxt
@@ -22,12 +21,12 @@ class ExchangeWS:
def __init__(self, config: Config, ccxt_object: ccxt.Exchange) -> None:
self.config = config
self.ccxt_object = ccxt_object
self._background_tasks: Set[asyncio.Task] = set()
self._background_tasks: set[asyncio.Task] = set()
self._klines_watching: Set[PairWithTimeframe] = set()
self._klines_scheduled: Set[PairWithTimeframe] = set()
self.klines_last_refresh: Dict[PairWithTimeframe, float] = {}
self.klines_last_request: Dict[PairWithTimeframe, float] = {}
self._klines_watching: set[PairWithTimeframe] = set()
self._klines_scheduled: set[PairWithTimeframe] = set()
self.klines_last_refresh: dict[PairWithTimeframe, float] = {}
self.klines_last_request: dict[PairWithTimeframe, float] = {}
self._thread = Thread(name="ccxt_ws", target=self._start_forever)
self._thread.start()
self.__cleanup_called = False
+46 -11
View File
@@ -2,12 +2,15 @@
import logging
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
import ccxt
from freqtrade.constants import BuySell
from freqtrade.enums import MarginMode, PriceType, TradingMode
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
from freqtrade.exchange import Exchange
from freqtrade.exchange.exchange_types import FtHas
from freqtrade.exchange.common import retrier
from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
from freqtrade.misc import safe_value_fallback2
@@ -24,6 +27,8 @@ class Gate(Exchange):
may still not work as expected.
"""
unified_account = False
_ft_has: FtHas = {
"ohlcv_candle_limit": 1000,
"order_time_in_force": ["GTC", "IOC"],
@@ -38,6 +43,7 @@ class Gate(Exchange):
_ft_has_futures: FtHas = {
"needs_trading_fees": True,
"marketOrderRequiresPrice": False,
"funding_fee_candle_limit": 90,
"stop_price_type_field": "price_type",
"stop_price_type_value_mapping": {
PriceType.LAST: 0,
@@ -46,13 +52,42 @@ class Gate(Exchange):
},
}
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
# TradingMode.SPOT always supported and not required in this list
# (TradingMode.MARGIN, MarginMode.CROSS),
# (TradingMode.FUTURES, MarginMode.CROSS),
(TradingMode.FUTURES, MarginMode.ISOLATED)
]
@retrier
def additional_exchange_init(self) -> None:
"""
Additional exchange initialization logic.
.api will be available at this point.
Must be overridden in child methods if required.
"""
try:
if not self._config["dry_run"]:
# TODO: This should work with 4.4.34 and later.
self._api.load_unified_status()
is_unified = self._api.options.get("unifiedAccount")
# Returns a tuple of bools, first for margin, second for Account
if is_unified:
self.unified_account = True
logger.info("Gate: Unified account.")
else:
self.unified_account = False
logger.info("Gate: Classic account.")
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
except (ccxt.OperationFailed, ccxt.ExchangeError) as e:
raise TemporaryError(
f"Error in additional_exchange_init due to {e.__class__.__name__}. Message: {e}"
) from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
def _get_params(
self,
side: BuySell,
@@ -60,7 +95,7 @@ class Gate(Exchange):
leverage: float,
reduceOnly: bool,
time_in_force: str = "GTC",
) -> Dict:
) -> dict:
params = super()._get_params(
side=side,
ordertype=ordertype,
@@ -74,8 +109,8 @@ class Gate(Exchange):
return params
def get_trades_for_order(
self, order_id: str, pair: str, since: datetime, params: Optional[Dict] = None
) -> List:
self, order_id: str, pair: str, since: datetime, params: dict | None = None
) -> list:
trades = super().get_trades_for_order(order_id, pair, since, params)
if self.trading_mode == TradingMode.FUTURES:
@@ -99,10 +134,12 @@ class Gate(Exchange):
}
return trades
def get_order_id_conditional(self, order: Dict[str, Any]) -> str:
def get_order_id_conditional(self, order: CcxtOrder) -> str:
return safe_value_fallback2(order, order, "id_stop", "id")
def fetch_stoploss_order(self, order_id: str, pair: str, params: Optional[Dict] = None) -> Dict:
def fetch_stoploss_order(
self, order_id: str, pair: str, params: dict | None = None
) -> CcxtOrder:
order = self.fetch_order(order_id=order_id, pair=pair, params={"stop": True})
if order.get("status", "open") == "closed":
# Places a real order - which we need to fetch explicitly.
@@ -119,7 +156,5 @@ class Gate(Exchange):
return order1
return order
def cancel_stoploss_order(
self, order_id: str, pair: str, params: Optional[Dict] = None
) -> Dict:
def cancel_stoploss_order(self, order_id: str, pair: str, params: dict | None = None) -> dict:
return self.cancel_order(order_id=order_id, pair=pair, params={"stop": True})
+1 -2
View File
@@ -1,7 +1,6 @@
"""HTX exchange subclass"""
import logging
from typing import Dict
from freqtrade.constants import BuySell
from freqtrade.exchange import Exchange
@@ -32,7 +31,7 @@ class Htx(Exchange):
"trades_has_history": False, # Endpoint doesn't have a "since" parameter
}
def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> Dict:
def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> dict:
params = self._params.copy()
params.update(
{
+151 -15
View File
@@ -1,13 +1,14 @@
"""Hyperliquid exchange subclass"""
import logging
from typing import Dict
from datetime import datetime
from ccxt import SIGNIFICANT_DIGITS
from freqtrade.enums import TradingMode
from freqtrade.constants import BuySell
from freqtrade.enums import MarginMode, TradingMode
from freqtrade.exceptions import ExchangeError, OperationalException
from freqtrade.exchange import Exchange
from freqtrade.exchange.exchange_types import FtHas
from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
from freqtrade.util.datetime_helpers import dt_from_ts
logger = logging.getLogger(__name__)
@@ -19,27 +20,162 @@ class Hyperliquid(Exchange):
"""
_ft_has: FtHas = {
# Only the most recent 5000 candles are available according to the
# exchange's API documentation.
"ohlcv_has_history": False,
"ohlcv_candle_limit": 5000,
"trades_has_history": False, # Trades endpoint doesn't seem available.
"l2_limit_range": [20],
"trades_has_history": False,
"tickers_have_bid_ask": False,
"stoploss_on_exchange": False,
"exchange_has_overrides": {"fetchTrades": False},
"marketOrderRequiresPrice": True,
}
_ft_has_futures: FtHas = {
"stoploss_on_exchange": True,
"stoploss_order_types": {"limit": "limit"},
"stop_price_prop": "stopPrice",
"funding_fee_timeframe": "1h",
"funding_fee_candle_limit": 500,
}
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
(TradingMode.FUTURES, MarginMode.ISOLATED)
]
@property
def _ccxt_config(self) -> Dict:
# Parameters to add directly to ccxt sync/async initialization.
# ccxt defaults to swap mode.
def _ccxt_config(self) -> dict:
# ccxt Hyperliquid defaults to swap
config = {}
if self.trading_mode == TradingMode.SPOT:
config.update({"options": {"defaultType": "spot"}})
config.update(super()._ccxt_config)
return config
@property
def precision_mode_price(self) -> int:
def get_max_leverage(self, pair: str, stake_amount: float | None) -> float:
# There are no leverage tiers
if self.trading_mode == TradingMode.FUTURES:
return self.markets[pair]["limits"]["leverage"]["max"]
else:
return 1.0
def _lev_prep(self, pair: str, leverage: float, side: BuySell, accept_fail: bool = False):
if self.trading_mode != TradingMode.SPOT:
# Hyperliquid expects leverage to be an int
leverage = int(leverage)
# Hyperliquid needs the parameter leverage.
# Don't use _set_leverage(), as this sets margin back to cross
self.set_margin_mode(pair, self.margin_mode, params={"leverage": leverage})
def dry_run_liquidation_price(
self,
pair: str,
open_rate: float, # Entry price of position
is_short: bool,
amount: float,
stake_amount: float,
leverage: float,
wallet_balance: float, # Or margin balance
open_trades: list,
) -> float | None:
"""
Override the default precision mode for price.
Optimized
Docs: https://hyperliquid.gitbook.io/hyperliquid-docs/trading/liquidations
Below can be done in fewer lines of code, but like this it matches the documentation.
Tested with 196 unique ccxt fetch_positions() position outputs
- Only first output per position where pnl=0.0
- Compare against returned liquidation price
Positions: 197 Average deviation: 0.00028980% Max deviation: 0.01309453%
Positions info:
{'leverage': {1.0: 23, 2.0: 155, 3.0: 8, 4.0: 7, 5.0: 4},
'side': {'long': 133, 'short': 64},
'symbol': {'BTC/USDC:USDC': 81,
'DOGE/USDC:USDC': 20,
'ETH/USDC:USDC': 53,
'SOL/USDC:USDC': 43}}
"""
return SIGNIFICANT_DIGITS
# Defining/renaming variables to match the documentation
isolated_margin = wallet_balance
position_size = amount
price = open_rate
position_value = price * position_size
max_leverage = self.markets[pair]["limits"]["leverage"]["max"]
# Docs: The maintenance margin is half of the initial margin at max leverage,
# which varies from 3-50x. In other words, the maintenance margin is between 1%
# (for 50x max leverage assets) and 16.7% (for 3x max leverage assets)
# depending on the asset
# The key thing here is 'Half of the initial margin at max leverage'.
# A bit ambiguous, but this interpretation leads to accurate results:
# 1. Start from the position value
# 2. Assume max leverage, calculate the initial margin by dividing the position value
# by the max leverage
# 3. Divide this by 2
maintenance_margin_required = position_value / max_leverage / 2
# Docs: margin_available (isolated) = isolated_margin - maintenance_margin_required
margin_available = isolated_margin - maintenance_margin_required
# Docs: The maintenance margin is half of the initial margin at max leverage
# The docs don't explicitly specify maintenance leverage, but this works.
# Double because of the statement 'half of the initial margin at max leverage'
maintenance_leverage = max_leverage * 2
# Docs: l = 1 / MAINTENANCE_LEVERAGE (Using 'll' to comply with PEP8: E741)
ll = 1 / maintenance_leverage
# Docs: side = 1 for long and -1 for short
side = -1 if is_short else 1
# Docs: liq_price = price - side * margin_available / position_size / (1 - l * side)
liq_price = price - side * margin_available / position_size / (1 - ll * side)
if self.trading_mode == TradingMode.FUTURES:
return liq_price
else:
raise OperationalException(
"Freqtrade only supports isolated futures for leverage trading"
)
def get_funding_fees(
self, pair: str, amount: float, is_short: bool, open_date: datetime
) -> float:
"""
Fetch funding fees, either from the exchange (live) or calculates them
based on funding rate/mark price history
:param pair: The quote/base pair of the trade
:param is_short: trade direction
:param amount: Trade amount
:param open_date: Open date of the trade
:return: funding fee since open_date
:raises: ExchangeError if something goes wrong.
"""
# Hyperliquid does not have fetchFundingHistory
if self.trading_mode == TradingMode.FUTURES:
try:
return self._fetch_and_calculate_funding_fees(pair, amount, is_short, open_date)
except ExchangeError:
logger.warning(f"Could not update funding fees for {pair}.")
return 0.0
def fetch_order(self, order_id: str, pair: str, params: dict | None = None) -> CcxtOrder:
order = super().fetch_order(order_id, pair, params)
if (
order["average"] is None
and order["status"] in ("canceled", "closed")
and order["filled"] > 0
):
# Hyperliquid does not fill the average price in the order response
# Fetch trades to calculate the average price to have the actual price
# the order was executed at
trades = self.get_trades_for_order(order_id, pair, since=dt_from_ts(order["timestamp"]))
if trades:
total_amount = sum(t["amount"] for t in trades)
order["average"] = (
sum(t["price"] * t["amount"] for t in trades) / total_amount
if total_amount
else None
)
return order
+30 -9
View File
@@ -2,7 +2,7 @@
import logging
from datetime import datetime
from typing import Any, Dict, List, Optional, Tuple
from typing import Any
import ccxt
from pandas import DataFrame
@@ -19,7 +19,7 @@ logger = logging.getLogger(__name__)
class Kraken(Exchange):
_params: Dict = {"trading_agreement": "agree"}
_params: dict = {"trading_agreement": "agree"}
_ft_has: FtHas = {
"stoploss_on_exchange": True,
"stop_price_param": "stopLossPrice",
@@ -35,13 +35,13 @@ class Kraken(Exchange):
"mark_ohlcv_timeframe": "4h",
}
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
# TradingMode.SPOT always supported and not required in this list
# (TradingMode.MARGIN, MarginMode.CROSS),
# (TradingMode.FUTURES, MarginMode.CROSS)
]
def market_is_tradable(self, market: Dict[str, Any]) -> bool:
def market_is_tradable(self, market: dict[str, Any]) -> bool:
"""
Check if the market symbol is tradable by Freqtrade.
Default checks + check if pair is darkpool pair.
@@ -50,12 +50,28 @@ class Kraken(Exchange):
return parent_check and market.get("darkpool", False) is False
def get_tickers(self, symbols: Optional[List[str]] = None, cached: bool = False) -> Tickers:
def get_tickers(self, symbols: list[str] | None = None, *, cached: bool = False) -> Tickers:
# Only fetch tickers for current stake currency
# Otherwise the request for kraken becomes too large.
symbols = list(self.get_markets(quote_currencies=[self._config["stake_currency"]]))
return super().get_tickers(symbols=symbols, cached=cached)
def consolidate_balances(self, balances: CcxtBalances) -> CcxtBalances:
"""
Consolidate balances for the same currency.
Kraken returns ".F" balances if rewards is enabled.
"""
consolidated: CcxtBalances = {}
for currency, balance in balances.items():
base_currency = currency[:-2] if currency.endswith(".F") else currency
if base_currency in consolidated:
consolidated[base_currency]["free"] += balance["free"]
consolidated[base_currency]["used"] += balance["used"]
consolidated[base_currency]["total"] += balance["total"]
else:
consolidated[base_currency] = balance
return consolidated
@retrier
def get_balances(self) -> CcxtBalances:
if self._config["dry_run"]:
@@ -68,6 +84,10 @@ class Kraken(Exchange):
balances.pop("free", None)
balances.pop("total", None)
balances.pop("used", None)
self._log_exchange_response("fetch_balances", balances)
# Consolidate balances
balances = self.consolidate_balances(balances)
orders = self._api.fetch_open_orders()
order_list = [
@@ -86,6 +106,7 @@ class Kraken(Exchange):
balances[bal]["used"] = sum(order[1] for order in order_list if order[0] == bal)
balances[bal]["free"] = balances[bal]["total"] - balances[bal]["used"]
self._log_exchange_response("fetch_balances2", balances)
return balances
except ccxt.DDoSProtection as e:
raise DDosProtection(e) from e
@@ -99,7 +120,7 @@ class Kraken(Exchange):
def _set_leverage(
self,
leverage: float,
pair: Optional[str] = None,
pair: str | None = None,
accept_fail: bool = False,
):
"""
@@ -115,7 +136,7 @@ class Kraken(Exchange):
leverage: float,
reduceOnly: bool,
time_in_force: str = "GTC",
) -> Dict:
) -> dict:
params = super()._get_params(
side=side,
ordertype=ordertype,
@@ -137,7 +158,7 @@ class Kraken(Exchange):
is_short: bool,
open_date: datetime,
close_date: datetime,
time_in_ratio: Optional[float] = None,
time_in_ratio: float | None = None,
) -> float:
"""
# ! This method will always error when run by Freqtrade because time_in_ratio is never
@@ -165,7 +186,7 @@ class Kraken(Exchange):
return fees if is_short else -fees
def _get_trade_pagination_next_value(self, trades: List[Dict]):
def _get_trade_pagination_next_value(self, trades: list[dict]):
"""
Extract pagination id for the next "from_id" value
Applies only to fetch_trade_history by id.
+3 -4
View File
@@ -1,11 +1,10 @@
"""Kucoin exchange subclass."""
import logging
from typing import Dict
from freqtrade.constants import BuySell
from freqtrade.exchange import Exchange
from freqtrade.exchange.exchange_types import FtHas
from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
logger = logging.getLogger(__name__)
@@ -32,7 +31,7 @@ class Kucoin(Exchange):
"ohlcv_candle_limit": 1500,
}
def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> Dict:
def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> dict:
params = self._params.copy()
params.update({"stopPrice": stop_price, "stop": "loss"})
return params
@@ -48,7 +47,7 @@ class Kucoin(Exchange):
leverage: float,
reduceOnly: bool = False,
time_in_force: str = "GTC",
) -> Dict:
) -> CcxtOrder:
res = super().create_order(
pair=pair,
ordertype=ordertype,
+21
View File
@@ -0,0 +1,21 @@
"""Lbank exchange subclass"""
import logging
from freqtrade.exchange import Exchange
from freqtrade.exchange.exchange_types import FtHas
logger = logging.getLogger(__name__)
class Lbank(Exchange):
"""
Lbank exchange class. Contains adjustments needed for Freqtrade to work
with this exchange.
"""
_ft_has: FtHas = {
"ohlcv_candle_limit": 1998, # lower than the allowed 2000 to avoid current_candle issue
"trades_has_history": False,
}
+14 -15
View File
@@ -1,6 +1,5 @@
import logging
from datetime import timedelta
from typing import Any, Dict, List, Optional, Tuple
import ccxt
@@ -14,7 +13,7 @@ from freqtrade.exceptions import (
)
from freqtrade.exchange import Exchange, date_minus_candles
from freqtrade.exchange.common import API_RETRY_COUNT, retrier
from freqtrade.exchange.exchange_types import FtHas
from freqtrade.exchange.exchange_types import CcxtOrder, FtHas
from freqtrade.misc import safe_value_fallback2
from freqtrade.util import dt_now, dt_ts
@@ -48,7 +47,7 @@ class Okx(Exchange):
"ws_enabled": True,
}
_supported_trading_mode_margin_pairs: List[Tuple[TradingMode, MarginMode]] = [
_supported_trading_mode_margin_pairs: list[tuple[TradingMode, MarginMode]] = [
# TradingMode.SPOT always supported and not required in this list
# (TradingMode.MARGIN, MarginMode.CROSS),
# (TradingMode.FUTURES, MarginMode.CROSS),
@@ -57,10 +56,10 @@ class Okx(Exchange):
net_only = True
_ccxt_params: Dict = {"options": {"brokerId": "ffb5405ad327SUDE"}}
_ccxt_params: dict = {"options": {"brokerId": "ffb5405ad327SUDE"}}
def ohlcv_candle_limit(
self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = None
self, timeframe: str, candle_type: CandleType, since_ms: int | None = None
) -> int:
"""
Exchange ohlcv candle limit
@@ -119,7 +118,7 @@ class Okx(Exchange):
leverage: float,
reduceOnly: bool,
time_in_force: str = "GTC",
) -> Dict:
) -> dict:
params = super()._get_params(
side=side,
ordertype=ordertype,
@@ -184,14 +183,14 @@ class Okx(Exchange):
pair_tiers = self._leverage_tiers[pair]
return pair_tiers[-1]["maxNotional"] / leverage
def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> Dict:
def _get_stop_params(self, side: BuySell, ordertype: str, stop_price: float) -> dict:
params = super()._get_stop_params(side, ordertype, stop_price)
if self.trading_mode == TradingMode.FUTURES and self.margin_mode:
params["tdMode"] = self.margin_mode.value
params["posSide"] = self._get_posSide(side, True)
return params
def _convert_stop_order(self, pair: str, order_id: str, order: Dict) -> Dict:
def _convert_stop_order(self, pair: str, order_id: str, order: CcxtOrder) -> CcxtOrder:
if (
order.get("status", "open") == "closed"
and (real_order_id := order.get("info", {}).get("ordId")) is not None
@@ -209,7 +208,9 @@ class Okx(Exchange):
return order
@retrier(retries=API_RETRY_COUNT)
def fetch_stoploss_order(self, order_id: str, pair: str, params: Optional[Dict] = None) -> Dict:
def fetch_stoploss_order(
self, order_id: str, pair: str, params: dict | None = None
) -> CcxtOrder:
if self._config["dry_run"]:
return self.fetch_dry_run_order(order_id)
@@ -231,7 +232,7 @@ class Okx(Exchange):
return self._fetch_stop_order_fallback(order_id, pair)
def _fetch_stop_order_fallback(self, order_id: str, pair: str) -> Dict:
def _fetch_stop_order_fallback(self, order_id: str, pair: str) -> CcxtOrder:
params2 = {"stop": True, "ordType": "conditional"}
for method in (
self._api.fetch_open_orders,
@@ -256,14 +257,12 @@ class Okx(Exchange):
raise OperationalException(e) from e
raise RetryableOrderError(f"StoplossOrder not found (pair: {pair} id: {order_id}).")
def get_order_id_conditional(self, order: Dict[str, Any]) -> str:
def get_order_id_conditional(self, order: CcxtOrder) -> str:
if order.get("type", "") == "stop":
return safe_value_fallback2(order, order, "id_stop", "id")
return order["id"]
def cancel_stoploss_order(
self, order_id: str, pair: str, params: Optional[Dict] = None
) -> Dict:
def cancel_stoploss_order(self, order_id: str, pair: str, params: dict | None = None) -> dict:
params1 = {"stop": True}
# 'ordType': 'conditional'
#
@@ -273,7 +272,7 @@ class Okx(Exchange):
params=params1,
)
def _fetch_orders_emulate(self, pair: str, since_ms: int) -> List[Dict]:
def _fetch_orders_emulate(self, pair: str, since_ms: int) -> list[CcxtOrder]:
orders = []
orders = self._api.fetch_closed_orders(pair, since=since_ms)
+6 -7
View File
@@ -2,7 +2,6 @@ import logging
import random
from abc import abstractmethod
from enum import Enum
from typing import List, Optional, Type, Union
import gymnasium as gym
import numpy as np
@@ -89,7 +88,7 @@ class BaseEnvironment(gym.Env):
self.fee = fee
# set here to default 5Ac, but all children envs can override this
self.actions: Type[Enum] = BaseActions
self.actions: type[Enum] = BaseActions
self.tensorboard_metrics: dict = {}
self.can_short: bool = can_short
self.live: bool = live
@@ -140,7 +139,7 @@ class BaseEnvironment(gym.Env):
self._end_tick: int = len(self.prices) - 1
self._done: bool = False
self._current_tick: int = self._start_tick
self._last_trade_tick: Optional[int] = None
self._last_trade_tick: int | None = None
self._position = Positions.Neutral
self._position_history: list = [None]
self.total_reward: float = 0
@@ -163,7 +162,7 @@ class BaseEnvironment(gym.Env):
Unique to the environment action count. Must be inherited.
"""
def action_masks(self) -> List[bool]:
def action_masks(self) -> list[bool]:
return [self._is_valid(action.value) for action in self.actions]
def seed(self, seed: int = 1):
@@ -173,8 +172,8 @@ class BaseEnvironment(gym.Env):
def tensorboard_log(
self,
metric: str,
value: Optional[Union[int, float]] = None,
inc: Optional[bool] = None,
value: int | float | None = None,
inc: bool | None = None,
category: str = "custom",
):
"""
@@ -375,7 +374,7 @@ class BaseEnvironment(gym.Env):
def current_price(self) -> float:
return self.prices.iloc[self._current_tick].open
def get_actions(self) -> Type[Enum]:
def get_actions(self) -> type[Enum]:
"""
Used by SubprocVecEnv to get actions from
initialized env for tensorboard callback
@@ -2,9 +2,10 @@ import copy
import importlib
import logging
from abc import abstractmethod
from collections.abc import Callable
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Callable, Dict, Optional, Tuple, Type, Union
from typing import Any
import gymnasium as gym
import numpy as np
@@ -49,9 +50,9 @@ class BaseReinforcementLearningModel(IFreqaiModel):
)
th.set_num_threads(self.max_threads)
self.reward_params = self.freqai_info["rl_config"]["model_reward_parameters"]
self.train_env: Union[VecMonitor, SubprocVecEnv, gym.Env] = gym.Env()
self.eval_env: Union[VecMonitor, SubprocVecEnv, gym.Env] = gym.Env()
self.eval_callback: Optional[MaskableEvalCallback] = None
self.train_env: VecMonitor | SubprocVecEnv | gym.Env = gym.Env()
self.eval_env: VecMonitor | SubprocVecEnv | gym.Env = gym.Env()
self.eval_callback: MaskableEvalCallback | None = None
self.model_type = self.freqai_info["rl_config"]["model_type"]
self.rl_config = self.freqai_info["rl_config"]
self.df_raw: DataFrame = DataFrame()
@@ -114,7 +115,7 @@ class BaseReinforcementLearningModel(IFreqaiModel):
training_filter=True,
)
dd: Dict[str, Any] = dk.make_train_test_datasets(features_filtered, labels_filtered)
dd: dict[str, Any] = dk.make_train_test_datasets(features_filtered, labels_filtered)
self.df_raw = copy.deepcopy(dd["train_features"])
dk.fit_labels() # FIXME useless for now, but just satiating append methods
@@ -151,7 +152,7 @@ class BaseReinforcementLearningModel(IFreqaiModel):
def set_train_and_eval_environments(
self,
data_dictionary: Dict[str, DataFrame],
data_dictionary: dict[str, DataFrame],
prices_train: DataFrame,
prices_test: DataFrame,
dk: FreqaiDataKitchen,
@@ -183,7 +184,7 @@ class BaseReinforcementLearningModel(IFreqaiModel):
actions = self.train_env.get_actions()
self.tensorboard_callback = TensorboardCallback(verbose=1, actions=actions)
def pack_env_dict(self, pair: str) -> Dict[str, Any]:
def pack_env_dict(self, pair: str) -> dict[str, Any]:
"""
Create dictionary of environment arguments
"""
@@ -204,7 +205,7 @@ class BaseReinforcementLearningModel(IFreqaiModel):
return env_info
@abstractmethod
def fit(self, data_dictionary: Dict[str, Any], dk: FreqaiDataKitchen, **kwargs):
def fit(self, data_dictionary: dict[str, Any], dk: FreqaiDataKitchen, **kwargs):
"""
Agent customizations and abstract Reinforcement Learning customizations
go in here. Abstract method, so this function must be overridden by
@@ -212,7 +213,7 @@ class BaseReinforcementLearningModel(IFreqaiModel):
"""
return
def get_state_info(self, pair: str) -> Tuple[float, float, int]:
def get_state_info(self, pair: str) -> tuple[float, float, int]:
"""
State info during dry/live (not backtesting) which is fed back
into the model.
@@ -250,7 +251,7 @@ class BaseReinforcementLearningModel(IFreqaiModel):
def predict(
self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs
) -> Tuple[DataFrame, npt.NDArray[np.int_]]:
) -> tuple[DataFrame, npt.NDArray[np.int_]]:
"""
Filter the prediction features data and predict with it.
:param unfiltered_dataframe: Full dataframe for the current backtest period.
@@ -303,7 +304,7 @@ class BaseReinforcementLearningModel(IFreqaiModel):
def build_ohlc_price_dataframes(
self, data_dictionary: dict, pair: str, dk: FreqaiDataKitchen
) -> Tuple[DataFrame, DataFrame]:
) -> tuple[DataFrame, DataFrame]:
"""
Builds the train prices and test prices for the environment.
"""
@@ -482,13 +483,13 @@ class BaseReinforcementLearningModel(IFreqaiModel):
def make_env(
MyRLEnv: Type[BaseEnvironment],
MyRLEnv: type[BaseEnvironment],
env_id: str,
rank: int,
seed: int,
train_df: DataFrame,
price: DataFrame,
env_info: Dict[str, Any] = {},
env_info: dict[str, Any] = {},
) -> Callable:
"""
Utility function for multiprocessed env.

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