Compare commits

...

275 Commits

Author SHA1 Message Date
Matthias d104fb13a0 Merge pull request #8978 from freqtrade/new_release
New Release 2023.7
2023-07-30 09:02:44 +02:00
Matthias 047de2e0ff Bump version to 2023.7 2023-07-29 18:19:04 +02:00
Matthias ead9e6f116 Merge pull request #8977 from Bloodhunter4rc/patch-3
showcase.md - Change links to https
2023-07-29 17:09:20 +02:00
Bloodhunter4rc b17918d8cf change link to https 2023-07-29 16:48:52 +02:00
Matthias 79910870a3 Fix bug resampling monthly candles
closes #8972
2023-07-29 08:58:30 +02:00
Matthias e7e7a17183 Add test confirming #8972 2023-07-29 08:50:28 +02:00
Matthias 3fa31bfe20 Better explain ROI assumption on level change
closes #8975
2023-07-29 08:14:12 +02:00
Matthias b2abdab7cd Fix bug where adjust_entry_price was called for exit orders
closes #8973
2023-07-28 07:16:32 +02:00
Matthias fd7dfc95e3 Enhance dca integration test for adjust_entry checks 2023-07-28 07:16:12 +02:00
Matthias 797617abaa Fix test comment 2023-07-28 07:07:10 +02:00
Matthias 9a400d0e6f Allow comments and trailing commas in remotepairlist files
closes #8971
2023-07-27 18:05:22 +02:00
Matthias 8f18a52cdf Fix Typo in remote pairlist docs
related: #8971
2023-07-27 09:24:42 +02:00
Matthias d638a4a0ff Raise proper error on strategy search
part of https://github.com/freqtrade/frequi/issues/1387
2023-07-27 07:03:35 +02:00
Matthias bbf472e69b Improve errorhandling on webserver endpoint
Part of https://github.com/freqtrade/frequi/issues/1387
2023-07-27 06:52:34 +02:00
Matthias 83f45d0e65 Call static method as static method, not as if it were an instance method 2023-07-27 06:39:48 +02:00
Matthias d6122585f7 Prevent pandas exception on Date assignment 2023-07-27 06:39:31 +02:00
Matthias 2fcff78756 Move comment to actually relevant line 2023-07-26 07:07:21 +02:00
Matthias af1d2ee2a2 Merge pull request #8968 from Bloodhunter4rc/patch-2
Update showcase.md / Change Domain due to recent dns issues
2023-07-26 06:43:48 +02:00
Bloodhunter4rc f56f5179d2 Update showcase.md
Change of domain due to recent dns issues.
2023-07-25 23:06:56 +02:00
Matthias c2b40da762 Bump Api Version 2023-07-25 20:51:33 +02:00
Matthias 05e4b63091 Extract backtest_result deletion logic to separate function 2023-07-25 20:42:07 +02:00
Matthias 8b2abf4422 Remove .json from backtesting output 2023-07-25 20:41:28 +02:00
Matthias 997b80fd7b Allow deleting of backtest files 2023-07-25 20:34:45 +02:00
Matthias 5a7e822342 Improve security of get_backtest_history_result 2023-07-25 20:20:09 +02:00
Matthias 1d39cc18bf Add is_file_in_dir helper function 2023-07-25 20:19:23 +02:00
Matthias e39af17207 Improve typing for is_relative_to 2023-07-25 20:07:44 +02:00
Matthias 4ce95dd1c3 Merge pull request #8955 from freqtrade/feat/bt_streaks
Backtesting - streak output
2023-07-25 18:06:11 +02:00
Matthias 47fca02ba0 Improve docstring 2023-07-25 07:06:42 +02:00
Matthias ed2485dd57 Move generate_wins_draw_losses to bt_output (it's an output function, not a calculation) 2023-07-25 07:04:25 +02:00
Matthias 6dc15fad01 Merge pull request #8962 from freqtrade/dependabot/pip/develop/uvicorn-0.23.1
Bump uvicorn from 0.23.0 to 0.23.1
2023-07-25 06:38:13 +02:00
Matthias 27566a4c8a Merge pull request #8963 from freqtrade/dependabot/pip/develop/ccxt-4.0.36
Bump ccxt from 4.0.35 to 4.0.36
2023-07-25 06:37:56 +02:00
Matthias 3860a5a8c5 Merge pull request #8965 from freqtrade/dependabot/pip/develop/ruff-0.0.280
Bump ruff from 0.0.278 to 0.0.280
2023-07-25 06:37:42 +02:00
Matthias 235721d9d9 Merge pull request #8964 from freqtrade/dependabot/pip/develop/types-requests-2.31.0.2
Bump types-requests from 2.31.0.1 to 2.31.0.2
2023-07-24 21:37:11 +02:00
Matthias 5a057e4b94 per-commit bump types-requests 2023-07-24 17:34:39 +02:00
dependabot[bot] b90e7d75c6 Bump ruff from 0.0.278 to 0.0.280
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.278 to 0.0.280.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.278...v0.0.280)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 15:32:13 +00:00
dependabot[bot] 1c26a6600b Bump types-requests from 2.31.0.1 to 2.31.0.2
Bumps [types-requests](https://github.com/python/typeshed) from 2.31.0.1 to 2.31.0.2.
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 15:31:56 +00:00
dependabot[bot] 161446393d Bump ccxt from 4.0.35 to 4.0.36
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.0.35 to 4.0.36.
- [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.0.35...4.0.36)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 15:31:53 +00:00
dependabot[bot] 9df302f2f0 Bump uvicorn from 0.23.0 to 0.23.1
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.23.0 to 0.23.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.23.0...0.23.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 15:31:45 +00:00
Matthias e95fa83ea2 Merge pull request #8953 from freqtrade/dependabot/pip/develop/types-python-dateutil-2.8.19.14
Bump types-python-dateutil from 2.8.19.13 to 2.8.19.14
2023-07-24 16:52:39 +02:00
Matthias b151a2c3e1 Merge pull request #8947 from freqtrade/dependabot/pip/develop/urllib3-2.0.4
Bump urllib3 from 2.0.3 to 2.0.4
2023-07-24 16:19:38 +02:00
Matthias ce7f7d828d pre-commit bump dateuti 2023-07-24 15:49:25 +02:00
dependabot[bot] 7a69bdff6a Bump types-python-dateutil from 2.8.19.13 to 2.8.19.14
Bumps [types-python-dateutil](https://github.com/python/typeshed) from 2.8.19.13 to 2.8.19.14.
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 13:34:35 +00:00
Matthias 1abb71d5a3 Merge pull request #8950 from freqtrade/dependabot/pip/develop/nbconvert-7.7.2
Bump nbconvert from 7.7.0 to 7.7.2
2023-07-24 15:34:10 +02:00
Matthias 2e57ad695f Merge pull request #8952 from freqtrade/dependabot/pip/develop/types-tabulate-0.9.0.3
Bump types-tabulate from 0.9.0.2 to 0.9.0.3
2023-07-24 15:33:49 +02:00
Matthias bb36c9500f Bump pre-commit types tabulate 2023-07-24 14:12:52 +02:00
dependabot[bot] 235067c79f Bump nbconvert from 7.7.0 to 7.7.2
Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 7.7.0 to 7.7.2.
- [Release notes](https://github.com/jupyter/nbconvert/releases)
- [Changelog](https://github.com/jupyter/nbconvert/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jupyter/nbconvert/compare/v7.7.0...v7.7.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 12:12:06 +00:00
dependabot[bot] 11e604d613 Bump types-tabulate from 0.9.0.2 to 0.9.0.3
Bumps [types-tabulate](https://github.com/python/typeshed) from 0.9.0.2 to 0.9.0.3.
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 12:12:03 +00:00
Matthias f7505536ff Merge pull request #8954 from freqtrade/dependabot/pip/develop/types-cachetools-5.3.0.6
Bump types-cachetools from 5.3.0.5 to 5.3.0.6
2023-07-24 14:11:19 +02:00
Matthias b096c857ae Merge pull request #8951 from freqtrade/dependabot/pip/develop/pyjwt-2.8.0
Bump pyjwt from 2.7.0 to 2.8.0
2023-07-24 10:03:49 +02:00
Matthias b41b966443 pre-commit bump cachetools 2023-07-24 10:03:08 +02:00
Matthias e01e712b8c Merge pull request #8948 from freqtrade/dependabot/pip/develop/ccxt-4.0.35
Bump ccxt from 4.0.34 to 4.0.35
2023-07-24 10:01:47 +02:00
Matthias 327b055468 Add consecutive wins/losses to backtest output 2023-07-24 07:22:33 +02:00
dependabot[bot] 221f242a4c Bump ccxt from 4.0.34 to 4.0.35
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.0.34 to 4.0.35.
- [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.0.34...4.0.35)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 05:19:04 +00:00
Matthias a9d310ca00 Disable bitvavo candle test temporarily after downtime 2023-07-24 07:17:29 +02:00
Matthias f26b49ee06 Ensure return value is an int, not a np.int 2023-07-24 07:09:19 +02:00
Matthias 380244f8b1 Improve calc_streak, rename method 2023-07-24 07:09:11 +02:00
Matthias c01c9ee411 Merge pull request #8949 from freqtrade/dependabot/pip/develop/mkdocs-material-9.1.19
Bump mkdocs-material from 9.1.18 to 9.1.19
2023-07-24 07:07:12 +02:00
Matthias 0f046ceaf2 Implement calc_consecutive_losses 2023-07-24 06:36:24 +02:00
Matthias a7bd6725f5 Add test to verify consecutive wins / losses calculation 2023-07-24 06:36:16 +02:00
dependabot[bot] c61b72e5cc Bump urllib3 from 2.0.3 to 2.0.4
Bumps [urllib3](https://github.com/urllib3/urllib3) from 2.0.3 to 2.0.4.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/2.0.3...2.0.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 04:33:05 +00:00
Matthias e2a19402ec Merge pull request #8946 from freqtrade/dependabot/pip/develop/jsonschema-4.18.4
Bump jsonschema from 4.18.3 to 4.18.4
2023-07-24 06:32:06 +02:00
dependabot[bot] d909fde4bb Bump types-cachetools from 5.3.0.5 to 5.3.0.6
Bumps [types-cachetools](https://github.com/python/typeshed) from 5.3.0.5 to 5.3.0.6.
- [Commits](https://github.com/python/typeshed/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 03:44:45 +00:00
dependabot[bot] 2bb4af911c Bump pyjwt from 2.7.0 to 2.8.0
Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.7.0 to 2.8.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.7.0...2.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 03:44:20 +00:00
dependabot[bot] 00a392b262 Bump mkdocs-material from 9.1.18 to 9.1.19
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.18 to 9.1.19.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.18...9.1.19)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 03:44:05 +00:00
dependabot[bot] 6f5ba27251 Bump jsonschema from 4.18.3 to 4.18.4
Bumps [jsonschema](https://github.com/python-jsonschema/jsonschema) from 4.18.3 to 4.18.4.
- [Release notes](https://github.com/python-jsonschema/jsonschema/releases)
- [Changelog](https://github.com/python-jsonschema/jsonschema/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/python-jsonschema/jsonschema/compare/v4.18.3...v4.18.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-24 03:43:35 +00:00
Matthias 6ddbc8c00d Move generate_wins_draw_losses to bt_output (it's an output function, not a calculation) 2023-07-23 19:57:47 +02:00
Matthias a00fcd68f8 Default to 0.0.0.0 if on API listen address for configs generated through docker 2023-07-23 19:44:43 +02:00
Matthias fb7fb7f59c Add helper function detecting (prebuilt) docker environment 2023-07-23 19:38:30 +02:00
Matthias d7916366bd Adjust webhook tests to include timeout 2023-07-23 19:21:55 +02:00
Matthias ad82ad4407 webhook docs improvements 2023-07-23 18:28:49 +02:00
Matthias 8dfe43f370 Add timeout for webhooks 2023-07-23 18:28:43 +02:00
Matthias 4549fb349c Add security notice about IP whitelisting on bybit docs 2023-07-23 17:57:48 +02:00
Matthias 889a732e06 Enhance bybit documentation 2023-07-23 17:57:04 +02:00
Matthias 52ec2324dd Merge pull request #8943 from stash86/bt-metrics
merge to use  expectancy and expectancy ratio from data/metrics
2023-07-23 08:37:08 +02:00
Stefano Ariestasia 8f04225282 another test fix 2023-07-23 15:00:08 +09:00
Stefano Ariestasia 4c23771d39 fix expectancy test 2023-07-23 14:42:48 +09:00
Stefano Ariestasia 0eddc6b7ad update expectancy test 2023-07-23 14:27:45 +09:00
Matthias 787e94924d Update default expectancy ratio to 100 2023-07-23 07:20:59 +02:00
Matthias 955a63725a Improve resiliance when showing older backtest results 2023-07-22 19:43:20 +02:00
dependabot[bot] 27a36bfb40 Bump lightgbm from 3.3.5 to 4.0.0 (#8923)
* Bump lightgbm from 3.3.5 to 4.0.0

Bumps [lightgbm](https://github.com/microsoft/LightGBM) from 3.3.5 to 4.0.0.
- [Release notes](https://github.com/microsoft/LightGBM/releases)
- [Commits](https://github.com/microsoft/LightGBM/compare/v3.3.5...v4.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>

* fix: ensure freqai lightgbm variants conform to v4.0.0

* remove random file

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: robcaulk <rob.caulk@gmail.com>
2023-07-22 15:30:58 +02:00
Stefano Ariestasia e5f01ab2e8 pre-commit fix 2023-07-22 17:45:58 +09:00
Stefano Ariestasia 40d7d05e4e merge 2 expectancy functions 2023-07-22 17:29:43 +09:00
Matthias 14d2e3e88e Merge pull request #8941 from SitoCH/develop
Map BUSD to correct coingecko id
2023-07-22 08:33:17 +02:00
Stefano Ariestasia c6ee8fcf54 remove unused check 2023-07-22 12:20:35 +09:00
Stefano Ariestasia 3552fa431b fix test 2023-07-22 11:40:52 +09:00
Stefano Ariestasia 3dd33cde00 fix test 2023-07-22 11:39:10 +09:00
Stefano Ariestasia ee3b69ea63 fix test 2023-07-22 11:37:22 +09:00
Stefano Ariestasia b0639ab319 flake8 2023-07-22 11:29:08 +09:00
Stefano Ariestasia cfd8b068e7 add test for expectancy 2023-07-22 11:25:53 +09:00
Stefano Ariestasia 8621dc96e7 fix tests 2023-07-22 09:44:24 +09:00
Stefano Ariestasia 11f24aff97 flake8 2023-07-22 09:19:36 +09:00
Stefano Ariestasia dcc3ef1309 flake8 fix 2023-07-22 09:18:22 +09:00
Stefano Ariestasia 4812bcc28b flake8 fiz 2023-07-22 09:13:24 +09:00
Stefano Ariestasia c048e7229a modify expectancy and expectancy ratio 2023-07-22 08:36:51 +09:00
Simone Grignola 4ea3f41d48 Map BUSD to correct coingecko id 2023-07-21 20:49:37 +00:00
Matthias 6874123974 Bump ccxt to 4.0.34
closes #8913
2023-07-21 21:11:30 +02:00
Matthias 8d332fb99e Ensure /pair_history endpoint correctly respects startup_candle_count
closes https://github.com/freqtrade/frequi/issues/1379
2023-07-21 20:58:26 +02:00
Matthias f4933a9cff Improve test for pair_history
Verifies that startup_candle_count is verified correctly.
2023-07-21 20:57:05 +02:00
Matthias 4369e3cdeb trim_dataframe should enforce kwargs for non-required arguments 2023-07-21 20:33:41 +02:00
Matthias 9bfe96d4d6 Simplify advise calls by extracting that part into a method. 2023-07-21 20:27:52 +02:00
Matthias 91bf8abf38 Add comment to clarify usage of trim_dataframes 2023-07-21 20:22:44 +02:00
Matthias 9c1fea0e7b Add winrate to several bt metrics 2023-07-20 20:51:38 +02:00
Matthias ac2147727f Update test for updated cost logic 2023-07-20 19:51:45 +02:00
Matthias ea45349235 Completely remove "fee_cost_in_contracts" functionality 2023-07-20 19:51:45 +02:00
Matthias e734ab52de okx fees are not in contracts. 2023-07-20 19:51:45 +02:00
Matthias 75628403b0 Invert order_props_in_contracts logic - cost is almost never in contracts 2023-07-20 19:51:45 +02:00
Matthias b3642749fd Merge pull request #8940 from freqtrade/dependabot/pip/aiohttp-3.8.5
Bump aiohttp from 3.8.4 to 3.8.5
2023-07-20 19:41:29 +02:00
dependabot[bot] f735973bf8 Bump aiohttp from 3.8.4 to 3.8.5
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.8.4 to 3.8.5.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/v3.8.5/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.8.4...v3.8.5)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-20 16:04:06 +00:00
Matthias bdb778cb9f Merge pull request #8912 from froggleston/rpc_expectancy
Add expectancy to RPC calls and telegram
2023-07-19 20:21:09 +02:00
Matthias ab5b272868 Fix double-%% 2023-07-19 19:53:00 +02:00
Matthias 49d6b0afb8 Merge pull request #8937 from freqtrade/froggleston-bt-analysis-docs-1
Add built-in trade fields in indicator list docs
2023-07-19 19:41:24 +02:00
Robert Davey bb5a12dc11 Add built-in trade fields in indicator list docs
There are some trade- and candle-related fields that are always available to output on the indicator-list so have updated the docs to include the most commonly used ones.
2023-07-19 11:11:10 +01:00
Matthias 0539c6bf26 Merge pull request #8918 from freqtrade/dependabot/pip/develop/ta-lib-0.4.27
Bump ta-lib from 0.4.26 to 0.4.27
2023-07-19 06:48:34 +02:00
froggleston f95f954df7 Convert winrate to ratio instead of % in calculations 2023-07-18 22:25:17 +01:00
Matthias 5ee4586989 use find-links for windows install 2023-07-18 21:14:55 +02:00
Matthias 9c39fd6e92 Update cached binance lev-tiers file 2023-07-18 20:25:55 +02:00
Matthias c8ee48bc98 Update comment on install script 2023-07-18 20:20:57 +02:00
Matthias ef52a7a328 Simplify windows install sscript 2023-07-18 20:20:20 +02:00
Matthias 635ab73706 Update path for install_windows helper script 2023-07-18 20:19:40 +02:00
Matthias c4d38e6de6 Add new ta-lib windows wheel 2023-07-18 20:19:30 +02:00
Matthias dd8a8b0f1f Merge pull request #8934 from freqtrade/fix/docker-compose
Update docker-compose-freqai.yml
2023-07-18 20:07:59 +02:00
Matthias 8fd96118a5 Bump pydantic to latest 1.10 version 2023-07-18 20:02:24 +02:00
Matthias db0cad04ab Pin pydantic to <2.0 for now 2023-07-18 18:25:44 +02:00
Robert Caulk d64d0e9f94 Update docker-compose-freqai.yml 2023-07-18 15:03:20 +02:00
Matthias adda506499 Merge pull request #8920 from freqtrade/dependabot/pip/develop/ruff-0.0.278
Bump ruff from 0.0.277 to 0.0.278
2023-07-17 19:57:32 +02:00
Robert Caulk c8525959ee Merge pull request #8932 from hom-bahrani/patch-1
Update freqai-feature-engineering.md
2023-07-17 19:13:18 +02:00
Matthias 2b95a0a7e5 Merge pull request #8931 from freqtrade/chore/remove-inlier-metric
chore: remove inlier metric
2023-07-17 18:24:41 +02:00
Matthias c64c10e76f Use Fstrings in hyperopt-tools 2023-07-17 18:20:26 +02:00
Matthias b9c439cdd9 Merge pull request #8929 from freqtrade/dependabot/pip/develop/ccxt-4.0.29
Bump ccxt from 4.0.28 to 4.0.29
2023-07-17 18:16:07 +02:00
dependabot[bot] d8ba2b5df3 Bump ccxt from 4.0.28 to 4.0.29
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.0.28 to 4.0.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.0.28...4.0.29)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 13:44:51 +00:00
Matthias 5e37dd901d Merge pull request #8930 from freqtrade/dependabot/pip/develop/nbconvert-7.7.0
Bump nbconvert from 7.6.0 to 7.7.0
2023-07-17 15:44:05 +02:00
Matthias 031ac01b8d Merge pull request #8922 from freqtrade/dependabot/pip/develop/sqlalchemy-2.0.19
Bump sqlalchemy from 2.0.18 to 2.0.19
2023-07-17 15:43:45 +02:00
froggleston 6ccc12f337 Fix calcs, rename ratio, add docs 2023-07-17 14:16:22 +01:00
Hom Bahrani 8738b8d551 Update freqai-feature-engineering.md
It seems that the closing parenthesis ) is missing at the end of line 65 in the pipeline example
2023-07-17 13:59:09 +01:00
dependabot[bot] f2ff85421a Bump nbconvert from 7.6.0 to 7.7.0
Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 7.6.0 to 7.7.0.
- [Release notes](https://github.com/jupyter/nbconvert/releases)
- [Changelog](https://github.com/jupyter/nbconvert/blob/main/CHANGELOG.md)
- [Commits](https://github.com/jupyter/nbconvert/compare/v7.6.0...v7.7.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 11:16:30 +00:00
Matthias cb5a67e9e0 Bump sqlalchemy pre-commit 2023-07-17 13:12:58 +02:00
Matthias ca934c7568 Merge pull request #8926 from freqtrade/dependabot/github_actions/develop/pypa/gh-action-pypi-publish-1.8.8
Bump pypa/gh-action-pypi-publish from 1.8.7 to 1.8.8
2023-07-17 13:12:01 +02:00
robcaulk 6f0204fcd3 chore: remove inlier metric 2023-07-17 13:03:43 +02:00
Matthias c81adf76c2 Merge pull request #8921 from freqtrade/dependabot/pip/develop/ccxt-4.0.28
Bump ccxt from 4.0.17 to 4.0.28
2023-07-17 10:30:16 +02:00
dependabot[bot] 78de614910 Bump ta-lib from 0.4.26 to 0.4.27
Bumps [ta-lib](https://github.com/ta-lib/ta-lib-python) from 0.4.26 to 0.4.27.
- [Changelog](https://github.com/TA-Lib/ta-lib-python/blob/master/CHANGELOG)
- [Commits](https://github.com/ta-lib/ta-lib-python/compare/TA_Lib-0.4.26...TA_Lib-0.4.27)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 07:36:12 +00:00
Matthias 201a8fe0ba Merge pull request #8925 from freqtrade/dependabot/pip/develop/jsonschema-4.18.3
Bump jsonschema from 4.18.0 to 4.18.3
2023-07-17 09:34:54 +02:00
dependabot[bot] e5221932e8 Bump ruff from 0.0.277 to 0.0.278
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.277 to 0.0.278.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.277...v0.0.278)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 05:02:21 +00:00
Matthias 3d5889060d Merge pull request #8919 from freqtrade/dependabot/pip/develop/pytest-asyncio-0.21.1
Bump pytest-asyncio from 0.21.0 to 0.21.1
2023-07-17 07:01:13 +02:00
Matthias 68c3c764b7 Merge pull request #8914 from freqtrade/fix/8877
Dry-run open balance should include realized profit
2023-07-17 06:47:10 +02:00
Matthias fba89d2082 Merge pull request #8916 from freqtrade/dependabot/pip/develop/uvicorn-0.23.0
Bump uvicorn from 0.22.0 to 0.23.0
2023-07-17 06:44:23 +02:00
Matthias 7823ed6cc0 Merge pull request #8917 from freqtrade/dependabot/pip/develop/pymdown-extensions-10.1
Bump pymdown-extensions from 10.0.1 to 10.1
2023-07-17 06:41:00 +02:00
dependabot[bot] c23ff9d7c2 Bump pypa/gh-action-pypi-publish from 1.8.7 to 1.8.8
Bumps [pypa/gh-action-pypi-publish](https://github.com/pypa/gh-action-pypi-publish) from 1.8.7 to 1.8.8.
- [Release notes](https://github.com/pypa/gh-action-pypi-publish/releases)
- [Commits](https://github.com/pypa/gh-action-pypi-publish/compare/v1.8.7...v1.8.8)

---
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>
2023-07-17 03:58:46 +00:00
dependabot[bot] 5f82108aae Bump jsonschema from 4.18.0 to 4.18.3
Bumps [jsonschema](https://github.com/python-jsonschema/jsonschema) from 4.18.0 to 4.18.3.
- [Release notes](https://github.com/python-jsonschema/jsonschema/releases)
- [Changelog](https://github.com/python-jsonschema/jsonschema/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/python-jsonschema/jsonschema/compare/v4.18.0...v4.18.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 03:51:14 +00:00
dependabot[bot] 454baa7b5b Bump sqlalchemy from 2.0.18 to 2.0.19
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.18 to 2.0.19.
- [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>
2023-07-17 03:51:00 +00:00
dependabot[bot] 69e9691730 Bump ccxt from 4.0.17 to 4.0.28
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.0.17 to 4.0.28.
- [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.0.17...4.0.28)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 03:50:49 +00:00
dependabot[bot] 8c8b315994 Bump pytest-asyncio from 0.21.0 to 0.21.1
Bumps [pytest-asyncio](https://github.com/pytest-dev/pytest-asyncio) from 0.21.0 to 0.21.1.
- [Release notes](https://github.com/pytest-dev/pytest-asyncio/releases)
- [Commits](https://github.com/pytest-dev/pytest-asyncio/compare/v0.21.0...v0.21.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 03:50:33 +00:00
dependabot[bot] e8ff5cb783 Bump pymdown-extensions from 10.0.1 to 10.1
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 10.0.1 to 10.1.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/10.0.1...10.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 03:50:26 +00:00
dependabot[bot] 256a8c686e Bump uvicorn from 0.22.0 to 0.23.0
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.22.0 to 0.23.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.22.0...0.23.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-17 03:50:21 +00:00
Matthias 69ddbe3944 Merge pull request #8909 from freqtrade/backtest_adjustment
introduce order.stake_amount
2023-07-16 20:03:46 +02:00
froggleston 79f7f82c59 Fix telegram output 2023-07-16 16:52:06 +01:00
Matthias 35714c469b Add explicit dry_wallet test 2023-07-16 16:06:23 +02:00
Matthias cf9ba527bb Include realized_profit in update_dry
closes #8877
2023-07-16 16:06:23 +02:00
Matthias d0e0e156b1 Update tests to account for realized profit/loss 2023-07-16 16:06:23 +02:00
Matthias 8a55423ac7 Add asserts for wallet size 2023-07-16 16:04:00 +02:00
Matthias 66131d5103 Improve integration test assertions 2023-07-15 20:55:56 +02:00
froggleston 1fd2a2532d Reduce trade stats function complexity 2023-07-15 17:06:52 +01:00
froggleston 9d36dc7ac6 Fix line length 2023-07-15 16:59:33 +01:00
froggleston 59cb9e39dd Fix another trailing whitespace 2023-07-15 16:55:24 +01:00
froggleston d4b282d6f7 Fix expectancy calc and tests 2023-07-15 16:51:45 +01:00
froggleston e57bb6bc97 Add api schema entries 2023-07-15 16:27:58 +01:00
froggleston 3ce17b740b Fix flake8 problems 2023-07-15 16:25:19 +01:00
froggleston 7eced953b3 Merge in develop changes 2023-07-15 16:16:08 +01:00
Matthias 768a7b47ec Fix some futures symbol naming in tests 2023-07-15 17:14:57 +02:00
froggleston 096cb0d1ee Add tests, fix winrate calc 2023-07-15 16:09:13 +01:00
froggleston 4235ab0c7e Add expectancy and winrate to telegram 2023-07-15 15:39:47 +01:00
froggleston 6e56f84fe3 Add expectancy and winrate to rpc trade statistics 2023-07-15 15:32:52 +01:00
Matthias ff14208105 Improve logic for from_json special case 2023-07-15 15:23:15 +02:00
Matthias 626ea6b119 Add backtesting support for order.stake_amount 2023-07-15 14:55:22 +02:00
Matthias d8c0621887 Add stake amount property to order object 2023-07-15 10:14:08 +02:00
Matthias d1662db813 Merge pull request #8907 from freqtrade/dependabot/pip/cryptography-41.0.2
Bump cryptography from 40.0.1 to 41.0.2
2023-07-15 10:10:21 +02:00
Matthias 17296fdf9c Use proper cost for order
closes #8906
2023-07-15 09:02:17 +02:00
Matthias e4cd29d88c Add test for trade.cost 2023-07-15 09:02:01 +02:00
Matthias eeec1b0b38 Don't bump armhv crypto dependency
needs https://piwheels.org/project/cryptography/  to work again.
2023-07-15 08:18:29 +02:00
dependabot[bot] e043fdba50 Bump cryptography from 40.0.1 to 41.0.2
Bumps [cryptography](https://github.com/pyca/cryptography) from 40.0.1 to 41.0.2.
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/40.0.1...41.0.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-15 01:04:17 +00:00
Matthias 7f0e1c27c6 Fix realized_profit for trade from_json 2023-07-14 07:35:58 +02:00
Matthias bdff34017a Merge pull request #8867 from freqtrade/doc/commmunityshowcase
Add community showcase
2023-07-14 06:43:14 +02:00
Matthias 7280d6bb3b Merge pull request #8890 from freqtrade/dependabot/pip/develop/pyarrow-12.0.1
Bump pyarrow from 12.0.0 to 12.0.1
2023-07-13 19:58:25 +02:00
Matthias 061e930eb1 Merge pull request #8901 from freqtrade/fix/8841
Avoid false-triggering "dust-eating" logic for rebuys
2023-07-13 18:06:33 +02:00
Matthias 240606c5a4 Only run once for an order 2023-07-13 07:14:20 +02:00
Matthias 6134764d5e Don't wrongly eat into dust on rebuys
closes #8841
2023-07-13 07:07:15 +02:00
Matthias 45a9c304b6 Add test for new conditional behavior 2023-07-13 07:07:15 +02:00
Matthias c970ae8add Add pyarrow pi wheel 2023-07-13 06:40:49 +02:00
Matthias 3cf419cbcd Fix ill-used type on order backpopulate mapping 2023-07-12 18:22:41 +02:00
Matthias 8ad32e7498 Merge pull request #8898 from jansmets/bybit_ohlcv_history
bybit provides up to 2years of historic ohlcv data on any timefame.
2023-07-12 18:16:11 +02:00
Matthias 722b5569bd Add Freqtrade backtest project 2023-07-12 18:06:37 +02:00
Jan Smets e8fe5a4f17 bybit provides up to 2years of historic ohlcv data on any timefame. 2023-07-12 11:39:32 +02:00
Matthias a314175565 Merge pull request #8882 from freqtrade/dependabot/pip/develop/orjson-3.9.2
Bump orjson from 3.9.1 to 3.9.2
2023-07-11 06:06:50 +02:00
Matthias 9505f380bf Merge pull request #8892 from freqtrade/dependabot/pip/develop/time-machine-2.11.0
Bump time-machine from 2.10.0 to 2.11.0
2023-07-10 20:53:18 +02:00
dependabot[bot] 127ca83dea Bump orjson from 3.9.1 to 3.9.2
Bumps [orjson](https://github.com/ijl/orjson) from 3.9.1 to 3.9.2.
- [Release notes](https://github.com/ijl/orjson/releases)
- [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ijl/orjson/compare/3.9.1...3.9.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 17:51:56 +00:00
Matthias 5acfe2c89c Merge pull request #8891 from freqtrade/dependabot/pip/develop/fastapi-0.100.0
Bump fastapi from 0.99.1 to 0.100.0
2023-07-10 19:50:56 +02:00
Matthias aa5a5ce8e1 Merge pull request #8887 from freqtrade/dependabot/pip/develop/sqlalchemy-2.0.18
Bump sqlalchemy from 2.0.17 to 2.0.18
2023-07-10 19:50:03 +02:00
Matthias e8eb28996d Merge pull request #8888 from freqtrade/dependabot/pip/develop/jsonschema-4.18.0
Bump jsonschema from 4.17.3 to 4.18.0
2023-07-10 19:49:07 +02:00
Matthias 9af9caae09 Merge pull request #8889 from freqtrade/dependabot/pip/develop/ccxt-4.0.17
Bump ccxt from 4.0.15 to 4.0.17
2023-07-10 19:48:50 +02:00
Matthias a0fff43648 Add fee_base to json output 2023-07-10 19:47:37 +02:00
dependabot[bot] b7bd1eba6f Bump time-machine from 2.10.0 to 2.11.0
Bumps [time-machine](https://github.com/adamchainz/time-machine) from 2.10.0 to 2.11.0.
- [Changelog](https://github.com/adamchainz/time-machine/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/adamchainz/time-machine/compare/2.10.0...2.11.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>
2023-07-10 16:16:34 +00:00
dependabot[bot] 6d63b9400b Bump fastapi from 0.99.1 to 0.100.0
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.99.1 to 0.100.0.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.99.1...0.100.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 16:16:30 +00:00
dependabot[bot] e0ba2f3a15 Bump pyarrow from 12.0.0 to 12.0.1
Bumps [pyarrow](https://github.com/apache/arrow) from 12.0.0 to 12.0.1.
- [Commits](https://github.com/apache/arrow/compare/go/v12.0.0...go/v12.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 16:16:25 +00:00
dependabot[bot] 7d16012f61 Bump ccxt from 4.0.15 to 4.0.17
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.0.15 to 4.0.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.0.15...4.0.17)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 16:16:19 +00:00
dependabot[bot] 4a422cac5b Bump jsonschema from 4.17.3 to 4.18.0
Bumps [jsonschema](https://github.com/python-jsonschema/jsonschema) from 4.17.3 to 4.18.0.
- [Release notes](https://github.com/python-jsonschema/jsonschema/releases)
- [Changelog](https://github.com/python-jsonschema/jsonschema/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/python-jsonschema/jsonschema/compare/v4.17.3...v4.18.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 16:16:12 +00:00
Matthias 884594a967 Bump sqlalchemy pre-commit 2023-07-10 18:09:08 +02:00
Matthias c7c5c41f41 Merge pull request #8885 from freqtrade/dependabot/pip/develop/ccxt-4.0.15
Bump ccxt from 4.0.14 to 4.0.15
2023-07-10 18:07:42 +02:00
dependabot[bot] 12d89a9061 Bump sqlalchemy from 2.0.17 to 2.0.18
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 2.0.17 to 2.0.18.
- [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>
2023-07-10 08:43:16 +00:00
Matthias 7d117e8ca2 Merge pull request #8886 from freqtrade/dependabot/pip/develop/python-telegram-bot-20.4
Bump python-telegram-bot from 20.3 to 20.4
2023-07-10 10:42:11 +02:00
Matthias f6322bd02d Merge pull request #8884 from freqtrade/dependabot/pip/develop/numpy-1.25.1
Bump numpy from 1.25.0 to 1.25.1
2023-07-10 07:53:18 +02:00
Matthias a3aafffbd2 Merge pull request #8883 from freqtrade/dependabot/pip/develop/mkdocs-material-9.1.18
Bump mkdocs-material from 9.1.17 to 9.1.18
2023-07-10 07:23:19 +02:00
Matthias 52b4a2c921 Merge pull request #8881 from freqtrade/dependabot/pip/develop/prompt-toolkit-3.0.39
Bump prompt-toolkit from 3.0.38 to 3.0.39
2023-07-10 07:22:54 +02:00
Matthias 32857f50ea Merge pull request #8879 from freqtrade/dependabot/pip/develop/ruff-0.0.277
Bump ruff from 0.0.275 to 0.0.277
2023-07-10 07:22:37 +02:00
Matthias 40945b4eff Merge pull request #8878 from freqtrade/dependabot/pip/develop/joblib-1.3.1
Bump joblib from 1.2.0 to 1.3.1
2023-07-10 07:22:14 +02:00
Matthias 1dbc294b80 Improve order __REPR__ with date 2023-07-10 07:11:29 +02:00
Matthias 5bc84dca56 Fix from_json with new attributes 2023-07-10 06:38:18 +02:00
Matthias 8c0e66008a Remove wrong/faulty "default" comment from cli options 2023-07-10 06:12:46 +02:00
dependabot[bot] 4a1a197943 Bump python-telegram-bot from 20.3 to 20.4
Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 20.3 to 20.4.
- [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/v20.3...v20.4)

---
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>
2023-07-10 04:00:59 +00:00
dependabot[bot] 43f6a7e3c9 Bump ccxt from 4.0.14 to 4.0.15
Bumps [ccxt](https://github.com/ccxt/ccxt) from 4.0.14 to 4.0.15.
- [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.0.14...4.0.15)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 04:00:46 +00:00
dependabot[bot] 1bece11eb3 Bump numpy from 1.25.0 to 1.25.1
Bumps [numpy](https://github.com/numpy/numpy) from 1.25.0 to 1.25.1.
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst)
- [Commits](https://github.com/numpy/numpy/compare/v1.25.0...v1.25.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 04:00:27 +00:00
dependabot[bot] 3c1cd72430 Bump mkdocs-material from 9.1.17 to 9.1.18
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 9.1.17 to 9.1.18.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/9.1.17...9.1.18)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 03:59:59 +00:00
dependabot[bot] 1ca2a8d638 Bump prompt-toolkit from 3.0.38 to 3.0.39
Bumps [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) from 3.0.38 to 3.0.39.
- [Changelog](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/CHANGELOG)
- [Commits](https://github.com/prompt-toolkit/python-prompt-toolkit/compare/3.0.38...3.0.39)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 03:59:21 +00:00
dependabot[bot] 7359a76b77 Bump ruff from 0.0.275 to 0.0.277
Bumps [ruff](https://github.com/astral-sh/ruff) from 0.0.275 to 0.0.277.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/BREAKING_CHANGES.md)
- [Commits](https://github.com/astral-sh/ruff/compare/v0.0.275...v0.0.277)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 03:59:01 +00:00
dependabot[bot] 75e6315aac Bump joblib from 1.2.0 to 1.3.1
Bumps [joblib](https://github.com/joblib/joblib) from 1.2.0 to 1.3.1.
- [Changelog](https://github.com/joblib/joblib/blob/master/CHANGES.rst)
- [Commits](https://github.com/joblib/joblib/commits)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-10 03:58:40 +00:00
Matthias d68eac92b8 Merge pull request #8874 from freqtrade/improve/convert_data
Improve/convert data
2023-07-09 17:14:12 +02:00
Matthias b4957a2e37 Update converter test 2023-07-09 15:37:56 +02:00
Matthias 448f02960f Improve behavior for convert-data 2023-07-09 15:36:44 +02:00
Matthias 5a43dd4766 don't hard-default --timeframes via argparse 2023-07-09 15:32:51 +02:00
Matthias 10f34563f8 Improve default for --candle-types 2023-07-09 15:02:47 +02:00
Matthias d8d0a60322 Merge pull request #8872 from freqtrade/ccxt_bump
bump ccxt to 4.0.14
2023-07-09 14:49:27 +02:00
Matthias 4c6eee8dfe Update proxy documentation to correspond to new ccxt mode 2023-07-09 13:52:46 +02:00
Matthias a598b8554d Bump ccxt and ccxt min requirement 2023-07-09 13:48:14 +02:00
Matthias 511023ee10 Fix typo in comment 2023-07-09 13:47:57 +02:00
Matthias 8212a5af77 Merge pull request #8819 from Bloodhunter4rc/remotepairlist
Remotepairlist - add blacklist mode
2023-07-09 12:42:25 +02:00
Matthias af5fc76dc6 Add test for different processing modes 2023-07-09 11:51:43 +02:00
Matthias e6ee55a69b Improve some test coverage 2023-07-09 11:37:06 +02:00
Matthias 4dda9c6daa Add explicit test for short-desc 2023-07-09 11:36:13 +02:00
Matthias 5da5369ca4 Update parameter sequence to make more sense 2023-07-09 11:09:59 +02:00
Matthias ea04210eb3 Merge pull request #8869 from stash86/patch-1
Update my link
2023-07-09 10:38:18 +02:00
Stefano Ariestasia e64327f353 Update my link 2023-07-09 17:12:21 +09:00
Bloodhunter4rc 4d4ec11a8a - print 2023-07-09 09:53:31 +02:00
Bloodhunter4rc 4f77e3f595 Merge branch 'remotepairlist' of https://github.com/Bloodhunter4rc/freqtrade into remotepairlist 2023-07-09 09:44:19 +02:00
Bloodhunter4rc 0b68ca6cb3 use pairlist_pos remove unused check, fixed Test 2023-07-09 09:42:33 +02:00
Bloodhunter4rc 0c2eb8dc58 Merge branch 'freqtrade:develop' into remotepairlist 2023-07-09 09:15:56 +02:00
Matthias b2106ef4a2 Update frequenthippo link description 2023-07-09 07:35:02 +02:00
Bloodhunter4rc ee1fa34df2 Add 'processing_mode' , blacklist checks 2023-07-08 18:05:46 +02:00
Matthias c4b0f24cd7 Use USD for kraken tests, as it has more volume. 2023-07-08 13:26:31 +02:00
Matthias a1d50dbfa2 Add community showcase 2023-07-08 13:06:12 +02:00
Matthias 8436f9ade4 Merge pull request #8802 from freqtrade/dependabot/pip/develop/numpy-1.25.0
Bump numpy from 1.24.3 to 1.25.0
2023-07-08 10:32:27 +02:00
Matthias 2e78f7503e Merge branch 'develop' into dependabot/pip/develop/numpy-1.25.0 2023-07-08 09:52:33 +02:00
Matthias 5c0f5588a6 Simplify sort_values in PerformanceFilter
Avoids potential regression in numpy 1.25.0 - which doesn't keep prior sort order in chained sort_values calls.
2023-07-08 09:49:01 +02:00
Matthias 3d6d006e84 Merge branch 'develop' into pr/Bloodhunter4rc/8819 2023-07-08 07:37:00 +02:00
Matthias 1c5ea317e6 Add mode as parameter for the UI 2023-07-08 07:31:55 +02:00
Matthias 31faad776e Merge branch 'stable' into develop 2023-07-07 20:31:35 +02:00
Matthias f020daa357 Merge pull request #8864 from freqtrade/dependabot/pip/develop/ccxt-4.0.12
Bump ccxt from 3.1.44 to 4.0.12
2023-07-07 15:46:08 +02:00
Matthias dacbcdb710 Merge pull request #8865 from freqtrade/online_test_trade_history
Online test trade history
2023-07-07 13:55:21 +02:00
Matthias a9e239ca7a Don't use future date for downloading new trade data
closes #8860
2023-07-07 11:23:34 +02:00
Matthias 65550335ee Add explicit online test for get_trade_history
part of #8860
2023-07-07 11:15:15 +02:00
Matthias 01db789d42 Improve release documentation 2023-07-07 10:56:41 +02:00
Matthias 6fe0895e74 Merge pull request #8847 from freqtrade/dependabot/pip/develop/scipy-1.11.1
Bump scipy from 1.10.1 to 1.11.1
2023-07-07 10:45:47 +02:00
dependabot[bot] c93a27af7d Bump ccxt from 3.1.44 to 4.0.12
Bumps [ccxt](https://github.com/ccxt/ccxt) from 3.1.44 to 4.0.12.
- [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/3.1.44...4.0.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-07 08:22:16 +00:00
Matthias 67ce7917cc Merge pull request #8863 from freqtrade/robcaulk-patch-1
Bump datasieve 0.1.7
2023-07-07 10:21:23 +02:00
Matthias 9b447cdf1e Bump pandas to 2.0.3 2023-07-07 09:37:14 +02:00
Robert Caulk 98ba0042d8 Bump datasieve 0.1.7 2023-07-07 09:27:09 +02:00
Matthias f64b9503f9 scipy 1.11 doesn't support python 3.8 any longer 2023-07-07 09:08:32 +02:00
Matthias e734a664b4 bump develop-version to 2023.7.dev 2023-07-07 08:59:10 +02:00
Matthias 942f0b4fbd Move format_ms_time to datetime_helpers 2023-07-07 08:59:07 +02:00
Matthias a2ba466918 Merge pull request #8851 from freqtrade/dependabot/pip/develop/stable-baselines3-2.0.0
Bump stable-baselines3 from 2.0.0a13 to 2.0.0
2023-07-07 08:51:45 +02:00
Matthias e6db5bd193 Pin numpy for python < 3.8 2023-07-06 21:09:06 +02:00
dependabot[bot] 9d3dda4e12 Bump stable-baselines3 from 2.0.0a13 to 2.0.0
Bumps [stable-baselines3](https://github.com/DLR-RM/stable-baselines3) from 2.0.0a13 to 2.0.0.
- [Release notes](https://github.com/DLR-RM/stable-baselines3/releases)
- [Commits](https://github.com/DLR-RM/stable-baselines3/commits/v2.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-03 03:29:40 +00:00
dependabot[bot] b204da3317 Bump scipy from 1.10.1 to 1.11.1
Bumps [scipy](https://github.com/scipy/scipy) from 1.10.1 to 1.11.1.
- [Release notes](https://github.com/scipy/scipy/releases)
- [Commits](https://github.com/scipy/scipy/compare/v1.10.1...v1.11.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-07-01 05:43:10 +00:00
Bloodhunter4rc ce1b90885e support wildcards 2023-06-24 21:32:20 +02:00
Bloodhunter4rc d534f88d1c unnecessary lines removed. 2023-06-24 14:36:31 +02:00
Bloodhunter4rc caca070c1a added tests 2023-06-24 14:31:30 +02:00
Bloodhunter4rc 36b33fb407 add mode to set the pairlist to blacklist additional to whitelist
adhere to _number_pairs
2023-06-24 12:38:31 +02:00
dependabot[bot] b2c87c3591 Bump numpy from 1.24.3 to 1.25.0
Bumps [numpy](https://github.com/numpy/numpy) from 1.24.3 to 1.25.0.
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/main/doc/RELEASE_WALKTHROUGH.rst)
- [Commits](https://github.com/numpy/numpy/compare/v1.24.3...v1.25.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2023-06-19 16:05:34 +00:00
103 changed files with 1808 additions and 542 deletions
+2 -2
View File
@@ -461,7 +461,7 @@ jobs:
python setup.py sdist bdist_wheel
- name: Publish to PyPI (Test)
uses: pypa/gh-action-pypi-publish@v1.8.7
uses: pypa/gh-action-pypi-publish@v1.8.8
if: (github.event_name == 'release')
with:
user: __token__
@@ -469,7 +469,7 @@ jobs:
repository_url: https://test.pypi.org/legacy/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@v1.8.7
uses: pypa/gh-action-pypi-publish@v1.8.8
if: (github.event_name == 'release')
with:
user: __token__
+5 -5
View File
@@ -13,12 +13,12 @@ repos:
- id: mypy
exclude: build_helpers
additional_dependencies:
- types-cachetools==5.3.0.5
- types-cachetools==5.3.0.6
- types-filelock==3.2.7
- types-requests==2.31.0.1
- types-tabulate==0.9.0.2
- types-python-dateutil==2.8.19.13
- SQLAlchemy==2.0.17
- types-requests==2.31.0.2
- types-tabulate==0.9.0.3
- types-python-dateutil==2.8.19.14
- SQLAlchemy==2.0.19
# stages: [push]
- repo: https://github.com/pycqa/isort
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4 -14
View File
@@ -1,21 +1,11 @@
# Downloads don't work automatically, since the URL is regenerated via javascript.
# Downloaded from https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib
# vendored Wheels compiled via https://github.com/xmatthias/ta-lib-python/tree/ta_bundled_040
python -m pip install --upgrade pip wheel
$pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
if ($pyv -eq '3.8') {
pip install build_helpers\TA_Lib-0.4.26-cp38-cp38-win_amd64.whl
}
if ($pyv -eq '3.9') {
pip install build_helpers\TA_Lib-0.4.26-cp39-cp39-win_amd64.whl
}
if ($pyv -eq '3.10') {
pip install build_helpers\TA_Lib-0.4.26-cp310-cp310-win_amd64.whl
}
if ($pyv -eq '3.11') {
pip install build_helpers\TA_Lib-0.4.26-cp311-cp311-win_amd64.whl
}
pip install --find-links=build_helpers\ TA-Lib
pip install -r requirements-dev.txt
pip install -e .
+2 -2
View File
@@ -32,5 +32,5 @@ services:
--logfile /freqtrade/user_data/logs/freqtrade.log
--db-url sqlite:////freqtrade/user_data/tradesv3.sqlite
--config /freqtrade/user_data/config.json
--freqai-model XGBoostClassifier
--strategy SampleStrategy
--freqaimodel XGBoostRegressor
--strategy FreqaiExampleStrategy
+16
View File
@@ -103,6 +103,22 @@ The indicators have to be present in your strategy's main DataFrame (either for
timeframe or for informative timeframes) otherwise they will simply be ignored in the script
output.
There are a range of candle and trade-related fields that are included in the analysis so are
automatically accessible by including them on the indicator-list, and these include:
- **open_date :** trade open datetime
- **close_date :** trade close datetime
- **min_rate :** minimum price seen throughout the position
- **max_rate :** maxiumum price seen throughout the position
- **open :** signal candle open price
- **close :** signal candle close price
- **high :** signal candle high price
- **low :** signal candle low price
- **volume :** signal candle volumne
- **profit_ratio :** trade profit ratio
- **profit_abs :** absolute profit return of the trade
### Filtering the trade output by date
To show only trades between dates within your backtested timerange, supply the usual `timerange` option in `YYYYMMDD-[YYYYMMDD]` format:
+6 -2
View File
@@ -305,7 +305,7 @@ A backtesting result will look like that:
| Sharpe | 2.97 |
| Calmar | 6.29 |
| Profit factor | 1.11 |
| Expectancy | -0.15 |
| Expectancy (Ratio) | -0.15 (-0.05) |
| Avg. stake amount | 0.001 BTC |
| Total trade volume | 0.429 BTC |
| | |
@@ -324,6 +324,7 @@ A backtesting result will look like that:
| Days win/draw/lose | 12 / 82 / 25 |
| Avg. Duration Winners | 4:23:00 |
| Avg. Duration Loser | 6:55:00 |
| Max Consecutive Wins / Loss | 3 / 4 |
| Rejected Entry signals | 3089 |
| Entry/Exit Timeouts | 0 / 0 |
| Canceled Trade Entries | 34 |
@@ -409,7 +410,7 @@ It contains some useful key metrics about performance of your strategy on backte
| Sharpe | 2.97 |
| Calmar | 6.29 |
| Profit factor | 1.11 |
| Expectancy | -0.15 |
| Expectancy (Ratio) | -0.15 (-0.05) |
| Avg. stake amount | 0.001 BTC |
| Total trade volume | 0.429 BTC |
| | |
@@ -428,6 +429,7 @@ It contains some useful key metrics about performance of your strategy on backte
| Days win/draw/lose | 12 / 82 / 25 |
| Avg. Duration Winners | 4:23:00 |
| Avg. Duration Loser | 6:55:00 |
| Max Consecutive Wins / Loss | 3 / 4 |
| Rejected Entry signals | 3089 |
| Entry/Exit Timeouts | 0 / 0 |
| Canceled Trade Entries | 34 |
@@ -467,6 +469,7 @@ It contains some useful key metrics about performance of your strategy on backte
- `Best day` / `Worst day`: Best and worst day based on daily profit.
- `Days win/draw/lose`: Winning / Losing days (draws are usually days without closed trade).
- `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades.
- `Max Consecutive Wins / Loss`: Maximum consecutive wins/losses in a row.
- `Rejected Entry signals`: Trade entry signals that could not be acted upon due to `max_open_trades` being reached.
- `Entry/Exit Timeouts`: Entry/exit orders which did not fill (only applicable if custom pricing is used).
- `Canceled Trade Entries`: Number of trades that have been canceled by user request via `adjust_entry_price`.
@@ -534,6 +537,7 @@ Since backtesting lacks some detailed information about what happens within a ca
- ROI
- exits are compared to high - but the ROI value is used (e.g. ROI = 2%, high=5% - so the exit will be at 2%)
- exits are never "below the candle", so a ROI of 2% may result in a exit at 2.4% if low was at 2.4% profit
- ROI entries which came into effect on the triggering candle (e.g. `120: 0.02` for 1h candles, from `60: 0.05`) will use the candle's open as exit rate
- Force-exits caused by `<N>=-1` ROI entries use low as exit value, unless N falls on the candle open (e.g. `120: -1` for 1h candles)
- Stoploss exits happen exactly at stoploss price, even if low was lower, but the loss will be `2 * fees` higher than the stoploss price
- Stoploss is evaluated before ROI within one candle. So you can often see more trades with the `stoploss` exit reason comparing to the results obtained with the same strategy in the Dry Run/Live Trade modes
+3 -5
View File
@@ -682,16 +682,14 @@ To use a proxy for exchange connections - you will have to define the proxies as
{
"exchange": {
"ccxt_config": {
"aiohttp_proxy": "http://addr:port",
"proxies": {
"http": "http://addr:port",
"https": "http://addr:port"
},
"httpsProxy": "http://addr:port",
}
}
}
```
For more information on available proxy types, please consult the [ccxt proxy documentation](https://docs.ccxt.com/#/README?id=proxy).
## Next step
Now you have configured your config.json, the next step is to [start your bot](bot-usage.md).
+7 -1
View File
@@ -453,7 +453,13 @@ Once the PR against stable is merged (best right after merging):
* Use the button "Draft a new release" in the Github UI (subsection releases).
* Use the version-number specified as tag.
* Use "stable" as reference (this step comes after the above PR is merged).
* Use the above changelog as release comment (as codeblock)
* Use the above changelog as release comment (as codeblock).
* Use the below snippet for the new release
??? Tip "Release template"
````
--8<-- "includes/release_template.md"
````
## Releases
+8 -1
View File
@@ -259,10 +259,17 @@ The configuration parameter `exchange.unknown_fee_rate` can be used to specify t
Futures trading on bybit is currently supported for USDT markets, and will use isolated futures mode.
Users with unified accounts (there's no way back) can create a Sub-account which will start as "non-unified", and can therefore use isolated futures.
On startup, freqtrade will set the position mode to "One-way Mode" for the whole (sub)account. This avoids making this call over and over again (slowing down bot operations), but means that changes to this setting may result in exceptions and errors.
On startup, freqtrade will set the position mode to "One-way Mode" for the whole (sub)account. This avoids making this call over and over again (slowing down bot operations), but means that changes to this setting may result in exceptions and errors
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 (Subaccount on non-unified) must have the following permissions:
* Read-write
* Contract - Orders
* Contract - Positions
We do strongly recommend to limit all API keys to the IP you're going to use it from.
!!! Tip "Stoploss on Exchange"
Bybit (futures only) supports `stoploss_on_exchange` and uses `stop-loss-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.
On futures, Bybit supports both `stop-limit` as well as `stop-market` orders. You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type to use.
+1 -1
View File
@@ -261,7 +261,7 @@ class MyFreqaiModel(BaseRegressionModel):
"""
feature_pipeline = Pipeline([
('qt', SKLearnWrapper(QuantileTransformer(output_distribution='normal'))),
('di', ds.DissimilarityIndex(di_threshold=1)
('di', ds.DissimilarityIndex(di_threshold=1))
])
return feature_pipeline
-1
View File
@@ -42,7 +42,6 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
| `use_SVM_to_remove_outliers` | Train a support vector machine to detect and remove outliers from the training dataset, as well as from incoming data points. See details about how it works [here](freqai-feature-engineering.md#identifying-outliers-using-a-support-vector-machine-svm). <br> **Datatype:** Boolean.
| `svm_params` | All parameters available in Sklearn's `SGDOneClassSVM()`. See details about some select parameters [here](freqai-feature-engineering.md#identifying-outliers-using-a-support-vector-machine-svm). <br> **Datatype:** Dictionary.
| `use_DBSCAN_to_remove_outliers` | Cluster data using the DBSCAN algorithm to identify and remove outliers from training and prediction data. See details about how it works [here](freqai-feature-engineering.md#identifying-outliers-with-dbscan). <br> **Datatype:** Boolean.
| `inlier_metric_window` | If set, FreqAI adds an `inlier_metric` to the training feature set and set the lookback to be the `inlier_metric_window`, i.e., the number of previous time points to compare the current candle to. Details of how the `inlier_metric` is computed can be found [here](freqai-feature-engineering.md#inlier-metric). <br> **Datatype:** Integer. <br> Default: `0`.
| `noise_standard_deviation` | If set, FreqAI adds noise to the training features with the aim of preventing overfitting. FreqAI generates random deviates from a gaussian distribution with a standard deviation of `noise_standard_deviation` and adds them to all data points. `noise_standard_deviation` should be kept relative to the normalized space, i.e., between -1 and 1. In other words, since data in FreqAI is always normalized to be between -1 and 1, `noise_standard_deviation: 0.05` would result in 32% of the data being randomly increased/decreased by more than 2.5% (i.e., the percent of data falling within the first standard deviation). <br> **Datatype:** Integer. <br> Default: `0`.
| `outlier_protection_percentage` | Enable to prevent outlier detection methods from discarding too much data. If more than `outlier_protection_percentage` % of points are detected as outliers by the SVM or DBSCAN, FreqAI will log a warning message and ignore outlier detection, i.e., the original dataset will be kept intact. If the outlier protection is triggered, no predictions will be made based on the training dataset. <br> **Datatype:** Float. <br> Default: `30`.
| `reverse_train_test_order` | Split the feature dataset (see below) and use the latest data split for training and test on historical split of the data. This allows the model to be trained up to the most recent data point, while avoiding overfitting. However, you should be careful to understand the unorthodox nature of this parameter before employing it. <br> **Datatype:** Boolean. <br> Default: `False` (no reversal).
+11 -1
View File
@@ -184,6 +184,8 @@ The RemotePairList is defined in the pairlists section of the configuration sett
"pairlists": [
{
"method": "RemotePairList",
"mode": "whitelist",
"processing_mode": "filter",
"pairlist_url": "https://example.com/pairlist",
"number_assets": 10,
"refresh_period": 1800,
@@ -194,6 +196,14 @@ The RemotePairList is defined in the pairlists section of the configuration sett
]
```
The optional `mode` option specifies if the pairlist should be used as a `blacklist` or as a `whitelist`. The default value is "whitelist".
The optional `processing_mode` option in the RemotePairList configuration determines how the retrieved pairlist is processed. It can have two values: "filter" or "append".
In "filter" mode, the retrieved pairlist is used as a filter. Only the pairs present in both the original pairlist and the retrieved pairlist are included in the final pairlist. Other pairs are filtered out.
In "append" mode, the retrieved pairlist is added to the original pairlist. All pairs from both lists are included in the final pairlist without any filtering.
The `pairlist_url` option specifies the URL of the remote server where the pairlist is located, or the path to a local file (if file:/// is prepended). This allows the user to use either a remote server or a local file as the source for the pairlist.
The user is responsible for providing a server or local file that returns a JSON object with the following structure:
@@ -201,7 +211,7 @@ The user is responsible for providing a server or local file that returns a JSON
```json
{
"pairs": ["XRP/USDT", "ETH/USDT", "LTC/USDT"],
"refresh_period": 1800,
"refresh_period": 1800
}
```
+37
View File
@@ -0,0 +1,37 @@
## Highlighted changes
- ...
### How to update
As always, you can update your bot using one of the following commands:
#### docker-compose
```bash
docker-compose pull
docker-compose up -d
```
#### Installation via setup script
```
# Deactivate venv and run
./setup.sh --update
```
#### Plain native installation
```
git pull
pip install -U -r requirements.txt
```
<details>
<summary>Expand full changelog</summary>
```
<Paste your changelog here>
```
</details>
+11
View File
@@ -0,0 +1,11 @@
This section will highlight a few projects from members of the community.
!!! Note
The projects below are for the most part not maintained by the freqtrade , therefore use your own caution before using them.
- [Example freqtrade strategies](https://github.com/freqtrade/freqtrade-strategies/)
- [FrequentHippo - Grafana dashboard with dry/live runs and backtests](http://frequenthippo.ddns.net:3000/) (by hippocritical).
- [Online pairlist generator](https://remotepairlist.com/) (by Blood4rc).
- [Freqtrade Backtesting Project](https://bt.robot.co.network/) (by Blood4rc).
- [Freqtrade analysis notebook](https://github.com/froggleston/freqtrade_analysis_notebook) (by Froggleston).
- [TUI for freqtrade](https://github.com/froggleston/freqtrade-frogtrade9000) (by Froggleston).
- [Bot Academy](https://botacademy.ddns.net/) (by stash86) - Blog about crypto bot projects.
+4
View File
@@ -63,6 +63,10 @@ Exchanges confirmed working by the community:
- [X] [Bitvavo](https://bitvavo.com/)
- [X] [Kucoin](https://www.kucoin.com/)
## Community showcase
--8<-- "includes/showcase.md"
## Requirements
### Hardware requirements
+2 -2
View File
@@ -1,6 +1,6 @@
markdown==3.3.7
mkdocs==1.4.3
mkdocs-material==9.1.17
mkdocs-material==9.1.19
mdx_truly_sane_lists==1.3
pymdown-extensions==10.0.1
pymdown-extensions==10.1
jinja2==3.1.2
+1 -1
View File
@@ -750,7 +750,7 @@ class DigDeeperStrategy(IStrategy):
# Hope you have a deep wallet!
try:
# This returns first order stake size
stake_amount = filled_entries[0].cost
stake_amount = filled_entries[0].stake_amount
# This then calculates current safety order size
stake_amount = stake_amount * (1 + (count_of_entries * 0.25))
return stake_amount
+5
View File
@@ -287,12 +287,17 @@ Return a summary of your profit/loss and performance.
> **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.
+2 -1
View File
@@ -141,7 +141,8 @@ Most properties here can be None as they are dependant on the exchange response.
`amount` | float | Amount in base currency
`filled` | float | Filled amount (in base currency)
`remaining` | float | Remaining amount
`cost` | float | Cost of the order - usually average * filled
`cost` | float | Cost of the order - usually average * filled (*Exchange dependant on futures, may contain the cost with or without leverage and may be in contracts.*)
`stake_amount` | float | Stake amount used for this order. *Added in 2023.7.*
`order_date` | datetime | Order creation date **use `order_date_utc` instead**
`order_date_utc` | datetime | Order creation date (in UTC)
`order_fill_date` | datetime | Order fill date **use `order_fill_utc` instead**
+9 -1
View File
@@ -80,12 +80,18 @@ When using the Form-Encoded or JSON-Encoded configuration you can configure any
The result would be a POST request with e.g. `Status: running` body and `Content-Type: text/plain` header.
Optional parameters are available to enable automatic retries for webhook messages. The `webhook.retries` parameter can be set for the maximum number of retries the webhook request should attempt if it is unsuccessful (i.e. HTTP response status is not 200). By default this is set to `0` which is disabled. An additional `webhook.retry_delay` parameter can be set to specify the time in seconds between retry attempts. By default this is set to `0.1` (i.e. 100ms). Note that increasing the number of retries or retry delay may slow down the trader if there are connectivity issues with the webhook. Example configuration for retries:
## Additional configurations
The `webhook.retries` parameter can be set for the maximum number of retries the webhook request should attempt if it is unsuccessful (i.e. HTTP response status is not 200). By default this is set to `0` which is disabled. An additional `webhook.retry_delay` parameter can be set to specify the time in seconds between retry attempts. By default this is set to `0.1` (i.e. 100ms). Note that increasing the number of retries or retry delay may slow down the trader if there are connectivity issues with the webhook.
You can also specify `webhook.timeout` - which defines how long the bot will wait until it assumes the other host as unresponsive (defaults to 10s).
Example configuration for retries:
```json
"webhook": {
"enabled": true,
"url": "https://<YOURHOOKURL>",
"timeout": 10,
"retries": 3,
"retry_delay": 0.2,
"status": {
@@ -109,6 +115,8 @@ Custom messages can be sent to Webhook endpoints via the `self.dp.send_msg()` fu
Different payloads can be configured for different events. Not all fields are necessary, but you should configure at least one of the dicts, otherwise the webhook will never be called.
## Webhook Message types
### Entry
The fields in `webhook.entry` are filled when the bot executes a long/short. Parameters are filled using string.format.
+1 -1
View File
@@ -1,5 +1,5 @@
""" Freqtrade bot """
__version__ = '2023.6'
__version__ = '2023.7'
if 'dev' in __version__:
from pathlib import Path
+1 -2
View File
@@ -67,8 +67,7 @@ ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"]
ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase", "exchange"]
ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes", "trading_mode",
"candle_types"]
ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes", "trading_mode", "candle_types"]
ARGS_CONVERT_TRADES = ["pairs", "timeframes", "exchange", "dataformat_ohlcv", "dataformat_trades"]
+2 -1
View File
@@ -5,6 +5,7 @@ from typing import Any, Dict, List
from questionary import Separator, prompt
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.exceptions import OperationalException
@@ -179,7 +180,7 @@ def ask_user_config() -> Dict[str, Any]:
"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",
"default": "127.0.0.1" if not running_in_docker() else "0.0.0.0",
"when": lambda x: x['api_server']
},
{
+2 -4
View File
@@ -381,7 +381,7 @@ AVAILABLE_CLI_OPTIONS = {
),
"candle_types": Arg(
'--candle-types',
help='Select candle type to use',
help='Select candle type to convert. Defaults to all available types.',
choices=[c.value for c in CandleType],
nargs='+',
),
@@ -450,14 +450,12 @@ AVAILABLE_CLI_OPTIONS = {
),
"exchange": Arg(
'--exchange',
help=f'Exchange name (default: `{constants.DEFAULT_EXCHANGE}`). '
f'Only valid if no config is provided.',
help='Exchange name. Only valid if no config is provided.',
),
"timeframes": Arg(
'-t', '--timeframes',
help='Specify which tickers to download. Space-separated list. '
'Default: `1m 5m`.',
default=['1m', '5m'],
nargs='+',
),
"prepend_data": Arg(
+8 -7
View File
@@ -4,10 +4,10 @@ from collections import defaultdict
from typing import Any, Dict
from freqtrade.configuration import TimeRange, setup_utils_configuration
from freqtrade.constants import DATETIME_PRINT_FORMAT, Config
from freqtrade.constants import DATETIME_PRINT_FORMAT, DL_DATA_TIMEFRAMES, Config
from freqtrade.data.converter import convert_ohlcv_format, convert_trades_format
from freqtrade.data.history import convert_trades_to_ohlcv, download_data_main
from freqtrade.enums import CandleType, RunMode, TradingMode
from freqtrade.enums import RunMode, TradingMode
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
@@ -57,6 +57,8 @@ def start_convert_trades(args: Dict[str, Any]) -> None:
raise OperationalException(
"Downloading data requires a list of pairs. "
"Please check the documentation on how to configure this.")
if 'timeframes' not in config:
config['timeframes'] = DL_DATA_TIMEFRAMES
# Init exchange
exchange = ExchangeResolver.load_exchange(config, validate=False)
@@ -86,11 +88,10 @@ def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None:
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
if ohlcv:
migrate_binance_futures_data(config)
candle_types = [CandleType.from_string(ct) for ct in config.get('candle_types', ['spot'])]
for candle_type in candle_types:
convert_ohlcv_format(config,
convert_from=args['format_from'], convert_to=args['format_to'],
erase=args['erase'], candle_type=candle_type)
convert_ohlcv_format(config,
convert_from=args['format_from'],
convert_to=args['format_to'],
erase=args['erase'])
else:
convert_trades_format(config,
convert_from=args['format_from'], convert_to=args['format_to'],
@@ -0,0 +1,8 @@
import os
def running_in_docker() -> bool:
"""
Check if we are running in a docker container
"""
return os.environ.get('FT_APP_ENV') == 'docker'
@@ -3,6 +3,7 @@ import shutil
from pathlib import Path
from typing import Optional
from freqtrade.configuration.detect_environment import running_in_docker
from freqtrade.constants import (USER_DATA_FILES, USERPATH_FREQAIMODELS, USERPATH_HYPEROPTS,
USERPATH_NOTEBOOKS, USERPATH_STRATEGIES, Config)
from freqtrade.exceptions import OperationalException
@@ -30,8 +31,7 @@ def chown_user_directory(directory: Path) -> None:
Use Sudo to change permissions of the home-directory if necessary
Only applies when running in docker!
"""
import os
if os.environ.get('FT_APP_ENV') == 'docker':
if running_in_docker():
try:
import subprocess
subprocess.check_output(
+1 -1
View File
@@ -10,7 +10,6 @@ from freqtrade.enums import CandleType, PriceType, RPCMessageType
DOCS_LINK = "https://www.freqtrade.io/en/stable"
DEFAULT_CONFIG = 'config.json'
DEFAULT_EXCHANGE = 'bittrex'
PROCESS_THROTTLE_SECS = 5 # sec
HYPEROPT_EPOCH = 100 # epochs
RETRY_TIMEOUT = 30 # sec
@@ -66,6 +65,7 @@ TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent']
WEBHOOK_FORMAT_OPTIONS = ['form', 'json', 'raw']
FULL_DATAFRAME_THRESHOLD = 100
CUSTOM_TAG_MAX_LENGTH = 255
DL_DATA_TIMEFRAMES = ['1m', '5m']
ENV_VAR_PREFIX = 'FREQTRADE__'
+13 -2
View File
@@ -170,6 +170,7 @@ def load_and_merge_backtest_result(strategy_name: str, filename: Path, results:
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'))))
@@ -184,7 +185,7 @@ def get_backtest_resultlist(dirname: Path):
continue
for s, v in metadata.items():
results.append({
'filename': filename.name,
'filename': filename.stem,
'strategy': s,
'run_id': v['run_id'],
'backtest_start_time': v['backtest_start_time'],
@@ -193,6 +194,17 @@ def get_backtest_resultlist(dirname: Path):
return results
def delete_backtest_result(file_abs: Path):
"""
Delete backtest result file and corresponding metadata file.
"""
# *.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()
def find_existing_backtest_stats(dirname: Union[Path, str], run_ids: Dict[str, str],
min_backtest_date: Optional[datetime] = None) -> Dict[str, Any]:
"""
@@ -211,7 +223,6 @@ def find_existing_backtest_stats(dirname: Union[Path, str], run_ids: Dict[str, s
'strategy_comparison': [],
}
# Weird glob expression here avoids including .meta.json files.
for filename in _get_backtest_files(dirname):
metadata = load_backtest_metadata(filename)
if not metadata:
+47 -35
View File
@@ -11,7 +11,7 @@ import pandas as pd
from pandas import DataFrame, to_datetime
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, Config, TradeList
from freqtrade.enums import CandleType
from freqtrade.enums import CandleType, TradingMode
logger = logging.getLogger(__name__)
@@ -96,8 +96,14 @@ def ohlcv_fill_up_missing_data(dataframe: DataFrame, timeframe: str, pair: str)
'volume': 'sum'
}
timeframe_minutes = timeframe_to_minutes(timeframe)
resample_interval = f'{timeframe_minutes}min'
if timeframe_minutes >= 43200 and timeframe_minutes < 525600:
# Monthly candles need special treatment to stick to the 1st of the month
resample_interval = f'{timeframe}S'
elif timeframe_minutes > 43200:
resample_interval = timeframe
# Resample to create "NAN" values
df = dataframe.resample(f'{timeframe_minutes}min', on='date').agg(ohlcv_dict)
df = dataframe.resample(resample_interval, on='date').agg(ohlcv_dict)
# Forwardfill close for missing columns
df['close'] = df['close'].fillna(method='ffill')
@@ -122,7 +128,7 @@ def ohlcv_fill_up_missing_data(dataframe: DataFrame, timeframe: str, pair: str)
return df
def trim_dataframe(df: DataFrame, timerange, df_date_col: str = 'date',
def trim_dataframe(df: DataFrame, timerange, *, df_date_col: str = 'date',
startup_candles: int = 0) -> DataFrame:
"""
Trim dataframe based on given timerange
@@ -264,7 +270,6 @@ def convert_ohlcv_format(
convert_from: str,
convert_to: str,
erase: bool,
candle_type: CandleType
):
"""
Convert OHLCV from one format to another
@@ -272,7 +277,6 @@ def convert_ohlcv_format(
:param convert_from: Source format
:param convert_to: Target format
:param erase: Erase source data (does not apply if source and target format are identical)
:param candle_type: Any of the enum CandleType (must match trading mode!)
"""
from freqtrade.data.history.idatahandler import get_datahandler
src = get_datahandler(config['datadir'], convert_from)
@@ -280,37 +284,45 @@ def convert_ohlcv_format(
timeframes = config.get('timeframes', [config.get('timeframe')])
logger.info(f"Converting candle (OHLCV) for timeframe {timeframes}")
if 'pairs' not in config:
config['pairs'] = []
# Check timeframes or fall back to timeframe.
for timeframe in timeframes:
config['pairs'].extend(src.ohlcv_get_pairs(
config['datadir'],
timeframe,
candle_type=candle_type
))
config['pairs'] = sorted(set(config['pairs']))
logger.info(f"Converting candle (OHLCV) data for {config['pairs']}")
candle_types = [CandleType.from_string(ct) for ct in config.get('candle_types', [
c.value for c in CandleType])]
logger.info(candle_types)
paircombs = src.ohlcv_get_available_data(config['datadir'], TradingMode.SPOT)
paircombs.extend(src.ohlcv_get_available_data(config['datadir'], TradingMode.FUTURES))
for timeframe in timeframes:
for pair in config['pairs']:
data = src.ohlcv_load(pair=pair, timeframe=timeframe,
timerange=None,
fill_missing=False,
drop_incomplete=False,
startup_candles=0,
candle_type=candle_type)
logger.info(f"Converting {len(data)} {timeframe} {candle_type} candles for {pair}")
if len(data) > 0:
trg.ohlcv_store(
pair=pair,
timeframe=timeframe,
data=data,
candle_type=candle_type
)
if erase and convert_from != convert_to:
logger.info(f"Deleting source data for {pair} / {timeframe}")
src.ohlcv_purge(pair=pair, timeframe=timeframe, candle_type=candle_type)
if 'pairs' in config:
# Filter pairs
paircombs = [comb for comb in paircombs if comb[0] in config['pairs']]
if 'timeframes' in config:
paircombs = [comb for comb in paircombs if comb[1] in config['timeframes']]
paircombs = [comb for comb in paircombs if comb[2] in candle_types]
paircombs = sorted(paircombs, key=lambda x: (x[0], x[1], x[2].value))
formatted_paircombs = '\n'.join([f"{pair}, {timeframe}, {candle_type}"
for pair, timeframe, candle_type in paircombs])
logger.info(f"Converting candle (OHLCV) data for the following pair combinations:\n"
f"{formatted_paircombs}")
for pair, timeframe, candle_type in paircombs:
data = src.ohlcv_load(pair=pair, timeframe=timeframe,
timerange=None,
fill_missing=False,
drop_incomplete=False,
startup_candles=0,
candle_type=candle_type)
logger.info(f"Converting {len(data)} {timeframe} {candle_type} candles for {pair}")
if len(data) > 0:
trg.ohlcv_store(
pair=pair,
timeframe=timeframe,
data=data,
candle_type=candle_type
)
if erase and convert_from != convert_to:
logger.info(f"Deleting source data for {pair} / {timeframe}")
src.ohlcv_purge(pair=pair, timeframe=timeframe, candle_type=candle_type)
def reduce_dataframe_footprint(df: DataFrame) -> DataFrame:
+6 -3
View File
@@ -7,15 +7,16 @@ 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, Config
from freqtrade.constants import (DATETIME_PRINT_FORMAT, DEFAULT_DATAFRAME_COLUMNS,
DL_DATA_TIMEFRAMES, Config)
from freqtrade.data.converter import (clean_ohlcv_dataframe, ohlcv_to_dataframe,
trades_remove_duplicates, trades_to_ohlcv)
from freqtrade.data.history.idatahandler import IDataHandler, get_datahandler
from freqtrade.enums import CandleType
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import Exchange
from freqtrade.misc import format_ms_time
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
from freqtrade.util import format_ms_time
from freqtrade.util.binance_mig import migrate_binance_futures_data
@@ -354,7 +355,7 @@ def _download_trades_history(exchange: Exchange,
trades = []
if not since:
since = int((datetime.now() - timedelta(days=-new_pairs_days)).timestamp()) * 1000
since = int((datetime.now() - timedelta(days=new_pairs_days)).timestamp()) * 1000
from_id = trades[-1][1] if trades else None
if trades and since < trades[-1][0]:
@@ -512,6 +513,8 @@ def download_data_main(config: Config) -> None:
]
expanded_pairs = dynamic_expand_pairlist(config, available_pairs)
if 'timeframes' not in config:
config['timeframes'] = DL_DATA_TIMEFRAMES
# Manual validations of relevant settings
if not config['exchange'].get('skip_pair_validation', False):
+21 -18
View File
@@ -194,32 +194,35 @@ 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) -> 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
:return: expectancy, expectancy_ratio
"""
if len(trades) == 0:
return 0
expectancy = 1
expectancy = 0
expectancy_ratio = 100
profit_sum = trades.loc[trades['profit_abs'] > 0, 'profit_abs'].sum()
loss_sum = abs(trades.loc[trades['profit_abs'] < 0, 'profit_abs'].sum())
nb_win_trades = len(trades.loc[trades['profit_abs'] > 0])
nb_loss_trades = len(trades.loc[trades['profit_abs'] < 0])
if len(trades) > 0:
winning_trades = trades.loc[trades['profit_abs'] > 0]
losing_trades = trades.loc[trades['profit_abs'] < 0]
profit_sum = winning_trades['profit_abs'].sum()
loss_sum = abs(losing_trades['profit_abs'].sum())
nb_win_trades = len(winning_trades)
nb_loss_trades = len(losing_trades)
if (nb_win_trades > 0) and (nb_loss_trades > 0):
average_win = profit_sum / nb_win_trades
average_loss = loss_sum / nb_loss_trades
risk_reward_ratio = average_win / average_loss
winrate = nb_win_trades / len(trades)
expectancy = ((1 + risk_reward_ratio) * winrate) - 1
elif nb_win_trades == 0:
expectancy = 0
average_win = (profit_sum / nb_win_trades) if nb_win_trades > 0 else 0
average_loss = (loss_sum / nb_loss_trades) if nb_loss_trades > 0 else 0
winrate = (nb_win_trades / len(trades))
loserate = (nb_loss_trades / len(trades))
return expectancy
expectancy = (winrate * average_win) - (loserate * average_loss)
if (average_loss > 0):
risk_reward_ratio = average_win / average_loss
expectancy_ratio = ((1 + risk_reward_ratio) * winrate) - 1
return expectancy, expectancy_ratio
def calculate_sortino(trades: pd.DataFrame, min_date: datetime, max_date: datetime,
+1 -7
View File
@@ -172,13 +172,7 @@ class Edge:
pair_data = pair_data.sort_values(by=['date'])
pair_data = pair_data.reset_index(drop=True)
df_analyzed = self.strategy.advise_exit(
dataframe=self.strategy.advise_entry(
dataframe=pair_data,
metadata={'pair': pair}
),
metadata={'pair': pair}
)[headers].copy()
df_analyzed = self.strategy.ft_advise_signals(pair_data, {'pair': pair})[headers].copy()
trades += self._find_trades_for_stoploss_range(df_analyzed, pair, self._stoploss_range)
+1
View File
@@ -34,6 +34,7 @@ class Binance(Exchange):
"tickers_have_price": False,
"floor_leverage": True,
"stop_price_type_field": "workingType",
"order_props_in_contracts": ['amount', 'cost', 'filled', 'remaining'],
"stop_price_type_value_mapping": {
PriceType.LAST: "CONTRACT_PRICE",
PriceType.MARK: "MARK_PRICE",
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -28,7 +28,7 @@ class Bybit(Exchange):
_ft_has: Dict = {
"ohlcv_candle_limit": 200,
"ohlcv_has_history": False,
"ohlcv_has_history": True,
}
_ft_has_futures: Dict = {
"ohlcv_has_history": True,
+1 -5
View File
@@ -80,9 +80,8 @@ class Exchange:
"mark_ohlcv_price": "mark",
"mark_ohlcv_timeframe": "8h",
"ccxt_futures_name": "swap",
"fee_cost_in_contracts": False, # Fee cost needs contract conversion
"needs_trading_fees": False, # use fetch_trading_fees to cache fees
"order_props_in_contracts": ['amount', 'cost', 'filled', 'remaining'],
"order_props_in_contracts": ['amount', 'filled', 'remaining'],
# Override createMarketBuyOrderRequiresPrice where ccxt has it wrong
"marketOrderRequiresPrice": False,
}
@@ -1859,9 +1858,6 @@ class Exchange:
if fee_curr is None:
return None
fee_cost = float(fee['cost'])
if self._ft_has['fee_cost_in_contracts']:
# Convert cost via "contracts" conversion
fee_cost = self._contracts_to_amount(symbol, fee['cost'])
# Calculate fee based on order details
if fee_curr == self.get_pair_base_currency(symbol):
-2
View File
@@ -33,8 +33,6 @@ class Gate(Exchange):
_ft_has_futures: Dict = {
"needs_trading_fees": True,
"marketOrderRequiresPrice": False,
"fee_cost_in_contracts": False, # Set explicitly to false for clarity
"order_props_in_contracts": ['amount', 'filled', 'remaining'],
"stop_price_type_field": "price_type",
"stop_price_type_value_mapping": {
PriceType.LAST: 0,
-1
View File
@@ -32,7 +32,6 @@ class Okx(Exchange):
}
_ft_has_futures: Dict = {
"tickers_have_quoteVolume": False,
"fee_cost_in_contracts": True,
"stop_price_type_field": "slTriggerPxType",
"stop_price_type_value_mapping": {
PriceType.LAST: "last",
-11
View File
@@ -86,8 +86,6 @@ class IFreqaiModel(ABC):
logger.warning("DI threshold is not configured for Keras models yet. Deactivating.")
self.CONV_WIDTH = self.freqai_info.get('conv_width', 1)
if self.ft_params.get("inlier_metric_window", 0):
self.CONV_WIDTH = self.ft_params.get("inlier_metric_window", 0) * 2
self.class_names: List[str] = [] # used in classification subclasses
self.pair_it = 0
self.pair_it_train = 0
@@ -676,15 +674,6 @@ class IFreqaiModel(ABC):
hist_preds_df['close_price'] = strat_df['close']
hist_preds_df['date_pred'] = strat_df['date']
# # for keras type models, the conv_window needs to be prepended so
# # viewing is correct in frequi
if self.ft_params.get('inlier_metric_window', 0):
n_lost_points = self.freqai_info.get('conv_width', 2)
zeros_df = DataFrame(np.zeros((n_lost_points, len(hist_preds_df.columns))),
columns=hist_preds_df.columns)
self.dd.historic_predictions[pair] = pd.concat(
[zeros_df, hist_preds_df], axis=0, ignore_index=True)
def fit_live_predictions(self, dk: FreqaiDataKitchen, pair: str) -> None:
"""
Fit the labels with a gaussian distribution
@@ -32,8 +32,8 @@ class LightGBMClassifier(BaseClassifierModel):
eval_set = None
test_weights = None
else:
eval_set = (data_dictionary["test_features"].to_numpy(),
data_dictionary["test_labels"].to_numpy()[:, 0])
eval_set = [(data_dictionary["test_features"].to_numpy(),
data_dictionary["test_labels"].to_numpy()[:, 0])]
test_weights = data_dictionary["test_weights"]
X = data_dictionary["train_features"].to_numpy()
y = data_dictionary["train_labels"].to_numpy()[:, 0]
@@ -42,7 +42,6 @@ class LightGBMClassifier(BaseClassifierModel):
init_model = self.get_init_model(dk.pair)
model = LGBMClassifier(**self.model_training_parameters)
model.fit(X=X, y=y, eval_set=eval_set, sample_weight=train_weights,
eval_sample_weight=[test_weights], init_model=init_model)
@@ -32,7 +32,7 @@ class LightGBMRegressor(BaseRegressionModel):
eval_set = None
eval_weights = None
else:
eval_set = (data_dictionary["test_features"], data_dictionary["test_labels"])
eval_set = [(data_dictionary["test_features"], data_dictionary["test_labels"])]
eval_weights = data_dictionary["test_weights"]
X = data_dictionary["train_features"]
y = data_dictionary["train_labels"]
@@ -42,10 +42,10 @@ class LightGBMRegressorMultiTarget(BaseRegressionModel):
eval_weights = [data_dictionary["test_weights"]]
eval_sets = [(None, None)] * data_dictionary['test_labels'].shape[1] # type: ignore
for i in range(data_dictionary['test_labels'].shape[1]):
eval_sets[i] = ( # type: ignore
eval_sets[i] = [( # type: ignore
data_dictionary["test_features"],
data_dictionary["test_labels"].iloc[:, i]
)
)]
init_model = self.get_init_model(dk.pair)
if init_model:
+15 -3
View File
@@ -231,7 +231,7 @@ class FreqtradeBot(LoggingMixin):
self.manage_open_orders()
# Protect from collisions with force_exit.
# Without this, freqtrade my try to recreate stoploss_on_exchange orders
# Without this, freqtrade may try to recreate stoploss_on_exchange orders
# while exiting is in process, since telegram messages arrive in an different thread.
with self._exit_lock:
trades = Trade.get_open_trades()
@@ -1383,7 +1383,10 @@ class FreqtradeBot(LoggingMixin):
latest_candle_close_date = timeframe_to_next_date(self.strategy.timeframe,
latest_candle_open_date)
# Check if new candle
if order_obj and latest_candle_close_date > order_obj.order_date_utc:
if (
order_obj and order_obj.side == trade.entry_side
and latest_candle_close_date > order_obj.order_date_utc
):
# New candle
proposed_rate = self.exchange.get_rate(
trade.pair, side='entry', is_short=trade.is_short, refresh=True)
@@ -1939,6 +1942,7 @@ class FreqtradeBot(LoggingMixin):
"""
Applies the fee to amount (either from Order or from Trades).
Can eat into dust if more than the required asset is available.
In case of trade adjustment orders, trade.amount will not have been adjusted yet.
Can't happen in Futures mode - where Fees are always in settlement currency,
never in base currency.
"""
@@ -1948,6 +1952,10 @@ class FreqtradeBot(LoggingMixin):
# check against remaining amount!
amount_ = trade.amount - amount
if trade.nr_of_successful_entries >= 1 and order_obj.ft_order_side == trade.entry_side:
# In case of rebuy's, trade.amount doesn't contain the amount of the last entry.
amount_ = trade.amount + amount
if fee_abs != 0 and self.wallets.get_free(trade_base_currency) >= amount_:
# Eat into dust if we own more than base currency
logger.info(f"Fee amount for {trade} was in base currency - "
@@ -1977,7 +1985,11 @@ class FreqtradeBot(LoggingMixin):
# Init variables
order_amount = safe_value_fallback(order, 'filled', 'amount')
# Only run for closed orders
if trade.fee_updated(order.get('side', '')) or order['status'] == 'open':
if (
trade.fee_updated(order.get('side', ''))
or order['status'] == 'open'
or order_obj.ft_fee_base
):
return None
trade_base_currency = self.exchange.get_pair_base_currency(trade.pair)
+7 -9
View File
@@ -3,7 +3,6 @@ Various tool function for Freqtrade and scripts
"""
import gzip
import logging
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, Iterator, List, Mapping, Optional, TextIO, Union
from urllib.parse import urlparse
@@ -117,20 +116,19 @@ def file_load_json(file: Path):
return pairdata
def is_file_in_dir(file: Path, directory: Path) -> bool:
"""
Helper function to check if file is in directory.
"""
return file.is_file() and file.parent.samefile(directory)
def pair_to_filename(pair: str) -> str:
for ch in ['/', ' ', '.', '@', '$', '+', ':']:
pair = pair.replace(ch, '_')
return pair
def format_ms_time(date: int) -> str:
"""
convert MS date to readable format.
: epoch-string in ms
"""
return datetime.fromtimestamp(date / 1000.0).strftime('%Y-%m-%dT%H:%M:%S')
def deep_merge_dicts(source, destination, allow_null_overrides: bool = True):
"""
Values from Source override destination, destination is returned (and modified!!)
+5 -6
View File
@@ -367,11 +367,7 @@ class Backtesting:
if not pair_data.empty:
# Cleanup from prior runs
pair_data.drop(HEADERS[5:] + ['buy', 'sell'], axis=1, errors='ignore')
df_analyzed = self.strategy.advise_exit(
self.strategy.advise_entry(pair_data, {'pair': pair}),
{'pair': pair}
).copy()
df_analyzed = self.strategy.ft_advise_signals(pair_data, {'pair': pair})
# Trim startup period from analyzed dataframe
df_analyzed = processed[pair] = pair_data = trim_dataframe(
df_analyzed, self.timerange, startup_candles=self.required_startup)
@@ -679,6 +675,7 @@ class Backtesting:
remaining=amount,
cost=amount * close_rate,
)
order._trade_bt = trade
trade.orders.append(order)
return trade
@@ -901,8 +898,9 @@ class Backtesting:
amount=amount,
filled=0,
remaining=amount,
cost=stake_amount + trade.fee_open,
cost=amount * propose_rate + trade.fee_open,
)
order._trade_bt = trade
trade.orders.append(order)
if pos_adjust and self._get_order_filled(order.ft_price, row):
order.close_bt_order(current_time, trade)
@@ -1275,6 +1273,7 @@ class Backtesting:
preprocessed = self.strategy.advise_all_indicators(data)
# Trim startup period from analyzed dataframe
# This only used to determine if trimming would result in an empty dataframe
preprocessed_tmp = trim_dataframes(preprocessed, timerange, self.required_startup)
if not preprocessed_tmp:
+2
View File
@@ -446,6 +446,8 @@ class Hyperopt:
preprocessed = self.backtesting.strategy.advise_all_indicators(data)
# Trim startup period from analyzed dataframe to get correct dates for output.
# This is only used to keep track of min/max date after trimming.
# The result is NOT returned from this method, actual trimming happens in backtesting.
trimmed = trim_dataframes(preprocessed, self.timerange, self.backtesting.required_startup)
self.min_date, self.max_date = get_timerange(trimmed)
if not self.market_change:
+2 -4
View File
@@ -432,12 +432,10 @@ class HyperoptTools:
for i in range(len(trials)):
if trials.loc[i]['is_profit']:
for j in range(len(trials.loc[i]) - 3):
trials.iat[i, j] = "{}{}{}".format(Fore.GREEN,
str(trials.loc[i][j]), Fore.RESET)
trials.iat[i, j] = f"{Fore.GREEN}{str(trials.loc[i][j])}{Fore.RESET}"
if trials.loc[i]['is_best'] and highlight_best:
for j in range(len(trials.loc[i]) - 3):
trials.iat[i, j] = "{}{}{}".format(Style.BRIGHT,
str(trials.loc[i][j]), Style.RESET_ALL)
trials.iat[i, j] = f"{Style.BRIGHT}{str(trials.loc[i][j])}{Style.RESET_ALL}"
trials = trials.drop(columns=['is_initial_point', 'is_best', 'is_profit', 'is_random'])
if remove_header > 0:
@@ -1,5 +1,6 @@
# flake8: noqa: F401
from freqtrade.optimize.optimize_reports.bt_output import (generate_edge_table,
generate_wins_draws_losses,
show_backtest_result,
show_backtest_results,
show_sorted_pairlist,
@@ -14,5 +15,4 @@ from freqtrade.optimize.optimize_reports.optimize_reports import (
generate_all_periodic_breakdown_stats, generate_backtest_stats, generate_daily_stats,
generate_exit_reason_stats, generate_pair_metrics, generate_periodic_breakdown_stats,
generate_rejected_signals, generate_strategy_comparison, generate_strategy_stats,
generate_tag_metrics, generate_trade_signal_candles, generate_trading_stats,
generate_wins_draws_losses)
generate_tag_metrics, generate_trade_signal_candles, generate_trading_stats)
@@ -5,8 +5,7 @@ from tabulate import tabulate
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT, Config
from freqtrade.misc import decimals_per_coin, round_coin_value
from freqtrade.optimize.optimize_reports.optimize_reports import (generate_periodic_breakdown_stats,
generate_wins_draws_losses)
from freqtrade.optimize.optimize_reports.optimize_reports import generate_periodic_breakdown_stats
logger = logging.getLogger(__name__)
@@ -30,6 +29,16 @@ def _get_line_header(first_column: str, stake_currency: str,
'Win Draw Loss Win%']
def generate_wins_draws_losses(wins, draws, losses):
if wins > 0 and losses == 0:
wl_ratio = '100'
elif wins == 0:
wl_ratio = '0'
else:
wl_ratio = f'{100.0 / (wins + draws + losses) * wins:.1f}' if losses > 0 else '100'
return f'{wins:>4} {draws:>4} {losses:>4} {wl_ratio:>4}'
def text_table_bt_results(pair_results: List[Dict[str, Any]], stake_currency: str) -> str:
"""
Generates and returns a text table for the given backtest data and the results dataframe
@@ -233,8 +242,9 @@ def text_table_add_metrics(strat_results: Dict) -> str:
('Calmar', f"{strat_results['calmar']:.2f}" if 'calmar' in strat_results else 'N/A'),
('Profit factor', f'{strat_results["profit_factor"]:.2f}' if 'profit_factor'
in strat_results else 'N/A'),
('Expectancy', f"{strat_results['expectancy']:.2f}" if 'expectancy'
in strat_results else 'N/A'),
('Expectancy (Ratio)', (
f"{strat_results['expectancy']:.2f} ({strat_results['expectancy_ratio']:.2f})" if
'expectancy_ratio' in strat_results else 'N/A')),
('Trades per day', strat_results['trades_per_day']),
('Avg. daily profit %',
f"{(strat_results['profit_total'] / strat_results['backtest_days']):.2%}"),
@@ -260,6 +270,9 @@ def text_table_add_metrics(strat_results: Dict) -> str:
f"{strat_results['draw_days']} / {strat_results['losing_days']}"),
('Avg. Duration Winners', f"{strat_results['winner_holding_avg']}"),
('Avg. Duration Loser', f"{strat_results['loser_holding_avg']}"),
('Max Consecutive Wins / Loss',
f"{strat_results['max_consecutive_wins']} / {strat_results['max_consecutive_losses']}"
if 'max_consecutive_losses' in strat_results else 'N/A'),
('Rejected Entry signals', strat_results.get('rejected_signals', 'N/A')),
('Entry/Exit Timeouts',
f"{strat_results.get('timedout_entry_orders', 'N/A')} / "
@@ -1,9 +1,10 @@
import logging
from copy import deepcopy
from datetime import datetime, timedelta, timezone
from typing import Any, Dict, List, Union
from typing import Any, Dict, List, Tuple, Union
from pandas import DataFrame, concat, to_datetime
import numpy as np
from pandas import DataFrame, Series, concat, to_datetime
from freqtrade.constants import BACKTEST_BREAKDOWNS, DATETIME_PRINT_FORMAT, IntOrInf
from freqtrade.data.metrics import (calculate_cagr, calculate_calmar, calculate_csum,
@@ -57,16 +58,6 @@ def generate_rejected_signals(preprocessed_df: Dict[str, DataFrame],
return rejected_candles_only
def generate_wins_draws_losses(wins, draws, losses):
if wins > 0 and losses == 0:
wl_ratio = '100'
elif wins == 0:
wl_ratio = '0'
else:
wl_ratio = f'{100.0 / (wins + draws + losses) * wins:.1f}' if losses > 0 else '100'
return f'{wins:>4} {draws:>4} {losses:>4} {wl_ratio:>4}'
def _generate_result_line(result: DataFrame, starting_balance: int, first_column: str) -> Dict:
"""
Generate one result dict, with "first_column" as key.
@@ -97,6 +88,7 @@ def _generate_result_line(result: DataFrame, starting_balance: int, first_column
'wins': len(result[result['profit_abs'] > 0]),
'draws': len(result[result['profit_abs'] == 0]),
'losses': len(result[result['profit_abs'] < 0]),
'winrate': len(result[result['profit_abs'] > 0]) / len(result) if len(result) else 0.0,
}
@@ -184,6 +176,7 @@ def generate_exit_reason_stats(max_open_trades: IntOrInf, results: DataFrame) ->
'wins': len(result[result['profit_abs'] > 0]),
'draws': len(result[result['profit_abs'] == 0]),
'losses': len(result[result['profit_abs'] < 0]),
'winrate': len(result[result['profit_abs'] > 0]) / count if count else 0.0,
'profit_mean': profit_mean,
'profit_mean_pct': round(profit_mean * 100, 2),
'profit_sum': profit_sum,
@@ -238,6 +231,7 @@ def generate_periodic_breakdown_stats(trade_list: List, period: str) -> List[Dic
wins = sum(day['profit_abs'] > 0)
draws = sum(day['profit_abs'] == 0)
loses = sum(day['profit_abs'] < 0)
trades = (wins + draws + loses)
stats.append(
{
'date': name.strftime('%d/%m/%Y'),
@@ -245,7 +239,8 @@ def generate_periodic_breakdown_stats(trade_list: List, period: str) -> List[Dic
'profit_abs': profit_abs,
'wins': wins,
'draws': draws,
'loses': loses
'loses': loses,
'winrate': wins / trades if trades else 0.0,
}
)
return stats
@@ -258,6 +253,23 @@ def generate_all_periodic_breakdown_stats(trade_list: List) -> Dict[str, List]:
return result
def calc_streak(dataframe: DataFrame) -> Tuple[int, int]:
"""
Calculate consecutive win and loss streaks
:param dataframe: Dataframe containing the trades dataframe, with profit_ratio column
:return: Tuple containing consecutive wins and losses
"""
df = Series(np.where(dataframe['profit_ratio'] > 0, 'win', 'loss')).to_frame('result')
df['streaks'] = df['result'].ne(df['result'].shift()).cumsum().rename('streaks')
df['counter'] = df['streaks'].groupby(df['streaks']).cumcount() + 1
res = df.groupby(df['result']).max()
#
cons_wins = int(res.loc['win', 'counter']) if 'win' in res.index else 0
cons_losses = int(res.loc['loss', 'counter']) if 'loss' in res.index else 0
return cons_wins, cons_losses
def generate_trading_stats(results: DataFrame) -> Dict[str, Any]:
""" Generate overall trade statistics """
if len(results) == 0:
@@ -265,9 +277,12 @@ def generate_trading_stats(results: DataFrame) -> Dict[str, Any]:
'wins': 0,
'losses': 0,
'draws': 0,
'winrate': 0,
'holding_avg': timedelta(),
'winner_holding_avg': timedelta(),
'loser_holding_avg': timedelta(),
'max_consecutive_wins': 0,
'max_consecutive_losses': 0,
}
winning_trades = results.loc[results['profit_ratio'] > 0]
@@ -280,17 +295,21 @@ def generate_trading_stats(results: DataFrame) -> Dict[str, Any]:
if not winning_trades.empty else timedelta())
loser_holding_avg = (timedelta(minutes=round(losing_trades['trade_duration'].mean()))
if not losing_trades.empty else timedelta())
winstreak, loss_streak = calc_streak(results)
return {
'wins': len(winning_trades),
'losses': len(losing_trades),
'draws': len(draw_trades),
'winrate': len(winning_trades) / len(results) if len(results) else 0.0,
'holding_avg': holding_avg,
'holding_avg_s': holding_avg.total_seconds(),
'winner_holding_avg': winner_holding_avg,
'winner_holding_avg_s': winner_holding_avg.total_seconds(),
'loser_holding_avg': loser_holding_avg,
'loser_holding_avg_s': loser_holding_avg.total_seconds(),
'max_consecutive_wins': winstreak,
'max_consecutive_losses': loss_streak,
}
@@ -383,6 +402,7 @@ def generate_strategy_stats(pairlist: List[str],
losing_profit = results.loc[results['profit_abs'] < 0, 'profit_abs'].sum()
profit_factor = winning_profit / abs(losing_profit) if losing_profit else 0.0
expectancy, expectancy_ratio = calculate_expectancy(results)
backtest_days = (max_date - min_date).days or 1
strat_stats = {
'trades': results.to_dict(orient='records'),
@@ -408,7 +428,8 @@ def generate_strategy_stats(pairlist: List[str],
'profit_total_long_abs': results.loc[~results['is_short'], 'profit_abs'].sum(),
'profit_total_short_abs': results.loc[results['is_short'], 'profit_abs'].sum(),
'cagr': calculate_cagr(backtest_days, start_balance, content['final_balance']),
'expectancy': calculate_expectancy(results),
'expectancy': expectancy,
'expectancy_ratio': expectancy_ratio,
'sortino': calculate_sortino(results, min_date, max_date, start_balance),
'sharpe': calculate_sharpe(results, min_date, max_date, start_balance),
'calmar': calculate_calmar(results, min_date, max_date, start_balance),
+23 -5
View File
@@ -38,6 +38,7 @@ class Order(ModelBase):
Mirrors CCXT Order structure
"""
__tablename__ = 'orders'
__allow_unmapped__ = True
session: ClassVar[SessionType]
# Uniqueness should be ensured over pair, order_id
@@ -47,7 +48,8 @@ class Order(ModelBase):
id: Mapped[int] = mapped_column(Integer, primary_key=True)
ft_trade_id: Mapped[int] = mapped_column(Integer, ForeignKey('trades.id'), index=True)
trade: Mapped[List["Trade"]] = relationship("Trade", back_populates="orders")
_trade_live: Mapped["Trade"] = relationship("Trade", back_populates="orders")
_trade_bt: "LocalTrade" = None # type: ignore
# order_side can only be 'buy', 'sell' or 'stoploss'
ft_order_side: Mapped[str] = mapped_column(String(25), nullable=False)
@@ -119,11 +121,20 @@ class Order(ModelBase):
def safe_amount_after_fee(self) -> float:
return self.safe_filled - self.safe_fee_base
@property
def trade(self) -> "LocalTrade":
return self._trade_bt or self._trade_live
@property
def stake_amount(self) -> float:
""" Amount in stake currency used for this order"""
return self.safe_amount * self.safe_price / self.trade.leverage
def __repr__(self):
return (f"Order(id={self.id}, order_id={self.order_id}, trade_id={self.ft_trade_id}, "
return (f"Order(id={self.id}, trade={self.ft_trade_id}, order_id={self.order_id}, "
f"side={self.side}, filled={self.safe_filled}, price={self.safe_price}, "
f"order_type={self.order_type}, status={self.status})")
f"status={self.status}, date={self.order_date:{DATETIME_PRINT_FORMAT}})")
def update_from_ccxt_object(self, order):
"""
@@ -212,6 +223,7 @@ class Order(ModelBase):
'order_type': self.order_type,
'price': self.price,
'remaining': self.remaining,
'ft_fee_base': self.ft_fee_base,
})
return resp
@@ -1298,9 +1310,12 @@ class Trade(ModelBase, LocalTrade):
Float(), nullable=True, default=None) # type: ignore
def __init__(self, **kwargs):
from_json = kwargs.pop('__FROM_JSON', None)
super().__init__(**kwargs)
self.realized_profit = 0
self.recalc_open_trade_value()
if not from_json:
# Skip recalculation when loading from json
self.realized_profit = 0
self.recalc_open_trade_value()
@validates('enter_tag', 'exit_reason')
def validate_string_len(self, key, value):
@@ -1654,6 +1669,7 @@ class Trade(ModelBase, LocalTrade):
import rapidjson
data = rapidjson.loads(json_str)
trade = cls(
__FROM_JSON=True,
id=data["trade_id"],
pair=data["pair"],
base_currency=data["base_currency"],
@@ -1708,6 +1724,7 @@ class Trade(ModelBase, LocalTrade):
order_obj = Order(
amount=order["amount"],
ft_amount=order["amount"],
ft_order_side=order["ft_order_side"],
ft_pair=order["pair"],
ft_is_open=order["is_open"],
@@ -1722,6 +1739,7 @@ class Trade(ModelBase, LocalTrade):
if order["order_filled_timestamp"] else None),
order_type=order["order_type"],
price=order["price"],
ft_price=order["price"],
remaining=order["remaining"],
)
trade.orders.append(order_obj)
+1 -1
View File
@@ -84,7 +84,7 @@ def init_plotscript(config, markets: List, startup_candles: int = 0):
except ValueError as e:
raise OperationalException(e) from e
if not trades.empty:
trades = trim_dataframe(trades, timerange, 'open_date')
trades = trim_dataframe(trades, timerange, df_date_col='open_date')
return {"ohlcv": data,
"trades": trades,
@@ -89,10 +89,10 @@ class PerformanceFilter(IPairList):
# Sort the list using:
# - primarily performance (high to low)
# - then count (low to high, so as to favor same performance with fewer trades)
# - then pair name alphametically
# - then by prior index, keeping original sorting order
sorted_df = list_df.merge(performance, on='pair', how='left')\
.fillna(0).sort_values(by=['count', 'prior_idx'], ascending=True)\
.sort_values(by=['profit_ratio'], ascending=False)
.fillna(0).sort_values(by=['profit_ratio', 'count', 'prior_idx'],
ascending=[False, True, True])
if self._min_profit is not None:
removed = sorted_df[sorted_df['profit_ratio'] < self._min_profit]
for _, row in removed.iterrows():
+62 -11
View File
@@ -3,19 +3,21 @@ Remote PairList provider
Provides pair list fetched from a remote source
"""
import json
import logging
from pathlib import Path
from typing import Any, Dict, List, Tuple
import rapidjson
import requests
from cachetools import TTLCache
from freqtrade import __version__
from freqtrade.configuration.load_config import CONFIG_PARSE_MODE
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException
from freqtrade.exchange.types import Tickers
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
logger = logging.getLogger(__name__)
@@ -40,6 +42,8 @@ class RemotePairList(IPairList):
'`pairlist_url` not specified. Please check your configuration '
'for "pairlist.config.pairlist_url"')
self._mode = self._pairlistconfig.get('mode', 'whitelist')
self._processing_mode = self._pairlistconfig.get('processing_mode', 'filter')
self._number_pairs = self._pairlistconfig['number_assets']
self._refresh_period: int = self._pairlistconfig.get('refresh_period', 1800)
self._keep_pairlist_on_failure = self._pairlistconfig.get('keep_pairlist_on_failure', True)
@@ -50,6 +54,21 @@ class RemotePairList(IPairList):
self._init_done = False
self._last_pairlist: List[Any] = list()
if self._mode not in ['whitelist', 'blacklist']:
raise OperationalException(
'`mode` not configured correctly. Supported Modes '
'are "whitelist","blacklist"')
if self._processing_mode not in ['filter', 'append']:
raise OperationalException(
'`processing_mode` not configured correctly. Supported Modes '
'are "filter","append"')
if self._pairlist_pos == 0 and self._mode == 'blacklist':
raise OperationalException(
'A `blacklist` mode RemotePairList can not be on the first '
'position of your pairlist.')
@property
def needstickers(self) -> bool:
"""
@@ -67,23 +86,37 @@ class RemotePairList(IPairList):
@staticmethod
def description() -> str:
return "Retrieve pairs from a remote API."
return "Retrieve pairs from a remote API or local file."
@staticmethod
def available_parameters() -> Dict[str, PairlistParameter]:
return {
"number_assets": {
"type": "number",
"default": 0,
"description": "Number of assets",
"help": "Number of assets to use from the pairlist.",
},
"pairlist_url": {
"type": "string",
"default": "",
"description": "URL to fetch pairlist from",
"help": "URL to fetch pairlist from",
},
"number_assets": {
"type": "number",
"default": 30,
"description": "Number of assets",
"help": "Number of assets to use from the pairlist.",
},
"mode": {
"type": "option",
"default": "whitelist",
"options": ["whitelist", "blacklist"],
"description": "Pairlist mode",
"help": "Should this pairlist operate as a whitelist or blacklist?",
},
"processing_mode": {
"type": "option",
"default": "filter",
"options": ["filter", "append"],
"description": "Processing mode",
"help": "Append pairs to incomming pairlist or filter them?",
},
**IPairList.refresh_period_parameter(),
"keep_pairlist_on_failure": {
"type": "boolean",
@@ -204,7 +237,7 @@ class RemotePairList(IPairList):
if file_path.exists():
with file_path.open() as json_file:
# Load the JSON data into a dictionary
jsonparse = json.load(json_file)
jsonparse = rapidjson.load(json_file, parse_mode=CONFIG_PARSE_MODE)
try:
pairlist = self.process_json(jsonparse)
@@ -223,6 +256,7 @@ class RemotePairList(IPairList):
self.log_once(f"Fetched pairs: {pairlist}", logger.debug)
pairlist = expand_pairlist(pairlist, list(self._exchange.get_markets().keys()))
pairlist = self._whitelist_for_active_markets(pairlist)
pairlist = pairlist[:self._number_pairs]
@@ -250,6 +284,23 @@ class RemotePairList(IPairList):
:return: new whitelist
"""
rpl_pairlist = self.gen_pairlist(tickers)
merged_list = pairlist + rpl_pairlist
merged_list = sorted(set(merged_list), key=merged_list.index)
merged_list = []
filtered = []
if self._mode == "whitelist":
if self._processing_mode == "filter":
merged_list = [pair for pair in pairlist if pair in rpl_pairlist]
elif self._processing_mode == "append":
merged_list = pairlist + rpl_pairlist
merged_list = sorted(set(merged_list), key=merged_list.index)
else:
for pair in pairlist:
if pair not in rpl_pairlist:
merged_list.append(pair)
else:
filtered.append(pair)
if filtered:
self.log_once(f"Blacklist - Filtered out pairs: {filtered}", logger.info)
merged_list = merged_list[:self._number_pairs]
return merged_list
+1 -2
View File
@@ -13,9 +13,8 @@ from freqtrade.constants import Config, ListPairsWithTimeframes
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_prev_date
from freqtrade.exchange.types import Tickers
from freqtrade.misc import format_ms_time
from freqtrade.plugins.pairlist.IPairList import IPairList, PairlistParameter
from freqtrade.util import dt_now
from freqtrade.util import dt_now, format_ms_time
logger = logging.getLogger(__name__)
+23 -4
View File
@@ -2,6 +2,7 @@ import asyncio
import logging
from copy import deepcopy
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List
from fastapi import APIRouter, BackgroundTasks, Depends
@@ -9,11 +10,12 @@ from fastapi.exceptions import HTTPException
from freqtrade.configuration.config_validation import validate_config_consistency
from freqtrade.constants import Config
from freqtrade.data.btanalysis import get_backtest_resultlist, load_and_merge_backtest_result
from freqtrade.data.btanalysis import (delete_backtest_result, get_backtest_resultlist,
load_and_merge_backtest_result)
from freqtrade.enums import BacktestState
from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.exchange.common import remove_exchange_credentials
from freqtrade.misc import deep_merge_dicts
from freqtrade.misc import deep_merge_dicts, is_file_in_dir
from freqtrade.rpc.api_server.api_schemas import (BacktestHistoryEntry, BacktestRequest,
BacktestResponse)
from freqtrade.rpc.api_server.deps import get_config
@@ -245,13 +247,16 @@ def api_backtest_history(config=Depends(get_config)):
tags=['webserver', 'backtest'])
def api_backtest_history_result(filename: str, strategy: str, config=Depends(get_config)):
# Get backtest result history, read from metadata files
fn = config['user_data_dir'] / 'backtest_results' / filename
bt_results_base: Path = config['user_data_dir'] / 'backtest_results'
fn = (bt_results_base / filename).with_suffix('.json')
results: Dict[str, Any] = {
'metadata': {},
'strategy': {},
'strategy_comparison': [],
}
if not is_file_in_dir(fn, bt_results_base):
raise HTTPException(status_code=404, detail="File not found.")
load_and_merge_backtest_result(strategy, fn, results)
return {
"status": "ended",
@@ -261,3 +266,17 @@ def api_backtest_history_result(filename: str, strategy: str, config=Depends(get
"status_msg": "Historic result",
"backtest_result": results,
}
@router.delete('/backtest/history/{file}', response_model=List[BacktestHistoryEntry],
tags=['webserver', 'backtest'])
def api_delete_backtest_history_entry(file: str, config=Depends(get_config)):
# Get backtest result history, read from metadata files
bt_results_base: Path = config['user_data_dir'] / 'backtest_results'
file_abs = (bt_results_base / file).with_suffix('.json')
# Ensure file is in backtest_results directory
if not is_file_in_dir(file_abs, bt_results_base):
raise HTTPException(status_code=404, detail="File not found.")
delete_backtest_result(file_abs)
return get_backtest_resultlist(config['user_data_dir'] / 'backtest_results')
+4
View File
@@ -136,6 +136,9 @@ class Profit(BaseModel):
winning_trades: int
losing_trades: int
profit_factor: float
winrate: float
expectancy: float
expectancy_ratio: float
max_drawdown: float
max_drawdown_abs: float
trading_volume: Optional[float]
@@ -238,6 +241,7 @@ class OrderSchema(BaseModel):
is_open: bool
order_timestamp: Optional[int]
order_filled_timestamp: Optional[int]
ft_fee_base: Optional[float]
class TradeSchema(BaseModel):
+11 -3
View File
@@ -49,7 +49,8 @@ logger = logging.getLogger(__name__)
# 2.28: Switch reload endpoint to Post
# 2.29: Add /exchanges endpoint
# 2.30: new /pairlists endpoint
API_VERSION = 2.30
# 2.31: new /backtest/history/ delete endpoint
API_VERSION = 2.31
# Public API, requires no auth.
router_public = APIRouter()
@@ -268,7 +269,10 @@ def pair_history(pair: str, timeframe: str, timerange: str, strategy: str,
'timerange': timerange,
'freqaimodel': freqaimodel if freqaimodel else config.get('freqaimodel'),
})
return RPC._rpc_analysed_history_full(config, pair, timeframe, exchange)
try:
return RPC._rpc_analysed_history_full(config, pair, timeframe, exchange)
except Exception as e:
raise HTTPException(status_code=502, detail=str(e))
@router.get('/plot_config', response_model=PlotConfig, tags=['candle data'])
@@ -283,7 +287,10 @@ def plot_config(strategy: Optional[str] = None, config=Depends(get_config),
config1.update({
'strategy': strategy
})
try:
return PlotConfig.parse_obj(RPC._rpc_plot_config_with_strategy(config1))
except Exception as e:
raise HTTPException(status_code=502, detail=str(e))
@router.get('/strategies', response_model=StrategyListResponse, tags=['strategy'])
@@ -308,7 +315,8 @@ def get_strategy(strategy: str, config=Depends(get_config)):
extra_dir=config_.get('strategy_path'))
except OperationalException:
raise HTTPException(status_code=404, detail='Strategy not found')
except Exception as e:
raise HTTPException(status_code=502, detail=str(e))
return {
'strategy': strategy_obj.get_strategy_name(),
'code': strategy_obj.__source__,
+1 -1
View File
@@ -30,7 +30,7 @@ async def ui_version():
}
def is_relative_to(path, base) -> bool:
def is_relative_to(path: Path, base: Path) -> bool:
# Helper function simulating behaviour of is_relative_to, which was only added in python 3.9
try:
path.relative_to(base)
+1
View File
@@ -25,6 +25,7 @@ coingecko_mapping = {
'bnb': 'binancecoin',
'sol': 'solana',
'usdt': 'tether',
'busd': 'binance-usd',
}
+26 -9
View File
@@ -18,7 +18,7 @@ from freqtrade import __version__
from freqtrade.configuration.timerange import TimeRange
from freqtrade.constants import CANCEL_REASON, DATETIME_PRINT_FORMAT, Config
from freqtrade.data.history import load_data
from freqtrade.data.metrics import calculate_max_drawdown
from freqtrade.data.metrics import calculate_expectancy, calculate_max_drawdown
from freqtrade.enums import (CandleType, ExitCheckTuple, ExitType, MarketDirection, SignalDirection,
State, TradingMode)
from freqtrade.exceptions import ExchangeError, PricingError
@@ -494,6 +494,8 @@ class RPC:
profit_all_coin.append(profit_abs)
profit_all_ratio.append(profit_ratio)
closed_trade_count = len([t for t in trades if not t.is_open])
best_pair = Trade.get_best_pair(start_date)
trading_volume = Trade.get_trading_volume(start_date)
@@ -521,9 +523,14 @@ class RPC:
profit_factor = winning_profit / abs(losing_profit) if losing_profit else float('inf')
winrate = (winning_trades / closed_trade_count) if closed_trade_count > 0 else 0
trades_df = DataFrame([{'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT),
'profit_abs': trade.close_profit_abs}
for trade in trades if not trade.is_open and trade.close_date])
expectancy, expectancy_ratio = calculate_expectancy(trades_df)
max_drawdown_abs = 0.0
max_drawdown = 0.0
if len(trades_df) > 0:
@@ -562,7 +569,7 @@ class RPC:
'profit_all_percent': round(profit_all_ratio_fromstart * 100, 2),
'profit_all_fiat': profit_all_fiat,
'trade_count': len(trades),
'closed_trade_count': len([t for t in trades if not t.is_open]),
'closed_trade_count': closed_trade_count,
'first_trade_date': first_date.strftime(DATETIME_PRINT_FORMAT) if first_date else '',
'first_trade_humanized': dt_humanize(first_date) if first_date else '',
'first_trade_timestamp': int(first_date.timestamp() * 1000) if first_date else 0,
@@ -576,6 +583,9 @@ class RPC:
'winning_trades': winning_trades,
'losing_trades': losing_trades,
'profit_factor': profit_factor,
'winrate': winrate,
'expectancy': expectancy,
'expectancy_ratio': expectancy_ratio,
'max_drawdown': max_drawdown,
'max_drawdown_abs': max_drawdown_abs,
'trading_volume': trading_volume,
@@ -1169,8 +1179,8 @@ class RPC:
""" Analyzed dataframe in Dict form """
_data, last_analyzed = self.__rpc_analysed_dataframe_raw(pair, timeframe, limit)
return self._convert_dataframe_to_dict(self._freqtrade.config['strategy'],
pair, timeframe, _data, last_analyzed)
return RPC._convert_dataframe_to_dict(self._freqtrade.config['strategy'],
pair, timeframe, _data, last_analyzed)
def __rpc_analysed_dataframe_raw(
self,
@@ -1240,27 +1250,34 @@ class RPC:
exchange) -> Dict[str, Any]:
timerange_parsed = TimeRange.parse_timerange(config.get('timerange'))
from freqtrade.data.converter import trim_dataframe
from freqtrade.data.dataprovider import DataProvider
from freqtrade.resolvers.strategy_resolver import StrategyResolver
strategy = StrategyResolver.load_strategy(config)
startup_candles = strategy.startup_candle_count
_data = load_data(
datadir=config["datadir"],
pairs=[pair],
timeframe=timeframe,
timerange=timerange_parsed,
data_format=config.get('dataformat_ohlcv', 'json'),
candle_type=config.get('candle_type_def', CandleType.SPOT)
candle_type=config.get('candle_type_def', CandleType.SPOT),
startup_candles=startup_candles,
)
if pair not in _data:
raise RPCException(
f"No data for {pair}, {timeframe} in {config.get('timerange')} found.")
from freqtrade.data.dataprovider import DataProvider
from freqtrade.resolvers.strategy_resolver import StrategyResolver
strategy = StrategyResolver.load_strategy(config)
strategy.dp = DataProvider(config, exchange=exchange, pairlists=None)
strategy.ft_bot_start()
df_analyzed = strategy.analyze_ticker(_data[pair], {'pair': pair})
df_analyzed = trim_dataframe(df_analyzed, timerange_parsed, startup_candles=startup_candles)
return RPC._convert_dataframe_to_dict(strategy.get_strategy_name(), pair, timeframe,
df_analyzed, dt_now())
df_analyzed.copy(), dt_now())
def _rpc_plot_config(self) -> Dict[str, Any]:
if (self._freqtrade.strategy.plot_config and
+7 -1
View File
@@ -849,6 +849,10 @@ class Telegram(RPCHandler):
avg_duration = stats['avg_duration']
best_pair = stats['best_pair']
best_pair_profit_ratio = stats['best_pair_profit_ratio']
winrate = stats['winrate']
expectancy = stats['expectancy']
expectancy_ratio = stats['expectancy_ratio']
if stats['trade_count'] == 0:
markdown_msg = f"No trades yet.\n*Bot started:* `{stats['bot_start_date']}`"
else:
@@ -873,7 +877,9 @@ class Telegram(RPCHandler):
f"*{'First Trade opened' if not timescale else 'Showing Profit since'}:* "
f"`{first_trade_date}`\n"
f"*Latest Trade opened:* `{latest_trade_date}`\n"
f"*Win / Loss:* `{stats['winning_trades']} / {stats['losing_trades']}`"
f"*Win / Loss:* `{stats['winning_trades']} / {stats['losing_trades']}`\n"
f"*Winrate:* `{winrate:.2%}`\n"
f"*Expectancy (Ratio):* `{expectancy:.2f} ({expectancy_ratio:.2f})`"
)
if stats['closed_trade_count'] > 0:
markdown_msg += (
+5 -3
View File
@@ -34,6 +34,7 @@ class Webhook(RPCHandler):
self._format = self._config['webhook'].get('format', 'form')
self._retries = self._config['webhook'].get('retries', 0)
self._retry_delay = self._config['webhook'].get('retry_delay', 0.1)
self._timeout = self._config['webhook'].get('timeout', 10)
def cleanup(self) -> None:
"""
@@ -107,12 +108,13 @@ class Webhook(RPCHandler):
try:
if self._format == 'form':
response = post(self._url, data=payload)
response = post(self._url, data=payload, timeout=self._timeout)
elif self._format == 'json':
response = post(self._url, json=payload)
response = post(self._url, json=payload, timeout=self._timeout)
elif self._format == 'raw':
response = post(self._url, data=payload['data'],
headers={'Content-Type': 'text/plain'})
headers={'Content-Type': 'text/plain'},
timeout=self._timeout)
else:
raise NotImplementedError(f'Unknown format: {self._format}')
+15
View File
@@ -825,6 +825,7 @@ class IStrategy(ABC, HyperStrategyMixin):
"""
Parses the given candle (OHLCV) data and returns a populated DataFrame
add several TA indicators and entry order signal to it
Should only be used in live.
:param dataframe: Dataframe containing data from exchange
:param metadata: Metadata dictionary with additional data (e.g. 'pair')
:return: DataFrame of candle (OHLCV) data with indicator data and signals added
@@ -1321,6 +1322,20 @@ class IStrategy(ABC, HyperStrategyMixin):
return {pair: self.advise_indicators(pair_data.copy(), {'pair': pair}).copy()
for pair, pair_data in data.items()}
def ft_advise_signals(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Call advise_entry and advise_exit and return the resulting dataframe.
:param dataframe: Dataframe containing data from exchange, as well as pre-calculated
indicators
:param metadata: Metadata dictionary with additional data (e.g. 'pair')
:return: DataFrame of candle (OHLCV) data with indicator data and signals added
"""
dataframe = self.advise_entry(dataframe, metadata)
dataframe = self.advise_exit(dataframe, metadata)
return dataframe
def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Populate indicators that will be used in the Buy, Sell, short, exit_short strategy
+4 -3
View File
@@ -1,5 +1,5 @@
from freqtrade.util.datetime_helpers import (dt_floor_day, dt_from_ts, dt_humanize, dt_now, dt_ts,
dt_utc, shorten_date)
dt_utc, format_ms_time, shorten_date)
from freqtrade.util.ft_precise import FtPrecise
from freqtrade.util.periodic_cache import PeriodicCache
@@ -7,11 +7,12 @@ from freqtrade.util.periodic_cache import PeriodicCache
__all__ = [
'dt_floor_day',
'dt_from_ts',
'dt_humanize',
'dt_now',
'dt_ts',
'dt_utc',
'dt_humanize',
'shorten_date',
'format_ms_time',
'FtPrecise',
'PeriodicCache',
'shorten_date',
]
+8
View File
@@ -61,3 +61,11 @@ def dt_humanize(dt: datetime, **kwargs) -> str:
:param kwargs: kwargs to pass to arrow's humanize()
"""
return arrow.get(dt).humanize(**kwargs)
def format_ms_time(date: int) -> str:
"""
convert MS date to readable format.
: epoch-string in ms
"""
return datetime.fromtimestamp(date / 1000.0).strftime('%Y-%m-%dT%H:%M:%S')
+1
View File
@@ -84,6 +84,7 @@ class Wallets:
tot_profit = Trade.get_total_closed_profit()
else:
tot_profit = LocalTrade.total_profit
tot_profit += sum(trade.realized_profit for trade in open_trades)
tot_in_trades = sum(trade.stake_amount for trade in open_trades)
used_stake = 0.0
+8 -8
View File
@@ -7,24 +7,24 @@
-r docs/requirements-docs.txt
coveralls==3.3.1
ruff==0.0.275
ruff==0.0.280
mypy==1.4.1
pre-commit==3.3.3
pytest==7.4.0
pytest-asyncio==0.21.0
pytest-asyncio==0.21.1
pytest-cov==4.1.0
pytest-mock==3.11.1
pytest-random-order==1.1.0
isort==5.12.0
# For datetime mocking
time-machine==2.10.0
time-machine==2.11.0
# Convert jupyter notebooks to markdown documents
nbconvert==7.6.0
nbconvert==7.7.2
# mypy types
types-cachetools==5.3.0.5
types-cachetools==5.3.0.6
types-filelock==3.2.7
types-requests==2.31.0.1
types-tabulate==0.9.0.2
types-python-dateutil==2.8.19.13
types-requests==2.31.0.2
types-tabulate==0.9.0.3
types-python-dateutil==2.8.19.14
+1 -1
View File
@@ -5,7 +5,7 @@
torch==2.0.1
#until these branches will be released we can use this
gymnasium==0.28.1
stable_baselines3==2.0.0a13
stable_baselines3==2.0.0
sb3_contrib>=2.0.0a9
# Progress bar for stable-baselines3 and sb3-contrib
tqdm==4.65.0
+3 -3
View File
@@ -4,9 +4,9 @@
# Required for freqai
scikit-learn==1.1.3
joblib==1.2.0
joblib==1.3.1
catboost==1.2; 'arm' not in platform_machine
lightgbm==3.3.5
lightgbm==4.0.0
xgboost==1.7.6
tensorboard==2.13.0
datasieve==0.1.6
datasieve==0.1.7
+2 -1
View File
@@ -2,7 +2,8 @@
-r requirements.txt
# Required for hyperopt
scipy==1.10.1
scipy==1.11.1; python_version >= '3.9'
scipy==1.10.1; python_version < '3.9'
scikit-learn==1.1.3
scikit-optimize==0.9.0
filelock==3.12.2
+19 -18
View File
@@ -1,30 +1,31 @@
numpy==1.24.3
pandas==2.0.2
numpy==1.25.1; python_version > '3.8'
numpy==1.24.3; python_version <= '3.8'
pandas==2.0.3
pandas-ta==0.3.14b
ccxt==3.1.44
cryptography==41.0.1; platform_machine != 'armv7l'
ccxt==4.0.36
cryptography==41.0.2; platform_machine != 'armv7l'
cryptography==40.0.1; platform_machine == 'armv7l'
aiohttp==3.8.4
SQLAlchemy==2.0.17
python-telegram-bot==20.3
aiohttp==3.8.5
SQLAlchemy==2.0.19
python-telegram-bot==20.4
# can't be hard-pinned due to telegram-bot pinning httpx with ~
httpx>=0.24.1
arrow==1.2.3
cachetools==5.3.1
requests==2.31.0
urllib3==2.0.3
jsonschema==4.17.3
TA-Lib==0.4.26
urllib3==2.0.4
jsonschema==4.18.4
TA-Lib==0.4.27
technical==1.4.0
tabulate==0.9.0
pycoingecko==3.1.0
jinja2==3.1.2
tables==3.8.0
blosc==1.11.1
joblib==1.2.0
joblib==1.3.1
rich==13.4.2
pyarrow==12.0.0; platform_machine != 'armv7l'
pyarrow==12.0.1; platform_machine != 'armv7l'
# find first, C search in arrays
py_find_1st==1.1.5
@@ -32,16 +33,16 @@ py_find_1st==1.1.5
# Load ticker files 30% faster
python-rapidjson==1.10
# Properly format api responses
orjson==3.9.1
orjson==3.9.2
# Notify systemd
sdnotify==0.3.2
# API Server
fastapi==0.99.1
pydantic==1.10.9
uvicorn==0.22.0
pyjwt==2.7.0
fastapi==0.100.0
pydantic==1.10.11
uvicorn==0.23.1
pyjwt==2.8.0
aiofiles==23.1.0
psutil==5.9.5
@@ -49,7 +50,7 @@ psutil==5.9.5
colorama==0.4.6
# Building config files interactively
questionary==1.10.0
prompt-toolkit==3.0.38
prompt-toolkit==3.0.39
# Extensions to datetime library
python-dateutil==2.8.2
+2 -2
View File
@@ -70,7 +70,7 @@ setup(
],
install_requires=[
# from requirements.txt
'ccxt>=3.0.0',
'ccxt>=4.0.0',
'SQLAlchemy>=2.0.6',
'python-telegram-bot>=20.1',
'arrow>=1.0.0',
@@ -97,7 +97,7 @@ setup(
'rich',
'pyarrow; platform_machine != "armv7l"',
'fastapi',
'pydantic>=1.8.0',
'pydantic>=1.8.0,<2.0',
'uvicorn',
'psutil',
'pyjwt',
+16 -16
View File
@@ -3001,85 +3001,85 @@ def mark_ohlcv():
def funding_rate_history_hourly():
return [
{
"symbol": "ADA/USDT",
"symbol": "ADA/USDT:USDT",
"fundingRate": -0.000008,
"timestamp": 1630454400000,
"datetime": "2021-09-01T00:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"symbol": "ADA/USDT:USDT",
"fundingRate": -0.000004,
"timestamp": 1630458000000,
"datetime": "2021-09-01T01:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"symbol": "ADA/USDT:USDT",
"fundingRate": 0.000012,
"timestamp": 1630461600000,
"datetime": "2021-09-01T02:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"symbol": "ADA/USDT:USDT",
"fundingRate": -0.000003,
"timestamp": 1630465200000,
"datetime": "2021-09-01T03:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"symbol": "ADA/USDT:USDT",
"fundingRate": -0.000007,
"timestamp": 1630468800000,
"datetime": "2021-09-01T04:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"symbol": "ADA/USDT:USDT",
"fundingRate": 0.000003,
"timestamp": 1630472400000,
"datetime": "2021-09-01T05:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"symbol": "ADA/USDT:USDT",
"fundingRate": 0.000019,
"timestamp": 1630476000000,
"datetime": "2021-09-01T06:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"symbol": "ADA/USDT:USDT",
"fundingRate": 0.000003,
"timestamp": 1630479600000,
"datetime": "2021-09-01T07:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"symbol": "ADA/USDT:USDT",
"fundingRate": -0.000003,
"timestamp": 1630483200000,
"datetime": "2021-09-01T08:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"symbol": "ADA/USDT:USDT",
"fundingRate": 0,
"timestamp": 1630486800000,
"datetime": "2021-09-01T09:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"symbol": "ADA/USDT:USDT",
"fundingRate": 0.000013,
"timestamp": 1630490400000,
"datetime": "2021-09-01T10:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"symbol": "ADA/USDT:USDT",
"fundingRate": 0.000077,
"timestamp": 1630494000000,
"datetime": "2021-09-01T11:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"symbol": "ADA/USDT:USDT",
"fundingRate": 0.000072,
"timestamp": 1630497600000,
"datetime": "2021-09-01T12:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"symbol": "ADA/USDT:USDT",
"fundingRate": 0.000097,
"timestamp": 1630501200000,
"datetime": "2021-09-01T13:00:00.000Z"
@@ -3091,13 +3091,13 @@ def funding_rate_history_hourly():
def funding_rate_history_octohourly():
return [
{
"symbol": "ADA/USDT",
"symbol": "ADA/USDT:USDT",
"fundingRate": -0.000008,
"timestamp": 1630454400000,
"datetime": "2021-09-01T00:00:00.000Z"
},
{
"symbol": "ADA/USDT",
"symbol": "ADA/USDT:USDT",
"fundingRate": -0.000003,
"timestamp": 1630483200000,
"datetime": "2021-09-01T08:00:00.000Z"
+15 -3
View File
@@ -343,12 +343,24 @@ def test_calculate_expectancy(testdatadir):
filename = testdatadir / "backtest_results/backtest-result.json"
bt_data = load_backtest_data(filename)
expectancy = calculate_expectancy(DataFrame())
expectancy, expectancy_ratio = calculate_expectancy(DataFrame())
assert expectancy == 0.0
assert expectancy_ratio == 100
expectancy = calculate_expectancy(bt_data)
expectancy, expectancy_ratio = calculate_expectancy(bt_data)
assert isinstance(expectancy, float)
assert pytest.approx(expectancy) == 0.07151374226574791
assert isinstance(expectancy_ratio, float)
assert pytest.approx(expectancy) == 5.820687070932315e-06
assert pytest.approx(expectancy_ratio) == 0.07151374226574791
data = {
'profit_abs': [100, 200, 50, -150, 300, -100, 80, -30]
}
df = DataFrame(data)
expectancy, expectancy_ratio = calculate_expectancy(df)
assert pytest.approx(expectancy) == 56.25
assert pytest.approx(expectancy_ratio) == 0.60267857
def test_calculate_sortino(testdatadir):
+69 -2
View File
@@ -135,6 +135,73 @@ def test_ohlcv_fill_up_missing_data2(caplog):
f"{len(data)} - after: {len(data2)}.*", caplog)
def test_ohlcv_to_dataframe_1M():
# Monthly ticks from 2019-09-01 to 2023-07-01
ticks = [
[1567296000000, 8042.08, 10475.54, 7700.67, 8041.96, 608742.1109999999],
[1569888000000, 8285.31, 10408.48, 7172.76, 9150.0, 2439561.887],
[1572566400000, 9149.88, 9550.0, 6510.19, 7542.93, 4042674.725],
[1575158400000, 7541.08, 7800.0, 6427.0, 7189.0, 4063882.296],
[1577836800000, 7189.43, 9599.0, 6863.44, 9364.51, 5165281.358],
[1580515200000, 9364.5, 10540.0, 8450.0, 8531.98, 4581788.124],
[1583020800000, 8532.5, 9204.0, 3621.81, 6407.1, 10859497.479],
[1585699200000, 6407.1, 9479.77, 6140.0, 8624.76, 11276526.968],
[1588291200000, 8623.61, 10080.0, 7940.0, 9446.43, 12469561.02],
[1590969600000, 9446.49, 10497.25, 8816.4, 9138.87, 6684044.201],
[1593561600000, 9138.88, 11488.0, 8900.0, 11343.68, 5709327.926],
[1596240000000, 11343.67, 12499.42, 10490.0, 11658.11, 6746487.129],
[1598918400000, 11658.11, 12061.07, 9808.58, 10773.0, 6442697.051],
[1601510400000, 10773.0, 14140.0, 10371.03, 13783.73, 7404103.004],
[1604188800000, 13783.73, 19944.0, 13195.0, 19720.0, 12328272.549],
[1606780800000, 19722.09, 29376.7, 17555.0, 28951.68, 10067314.24],
[1609459200000, 28948.19, 42125.51, 27800.0, 33126.21, 12408873.079],
[1612137600000, 33125.11, 58472.14, 32322.47, 45163.36, 8784474.482],
[1614556800000, 45162.64, 61950.0, 44972.49, 58807.24, 9459821.267],
[1617235200000, 58810.99, 64986.11, 46930.43, 57684.16, 7895051.389],
[1619827200000, 57688.29, 59654.0, 28688.0, 37243.38, 16790964.443],
[1622505600000, 37244.36, 41413.0, 28780.01, 35031.39, 23474519.886],
[1625097600000, 35031.39, 48168.6, 29242.24, 41448.11, 16932491.175],
[1627776000000, 41448.1, 50600.0, 37291.0, 47150.32, 13645800.254],
[1630454400000, 47150.32, 52950.0, 39503.58, 43796.57, 10734742.869],
[1633046400000, 43799.49, 67150.0, 43260.01, 61348.61, 9111112.847],
[1635724800000, 61347.14, 69198.7, 53245.0, 56975.0, 7111424.463],
[1638316800000, 56978.06, 59100.0, 40888.89, 46210.56, 8404449.024],
[1640995200000, 46210.57, 48000.0, 32853.83, 38439.04, 11047479.277],
[1643673600000, 38439.04, 45847.5, 34303.7, 43155.0, 10910339.91],
[1646092800000, 43155.0, 48200.0, 37134.0, 45506.0, 10459721.586],
[1648771200000, 45505.9, 47448.0, 37550.0, 37614.5, 8463568.862],
[1651363200000, 37614.4, 40071.7, 26631.0, 31797.8, 14463715.774],
[1654041600000, 31797.9, 31986.1, 17593.2, 19923.5, 20710810.306],
[1656633600000, 19923.3, 24700.0, 18780.1, 23290.1, 20582518.513],
[1659312000000, 23290.1, 25200.0, 19508.0, 20041.5, 17221921.557],
[1661990400000, 20041.4, 22850.0, 18084.3, 19411.7, 21935261.414],
[1664582400000, 19411.6, 21088.0, 17917.8, 20482.0, 16625843.584],
[1667260800000, 20482.1, 21473.7, 15443.2, 17153.3, 18460614.013],
[1669852800000, 17153.4, 18400.0, 16210.0, 16537.6, 9702408.711],
[1672531200000, 16537.5, 23962.7, 16488.0, 23119.4, 14732180.645],
[1675209600000, 23119.5, 25347.6, 21338.0, 23129.6, 15025197.415],
[1677628800000, 23129.7, 29184.8, 19521.6, 28454.9, 23317458.541],
[1680307200000, 28454.8, 31059.0, 26919.3, 29223.0, 14654208.219],
[1682899200000, 29223.0, 29840.0, 25751.0, 27201.1, 13328157.284],
[1685577600000, 27201.1, 31500.0, 24777.0, 30460.2, 14099299.273],
[1688169600000, 30460.2, 31850.0, 28830.0, 29338.8, 8760361.377]
]
data = ohlcv_to_dataframe(ticks, '1M', pair="UNITTEST/USDT",
fill_missing=False, drop_incomplete=False)
assert len(data) == len(ticks)
assert data.iloc[0]['date'].strftime('%Y-%m-%d') == '2019-09-01'
assert data.iloc[-1]['date'].strftime('%Y-%m-%d') == '2023-07-01'
# Test with filling missing data
data = ohlcv_to_dataframe(ticks, '1M', pair="UNITTEST/USDT",
fill_missing=True, drop_incomplete=False)
assert len(data) == len(ticks)
assert data.iloc[0]['date'].strftime('%Y-%m-%d') == '2019-09-01'
assert data.iloc[-1]['date'].strftime('%Y-%m-%d') == '2023-07-01'
def test_ohlcv_drop_incomplete(caplog):
timeframe = '1d'
ticks = [
@@ -315,6 +382,8 @@ def test_convert_ohlcv_format(default_conf, testdatadir, tmpdir, file_base, cand
files_new.append(file_new)
default_conf['datadir'] = tmpdir1
default_conf['candle_types'] = [candletype]
if candletype == CandleType.SPOT:
default_conf['pairs'] = ['XRP/ETH', 'XRP/USDT', 'UNITTEST/USDT']
else:
@@ -328,7 +397,6 @@ def test_convert_ohlcv_format(default_conf, testdatadir, tmpdir, file_base, cand
convert_from='json',
convert_to='jsongz',
erase=False,
candle_type=candletype
)
for file in (files_temp + files_new):
assert file.exists()
@@ -342,7 +410,6 @@ def test_convert_ohlcv_format(default_conf, testdatadir, tmpdir, file_base, cand
convert_from='jsongz',
convert_to='json',
erase=True,
candle_type=candletype
)
for file in (files_temp):
assert file.exists()
+22 -8
View File
@@ -43,6 +43,7 @@ EXCHANGES = {
'hasQuoteVolumeFutures': True,
'leverage_tiers_public': False,
'leverage_in_spot_market': False,
'trades_lookback_hours': 4,
'private_methods': [
'fapiPrivateGetPositionSideDual',
'fapiPrivateGetMultiAssetsMargin'
@@ -92,12 +93,13 @@ EXCHANGES = {
}]
},
'kraken': {
'pair': 'BTC/USDT',
'stake_currency': 'USDT',
'pair': 'BTC/USD',
'stake_currency': 'USD',
'hasQuoteVolume': True,
'timeframe': '1h',
'leverage_tiers_public': False,
'leverage_in_spot_market': True,
'trades_lookback_hours': 12,
},
'kucoin': {
'pair': 'XRP/USDT',
@@ -291,11 +293,7 @@ def set_test_proxy(config: Config, use_proxy: bool) -> Config:
if use_proxy and (proxy := os.environ.get('CI_WEB_PROXY')):
config1 = deepcopy(config)
config1['exchange']['ccxt_config'] = {
"aiohttp_proxy": proxy,
'proxies': {
'https': proxy,
'http': proxy,
}
"httpsProxy": proxy,
}
return config1
@@ -546,6 +544,8 @@ class TestCCXTExchange:
if exchangename in ('bittrex'):
# For some weired reason, this test returns random lengths for bittrex.
pytest.skip("Exchange doesn't provide stable ohlcv history")
if exchangename in ('bitvavo'):
pytest.skip("Exchange Downtime ")
if not exc._ft_has['ohlcv_has_history']:
pytest.skip("Exchange does not support candle history")
@@ -640,7 +640,21 @@ class TestCCXTExchange:
assert isinstance(funding_fee, float)
# assert funding_fee > 0
# TODO: tests fetch_trades (?)
def test_ccxt__async_get_trade_history(self, exchange: EXCHANGE_FIXTURE_TYPE):
exch, exchangename = exchange
if not (lookback := EXCHANGES[exchangename].get('trades_lookback_hours')):
pytest.skip('test_fetch_trades not enabled for this exchange')
pair = EXCHANGES[exchangename]['pair']
since = int((datetime.now(timezone.utc) - timedelta(hours=lookback)).timestamp() * 1000)
res = exch.loop.run_until_complete(
exch._async_get_trade_history(pair, since, None, None)
)
assert len(res) == 2
res_pair, res_trades = res
assert res_pair == pair
assert isinstance(res_trades, list)
assert res_trades[0][0] >= since
assert len(res_trades) > 1200
def test_ccxt_get_fee(self, exchange: EXCHANGE_FIXTURE_TYPE):
exch, exchangename = exchange
+4 -4
View File
@@ -4343,11 +4343,11 @@ def test__fetch_and_calculate_funding_fees(
ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange)
mocker.patch(f'{EXMS}.timeframes', PropertyMock(return_value=['1h', '4h', '8h']))
funding_fees = ex._fetch_and_calculate_funding_fees(
pair='ADA/USDT', amount=amount, is_short=True, open_date=d1, close_date=d2)
pair='ADA/USDT:USDT', amount=amount, is_short=True, open_date=d1, close_date=d2)
assert pytest.approx(funding_fees) == expected_fees
# Fees for Longs are inverted
funding_fees = ex._fetch_and_calculate_funding_fees(
pair='ADA/USDT', amount=amount, is_short=False, open_date=d1, close_date=d2)
pair='ADA/USDT:USDT', amount=amount, is_short=False, open_date=d1, close_date=d2)
assert pytest.approx(funding_fees) == -expected_fees
# Return empty "refresh_latest"
@@ -4355,7 +4355,7 @@ def test__fetch_and_calculate_funding_fees(
ex = get_patched_exchange(mocker, default_conf, api_mock, id=exchange)
with pytest.raises(ExchangeError, match="Could not find funding rates."):
ex._fetch_and_calculate_funding_fees(
pair='ADA/USDT', amount=amount, is_short=False, open_date=d1, close_date=d2)
pair='ADA/USDT:USDT', amount=amount, is_short=False, open_date=d1, close_date=d2)
@pytest.mark.parametrize('exchange,expected_fees', [
@@ -5424,7 +5424,7 @@ def test_stoploss_contract_size(mocker, default_conf, contract_size, order_amoun
assert api_mock.create_order.call_args_list[0][1]['amount'] == order_amount
assert order['amount'] == 100
assert order['cost'] == 100
assert order['cost'] == order_amount
assert order['filled'] == 100
assert order['remaining'] == 100
+3
View File
@@ -601,6 +601,9 @@ def test_backtest__enter_trade_futures(default_conf_usdt, fee, mocker) -> None:
trade = backtesting._enter_trade(pair, row=row, direction='short')
assert pytest.approx(trade.liquidation_price) == 0.11787191
assert pytest.approx(trade.orders[0].cost) == (
trade.stake_amount * trade.leverage + trade.fee_open)
assert pytest.approx(trade.orders[-1].stake_amount) == trade.stake_amount
# Stake-amount too high!
mocker.patch(f"{EXMS}.get_min_pair_stake_amount", return_value=600.0)
+28 -1
View File
@@ -23,7 +23,8 @@ from freqtrade.optimize.optimize_reports import (generate_backtest_stats, genera
store_backtest_analysis_results,
store_backtest_stats, text_table_bt_results,
text_table_exit_reason, text_table_strategy)
from freqtrade.optimize.optimize_reports.optimize_reports import _get_resample_from_period
from freqtrade.optimize.optimize_reports.optimize_reports import (_get_resample_from_period,
calc_streak)
from freqtrade.resolvers.strategy_resolver import StrategyResolver
from freqtrade.util import dt_ts
from freqtrade.util.datetime_helpers import dt_from_ts, dt_utc
@@ -348,6 +349,32 @@ def test_generate_trading_stats(testdatadir):
assert res['losses'] == 0
def test_calc_streak(testdatadir):
df = pd.DataFrame({
'profit_ratio': [0.05, -0.02, -0.03, -0.05, 0.01, 0.02, 0.03, 0.04, -0.02, -0.03],
})
# 4 consecutive wins, 3 consecutive losses
res = calc_streak(df)
assert res == (4, 3)
assert isinstance(res[0], int)
assert isinstance(res[1], int)
# invert situation
df1 = df.copy()
df1['profit_ratio'] = df1['profit_ratio'] * -1
assert calc_streak(df1) == (3, 4)
df_empty = pd.DataFrame({
'profit_ratio': [],
})
assert df_empty.empty
assert calc_streak(df_empty) == (0, 0)
filename = testdatadir / "backtest_results/backtest-result.json"
bt_data = load_backtest_data(filename)
assert calc_streak(bt_data) == (7, 18)
def test_text_table_exit_reason():
results = pd.DataFrame(
+2 -2
View File
@@ -563,14 +563,14 @@ def test_calc_open_close_trade_price(
trade.open_order_id = f'something-{is_short}-{lev}-{exchange}'
oobj = Order.parse_from_ccxt_object(entry_order, 'ADA/USDT', trade.entry_side)
oobj.trade = trade
oobj._trade_live = trade
oobj.update_from_ccxt_object(entry_order)
trade.update_trade(oobj)
trade.funding_fees = funding_fees
oobj = Order.parse_from_ccxt_object(exit_order, 'ADA/USDT', trade.exit_side)
oobj.trade = trade
oobj._trade_live = trade
oobj.update_from_ccxt_object(exit_order)
trade.update_trade(oobj)
+8 -2
View File
@@ -1,8 +1,11 @@
from datetime import datetime, timezone
import pytest
from freqtrade.persistence.trade_model import Trade
@pytest.mark.usefixtures("init_persistence")
def test_trade_fromjson():
"""Test the Trade.from_json() method."""
trade_string = """{
@@ -168,14 +171,17 @@ def test_trade_fromjson():
]
}"""
trade = Trade.from_json(trade_string)
Trade.session.add(trade)
Trade.commit()
assert trade.id == 25
assert trade.pair == 'ETH/USDT'
assert trade.open_date == datetime(2022, 10, 18, 9, 12, 42, tzinfo=timezone.utc)
assert trade.open_date_utc == datetime(2022, 10, 18, 9, 12, 42, tzinfo=timezone.utc)
assert isinstance(trade.open_date, datetime)
assert trade.exit_reason == 'no longer good'
assert trade.realized_profit == 2.76315361
assert len(trade.orders) == 5
last_o = trade.orders[-1]
assert last_o.order_filled_date == datetime(2022, 10, 18, 9, 45, 22, tzinfo=timezone.utc)
assert last_o.order_filled_utc == datetime(2022, 10, 18, 9, 45, 22, tzinfo=timezone.utc)
assert isinstance(last_o.order_date, datetime)
+4
View File
@@ -1200,6 +1200,10 @@ def test_spreadfilter_invalid_data(mocker, default_conf, markets, tickers, caplo
"[{'ProducerPairList': 'ProducerPairList - default'}]",
None
),
({"method": "RemotePairList", "number_assets": 10, "pairlist_url": "https://example.com"},
"[{'RemotePairList': 'RemotePairList - 10 pairs from RemotePairlist.'}]",
None
),
])
def test_pricefilter_desc(mocker, whitelist_conf, markets, pairlistconfig,
desc_expected, exception_expected):
+161 -4
View File
@@ -1,5 +1,5 @@
import json
from unittest.mock import MagicMock
from unittest.mock import MagicMock, PropertyMock
import pytest
import requests
@@ -7,7 +7,7 @@ import requests
from freqtrade.exceptions import OperationalException
from freqtrade.plugins.pairlist.RemotePairList import RemotePairList
from freqtrade.plugins.pairlistmanager import PairListManager
from tests.conftest import get_patched_exchange, get_patched_freqtradebot, log_has
from tests.conftest import EXMS, get_patched_exchange, get_patched_freqtradebot, log_has
@pytest.fixture(scope="function")
@@ -16,11 +16,12 @@ def rpl_config(default_conf):
default_conf['exchange']['pair_whitelist'] = [
'ETH/USDT',
'BTC/USDT',
'XRP/USDT',
]
default_conf['exchange']['pair_blacklist'] = [
'BLK/USDT'
]
return default_conf
@@ -34,7 +35,7 @@ def test_gen_pairlist_with_local_file(mocker, rpl_config):
mock_file_path.exists.return_value = True
jsonparse = json.loads(mock_file.read.return_value)
mocker.patch('freqtrade.plugins.pairlist.RemotePairList.json.load', return_value=jsonparse)
mocker.patch('freqtrade.plugins.pairlist.RemotePairList.rapidjson.load', return_value=jsonparse)
rpl_config['pairlists'] = [
{
@@ -183,3 +184,159 @@ def test_fetch_pairlist_mock_response_valid(mocker, rpl_config):
assert pairs == ["ETH/USDT", "XRP/USDT", "LTC/USDT", "EOS/USDT"]
assert time_elapsed == 0.4
assert remote_pairlist._refresh_period == 60
def test_remote_pairlist_init_wrong_mode(mocker, rpl_config):
rpl_config['pairlists'] = [
{
"method": "RemotePairList",
"mode": "blacklis",
"number_assets": 20,
"pairlist_url": "http://example.com/pairlist",
"keep_pairlist_on_failure": True,
}
]
with pytest.raises(
OperationalException,
match=r'`mode` not configured correctly. Supported Modes are "whitelist","blacklist"'
):
get_patched_freqtradebot(mocker, rpl_config)
rpl_config['pairlists'] = [
{
"method": "RemotePairList",
"mode": "blacklist",
"number_assets": 20,
"pairlist_url": "http://example.com/pairlist",
"keep_pairlist_on_failure": True,
}
]
with pytest.raises(
OperationalException,
match=r'A `blacklist` mode RemotePairList can not be.*first.*'
):
get_patched_freqtradebot(mocker, rpl_config)
def test_remote_pairlist_init_wrong_proc_mode(mocker, rpl_config):
rpl_config['pairlists'] = [
{
"method": "RemotePairList",
"processing_mode": "filler",
"mode": "whitelist",
"number_assets": 20,
"pairlist_url": "http://example.com/pairlist",
"keep_pairlist_on_failure": True,
}
]
get_patched_exchange(mocker, rpl_config)
with pytest.raises(
OperationalException,
match=r'`processing_mode` not configured correctly. Supported Modes are "filter","append"'
):
get_patched_freqtradebot(mocker, rpl_config)
def test_remote_pairlist_blacklist(mocker, rpl_config, caplog, markets, tickers):
mock_response = MagicMock()
mock_response.json.return_value = {
"pairs": ["XRP/USDT"],
"refresh_period": 60
}
mock_response.headers = {
"content-type": "application/json"
}
rpl_config['pairlists'] = [
{
"method": "StaticPairList",
},
{
"method": "RemotePairList",
"mode": "blacklist",
"pairlist_url": "http://example.com/pairlist",
"number_assets": 3
}
]
mocker.patch.multiple(EXMS,
markets=PropertyMock(return_value=markets),
exchange_has=MagicMock(return_value=True),
get_tickers=tickers
)
mocker.patch("freqtrade.plugins.pairlist.RemotePairList.requests.get",
return_value=mock_response)
exchange = get_patched_exchange(mocker, rpl_config)
pairlistmanager = PairListManager(exchange, rpl_config)
remote_pairlist = RemotePairList(exchange, pairlistmanager, rpl_config,
rpl_config["pairlists"][1], 1)
pairs, time_elapsed = remote_pairlist.fetch_pairlist()
assert pairs == ["XRP/USDT"]
whitelist = remote_pairlist.filter_pairlist(rpl_config['exchange']['pair_whitelist'], {})
assert whitelist == ["ETH/USDT"]
assert log_has(f"Blacklist - Filtered out pairs: {pairs}", caplog)
@pytest.mark.parametrize("processing_mode", ["filter", "append"])
def test_remote_pairlist_whitelist(mocker, rpl_config, processing_mode, markets, tickers):
mock_response = MagicMock()
mock_response.json.return_value = {
"pairs": ["XRP/USDT"],
"refresh_period": 60
}
mock_response.headers = {
"content-type": "application/json"
}
rpl_config['pairlists'] = [
{
"method": "StaticPairList",
},
{
"method": "RemotePairList",
"mode": "whitelist",
"processing_mode": processing_mode,
"pairlist_url": "http://example.com/pairlist",
"number_assets": 3
}
]
mocker.patch.multiple(EXMS,
markets=PropertyMock(return_value=markets),
exchange_has=MagicMock(return_value=True),
get_tickers=tickers
)
mocker.patch("freqtrade.plugins.pairlist.RemotePairList.requests.get",
return_value=mock_response)
exchange = get_patched_exchange(mocker, rpl_config)
pairlistmanager = PairListManager(exchange, rpl_config)
remote_pairlist = RemotePairList(exchange, pairlistmanager, rpl_config,
rpl_config["pairlists"][1], 1)
pairs, time_elapsed = remote_pairlist.fetch_pairlist()
assert pairs == ["XRP/USDT"]
whitelist = remote_pairlist.filter_pairlist(rpl_config['exchange']['pair_whitelist'], {})
assert whitelist == (["XRP/USDT"] if processing_mode == "filter" else ['ETH/USDT', 'XRP/USDT'])
+6 -1
View File
@@ -97,7 +97,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY,
'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05,
'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY,
'remaining': ANY, 'status': ANY, 'ft_is_entry': True,
'remaining': ANY, 'status': ANY, 'ft_is_entry': True, 'ft_fee_base': None,
}],
}
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@@ -402,6 +402,8 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None:
assert res['first_trade_timestamp'] == 0
assert res['latest_trade_date'] == ''
assert res['latest_trade_timestamp'] == 0
assert res['expectancy'] == 0
assert res['expectancy_ratio'] == 100
# Create some test data
create_mock_trades_usdt(fee)
@@ -413,6 +415,9 @@ def test_rpc_trade_statistics(default_conf_usdt, ticker, fee, mocker) -> None:
assert pytest.approx(stats['profit_all_coin']) == -77.45964918
assert pytest.approx(stats['profit_all_percent_mean']) == -57.86
assert pytest.approx(stats['profit_all_fiat']) == -85.205614098
assert pytest.approx(stats['winrate']) == 0.666666667
assert pytest.approx(stats['expectancy']) == 0.913333333
assert pytest.approx(stats['expectancy_ratio']) == 0.223308883
assert stats['trade_count'] == 7
assert stats['first_trade_humanized'] == '2 days ago'
assert stats['latest_trade_humanized'] == '17 minutes ago'
+72 -16
View File
@@ -829,7 +829,8 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
'profit_closed_percent_mean': -0.75, 'profit_closed_ratio_sum': -0.015,
'profit_closed_percent_sum': -1.5, 'profit_closed_ratio': -6.739057628404269e-06,
'profit_closed_percent': -0.0, 'winning_trades': 0, 'losing_trades': 2,
'profit_factor': 0.0, 'trading_volume': 91.074,
'profit_factor': 0.0, 'winrate': 0.0, 'expectancy': -0.0033695635,
'expectancy_ratio': -1.0, 'trading_volume': 91.074,
}
),
(
@@ -844,7 +845,8 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
'profit_closed_percent_mean': 0.75, 'profit_closed_ratio_sum': 0.015,
'profit_closed_percent_sum': 1.5, 'profit_closed_ratio': 7.391275897987988e-07,
'profit_closed_percent': 0.0, 'winning_trades': 2, 'losing_trades': 0,
'profit_factor': None, 'trading_volume': 91.074,
'profit_factor': None, 'winrate': 1.0, 'expectancy': 0.0003695635,
'expectancy_ratio': 100, 'trading_volume': 91.074,
}
),
(
@@ -859,7 +861,9 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
'profit_closed_percent_mean': 0.25, 'profit_closed_ratio_sum': 0.005,
'profit_closed_percent_sum': 0.5, 'profit_closed_ratio': -5.429078808526421e-06,
'profit_closed_percent': -0.0, 'winning_trades': 1, 'losing_trades': 1,
'profit_factor': 0.02775724835771106, 'trading_volume': 91.074,
'profit_factor': 0.02775724835771106, 'winrate': 0.5,
'expectancy': -0.0027145635000000003, 'expectancy_ratio': -0.48612137582114445,
'trading_volume': 91.074,
}
)
])
@@ -916,6 +920,9 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected)
'winning_trades': expected['winning_trades'],
'losing_trades': expected['losing_trades'],
'profit_factor': expected['profit_factor'],
'winrate': expected['winrate'],
'expectancy': expected['expectancy'],
'expectancy_ratio': expected['expectancy_ratio'],
'max_drawdown': ANY,
'max_drawdown_abs': ANY,
'trading_volume': expected['trading_volume'],
@@ -1469,30 +1476,47 @@ def test_api_pair_history(botclient, mocker):
"&timerange=20180111-20180112")
assert_response(rc, 422)
# Invalid strategy
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}11")
assert_response(rc, 502)
# Working
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 200)
assert rc.json()['length'] == 289
assert len(rc.json()['data']) == rc.json()['length']
assert 'columns' in rc.json()
assert 'data' in rc.json()
result = rc.json()
assert result['length'] == 289
assert len(result['data']) == result['length']
assert 'columns' in result
assert 'data' in result
data = result['data']
assert len(data) == 289
# analyed DF has 28 columns
assert len(result['columns']) == 28
assert len(data[0]) == 28
date_col_idx = [idx for idx, c in enumerate(result['columns']) if c == 'date'][0]
rsi_col_idx = [idx for idx, c in enumerate(result['columns']) if c == 'rsi'][0]
assert data[0][date_col_idx] == '2018-01-11 00:00:00'
assert data[0][rsi_col_idx] is not None
assert data[0][rsi_col_idx] > 0
assert lfm.call_count == 1
assert rc.json()['pair'] == 'UNITTEST/BTC'
assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00'
assert rc.json()['data_start_ts'] == 1515628800000
assert rc.json()['data_stop'] == '2018-01-12 00:00:00+00:00'
assert rc.json()['data_stop_ts'] == 1515715200000
assert result['pair'] == 'UNITTEST/BTC'
assert result['strategy'] == CURRENT_TEST_STRATEGY
assert result['data_start'] == '2018-01-11 00:00:00+00:00'
assert result['data_start_ts'] == 1515628800000
assert result['data_stop'] == '2018-01-12 00:00:00+00:00'
assert result['data_stop_ts'] == 1515715200000
# No data found
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
f"&timerange=20200111-20200112&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 502)
assert rc.json()['error'] == ("Error querying /api/v1/pair_history: "
"No data for UNITTEST/BTC, 5m in 20200111-20200112 found.")
assert rc.json()['detail'] == ("No data for UNITTEST/BTC, 5m in 20200111-20200112 found.")
def test_api_plot_config(botclient, mocker):
@@ -1529,6 +1553,10 @@ def test_api_plot_config(botclient, mocker):
assert_response(rc)
assert rc.json()['subplots'] == {}
rc = client_get(client, f"{BASE_URI}/plot_config?strategy=NotAStrategy")
assert_response(rc, 502)
assert rc.json()['detail'] is not None
mocker.patch('freqtrade.rpc.api_server.api_v1.get_rpc_optional', return_value=None)
rc = client_get(client, f"{BASE_URI}/plot_config")
@@ -1981,7 +2009,7 @@ def test_api_backtest_history(botclient, mocker, testdatadir):
result = rc.json()
assert len(result) == 3
fn = result[0]['filename']
assert fn == "backtest-result_multistrat.json"
assert fn == "backtest-result_multistrat"
strategy = result[0]['strategy']
rc = client_get(client, f"{BASE_URI}/backtest/history/result?filename={fn}&strategy={strategy}")
assert_response(rc)
@@ -1995,6 +2023,34 @@ def test_api_backtest_history(botclient, mocker, testdatadir):
assert result2['backtest_result']['strategy'][strategy]
def test_api_delete_backtest_history_entry(botclient, mocker, tmp_path: Path):
ftbot, client = botclient
# Create a temporary directory and file
bt_results_base = tmp_path / "backtest_results"
bt_results_base.mkdir()
file_path = bt_results_base / "test.json"
file_path.touch()
meta_path = file_path.with_suffix('.meta.json')
meta_path.touch()
rc = client_delete(client, f"{BASE_URI}/backtest/history/randomFile.json")
assert_response(rc, 503)
assert rc.json()['detail'] == 'Bot is not in the correct state.'
ftbot.config['user_data_dir'] = tmp_path
ftbot.config['runmode'] = RunMode.WEBSERVER
rc = client_delete(client, f"{BASE_URI}/backtest/history/randomFile.json")
assert rc.status_code == 404
assert rc.json()['detail'] == 'File not found.'
rc = client_delete(client, f"{BASE_URI}/backtest/history/{file_path.name}")
assert rc.status_code == 200
assert not file_path.exists()
assert not meta_path.exists()
def test_health(botclient):
ftbot, client = botclient
+2
View File
@@ -799,6 +799,8 @@ async def test_telegram_profit_handle(
assert '*Best Performing:* `ETH/USDT: 9.45%`' in msg_mock.call_args_list[-1][0][0]
assert '*Max Drawdown:*' in msg_mock.call_args_list[-1][0][0]
assert '*Profit factor:*' in msg_mock.call_args_list[-1][0][0]
assert '*Winrate:*' in msg_mock.call_args_list[-1][0][0]
assert '*Expectancy (Ratio):*' in msg_mock.call_args_list[-1][0][0]
assert '*Trading volume:* `126 USDT`' in msg_mock.call_args_list[-1][0][0]
+7 -3
View File
@@ -381,7 +381,7 @@ def test__send_msg(default_conf, mocker, caplog):
webhook._send_msg(msg)
assert post.call_count == 1
assert post.call_args[1] == {'data': msg}
assert post.call_args[1] == {'data': msg, 'timeout': 10}
assert post.call_args[0] == (default_conf['webhook']['url'], )
post = MagicMock(side_effect=RequestException)
@@ -399,7 +399,7 @@ def test__send_msg_with_json_format(default_conf, mocker, caplog):
mocker.patch("freqtrade.rpc.webhook.post", post)
webhook._send_msg(msg)
assert post.call_args[1] == {'json': msg}
assert post.call_args[1] == {'json': msg, 'timeout': 10}
def test__send_msg_with_raw_format(default_conf, mocker, caplog):
@@ -411,7 +411,11 @@ def test__send_msg_with_raw_format(default_conf, mocker, caplog):
mocker.patch("freqtrade.rpc.webhook.post", post)
webhook._send_msg(msg)
assert post.call_args[1] == {'data': msg['data'], 'headers': {'Content-Type': 'text/plain'}}
assert post.call_args[1] == {
'data': msg['data'],
'headers': {'Content-Type': 'text/plain'},
'timeout': 10
}
def test_send_msg_discord(default_conf, mocker):
+57 -3
View File
@@ -2793,7 +2793,7 @@ def test_manage_open_orders_entry(
freqtrade.strategy.check_entry_timeout = MagicMock(return_value=False)
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=1234)
# check it does cancel buy orders over the time limit
# check it does cancel entry orders over the time limit
freqtrade.manage_open_orders()
assert cancel_order_mock.call_count == 1
assert rpc_mock.call_count == 2
@@ -2801,7 +2801,7 @@ def test_manage_open_orders_entry(
select(Trade).filter(Trade.open_order_id.is_(open_trade.open_order_id))).all()
nb_trades = len(trades)
assert nb_trades == 0
# Custom user buy-timeout is never called
# Custom user entry-timeout is never called
assert freqtrade.strategy.check_entry_timeout.call_count == 0
# Entry adjustment is never called
assert freqtrade.strategy.adjust_entry_price.call_count == 0
@@ -5023,7 +5023,7 @@ def test_get_real_amount_in_point(default_conf_usdt, buy_order_fee, fee, mocker,
(8.0, 0.1, 8.0, None),
(8.0, 0.1, 7.9, 0.1),
])
def test_apply_fee_conditional(default_conf_usdt, fee, mocker,
def test_apply_fee_conditional(default_conf_usdt, fee, mocker, caplog,
amount, fee_abs, wallet, amount_exp):
walletmock = mocker.patch('freqtrade.wallets.Wallets.update')
mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=wallet)
@@ -5048,6 +5048,60 @@ def test_apply_fee_conditional(default_conf_usdt, fee, mocker,
# Amount is kept as is
assert freqtrade.apply_fee_conditional(trade, 'LTC', amount, fee_abs, order) == amount_exp
assert walletmock.call_count == 1
if fee_abs != 0 and amount_exp is None:
assert log_has_re(r"Fee amount.*Eating.*dust\.", caplog)
@pytest.mark.parametrize('amount,fee_abs,wallet,amount_exp', [
(8.0, 0.0, 16, None),
(8.0, 0.0, 0, None),
(8.0, 0.1, 8, 0.1),
(8.0, 0.1, 20, None),
(8.0, 0.1, 16.0, None),
(8.0, 0.1, 7.9, 0.1),
(8.0, 0.1, 12, 0.1),
(8.0, 0.1, 15.9, 0.1),
])
def test_apply_fee_conditional_multibuy(default_conf_usdt, fee, mocker, caplog,
amount, fee_abs, wallet, amount_exp):
walletmock = mocker.patch('freqtrade.wallets.Wallets.update')
mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=wallet)
trade = Trade(
pair='LTC/ETH',
amount=amount,
exchange='binance',
open_rate=0.245441,
fee_open=fee.return_value,
fee_close=fee.return_value,
open_order_id="123456"
)
# One closed order
order = Order(
ft_order_side='buy',
order_id='10',
ft_pair=trade.pair,
ft_is_open=False,
filled=amount,
status="closed"
)
trade.orders.append(order)
# Add additional order - this should NOT eat into dust unless the wallet was bigger already.
order1 = Order(
ft_order_side='buy',
order_id='100',
ft_pair=trade.pair,
ft_is_open=True,
)
trade.orders.append(order1)
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
walletmock.reset_mock()
# The new trade amount will be 2x amount - fee / wallet will have to be adapted to this.
assert freqtrade.apply_fee_conditional(trade, 'LTC', amount, fee_abs, order1) == amount_exp
assert walletmock.call_count == 1
if fee_abs != 0 and amount_exp is None:
assert log_has_re(r"Fee amount.*Eating.*dust\.", caplog)
@pytest.mark.parametrize("delta, is_high_delta", [
+53 -2
View File
@@ -429,6 +429,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
assert pytest.approx(trade.stop_loss) == 1.99 * (1 - 0.1 / leverage)
assert pytest.approx(trade.initial_stop_loss) == 1.96 * (1 - 0.1 / leverage)
assert trade.initial_stop_loss_pct == -0.1
assert pytest.approx(trade.orders[-1].stake_amount) == trade.stake_amount
# 2nd order - not filling
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=120)
@@ -473,13 +474,38 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
assert pytest.approx(trade.orders[1].amount) == 30.150753768 * leverage
assert pytest.approx(trade.orders[-1].amount) == 61.538461232 * leverage
# Full exit
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=False)
freqtrade.strategy.custom_exit = MagicMock(return_value='Exit now')
freqtrade.strategy.adjust_entry_price = MagicMock(return_value=2.02)
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 5
assert trade.orders[-1].side == trade.exit_side
assert trade.orders[-1].status == 'open'
assert trade.orders[-1].price == 2.02
assert pytest.approx(trade.amount) == 91.689215 * leverage
assert pytest.approx(trade.orders[-1].amount) == 91.689215 * leverage
assert freqtrade.strategy.adjust_entry_price.call_count == 0
# Process again, should not adjust entry price
freqtrade.process()
trade = Trade.get_trades().first()
assert len(trade.orders) == 5
assert trade.orders[-1].status == 'open'
assert trade.orders[-1].price == 2.02
# Adjust entry price cannot be called - this is an exit order
assert freqtrade.strategy.adjust_entry_price.call_count == 0
@pytest.mark.parametrize('leverage', [1, 2])
def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, leverage) -> None:
default_conf_usdt['position_adjustment_enable'] = True
spot = leverage == 1
if not spot:
default_conf_usdt['trading_mode'] = 'futures'
default_conf_usdt['margin_mode'] = 'isolated'
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
freqtrade.trading_mode = TradingMode.FUTURES
assert freqtrade.trading_mode == TradingMode.FUTURES if not spot else TradingMode.SPOT
mocker.patch.multiple(
EXMS,
fetch_ticker=ticker_usdt,
@@ -487,8 +513,11 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
amount_to_precision=lambda s, x, y: y,
price_to_precision=lambda s, x, y: y,
get_min_pair_stake_amount=MagicMock(return_value=10),
get_funding_fees=MagicMock(return_value=0),
)
mocker.patch(f"{EXMS}.get_max_leverage", return_value=10)
starting_amount = freqtrade.wallets.get_total('USDT')
assert starting_amount == 1000
patch_get_signal(freqtrade)
freqtrade.strategy.leverage = MagicMock(return_value=leverage)
@@ -498,8 +527,14 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
trade = Trade.get_trades().first()
assert len(trade.orders) == 1
assert pytest.approx(trade.stake_amount) == 60
assert trade.leverage == leverage
assert pytest.approx(trade.amount) == 30.0 * leverage
assert trade.open_rate == 2.0
assert pytest.approx(freqtrade.wallets.get_free('USDT')) == starting_amount - 60
if spot:
assert pytest.approx(freqtrade.wallets.get_total('USDT')) == starting_amount - 60
else:
assert freqtrade.wallets.get_total('USDT') == starting_amount
# Too small size
freqtrade.strategy.adjust_trade_position = MagicMock(return_value=-59)
@@ -521,6 +556,15 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
assert pytest.approx(trade.amount) == 20.099 * leverage
assert trade.open_rate == 2.0
assert trade.is_open
assert trade.realized_profit > 0.098 * leverage
expected_profit = starting_amount - 40.1980 + trade.realized_profit
assert pytest.approx(freqtrade.wallets.get_free('USDT')) == expected_profit
if spot:
assert pytest.approx(freqtrade.wallets.get_total('USDT')) == expected_profit
else:
# total won't change in futures mode, only free / used will.
assert freqtrade.wallets.get_total('USDT') == starting_amount + trade.realized_profit
caplog.clear()
# Sell more than what we got (we got ~20 coins left)
@@ -545,3 +589,10 @@ def test_dca_exiting(default_conf_usdt, ticker_usdt, fee, mocker, caplog, levera
assert pytest.approx(trade.stake_amount) == 40.198
assert trade.is_open
assert log_has_re('Amount to exit is 0.0 due to exchange limits - not exiting.', caplog)
expected_profit = starting_amount - 40.1980 + trade.realized_profit
assert pytest.approx(freqtrade.wallets.get_free('USDT')) == expected_profit
if spot:
assert pytest.approx(freqtrade.wallets.get_total('USDT')) == expected_profit
else:
# total won't change in futures mode, only free / used will.
assert freqtrade.wallets.get_total('USDT') == starting_amount + trade.realized_profit

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