Compare commits

..

680 Commits

Author SHA1 Message Date
Matthias
851d1e9da1 Version bump 2022.9.1 2022-10-02 06:59:10 +02:00
Matthias
59cfde3767 Fix pandas deprecation warnings from freqAI 2022-10-02 06:59:10 +02:00
Matthias
c53ff94b8e Force joblib update via setup.py 2022-10-02 06:54:08 +02:00
Robert Caulk
03256fc776 Merge pull request #7508 from aemr3/fix-pca-errors
Fix feature list match for PCA
2022-10-02 06:53:08 +02:00
Matthias
19b3669d97 Decrease message throughput
fixes memory leak by queue raising indefinitely
2022-10-02 06:50:34 +02:00
Matthias
6841bdaa81 Update test to verify webhook won't log-spam on new messagetypes 2022-10-02 06:50:19 +02:00
Matthias
8e101a9f1c Disable log spam from analyze_df in webhook/discord 2022-10-02 06:50:12 +02:00
Matthias
0680ca2fe8 Merge pull request #7497 from freqtrade/new_release
New release 2022.9
2022-09-29 18:06:57 +02:00
Matthias
d0456b698c Version bump 2022.9 2022-09-29 07:22:41 +02:00
Matthias
f3085443d5 Merge branch 'stable' into new_release 2022-09-29 07:22:29 +02:00
Matthias
80d0e66b48 Update log level in test 2022-09-29 07:19:16 +02:00
Matthias
388a572cb3 Version bump develop version 2022-09-29 07:17:38 +02:00
Matthias
ac229b7a42 Reduce message consumer verbosity 2022-09-29 07:10:00 +02:00
Matthias
4e920e9c53 Reduce verbosity of sending-message 2022-09-29 06:41:16 +02:00
Matthias
fb3d408338 Respect max_open_trades when forceentering
closes #7489
2022-09-28 09:32:07 +00:00
Matthias
e9abe3cb68 Ease end of line rules 2022-09-28 07:24:43 +00:00
Matthias
7e124618d4 Move "success" notification to last pipeline step 2022-09-28 06:27:15 +00:00
Matthias
7c84edbc23 Avoid online call when asking for /status. 2022-09-28 07:21:52 +02:00
Matthias
a06372c7b2 Improve jupyter container install
closes #7484
2022-09-28 07:21:20 +02:00
Matthias
42cecb83f2 Disable base64 loading via API
closes severe RCE vulnerability reported privately.
2022-09-27 20:37:16 +02:00
Matthias
e668bf7138 Test case to disable base64 strategies via API 2022-09-27 20:36:27 +02:00
Matthias
6c491ee02e Update missed changes to plot_feature_importance 2022-09-27 18:17:49 +02:00
Matthias
895b15abbc Fix rpi CI 2022-09-27 11:06:13 +00:00
Matthias
24c1d84982 Fix lineending 2022-09-27 09:26:52 +00:00
Matthias
9d462af047 Temporary workaround for PI image 2022-09-27 08:54:18 +00:00
Matthias
8c2e473ee5 Fix test warning 2022-09-27 08:53:29 +00:00
Matthias
43e847ff2f Update to pandas 1.5.0 syntax, avoiding warnings 2022-09-27 08:02:51 +00:00
Matthias
bc007ce038 Update binance leverage tiers
closes #7485
2022-09-27 07:14:55 +02:00
Matthias
ba8c714698 Require kwargs on ohlcv_load 2022-09-26 20:33:49 +02:00
Matthias
853a4d1014 Merge pull request #7395 from freqtrade/improve-freqai-docs
Reorganize and improve FreqAI docs
2022-09-26 20:12:25 +02:00
Matthias
eb36105de4 Fix some random typos 2022-09-26 19:47:56 +02:00
Matthias
cf5267a4d3 Merge pull request #7481 from chusri/develop
✏️ fixed typo from StaticPairlist to StaticPairList
2022-09-26 18:11:56 +02:00
Matthias
9f32e02bba Update missing testcase 2022-09-26 17:10:23 +02:00
Thaweesak Chusri
4faa6a0bd7 🐛 updated test case from StaticPairlist to StaticPairList 2022-09-26 21:51:59 +08:00
Matthias
3d72168c01 Merge pull request #7479 from freqtrade/dependabot/pip/develop/types-requests-2.28.11
Bump types-requests from 2.28.10 to 2.28.11
2022-09-26 14:15:48 +02:00
Matthias
d493b2b7e7 Merge pull request #7406 from freqtrade/dependabot/pip/develop/ta-lib-0.4.25
Bump ta-lib from 0.4.24 to 0.4.25
2022-09-26 14:13:15 +02:00
Matthias
5477966cb6 Merge pull request #7478 from freqtrade/dependabot/pip/develop/pandas-1.5.0
Bump pandas from 1.4.4 to 1.5.0
2022-09-26 13:23:15 +02:00
dependabot[bot]
32930a269e Bump pandas from 1.4.4 to 1.5.0
Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.4.4 to 1.5.0.
- [Release notes](https://github.com/pandas-dev/pandas/releases)
- [Changelog](https://github.com/pandas-dev/pandas/blob/main/RELEASE.md)
- [Commits](https://github.com/pandas-dev/pandas/compare/v1.4.4...v1.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-26 09:36:05 +00:00
Matthias
8e0811d9de Call cleanup explicitly 2022-09-26 09:35:21 +00:00
Matthias
cde1d1c2b3 Merge pull request #7474 from wizrds/bugfix-emc
Fix bug in API Server WebSocket
2022-09-26 10:45:44 +02:00
Matthias
ed2c960a93 Update types-requests for pre-commit 2022-09-26 10:25:49 +02:00
Matthias
a1a62681bf add fixes for random test failures 2022-09-26 08:11:00 +00:00
Matthias
041258a549 Fix arm test failure 2022-09-26 07:02:40 +00:00
Matthias
ecb41ff9aa Merge pull request #7476 from freqtrade/dependabot/pip/develop/ccxt-1.93.98
Bump ccxt from 1.93.66 to 1.93.98
2022-09-26 09:01:19 +02:00
Matthias
965f8ff39b Merge pull request #7477 from freqtrade/dependabot/pip/develop/mkdocs-material-8.5.3
Bump mkdocs-material from 8.5.2 to 8.5.3
2022-09-26 08:07:32 +02:00
dependabot[bot]
8ef3a41a0e Bump ccxt from 1.93.66 to 1.93.98
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.93.66 to 1.93.98.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg)
- [Commits](https://github.com/ccxt/ccxt/compare/1.93.66...1.93.98)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-26 05:18:23 +00:00
Matthias
b90513d676 Merge pull request #7480 from freqtrade/dependabot/pip/develop/aiohttp-3.8.3
Bump aiohttp from 3.8.1 to 3.8.3
2022-09-26 07:17:13 +02:00
Thaweesak Chusri
c4784c6695 ✏️ fixed typo from StaticPairlist to StaticPairList 2022-09-26 11:05:27 +08:00
dependabot[bot]
5faaa25faf Bump aiohttp from 3.8.1 to 3.8.3
Bumps [aiohttp](https://github.com/aio-libs/aiohttp) from 3.8.1 to 3.8.3.
- [Release notes](https://github.com/aio-libs/aiohttp/releases)
- [Changelog](https://github.com/aio-libs/aiohttp/blob/master/CHANGES.rst)
- [Commits](https://github.com/aio-libs/aiohttp/compare/v3.8.1...v3.8.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-26 03:02:00 +00:00
dependabot[bot]
95a2d43e1a Bump types-requests from 2.28.10 to 2.28.11
Bumps [types-requests](https://github.com/python/typeshed) from 2.28.10 to 2.28.11.
- [Release notes](https://github.com/python/typeshed/releases)
- [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>
2022-09-26 03:01:52 +00:00
dependabot[bot]
a38f47e1a5 Bump mkdocs-material from 8.5.2 to 8.5.3
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 8.5.2 to 8.5.3.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/8.5.2...8.5.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-26 03:01:37 +00:00
Timothy Pogue
e54ed5b10e fix runtime error: dict changed size during iteration 2022-09-25 15:05:56 -06:00
robcaulk
f5535e780c change wording, switch FreqAI word format 2022-09-25 21:18:51 +02:00
robcaulk
117e510e61 Merge branch 'develop' into improve-freqai-docs 2022-09-25 20:53:36 +02:00
Robert Caulk
8051235171 Merge pull request #7465 from freqtrade/aggregate-fixes-fai
Aggregate recent feature requests
2022-09-25 20:43:23 +02:00
robcaulk
48e89e68b9 fix typos 2022-09-25 20:22:19 +02:00
Matthias
e53f0ce897 Update wheels to build with crypto fix 2022-09-25 19:33:12 +02:00
Matthias
adb5b98a3d Fix pre-commit lineending 2022-09-25 19:29:20 +02:00
Robert Caulk
0e7ec182a3 Merge pull request #7466 from th0rntwig/improve-freqai-docs
Revise FreqAI multipage docs structure
2022-09-25 17:51:58 +02:00
robcaulk
677c5719bf improve inlier-metric figure 2022-09-25 15:22:08 +02:00
th0rntwig
02dba5304b Revise FreqAI multipage docs structure 2022-09-25 12:12:35 +02:00
robcaulk
873d2a5069 no model save backtest, plot features backtest, ensure inlier plays nice, doc 2022-09-25 11:18:10 +02:00
Matthias
f4fac53a13 Merge pull request #7458 from xmatthias/dataformat/feather
new Dataformats feather and Parquet
2022-09-25 09:00:22 +02:00
Matthias
96336cb552 Merge pull request #7462 from freqtrade/pre-commit-ci
Use pre-commit in Ci to check for all things
2022-09-25 08:50:40 +02:00
Matthias
873eb5f2ca Improve EMC config validations 2022-09-24 16:43:58 +02:00
Matthias
1bd742f7e9 Properly setup pre-commit job 2022-09-24 16:31:29 +02:00
Matthias
585342f193 Merge pull request #7454 from freqtrade/fix_backtesting_dfsize_freqai
Ensure the DF has the same size in backtesting FreqAI
2022-09-24 16:17:39 +02:00
Matthias
e63f9e1c14 Use pre-commit in Ci to check for all things 2022-09-24 16:16:47 +02:00
Matthias
8d77ba118c Fix line endings 2022-09-24 16:15:15 +02:00
Matthias
50dfde7048 Remove unnecessary typing import 2022-09-24 16:11:15 +02:00
Matthias
53c8e0923f Improve typing in message_consumer 2022-09-24 16:10:42 +02:00
Matthias
166ae8e3a1 Remove missleading comment 2022-09-24 15:51:20 +02:00
Matthias
98ba57ffaa Better test for contract calculation change
closes #7449
2022-09-24 15:25:04 +02:00
Matthias
4efe2e9bc4 use FtPrecise to convert to contracts and back 2022-09-24 14:55:58 +02:00
Matthias
00b192b4df Add test to verify #7449 2022-09-24 14:51:58 +02:00
paranoidandy
2cc00a1a2c Allow use of --strategy-list with freqai, with warning (#7455)
* Allow use of --strategy-list with freqai, with warning

* ensure populate_any_indicators is identical for resused identifiers

* use pair instead of metadata["pair"]

Co-authored-by: robcaulk <rob.caulk@gmail.com>
2022-09-24 13:21:01 +02:00
Matthias
e429aa16f3 Add note about enter_tag handling
closes #7336
2022-09-24 11:21:50 +02:00
Matthias
6643d90e64 simplify freqAI start_backtesting 2022-09-24 10:34:14 +02:00
Matthias
5d27d5689f Merge pull request #7457 from aemr3/add-training-time
Add elapsed time to Freqai training logs
2022-09-24 10:27:08 +02:00
Matthias
d9c8e7157b Merge pull request #7460 from wizrds/bugfix-emc
Bug fix in External Message Consumer
2022-09-24 06:49:18 +02:00
Timothy Pogue
af974443cd add test 2022-09-23 13:37:46 -06:00
Timothy Pogue
6b5d71049e add sleep 2022-09-23 13:10:45 -06:00
Timothy Pogue
4c7cef570f typo in exception 2022-09-23 12:58:26 -06:00
Timothy Pogue
b8e1d29a1b catch connectionclosederror 2022-09-23 12:36:05 -06:00
Matthias
cf05f374cf Merge pull request #7459 from wizrds/fix-producer-docs
Fix typo in Configuration docs for Producer
2022-09-23 20:28:55 +02:00
Timothy Pogue
255ff000af typo in configuration.md 2022-09-23 12:12:47 -06:00
Matthias
2fffe7c5dd Fix missing comma 2022-09-23 20:03:33 +02:00
Matthias
7c093388e7 Add pyarrow dependency 2022-09-23 19:36:23 +02:00
Matthias
4576d291a9 Update data command outputs 2022-09-23 18:25:26 +02:00
Matthias
7e1e388b9c Add feather/parquet docs 2022-09-23 18:24:30 +02:00
Matthias
48352b8a37 Update hdf5 handler to reset index on load 2022-09-23 14:49:17 +00:00
Matthias
0bbb6faeba Add generic datahandler test 2022-09-23 13:23:17 +00:00
Emre
a4eaff4da6 Add training elapsed time 2022-09-23 01:18:34 -07:00
Matthias
5fb56b09f2 Test Feather/parquet datahandler init 2022-09-23 07:20:09 +02:00
Matthias
983a16d937 Rudimentary "not implemented" test 2022-09-23 07:18:18 +02:00
Matthias
044891f543 Add conditional formats depending on mode 2022-09-23 07:18:18 +02:00
Matthias
dc2b93228b Add ParquetDataHandler 2022-09-23 07:18:18 +02:00
Matthias
3c0d2c446d Add Feather datahandler (no trade mode yet) 2022-09-23 07:18:18 +02:00
Matthias
7b4af85425 Remove double-init in test 2022-09-23 07:18:08 +02:00
Matthias
f62f2bb1ca Improve datahandler tests 2022-09-23 07:17:33 +02:00
Matthias
2a5bc58df8 Split datahandler and history tests 2022-09-23 07:09:34 +02:00
Matthias
1db8421b9d Merge pull request #7303 from wizrds/feat/externalsignals
Producer/consumer mode
2022-09-23 06:29:58 +02:00
robcaulk
b7c60e810a improve class diagram 2022-09-22 21:32:12 +02:00
Matthias
06a5cfa401 Update "branding" to producer/follower mode 2022-09-22 21:08:06 +02:00
Matthias
1626eb7f97 Update dataprovider function name to get_producer_df 2022-09-22 20:46:40 +02:00
Matthias
e6c5c22ea0 Update websocket/follower docs 2022-09-22 20:31:42 +02:00
th0rntwig
71e6c54ea4 Normalise distances before Weibull fit (#7432)
* Normalise distances before Weibull

* Track inlier-metric params
2022-09-22 18:11:50 +02:00
Wagner Costa Santos
b1dbc3a65f remove function remove_training_from_backtesting and ensure BT period is correct with startup_candle_count 2022-09-22 12:13:51 -03:00
Robert Caulk
3e1e530aca Merge branch 'develop' into improve-freqai-docs 2022-09-22 15:58:27 +02:00
Timothy Pogue
6a6ae809f4 fix jwt auth 2022-09-21 18:23:00 -06:00
Timothy Pogue
77ed713232 add catch for invalid message error 2022-09-21 16:04:25 -06:00
Timothy Pogue
128b117af6 support list of tokens in ws_token 2022-09-21 16:02:21 -06:00
Timothy Pogue
0811bca8b4 revise docs, update dp method docstring 2022-09-21 15:50:11 -06:00
Matthias
91dc5e7aa6 Be sure to provide an amount in entry notifications 2022-09-21 21:12:08 +02:00
Matthias
08e183fb55 Add note about okx trading mode 2022-09-21 20:59:12 +02:00
paranoidandy
366c6c24d8 Add docs for External Signals API 2022-09-21 12:52:23 -06:00
Matthias
923182680e Explicitly define notification defaults 2022-09-21 12:46:58 +00:00
Matthias
f7b8c5a767 Reorder telegram noise sample 2022-09-21 12:46:58 +00:00
Matthias
02f2096fc3 Reverse and fix rangestability conditions
closes #7447
2022-09-21 06:53:07 +02:00
Matthias
8f41f943b4 Fix 0.0 amount message wording 2022-09-21 06:42:55 +02:00
Matthias
ff36431680 Adjust tests for new messageType handling 2022-09-20 20:34:56 +02:00
Matthias
3b0874eb37 Update exit message handling to gracefully handle kucoins "empty" responses
closes #7444
2022-09-20 20:00:08 +02:00
Matthias
0c01b23cba Capture exceptions in send_msg calls 2022-09-20 18:09:14 +02:00
Matthias
0bd6ad55a1 Always show freqtrade version 2022-09-20 14:14:54 +00:00
Matthias
8a91c8e220 Sort and dedup pairs before data conversion 2022-09-20 13:36:28 +00:00
Matthias
6c18fa0847 Update proxy docs 2022-09-20 09:30:48 +00:00
Matthias
914eccecec Merge branch 'develop' into pr/wizrds/7303 2022-09-20 06:56:15 +02:00
Matthias
3274bb0751 Remove msgpack for now 2022-09-20 06:55:22 +02:00
robcaulk
b5fd11f91b protect against unforeseen issues in scanning thread 2022-09-19 21:18:31 +02:00
Matthias
32d46e8a6b Improve fixture naming 2022-09-19 20:59:53 +02:00
Matthias
703bcc099a Fix list-pair regex to also support 1INCH/USDT 2022-09-19 20:32:54 +02:00
Matthias
eb9ac9cbda add --exchange to convert-trade-data 2022-09-19 20:29:28 +02:00
Matthias
a023ac26f3 Merge pull request #7441 from freqtrade/train-queue
Ensure train ordering after restart
2022-09-19 19:33:16 +02:00
robcaulk
42c75b4a7b combine log messages 2022-09-19 19:16:32 +02:00
Robert Caulk
9b66297cc0 Fix append 2022-09-19 12:47:20 +02:00
Robert Caulk
995396c775 Add useful log info 2022-09-19 11:42:56 +02:00
Robert Caulk
ad652817ef Ensure train ordering after restart
Ensure lowest timestamps get trained first after restart
2022-09-19 11:11:23 +02:00
Robert Caulk
f9460c80c2 Merge pull request #7434 from freqtrade/improve-train-queue
improve train queue system in FreqAI
2022-09-19 10:55:53 +02:00
Matthias
ea58c29ded Add plot_feature_importance to schema definition 2022-09-19 08:13:54 +00:00
Matthias
225f7cd5f8 Merge pull request #7431 from initrv/add-plot-feature-importance
Add plot feature importance
2022-09-19 08:41:10 +02:00
Matthias
2c4137e5ca Merge pull request #7440 from freqtrade/dependabot/pip/develop/fastapi-0.85.0
Bump fastapi from 0.83.0 to 0.85.0
2022-09-19 08:01:12 +02:00
Matthias
ec7642febf Merge pull request #7437 from freqtrade/dependabot/pip/develop/ccxt-1.93.66
Bump ccxt from 1.93.35 to 1.93.66
2022-09-19 08:00:57 +02:00
Matthias
4a0a0c307c Use json_load to load leverage tiers 2022-09-19 07:23:26 +02:00
Matthias
d930931000 Bring back sleep - it'll ensure we give up control over the thread. 2022-09-19 07:14:33 +02:00
Matthias
1d0e686cd4 Merge pull request #7436 from freqtrade/dependabot/pip/develop/joblib-1.2.0
Bump joblib from 1.1.0 to 1.2.0
2022-09-19 06:59:10 +02:00
Matthias
cc89b4127a Merge pull request #7439 from freqtrade/dependabot/pip/develop/mkdocs-material-8.5.2
Bump mkdocs-material from 8.4.3 to 8.5.2
2022-09-19 06:57:47 +02:00
dependabot[bot]
b5f51b5ec2 Bump fastapi from 0.83.0 to 0.85.0
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.83.0 to 0.85.0.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.83.0...0.85.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-19 04:34:24 +00:00
Matthias
613ad8cb31 Merge pull request #7438 from freqtrade/dependabot/pip/develop/pyjwt-2.5.0
Bump pyjwt from 2.4.0 to 2.5.0
2022-09-19 06:33:29 +02:00
dependabot[bot]
15c9b6bf41 Bump mkdocs-material from 8.4.3 to 8.5.2
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 8.4.3 to 8.5.2.
- [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/8.4.3...8.5.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-19 03:02:33 +00:00
dependabot[bot]
cbdb0ce3e7 Bump pyjwt from 2.4.0 to 2.5.0
Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.4.0 to 2.5.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.4.0...2.5.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-19 03:02:23 +00:00
dependabot[bot]
f512717943 Bump ccxt from 1.93.35 to 1.93.66
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.93.35 to 1.93.66.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg)
- [Commits](https://github.com/ccxt/ccxt/compare/1.93.35...1.93.66)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-19 03:02:16 +00:00
dependabot[bot]
4cdc89706e Bump joblib from 1.1.0 to 1.2.0
Bumps [joblib](https://github.com/joblib/joblib) from 1.1.0 to 1.2.0.
- [Release notes](https://github.com/joblib/joblib/releases)
- [Changelog](https://github.com/joblib/joblib/blob/master/CHANGES.rst)
- [Commits](https://github.com/joblib/joblib/compare/1.1.0...1.2.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-19 03:02:04 +00:00
Matthias
8116ca847b move trades_get_pairs to parent class 2022-09-18 19:40:03 +02:00
Matthias
a06eee300a move ohlcv_get_pairs to parent class 2022-09-18 19:36:23 +02:00
Matthias
584b2381d1 Fix Imports 2022-09-18 19:36:11 +02:00
Matthias
9e01ff5a72 Merge pull request #7435 from th0rntwig/improve-freqai-docs
Add Common pitfalls
2022-09-18 19:24:10 +02:00
robcaulk
470d5d8405 ensure full new pairlist is in the queue 2022-09-18 17:08:07 +02:00
robcaulk
eaa43337d2 improve train queue system, ensure crash resilience in train queue. 2022-09-18 17:00:55 +02:00
th0rntwig
edbe9137da Add Common pitfalls 2022-09-18 14:51:11 +02:00
robcaulk
95457d23ca escape freqai-specific characters from file naming 2022-09-18 13:59:30 +02:00
Matthias
994c1c5ea0 use Config typing in more places 2022-09-18 13:31:52 +02:00
Matthias
667853c504 Use Alias to type config objects 2022-09-18 13:20:55 +02:00
robcaulk
188f75d8ec set model in models dict 2022-09-18 12:50:44 +02:00
Matthias
27c46300a7 Merge pull request #7430 from th0rntwig/improve-freqai-docs
Reorganise multipage doc
2022-09-18 10:22:45 +02:00
Matthias
9f23588154 strategy template - remove pointless noqa's 2022-09-18 08:58:33 +02:00
Matthias
7a73adb955 Improve default strategy template 2022-09-18 08:57:26 +02:00
Matthias
faf84295a5 Separate strategy subtemplates for better overview 2022-09-18 08:52:53 +02:00
Matthias
ab78fb373a Improve freqAI strategy formatting and readability 2022-09-18 08:45:24 +02:00
Matthias
4634936265 additional support for --data-dir 2022-09-18 08:39:03 +02:00
Matthias
fa3d4b58ab Revert unnecessary formatting 2022-09-18 08:30:59 +02:00
Robert Caulk
bdeb2f9c6a Merge branch 'develop' into add-plot-feature-importance 2022-09-18 00:02:46 +02:00
robcaulk
1ef875901a maintian user privacy by keeping plotly offline 2022-09-18 00:01:42 +02:00
robcaulk
68f7a31504 ensure continued operation despite not being able to plot 2022-09-18 00:00:14 +02:00
robcaulk
2c23effbf2 allow plot to plot multitargets, add test 2022-09-17 19:17:44 +02:00
initrv
1c92734f39 simplify plot_feature_importance call 2022-09-17 18:53:43 +03:00
th0rntwig
c210d6614c Reorganise multipage doc 2022-09-17 17:43:39 +02:00
Matthias
92a32ab31b Add documentation for stop-market on binance futures
part of #7426
2022-09-17 17:14:45 +02:00
Matthias
063511826c Update stoploss on exchange logic
closes #7424
2022-09-17 17:11:00 +02:00
Matthias
9f266cbcb2 Allow safe_price for market stop orders 2022-09-17 17:11:00 +02:00
Matthias
ca6dec3d4c Binance spot also allows market orders
closes #7426
2022-09-17 17:11:00 +02:00
Matthias
8639c1f23d Reduce complexity in binance stoploss handling 2022-09-17 17:11:00 +02:00
Matthias
93237efc15 Merge pull request #7428 from freqtrade/informative_freqai
Informative freqai
2022-09-17 16:44:28 +02:00
Matthias
38b28fc4da Update duplicated test 2022-09-17 14:19:20 +02:00
Matthias
4182a7891a Allow leverage tier cache to be 4 weeks old.
we've seen from binance that it's not changing this often.
2022-09-17 10:41:48 +02:00
Matthias
6682ae35b3 Update cached binance_leverage_tiers 2022-09-17 10:37:47 +02:00
Matthias
10ec681b30 Clean up no longer needed informative sample code 2022-09-17 10:19:46 +02:00
Matthias
d62cef01be Add test for __informative_pairs_freqai 2022-09-17 10:18:08 +02:00
Matthias
0aada271ca Move informative_pairs for freqAI to backend 2022-09-17 10:17:22 +02:00
Timothy Pogue
4422ac7f45 constrain port in config, catch value error 2022-09-16 19:22:24 -06:00
initrv
86aa875bc9 plot features as html instead of png 2022-09-16 21:47:12 +03:00
initrv
b707a6da35 Add ability to plot feature importance 2022-09-16 19:17:41 +03:00
Matthias
e5368f5a14 backtesting confirm_trade_entry should pass correct amount, not stake-amount
closes #7423
2022-09-16 13:24:20 +00:00
Timothy Pogue
b0b575ead9 change json serialize to split orient 2022-09-16 00:02:27 -06:00
Timothy Pogue
1ad25095c1 change test server from localhost to 127.0.0.1 2022-09-15 19:40:45 -06:00
Timothy Pogue
efaef68ad7 Merge branch 'develop' into feat/externalsignals 2022-09-15 18:09:25 -06:00
Timothy Pogue
7d1645ac20 fix tests and warning message 2022-09-15 17:54:31 -06:00
Timothy Pogue
8e75852ff3 fix constants, update config example, add emc config validation 2022-09-15 11:12:05 -06:00
Timothy Pogue
6126925dbe message size limit in mb, default to 8mb 2022-09-14 16:42:14 -06:00
Matthias
711849abd6 Use Wheels for win builds 2022-09-14 07:16:57 +02:00
Matthias
8a236c3c4f Merge pull request #7412 from initrv/fix-purge-old-models
Fixed a bug that prevents clearing old models
2022-09-14 07:10:57 +02:00
Matthias
91bc3d1161 Update docs aroudn use_exit_signal
close #7413
2022-09-14 07:04:14 +02:00
Matthias
49800e4cc3 pin ci python to 3.10.6 for now 2022-09-14 06:55:05 +02:00
Timothy Pogue
aed19ff6ce fix The future belongs to a different loop error 2022-09-13 19:17:12 -06:00
initrv
37dd22c89e Fixed a bug that prevents clearing old models
Corrects the error of clearing old models when the model directory contains directories with names that do not match a regular expression
2022-09-14 03:40:13 +03:00
Timothy Pogue
06350a13cb support specifying message size in emc config 2022-09-13 16:39:53 -06:00
Timothy Pogue
d75d5a7dad debug ping error message 2022-09-13 16:06:25 -06:00
Matthias
7a98775f01 Version bump apiVersion 2022-09-13 22:07:59 +02:00
Timothy Pogue
46a425d1b6 fix OOM on emc test 2022-09-13 13:36:21 -06:00
Matthias
7b6e465d57 Remove gate live test skip 2022-09-13 21:28:11 +02:00
Matthias
877d24bcdd Fix external dependency of test 2022-09-13 20:52:07 +02:00
Matthias
d2abc9417f Simplify ws imports 2022-09-13 20:51:13 +02:00
Matthias
79c70bd52d use WebSocketState from fastapi
available since 0.82.0
2022-09-13 20:50:12 +02:00
Timothy Pogue
aeaca78940 change port in send_msg test 2022-09-13 12:39:12 -06:00
Timothy Pogue
07aa206f71 real fix for reconnecting 2022-09-13 12:36:40 -06:00
Timothy Pogue
6d0dfd4dc8 continue trying connect on ping error 2022-09-13 12:27:41 -06:00
Timothy Pogue
75ce9067dc fix dp test 2022-09-12 16:39:16 -06:00
Robert Caulk
26441820a9 Merge pull request #7390 from aemr3/add-xgboostclassifier
Add XGBoostClassifier
2022-09-12 23:38:04 +02:00
Timothy Pogue
bf2e5dee75 add running false on shutdown, fix dp typing 2022-09-12 14:21:39 -06:00
Timothy Pogue
12a3e90f78 fix tests 2022-09-12 14:12:39 -06:00
Timothy Pogue
0697041f14 remove copy statement where not needed 2022-09-12 14:09:12 -06:00
Timothy Pogue
c19a5fbe06 copy data being transferred, remove debug messages in emc 2022-09-12 13:57:29 -06:00
Matthias
b6434040de Remove plain json serializer implementation 2022-09-12 20:24:28 +02:00
Matthias
867d59b930 Improve type specifitivity 2022-09-12 20:00:01 +02:00
Matthias
0052e58917 emc: Fix potential startup timing issue 2022-09-12 19:50:22 +02:00
Timothy Pogue
a477b3c244 remove log line, fix tests to not connect to actual ip 2022-09-12 10:45:59 -06:00
Timothy Pogue
10852555e5 change verbosity of testing log 2022-09-12 09:53:47 -06:00
Matthias
fac8f19554 Merge pull request #7396 from freqtrade/hyperopt_per_epoch
Hyperopt per epoch
2022-09-12 15:56:03 +02:00
Timothy Pogue
457075b823 one more line 2022-09-12 07:47:30 -06:00
Timothy Pogue
d6205e6cfb test logging lines 2022-09-12 07:36:11 -06:00
Timothy Pogue
85b43a7c34 Merge branch 'develop' into feat/externalsignals 2022-09-12 07:28:08 -06:00
Matthias
a93e355175 Merge pull request #7403 from freqtrade/dependabot/pip/develop/types-requests-2.28.10
Bump types-requests from 2.28.9 to 2.28.10
2022-09-12 10:37:42 +02:00
Matthias
f45824acf5 Bump precommit types/requests 2022-09-12 09:23:02 +02:00
Matthias
2599e57fe6 Merge pull request #7401 from freqtrade/dependabot/pip/develop/aiofiles-22.1.0
Bump aiofiles from 0.8.0 to 22.1.0
2022-09-12 09:21:58 +02:00
Matthias
e20ebc99c4 Merge pull request #7398 from freqtrade/dependabot/pip/develop/cryptography-38.0.1
Bump cryptography from 37.0.4 to 38.0.1
2022-09-12 08:41:01 +02:00
Matthias
21ed992b78 Merge pull request #7400 from freqtrade/dependabot/pip/develop/numpy-1.23.3
Bump numpy from 1.23.2 to 1.23.3
2022-09-12 08:40:16 +02:00
dependabot[bot]
7ee92db7a2 Bump aiofiles from 0.8.0 to 22.1.0
Bumps [aiofiles](https://github.com/Tinche/aiofiles) from 0.8.0 to 22.1.0.
- [Release notes](https://github.com/Tinche/aiofiles/releases)
- [Commits](https://github.com/Tinche/aiofiles/compare/v0.8.0...v22.1.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-12 06:07:40 +00:00
Matthias
91b7e152c2 Merge pull request #7404 from freqtrade/dependabot/pip/develop/fastapi-0.83.0
Bump fastapi from 0.82.0 to 0.83.0
2022-09-12 08:06:43 +02:00
dependabot[bot]
00db473f10 Bump cryptography from 37.0.4 to 38.0.1
Bumps [cryptography](https://github.com/pyca/cryptography) from 37.0.4 to 38.0.1.
- [Release notes](https://github.com/pyca/cryptography/releases)
- [Changelog](https://github.com/pyca/cryptography/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pyca/cryptography/compare/37.0.4...38.0.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-12 06:05:22 +00:00
dependabot[bot]
4ac804f795 Bump numpy from 1.23.2 to 1.23.3
Bumps [numpy](https://github.com/numpy/numpy) from 1.23.2 to 1.23.3.
- [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.23.2...v1.23.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-12 06:05:04 +00:00
Matthias
d3f4d742bb Merge pull request #7399 from freqtrade/dependabot/pip/develop/ccxt-1.93.35
Bump ccxt from 1.93.3 to 1.93.35
2022-09-12 08:04:16 +02:00
dependabot[bot]
09cdce864e Bump ta-lib from 0.4.24 to 0.4.25
Bumps [ta-lib](https://github.com/mrjbq7/ta-lib) from 0.4.24 to 0.4.25.
- [Release notes](https://github.com/mrjbq7/ta-lib/releases)
- [Changelog](https://github.com/mrjbq7/ta-lib/blob/master/CHANGELOG)
- [Commits](https://github.com/mrjbq7/ta-lib/compare/TA_Lib-0.4.24...TA_Lib-0.4.25)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-12 06:02:21 +00:00
Matthias
1f11d6091c Merge pull request #7405 from freqtrade/dependabot/pip/develop/jsonschema-4.16.0
Bump jsonschema from 4.15.0 to 4.16.0
2022-09-12 08:01:36 +02:00
Matthias
dae2ee446c Merge pull request #7402 from freqtrade/dependabot/pip/develop/mkdocs-material-8.4.3
Bump mkdocs-material from 8.4.2 to 8.4.3
2022-09-12 08:01:04 +02:00
Matthias
715a71465d Fix auth bug when no token is set 2022-09-12 07:28:31 +02:00
dependabot[bot]
c149c47afb Bump ccxt from 1.93.3 to 1.93.35
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.93.3 to 1.93.35.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg)
- [Commits](https://github.com/ccxt/ccxt/compare/1.93.3...1.93.35)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-12 05:05:30 +00:00
Matthias
3c2e0b5ad6 Merge pull request #7397 from freqtrade/dependabot/pip/develop/sqlalchemy-1.4.41
Bump sqlalchemy from 1.4.40 to 1.4.41
2022-09-12 07:04:36 +02:00
dependabot[bot]
6968fc333b Bump jsonschema from 4.15.0 to 4.16.0
Bumps [jsonschema](https://github.com/python-jsonschema/jsonschema) from 4.15.0 to 4.16.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.15.0...v4.16.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-12 03:02:10 +00:00
dependabot[bot]
a4b7e0a714 Bump fastapi from 0.82.0 to 0.83.0
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.82.0 to 0.83.0.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.82.0...0.83.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-12 03:02:06 +00:00
dependabot[bot]
1ef334411e Bump types-requests from 2.28.9 to 2.28.10
Bumps [types-requests](https://github.com/python/typeshed) from 2.28.9 to 2.28.10.
- [Release notes](https://github.com/python/typeshed/releases)
- [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>
2022-09-12 03:02:01 +00:00
dependabot[bot]
6bfe996061 Bump mkdocs-material from 8.4.2 to 8.4.3
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 8.4.2 to 8.4.3.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/8.4.2...8.4.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-12 03:01:56 +00:00
dependabot[bot]
6777d43aea Bump sqlalchemy from 1.4.40 to 1.4.41
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.4.40 to 1.4.41.
- [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>
2022-09-12 03:01:27 +00:00
Timothy Pogue
5483cf21f6 remove default secret_ws_token, set timeout min to 0 2022-09-11 11:42:13 -06:00
Matthias
982c0315fa Rename variable 2022-09-11 19:31:11 +02:00
Matthias
816c1f7603 add test for per epoch hyperopt 2022-09-11 17:51:30 +02:00
Matthias
72d197a99d Run first epoch in non-parallel mode
this allows dataprovider to load it's cache.

closes #7384
2022-09-11 17:51:07 +02:00
robcaulk
ede282392f add a developers guide 2022-09-11 17:50:50 +02:00
Timothy Pogue
818f7bfc40 Merge branch 'develop' into feat/externalsignals 2022-09-11 09:48:16 -06:00
robcaulk
68a900a9b7 reorganize freqai docs for easier reading, add detailed file structure description 2022-09-11 17:29:14 +02:00
Matthias
78cd46ecd5 hyperopt Remove unnecessary arguments 2022-09-11 14:59:39 +02:00
Matthias
32e13d65c3 Refactor hyperopt to extract evaluate_result 2022-09-11 14:59:39 +02:00
Matthias
a48923c0e4 Extract widget colorization to separate function 2022-09-11 14:59:39 +02:00
Matthias
9c8c7a03a1 Improve typehint 2022-09-11 14:59:39 +02:00
Matthias
ccc70a21f2 Update pairs_file cli argument description 2022-09-11 14:59:39 +02:00
Matthias
4476b5a7f4 add user_data arg to test-pairlist 2022-09-11 14:59:39 +02:00
Matthias
24b35bfb44 Merge pull request #7394 from freqtrade/add-search-share
add search share button to website
2022-09-11 14:57:11 +02:00
robcaulk
d598f4334e add search share button to website 2022-09-11 13:28:14 +02:00
Timothy Pogue
0a8b7686d6 reworked emc tests 2022-09-11 00:50:18 -06:00
Timothy Pogue
ed4ba8801f more emc tests 2022-09-10 23:57:17 -06:00
Timothy Pogue
9a1a4dfb5b more ws endpoint tests 2022-09-10 16:08:05 -06:00
Timothy Pogue
0f8eaf98e7 Merge branch 'develop' into feat/externalsignals 2022-09-10 15:14:10 -06:00
Timothy Pogue
0bc18ea33c call websocket close in channel close 2022-09-10 15:12:18 -06:00
Emre
330d7068ab Merge branch 'develop' into add-xgboostclassifier 2022-09-10 23:59:11 +03:00
Robert Caulk
075748b21a Merge pull request #7392 from freqtrade/improve_ai_tests
Improve freqai tests by utilizing parametrization
2022-09-10 22:37:21 +02:00
Timothy Pogue
2afd5c202c update message parsing, tests 2022-09-10 14:29:15 -06:00
robcaulk
5a0cfee27e allow user to multithread jobs (advanced users only) 2022-09-10 22:16:49 +02:00
Robert Caulk
73e122ad10 Merge pull request #7391 from wagnercosta/fixBTfreqaiV2
Fix FreaqAI backtesting - startup_candle_count bug
2022-09-10 21:53:00 +02:00
Timothy Pogue
c5d031733b remove old param in test fixture 2022-09-10 13:50:36 -06:00
Timothy Pogue
a7baccdb7d update log messages in emc, more tests 2022-09-10 13:44:27 -06:00
Timothy Pogue
866a564958 update emc start/shutdown, initial emc tests 2022-09-10 12:51:29 -06:00
Matthias
b3fc1cfde9 Parametrize classifier tests 2022-09-10 20:18:00 +02:00
Matthias
88892ba663 Parametrize regressor tests 2022-09-10 20:18:00 +02:00
Matthias
f97f1dc5c3 Test CatboostRegressorMultiTarget, simplify test setup via parametrization 2022-09-10 20:18:00 +02:00
Matthias
e4caccc353 Merge pull request #7367 from freqtrade/add-continual-learning
add continual learning to catboost and friends
2022-09-10 20:17:28 +02:00
Timothy Pogue
d8cdd92140 wrap background cleanup in finally, add tests 2022-09-10 11:47:21 -06:00
Wagner Costa Santos
311ae8bf1f freqai backtesting - add startup_candle_count at function description 2022-09-10 14:45:42 -03:00
Emre
60eb02bb62 Add XGBoostClassifier 2022-09-10 20:13:16 +03:00
Robert Caulk
2077f84f9b Merge pull request #7363 from wagnercosta/fixHyperoptFreqai
Fix hyperopt - freqai
2022-09-10 17:02:01 +02:00
Robert Caulk
4a47c63f71 Merge pull request #7374 from th0rntwig/filter-constant-features 2022-09-10 16:58:58 +02:00
robcaulk
10b6aebc5f enable continual learning and evaluation sets on multioutput models. 2022-09-10 16:54:13 +02:00
Matthias
daf352e6a5 Test online candle fetching in futures, too
disable gateio checking on spot markets
2022-09-10 16:01:06 +02:00
Wagner Costa Santos
a1f88cca80 merge develop 2022-09-10 10:35:16 -03:00
Wagner Costa Santos
79985fda01 fix backtesting freqai startup candles bug 2022-09-10 10:27:17 -03:00
Matthias
4250174de9 Fix ws exception when no token is provided 2022-09-10 14:29:58 +02:00
Matthias
b344f78d00 Improve logic for token validation 2022-09-10 14:20:49 +02:00
Matthias
170bec0438 Fix failing XGBoost tests 2022-09-10 08:24:23 +02:00
Matthias
5705b8759b Merge branch 'develop' into add-continual-learning 2022-09-10 08:24:04 +02:00
Timothy Pogue
2f6a61521f add more tests 2022-09-09 17:14:40 -06:00
robcaulk
05581db4e3 ensure columns are only dropped in training 2022-09-09 20:37:21 +02:00
Matthias
939fb7acb3 Fix enum imports 2022-09-09 20:31:30 +02:00
Matthias
97be3318f4 Use Datetime_format from constants 2022-09-09 20:31:02 +02:00
Matthias
37fcbeba58 Update backtesting output wording to Entries 2022-09-09 19:58:55 +02:00
Timothy Pogue
826eb85254 update confige example 2022-09-09 11:58:30 -06:00
robcaulk
c13bec26d1 add freqaimodel to hyperopt command 2022-09-09 19:41:28 +02:00
Timothy Pogue
6cbc03a96a support jwt token in place of ws token 2022-09-09 11:38:42 -06:00
robcaulk
a826c0eb83 ensure signatures match, reduce verbosity 2022-09-09 19:30:53 +02:00
Emre
acb410a0de Remove verbosity params 2022-09-09 19:30:53 +02:00
Emre
df6e43d2c5 Add XGBoostRegressorMultiTarget class 2022-09-09 19:30:53 +02:00
Emre
1b6410d7d1 Add XGBoostRegressor for freqAI, fix mypy errors 2022-09-09 19:30:53 +02:00
Timothy Pogue
09679cc798 fix dependency 2022-09-09 11:27:20 -06:00
Wagner Costa Santos
d38cc06139 Fix freqai backtesting time range issue 2022-09-09 19:27:05 +02:00
Timothy Pogue
ad96597693 wording 2022-09-09 10:59:38 -06:00
Timothy Pogue
445ab1beee update docs 2022-09-09 10:56:54 -06:00
Timothy Pogue
426f8f37e9 change var names 2022-09-09 10:45:49 -06:00
Matthias
e256ebd727 Add ws_token to auto-generated config 2022-09-09 07:13:05 +02:00
Matthias
afc17c5ec9 Merge pull request #7383 from freqtrade/dependabot/docker/python-3.10.7-slim-bullseye
Bump python from 3.10.6-slim-bullseye to 3.10.7-slim-bullseye
2022-09-09 06:30:19 +02:00
dependabot[bot]
92d71ebdb7 Bump python from 3.10.6-slim-bullseye to 3.10.7-slim-bullseye
Bumps python from 3.10.6-slim-bullseye to 3.10.7-slim-bullseye.

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-09 03:03:30 +00:00
Timothy Pogue
46cd0ce994 fix sentence in docs 2022-09-08 16:30:31 -06:00
Timothy Pogue
1466d2d26f initial message ws docs 2022-09-08 16:27:09 -06:00
Timothy Pogue
75cf8dbfe4 missed await 2022-09-08 15:11:36 -06:00
robcaulk
c5d9180758 isort 2022-09-08 22:35:52 +02:00
robcaulk
bc7295579f improve docs, make example strat hyperoptable 2022-09-08 22:22:50 +02:00
Timothy Pogue
c9d4f666c5 minor apiserver test change 2022-09-08 14:00:22 -06:00
Timothy Pogue
2b9c8550b0 moved ws_schemas, first ws tests 2022-09-08 13:58:28 -06:00
Timothy Pogue
b9e7af1ce2 fix ws token auth 2022-09-08 11:25:30 -06:00
Timothy Pogue
fac6626459 update default timeouts 2022-09-08 10:54:31 -06:00
Timothy Pogue
b3b0c918d9 cleanup old code 2022-09-08 10:44:03 -06:00
Timothy Pogue
379b1cbc90 remove unnecessary returns 2022-09-08 10:37:41 -06:00
Timothy Pogue
df3c126146 fix mypy error 2022-09-08 10:34:37 -06:00
Timothy Pogue
9b752475db re-add fix to freqtradebot test 2022-09-08 10:20:03 -06:00
Timothy Pogue
4104d0f68a Merge branch 'develop' into feat/externalsignals 2022-09-08 10:19:23 -06:00
Timothy Pogue
4fac125443 fix apiserver cleanup issues in tests 2022-09-08 10:14:30 -06:00
Timothy Pogue
83770d20fa fix existing freqtradebot tests 2022-09-08 10:10:32 -06:00
Matthias
883abe5b4f Merge pull request #7379 from freqtrade/fix/keyerror_fundingfee
Fix keyerror fundingfee update
2022-09-08 12:06:52 +02:00
Matthias
08726a264b Update FAQ to point out multiple parallel positions per trade 2022-09-08 07:48:36 +00:00
Matthias
5e42defafc Update telegram docs to disable partial exit notifications 2022-09-08 07:36:37 +00:00
Matthias
9ef0ffe277 Update tests for funding-Fee exceptions 2022-09-08 07:19:17 +00:00
Matthias
791f61c089 Add test case for funding fee update failure 2022-09-08 07:13:15 +00:00
Matthias
b91ad609f2 Merge pull request #7370 from wizrds/feat/suffix-merge-informative
Add optional `suffix` parameter to `merge_informative_pair`
2022-09-08 08:08:55 +02:00
Matthias
39b6cadd14 Test keyerror case for funding_Fee calculation 2022-09-08 07:24:57 +02:00
Matthias
4e15611b05 Don't crash in case of funding fee fetch error 2022-09-08 07:18:38 +02:00
Matthias
045c3f0f3a Reduce diff by avoiding unnecessary changes 2022-09-08 07:01:37 +02:00
Matthias
f3417a8690 Revert condition sequence to simplify conditions 2022-09-08 06:59:14 +02:00
Matthias
20bf44a856 Merge pull request #7375 from freqtrade/combine-metrics
ensure inlier metric can be combined with other cleaning methods
2022-09-08 06:43:02 +02:00
Timothy Pogue
a50923f796 add producers attribute to dataprovider 2022-09-07 17:14:26 -06:00
Wagner Costa Santos
f4f2884a66 Fix freqai backtesting time range issue 2022-09-07 18:52:58 -03:00
Timothy Pogue
1ef1fc269e docstring and tests 2022-09-07 15:26:38 -06:00
Timothy Pogue
5934495dda add websocket request/message schemas 2022-09-07 15:08:01 -06:00
Wagner Costa Santos
bf3ee51167 validate freqai hyperopt with freqai enabled param 2022-09-07 14:42:05 -03:00
Matthias
f8e7ed5d7d Reduce shutdown verbosity of exchange 2022-09-07 19:28:42 +02:00
Matthias
a9fd12b816 Allow more dynamic timeframes by disabling "choice"
part of #7366
2022-09-07 19:28:42 +02:00
robcaulk
e51d352777 ensure pca is handling same DF as inlier 2022-09-07 19:11:54 +02:00
robcaulk
4c9ac6b7c0 add kwargs, reduce duplicated code 2022-09-07 18:58:55 +02:00
Timothy Pogue
5d338e697c change window to timeperiod in hybrid 2022-09-07 18:49:47 +02:00
Timothy Pogue
48cadbf933 remove duplicate line, change window to timeperiod 2022-09-07 18:49:47 +02:00
robcaulk
ea7bdac9ed ensure inlier metric can be combined with other cleaning methods 2022-09-07 18:45:16 +02:00
Matthias
322f00e3e8 Fix empty entry message if order doesn't fill immediately
closes #7368
2022-09-07 18:19:43 +02:00
th0rntwig
cdc72bf8ca Correct indexing 2022-09-07 18:14:13 +02:00
th0rntwig
047ded1baa Check for constant columns 2022-09-07 17:47:27 +02:00
Timothy Pogue
2c9b765953 add suffix parameter 2022-09-07 09:35:37 -06:00
Wagner Costa Santos
972b699105 hyperopt - freqai - change validation to config_validation 2022-09-07 11:11:31 -03:00
wagnercosta
6b7644029c Merge branch 'freqtrade:develop' into fixHyperoptFreqai 2022-09-07 11:07:51 -03:00
Matthias
c08c82bc40 Merge pull request #7322 from freqtrade/add-inlier-metric
Add inlier metric
2022-09-07 07:05:57 +02:00
Matthias
3454a52b95 Explicitly test amount_to_contract_precision 2022-09-07 06:55:22 +02:00
Matthias
4d69df08dd trunc to amount precision before checking valid partial exits
closes #7368
2022-09-07 06:43:08 +02:00
Matthias
83d9f3aeba Add test showing #7365 2022-09-07 06:37:53 +02:00
Matthias
95a33ab2e6 Add amount_to_contract helper in the exchange 2022-09-07 06:34:03 +02:00
Wagner Costa Santos
5aba5de20f fix link - hyperopt spaces 2022-09-06 16:17:10 -03:00
Timothy Pogue
8bfaf0a998 Merge branch 'develop' into feat/externalsignals 2022-09-06 13:02:36 -06:00
Wagner Costa Santos
e0490b3efc Merge branch 'fixHyperoptFreqai' of https://github.com/wagnercosta/freqtrade into fixHyperoptFreqai 2022-09-06 15:43:08 -03:00
Wagner Costa Santos
8d16dd804d hyperopt - freqai - docs and refactoring 2022-09-06 15:42:47 -03:00
Timothy Pogue
b1c0267449 mypy fixes 2022-09-06 12:40:58 -06:00
Robert Caulk
d7585161b2 Merge branch 'develop' into add-inlier-metric 2022-09-06 20:40:21 +02:00
wagnercosta
55195260e4 Merge branch 'freqtrade:develop' into fixHyperoptFreqai 2022-09-06 15:32:28 -03:00
robcaulk
97077ba18a add continual learning to catboost and friends 2022-09-06 20:30:46 +02:00
Matthias
dc4a4bdf09 Wrap cleanup in try/finally handler
If a database has errors, the database cleanups would fail, causing
cleanup to be incomplete.

closes #7364
2022-09-06 20:26:42 +02:00
Timothy Pogue
3535aa7724 add last_analyzed to emitted dataframe 2022-09-06 12:12:05 -06:00
robcaulk
d44296783e isort datakitchen 2022-09-06 20:10:12 +02:00
th0rntwig
90ec336c70 Update+correct descriptions and figure (#7323) 2022-09-06 19:58:25 +02:00
robcaulk
e83c9b276d fix whitespace 2022-09-06 19:56:52 +02:00
Matthias
f2f811a2fe Fix telegram bug with open partial exit orders 2022-09-06 19:55:18 +02:00
robcaulk
4b28d0495f fix timestamping, move imports, add words to doc 2022-09-06 19:46:58 +02:00
Matthias
98ec84fca6 Merge pull request #7339 from freqtrade/fix/fundingfee_handling
Fix/fundingfee handling
2022-09-06 19:21:40 +02:00
Matthias
8597b52e34 Slightly update docs to link to full sample 2022-09-06 16:29:24 +02:00
wagnercosta
1820bc6832 Merge branch 'freqtrade:develop' into fixHyperoptFreqai 2022-09-06 10:02:17 -03:00
Matthias
3f3099cbfc Merge pull request #7361 from italodamato/develop
fix hyperopt df preprocessing
2022-09-06 07:26:14 +02:00
Timothy Pogue
38f14349e9 move bytes decoding to serializer 2022-09-05 23:25:25 -06:00
Matthias
d526dfb171 Revert some more changes in rpc_manager 2022-09-06 07:03:31 +02:00
Matthias
4dec19de9f Add comment to explain why we use the non-trimmed DF 2022-09-06 06:52:50 +02:00
Matthias
4e5153609e Merge pull request #7362 from freqtrade/improve-docs
improve clarity on data download requirements
2022-09-06 06:38:04 +02:00
Matthias
3b5c3a366e Merge pull request #7353 from freqtrade/dependabot/pip/develop/jsonschema-4.15.0
Bump jsonschema from 4.14.0 to 4.15.0
2022-09-06 06:21:10 +02:00
Timothy Pogue
a0d774fdc4 change default initial candle limit to 1500 2022-09-05 20:23:00 -06:00
Timothy Pogue
b949ea301c fix failed apiserver tests 2022-09-05 19:29:07 -06:00
Timothy Pogue
cd8455ccb7 Merge branch 'develop' into feat/externalsignals 2022-09-05 15:10:25 -06:00
Wagner Costa Santos
2c8e5b191b fix hyperopt - freqai 2022-09-05 17:43:55 -03:00
Wagner Costa Santos
5b826150df fix hyperopt - freqai 2022-09-05 17:43:28 -03:00
robcaulk
1ea703d527 remove download-data command 2022-09-05 22:20:38 +02:00
Timothy Pogue
8f261d8edf change from bytes to text in websocket, remove old logs 2022-09-05 13:47:17 -06:00
Matthias
9fb3517adc Fix non-resolvable freqai test
this test could never succeed in UI's as the name was constantly changing.
2022-09-05 21:08:01 +02:00
robcaulk
689b193240 improve clarity on data download requirements 2022-09-05 20:57:42 +02:00
Matthias
36e5c18fa6 Don't raise exception when a message is not implemented in telegram 2022-09-05 20:54:03 +02:00
dependabot[bot]
4628bfa580 Bump jsonschema from 4.14.0 to 4.15.0
Bumps [jsonschema](https://github.com/python-jsonschema/jsonschema) from 4.14.0 to 4.15.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.14.0...v4.15.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-05 17:30:55 +00:00
Matthias
545cfdf913 Merge pull request #7358 from freqtrade/dependabot/pip/develop/pycoingecko-3.0.0
Bump pycoingecko from 2.2.0 to 3.0.0
2022-09-05 19:30:06 +02:00
Matthias
fda0e547f2 Merge pull request #7356 from freqtrade/dependabot/pip/develop/python-telegram-bot-13.14
Bump python-telegram-bot from 13.13 to 13.14
2022-09-05 19:18:50 +02:00
dependabot[bot]
949f618d42 Bump python-telegram-bot from 13.13 to 13.14
Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 13.13 to 13.14.
- [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases)
- [Changelog](https://github.com/python-telegram-bot/python-telegram-bot/blob/v13.14/CHANGES.rst)
- [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v13.13...v13.14)

---
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>
2022-09-05 16:30:43 +00:00
Italo
9f5642fd97 fix hyperopt df preprocessing 2022-09-05 18:12:19 +02:00
Matthias
da183364f2 Merge pull request #7357 from freqtrade/dependabot/pip/develop/pandas-1.4.4
Bump pandas from 1.4.3 to 1.4.4
2022-09-05 16:53:36 +02:00
Matthias
205ebfc801 Merge pull request #7352 from freqtrade/dependabot/pip/develop/ccxt-1.93.3
Bump ccxt from 1.92.84 to 1.93.3
2022-09-05 16:41:41 +02:00
Matthias
8d61ee7dd7 Merge pull request #7355 from freqtrade/dependabot/pip/develop/fastapi-0.82.0
Bump fastapi from 0.81.0 to 0.82.0
2022-09-05 16:35:09 +02:00
Matthias
7d48d5cfc6 Merge pull request #7354 from freqtrade/dependabot/pip/develop/prompt-toolkit-3.0.31
Bump prompt-toolkit from 3.0.30 to 3.0.31
2022-09-05 16:16:49 +02:00
Matthias
523f9ebe84 Merge pull request #7351 from freqtrade/dependabot/pip/develop/pytest-7.1.3
Bump pytest from 7.1.2 to 7.1.3
2022-09-05 16:15:16 +02:00
Matthias
d829dbb177 Merge pull request #7350 from freqtrade/dependabot/pip/develop/psutil-5.9.2
Bump psutil from 5.9.1 to 5.9.2
2022-09-05 16:13:16 +02:00
Matthias
15383a03e6 Merge pull request #7349 from freqtrade/dependabot/pip/develop/arrow-1.2.3
Bump arrow from 1.2.2 to 1.2.3
2022-09-05 16:12:53 +02:00
dependabot[bot]
48dc1f2d88 Bump pycoingecko from 2.2.0 to 3.0.0
Bumps [pycoingecko](https://github.com/man-c/pycoingecko) from 2.2.0 to 3.0.0.
- [Release notes](https://github.com/man-c/pycoingecko/releases)
- [Changelog](https://github.com/man-c/pycoingecko/blob/master/CHANGELOG.md)
- [Commits](https://github.com/man-c/pycoingecko/compare/2.2.0...3.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-05 13:11:29 +00:00
dependabot[bot]
f5500350f9 Bump pandas from 1.4.3 to 1.4.4
Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.4.3 to 1.4.4.
- [Release notes](https://github.com/pandas-dev/pandas/releases)
- [Changelog](https://github.com/pandas-dev/pandas/blob/main/RELEASE.md)
- [Commits](https://github.com/pandas-dev/pandas/compare/v1.4.3...v1.4.4)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-05 13:11:24 +00:00
dependabot[bot]
6f6afca027 Bump fastapi from 0.81.0 to 0.82.0
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.81.0 to 0.82.0.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.81.0...0.82.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-05 13:10:53 +00:00
dependabot[bot]
90fbb79471 Bump prompt-toolkit from 3.0.30 to 3.0.31
Bumps [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) from 3.0.30 to 3.0.31.
- [Release notes](https://github.com/prompt-toolkit/python-prompt-toolkit/releases)
- [Changelog](https://github.com/prompt-toolkit/python-prompt-toolkit/blob/master/CHANGELOG)
- [Commits](https://github.com/prompt-toolkit/python-prompt-toolkit/compare/3.0.30...3.0.31)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-05 13:10:44 +00:00
dependabot[bot]
3d03856845 Bump ccxt from 1.92.84 to 1.93.3
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.92.84 to 1.93.3.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg)
- [Commits](https://github.com/ccxt/ccxt/compare/1.92.84...1.93.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-05 13:10:31 +00:00
dependabot[bot]
af7e4d7bf0 Bump pytest from 7.1.2 to 7.1.3
Bumps [pytest](https://github.com/pytest-dev/pytest) from 7.1.2 to 7.1.3.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/main/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/7.1.2...7.1.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-05 13:10:11 +00:00
dependabot[bot]
7cc8ac0a34 Bump psutil from 5.9.1 to 5.9.2
Bumps [psutil](https://github.com/giampaolo/psutil) from 5.9.1 to 5.9.2.
- [Release notes](https://github.com/giampaolo/psutil/releases)
- [Changelog](https://github.com/giampaolo/psutil/blob/master/HISTORY.rst)
- [Commits](https://github.com/giampaolo/psutil/compare/release-5.9.1...release-5.9.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-05 13:10:02 +00:00
dependabot[bot]
a035a69a61 Bump arrow from 1.2.2 to 1.2.3
Bumps [arrow](https://github.com/arrow-py/arrow) from 1.2.2 to 1.2.3.
- [Release notes](https://github.com/arrow-py/arrow/releases)
- [Changelog](https://github.com/arrow-py/arrow/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/arrow-py/arrow/compare/1.2.2...1.2.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-09-05 13:09:53 +00:00
Matthias
52b20fd4b7 Merge pull request #7346 from freqtrade/update-freqai-tests
fix broken CI
2022-09-05 06:18:21 +02:00
robcaulk
c8a9ac900c fix broken CI 2022-09-05 00:52:26 +02:00
robcaulk
78d01810ed reduce code redundancy, ensure live always gets the latest data 2022-09-05 00:12:00 +02:00
Wagner Costa Santos
d43ed186fc fix issue with freqai backtesting at slice dataframe 2022-09-05 00:12:00 +02:00
Timothy Pogue
8a08f8ff8d revert rpc manager 2022-09-04 10:27:34 -06:00
Timothy Pogue
07f806a314 minor improvements, fixes, old config+constant removal 2022-09-04 10:22:10 -06:00
Timothy Pogue
1601868854 dataprovider fix, updated config example 2022-09-04 09:42:43 -06:00
Robert Caulk
956ea43e55 Merge pull request #7340 from wizrds/sigint-freqai
Support SIGINT in FreqAI
2022-09-04 16:43:36 +02:00
robcaulk
ec76214d02 backup historical predictions pickle and load the backup in case of corruption 2022-09-04 15:56:07 +02:00
Timothy Pogue
3b5e5fc57b fix method name in dummy class 2022-09-03 14:10:23 -06:00
Robert Caulk
8545d74378 Merge pull request #7331 from th0rntwig/pca
Normalise PCA space
2022-09-03 21:49:54 +02:00
Timothy Pogue
dae3b3d86a support shutting down freqai 2022-09-03 13:24:14 -06:00
Matthias
16573b19e3 Fix migration syntax error 2022-09-03 19:51:44 +02:00
robcaulk
5cfb4154eb revert all changes in normalize_data() 2022-09-03 19:48:30 +02:00
Robert Caulk
63514b0443 Merge pull request #7325 from wagnercosta/develop
Improve Freqai backtesting performance
2022-09-03 19:28:04 +02:00
robcaulk
c21808ff98 remove metadata redundancy, fix pca bug 2022-09-03 16:54:30 +02:00
robcaulk
fa8d5b9834 add documentation for noise_standard_deviation` 2022-09-03 16:05:18 +02:00
robcaulk
c9be66b5b6 increase test coverage for dk, improve function naming, extra cleaning 2022-09-03 15:52:29 +02:00
Matthias
ed4cc18cdd Migration to check order funding fee 2022-09-03 15:19:40 +02:00
Matthias
b95b3d8391 Update test to actually test funding fee appliance 2022-09-03 15:09:50 +02:00
Matthias
0f483ee31f Use "since last order" approach for live as well. 2022-09-03 15:01:42 +02:00
Matthias
0c6a02687a Don't calculate funding fees if we're not going to use them. 2022-09-03 15:01:42 +02:00
Matthias
df50b1928d Fix funding fee calculation for backtesting 2022-09-03 15:01:42 +02:00
Matthias
80b5f035ab Remove typo in log message 2022-09-03 15:01:28 +02:00
robcaulk
599c1c79fb reorganized backtest utilities, test new functionality, improve/update doc 2022-09-03 14:00:01 +02:00
Matthias
be192fae91 Test should use proper Order objects 2022-09-03 10:54:58 +02:00
Matthias
966de19611 Improve test resiliance by properly setting Order object 2022-09-03 08:16:33 +02:00
Wagner Costa Santos
af5460cebf Add option to keep models only in memory for backtest 2022-09-02 22:01:53 -03:00
Timothy Pogue
05cbcf834c minor logging changes 2022-09-02 16:01:33 -06:00
Timothy Pogue
cf917ad2f5 initial candle request limit, better error reporting, split up _handle_producer_connection 2022-09-02 15:05:16 -06:00
Matthias
b26126cb57 Don't use ticker['symbol'] but use "pair" instead
closes #7262
2022-09-02 20:09:30 +02:00
Matthias
6a5774b476 Merge pull request #7329 from epigramx/epigramx-fee-docs-patch
Make the recommendation for Binance/Kucoin blacklisting more accurate.
2022-09-02 19:58:38 +02:00
Matthias
a948e51389 Update futures docs to define pair namings
#7334, #7136, ...
2022-09-02 19:56:12 +02:00
Timothy Pogue
5b0b802f31 hybrid json ws serializer 2022-09-02 00:05:36 -06:00
Timothy Pogue
eb4cd6ba82 split initial data into separate requests 2022-09-01 23:52:13 -06:00
Timothy Pogue
dccde88c83 fix dataframe serializing 2022-09-01 23:15:03 -06:00
Matthias
b53791fef2 Futures volumepairlist to account for contract size 2022-09-02 07:11:32 +02:00
Timothy Pogue
00f35f4870 remove old constant, add initial_data requesting, minor changes 2022-09-01 20:06:36 -06:00
th0rntwig
11b2bc269e Added missing s 2022-09-01 22:37:32 +02:00
th0rntwig
3f8400df10 Normalise PCA space 2022-09-01 21:51:33 +02:00
Matthias
11fbfd3402 Remove unnecessary assignment 2022-09-01 19:39:20 +02:00
epigramx
61d5fc0e08 Make the recommendation for Binance/Kucoin blacklisting more accurate.
Now that a recent bug regarding selling BNB is fixed, it should be safe to trade it, but with a warning that the user may have to manually maintain extra BNB. 
Also the old text implied those features are always unabled so this texts makes it clear those fee-related features can be also disabled.
I'm not sure if it's still true that an "eaten by fees" position becomes unsellable but I left that as it is.
2022-09-01 17:22:34 +03:00
Wagner Costa Santos
d6e115178a refactoring freqai backtesting - remove duplicate code 2022-09-01 07:09:23 -03:00
Matthias
f3c73189d5 Remove pointless default on wallet_balance argument 2022-09-01 06:49:51 +02:00
Matthias
ba2eb7cf0f Fix BNB fee bug when selling
thanks @epigramx, for reporting and for the detailed data.
2022-09-01 06:42:51 +02:00
Timothy Pogue
57e9078727 update example config 2022-08-31 14:44:52 -06:00
Wagner Costa Santos
44d3a9140d Merge branch 'develop' of https://github.com/wagnercosta/freqtrade into develop 2022-08-31 15:37:06 -03:00
Wagner Costa Santos
7bed0450d2 pr review - refactoring backtesting freqai 2022-08-31 15:36:29 -03:00
Timothy Pogue
c72a2c26c7 remove external pairlist 2022-08-31 12:06:24 -06:00
Timothy Pogue
6e8abf8674 add producer name to required fields in config 2022-08-31 11:58:58 -06:00
wagnercosta
3d4497467c Merge branch 'freqtrade:develop' into develop 2022-08-31 14:47:06 -03:00
Matthias
3d4ad1de4c Merge pull request #7244 from freqtrade/move_datadownload
extract download-data from freqai to prepare for future async changes
2022-08-31 19:46:14 +02:00
Timothy Pogue
865b34cd6f add producer names 2022-08-31 11:43:02 -06:00
Timothy Pogue
510cf4f305 remove data waiting, remove explicit analyzing of external df 2022-08-31 10:40:26 -06:00
Wagner Costa Santos
df51da22ee refactoring freqai backtesting 2022-08-31 11:23:48 -03:00
Matthias
57ff6f8ac5 Init timerange object properly 2022-08-31 10:28:31 +00:00
Matthias
13ccd940d5 Remove startup_candle_count from freqai sample config to avoid confusion 2022-08-31 10:27:08 +00:00
Matthias
7ba4fda5d7 Implement PR feedback 2022-08-31 10:26:47 +00:00
Matthias
a88ffd2c9d Merge branch 'develop' into move_datadownload 2022-08-31 10:23:45 +00:00
Matthias
4aec2db14d Remove isClose from tests in favor of pytest.approx 2022-08-31 08:25:56 +00:00
Timothy Pogue
115a901773 minor fix for conditional in handle func 2022-08-30 19:34:43 -06:00
Timothy Pogue
ddc45ce2eb message handling fix, data waiting fix 2022-08-30 19:30:14 -06:00
Timothy Pogue
346e73dd75 client implementation, minor fixes 2022-08-30 19:21:34 -06:00
Matthias
958a4565db Merge pull request #7313 from freqtrade/new_release
New release 2022.8
2022-08-30 23:01:19 +02:00
Matthias
10e0d53860 Simplify 2 more tests 2022-08-30 20:49:53 +02:00
Matthias
c9aa09ec89 Simplify base fee handling 2022-08-30 20:46:06 +02:00
robcaulk
7e8e29e42d use continuous value for inlier_metric 2022-08-30 20:41:37 +02:00
robcaulk
0b8482360f add documentation for inlier metric 2022-08-30 20:32:49 +02:00
Timothy Pogue
418bd26a80 minor fixes, rework consumer request, update requirements.txt 2022-08-30 11:04:16 -06:00
robcaulk
7f52908e87 ensure the lost points are prepended for FreqUI 2022-08-30 18:55:58 +02:00
robcaulk
a58dd0bbf9 add noise feature, improve docstrings 2022-08-30 18:26:24 +02:00
robcaulk
b11742a4c5 integrate inlier metric function 2022-08-30 18:26:24 +02:00
th0rntwig
d3cb211283 Add inlier metric computation 2022-08-30 18:26:24 +02:00
th0rntwig
50e2808667 Fix bug in DI (#7321) 2022-08-30 12:54:39 +02:00
Timothy Pogue
47f7c384fb consumer subscriptions, fix serializer bug 2022-08-29 15:48:29 -06:00
Matthias
71846ecbf2 Partially revert prior commit, use correct exchange class 2022-08-29 21:51:10 +02:00
Timothy Pogue
7952e0df25 initial rework separating server and client impl 2022-08-29 13:41:15 -06:00
Matthias
eee2071e32 Fix non-testing liquidation test 2022-08-29 21:04:09 +02:00
Matthias
efc3b39fb8 Improve config overwriting wording
#7181
2022-08-29 20:01:03 +02:00
Matthias
93cbfc5f29 Merge pull request #7315 from freqtrade/reverse-train-test-order
allow users to properly reverse train-test data ordering
2022-08-29 15:27:28 +02:00
robcaulk
62c0a174c8 allow users to properly reverse train-test data ordering 2022-08-29 11:04:58 +02:00
Matthias
c54484dad5 Merge pull request #7305 from freqtrade/dependabot/pip/develop/mkdocs-material-8.4.2
Bump mkdocs-material from 8.4.1 to 8.4.2
2022-08-29 08:08:33 +02:00
Matthias
db3d972d47 Merge pull request #7306 from freqtrade/dependabot/pip/develop/fastapi-0.81.0
Bump fastapi from 0.79.1 to 0.81.0
2022-08-29 08:08:14 +02:00
Matthias
25a7f44856 Merge pull request #7312 from freqtrade/dependabot/pip/develop/ccxt-1.92.84
Bump ccxt from 1.92.52 to 1.92.84
2022-08-29 08:07:53 +02:00
Matthias
b7b87c398b Merge pull request #7308 from freqtrade/dependabot/pip/develop/nbconvert-7.0.0
Bump nbconvert from 6.5.3 to 7.0.0
2022-08-29 07:58:46 +02:00
Matthias
27a9f98d5f Simplify liquidation price structure, improve test cov 2022-08-29 07:04:48 +02:00
Matthias
226fa5d93c Simplify liquidation price calling structure 2022-08-29 07:04:48 +02:00
dependabot[bot]
56cd80926a Bump fastapi from 0.79.1 to 0.81.0
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.79.1 to 0.81.0.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.79.1...0.81.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-29 04:52:12 +00:00
Matthias
f664ebd262 Merge pull request #7309 from freqtrade/dependabot/pip/develop/scipy-1.9.1
Bump scipy from 1.9.0 to 1.9.1
2022-08-29 06:51:37 +02:00
Matthias
9b5f85b970 Merge pull request #7310 from freqtrade/dependabot/pip/develop/uvicorn-0.18.3
Bump uvicorn from 0.18.2 to 0.18.3
2022-08-29 06:51:19 +02:00
Matthias
ebbb2cc552 Merge pull request #7311 from freqtrade/dependabot/pip/develop/orjson-3.8.0
Bump orjson from 3.7.12 to 3.8.0
2022-08-29 06:50:44 +02:00
Matthias
a0db6652a7 Merge pull request #7307 from freqtrade/dependabot/pip/develop/urllib3-1.26.12
Bump urllib3 from 1.26.11 to 1.26.12
2022-08-29 06:50:30 +02:00
Matthias
4def3678b7 Merge pull request #7298 from freqtrade/tif_align
align TimeInForce to ccxt usage
2022-08-29 06:36:51 +02:00
Matthias
8be8a12cc4 Merge pull request #7260 from JohanVlugt/develop
Example FreqAI hybrid strategy
2022-08-29 06:34:45 +02:00
Matthias
40c00d2d8f Version bump dev version to 2022.9 2022-08-29 06:34:20 +02:00
Matthias
2403a03fcb Version bump 2022.8 2022-08-29 06:28:53 +02:00
Matthias
a01402fa46 Merge branch 'stable' into develop 2022-08-29 06:28:21 +02:00
dependabot[bot]
d7189847a7 Bump ccxt from 1.92.52 to 1.92.84
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.92.52 to 1.92.84.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg)
- [Commits](https://github.com/ccxt/ccxt/compare/1.92.52...1.92.84)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-29 03:01:59 +00:00
dependabot[bot]
d734f7612f Bump orjson from 3.7.12 to 3.8.0
Bumps [orjson](https://github.com/ijl/orjson) from 3.7.12 to 3.8.0.
- [Release notes](https://github.com/ijl/orjson/releases)
- [Changelog](https://github.com/ijl/orjson/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ijl/orjson/compare/3.7.12...3.8.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-29 03:01:50 +00:00
dependabot[bot]
3a7441a27d Bump uvicorn from 0.18.2 to 0.18.3
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.18.2 to 0.18.3.
- [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.18.2...0.18.3)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-29 03:01:41 +00:00
dependabot[bot]
49dd1c1d49 Bump scipy from 1.9.0 to 1.9.1
Bumps [scipy](https://github.com/scipy/scipy) from 1.9.0 to 1.9.1.
- [Release notes](https://github.com/scipy/scipy/releases)
- [Commits](https://github.com/scipy/scipy/compare/v1.9.0...v1.9.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-29 03:01:36 +00:00
dependabot[bot]
e9f46f4768 Bump nbconvert from 6.5.3 to 7.0.0
Bumps [nbconvert](https://github.com/jupyter/nbconvert) from 6.5.3 to 7.0.0.
- [Release notes](https://github.com/jupyter/nbconvert/releases)
- [Commits](https://github.com/jupyter/nbconvert/compare/6.5.3...7.0.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-29 03:01:30 +00:00
dependabot[bot]
0af4bd2944 Bump urllib3 from 1.26.11 to 1.26.12
Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.11 to 1.26.12.
- [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/1.26.11...1.26.12)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-29 03:01:25 +00:00
dependabot[bot]
e9e872ca20 Bump mkdocs-material from 8.4.1 to 8.4.2
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 8.4.1 to 8.4.2.
- [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/8.4.1...8.4.2)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-29 03:01:18 +00:00
th0rntwig
8b0cfe1236 Reduce image sizes in freqai doc (#7304) 2022-08-28 23:27:12 +02:00
Timothy Pogue
8c4e68b8eb updated example configs 2022-08-28 13:00:52 -06:00
Robert Caulk
39a739eadb Merge pull request #7296 from th0rntwig/dbscan
Improve MinPts calculation in DBSCAN, add outlier protection, and add data_kitchen tests
2022-08-28 14:37:47 +02:00
robcaulk
a44a235b56 isort imports in tests/freqai 2022-08-28 13:47:01 +02:00
robcaulk
6634229cc1 appease the flake8 gods 2022-08-28 13:21:29 +02:00
robcaulk
fcb5d1cb5a remove debugging flag 2022-08-28 13:01:39 +02:00
robcaulk
dd628eb525 add tests for outlier detection and removal functions 2022-08-28 12:56:39 +02:00
robcaulk
1e41c773a0 fix outlier protection 2022-08-28 12:11:29 +02:00
robcaulk
22b42e91f3 add new parameter to freqai doc 2022-08-28 11:53:24 +02:00
smarmau
ff3a4995c1 remove unnecessary code 2022-08-28 11:45:20 +02:00
Matthias
b9f35cadb3 add /stopentry alias for /stopbuy 2022-08-28 11:37:22 +02:00
smarmau
005594c29c simplify hybrid template 2022-08-28 11:29:48 +02:00
Matthias
59a723aec8 Add /health to rest client
discovered in #7299
2022-08-27 15:12:04 +02:00
th0rntwig
71f7d68783 Fixed mypy error 2022-08-27 12:44:55 +02:00
Matthias
c61b986c3d FTX - support time_in_force (and PO ordertype)
closes #7175
2022-08-27 10:30:38 +02:00
Matthias
104a73025d Uppercase TimeInForce (align with ccxt) 2022-08-27 10:30:06 +02:00
Matthias
6686489c06 Merge pull request #7258 from freqtrade/feat/hyp_optinal_indicator
Add flag to move hyperopt populate_indicators to epoch
2022-08-27 09:21:16 +02:00
Matthias
c3e74e6e8d Improve doc wording 2022-08-27 08:55:29 +02:00
Matthias
2b70c3d0c0 support price callback for partial exits in bt
This will align results to how live works.
closes #7292
2022-08-27 08:50:09 +02:00
Timothy Pogue
05ca673883 Catch status code errors 2022-08-27 00:06:03 -06:00
Timothy Pogue
fcceb744c5 Add janus to requirements.txt 2022-08-26 23:43:05 -06:00
Timothy Pogue
2b5f067877 Refactoring, minor improvements, data provider improvements 2022-08-26 23:40:13 -06:00
Matthias
9204f01312 Don't lock pairs on partial exit 2022-08-27 07:23:02 +02:00
elintornquist
86c5ac44e4 Add outlier percentage check 2022-08-26 23:05:07 +02:00
Timothy Pogue
a998d6d773 fix tests 2022-08-26 14:52:15 -06:00
Matthias
2ef4534fee Fix ccxt / longrun tests 2022-08-26 20:44:36 +02:00
Matthias
efe4fd3e24 Add libgomp1 to dockerfile 2022-08-26 20:21:19 +02:00
Matthias
01126c43f7 Fix liquidation price tier calculation
closes #7294
2022-08-26 20:14:24 +02:00
Matthias
753d1b2aad Update leverage tier terminology to be clear and aligned with ccxt 2022-08-26 19:34:51 +02:00
elintornquist
b2d664c63c Change MinPts calculation 2022-08-26 18:57:27 +02:00
robcaulk
bb3523f383 download data homogeneously across timeframes 2022-08-26 18:51:42 +02:00
robcaulk
e7261cf515 add freqai utils.py file 2022-08-26 15:30:28 +02:00
robcaulk
65b552e310 make docs reflect reality, move download_all_data to new utils.py file, automatic startup_candle detection 2022-08-26 15:30:01 +02:00
robcaulk
4b7e640f31 reduce code duplication, optimize auto data download per tf 2022-08-26 13:56:44 +02:00
Matthias
53d46a0385 align max_entry_position_adjustment behavior of backtesting to live
closes #7293
2022-08-25 20:36:17 +02:00
Matthias
1fd223c815 Update --prepend help string
closes #7290
2022-08-25 17:03:41 +02:00
Matthias
f2a356a80c Fix some imports 2022-08-25 07:08:58 +02:00
Matthias
6636f17e0f Simplify usage of amount_to_contract precision 2022-08-25 07:08:22 +02:00
Matthias
9e48e6a40b Update docs about precision limits in backtesting 2022-08-25 06:50:10 +02:00
Matthias
205ab26e92 Remove TODO in test 2022-08-25 06:50:10 +02:00
Matthias
70df037690 Improve test precision 2022-08-25 06:50:10 +02:00
Timothy Pogue
3e786a9b8b added example configs 2022-08-24 22:44:22 -06:00
Timothy Pogue
d474111a65 Renamed to external signals, controller class refactored 2022-08-24 22:42:29 -06:00
Timothy Pogue
592373f096 Remove pairlist waiting, add .db files to .gitignore 2022-08-24 18:30:30 -06:00
Matthias
32faad9333 Fix backtest calculation problem with DCA
closes #7287
2022-08-24 20:36:08 +02:00
Matthias
a6d78a8615 initialize Since parameter properly
closes #7285
2022-08-23 06:43:04 +02:00
Matthias
fe7108ae75 Convert amount to contracts before comparing for close
closes #7279
2022-08-23 06:37:38 +02:00
Matthias
78b161e14c add contract_size to database 2022-08-23 06:37:38 +02:00
Matthias
6036018f35 Extract contracts_to_amount and amount_to_contracts to standalone functions 2022-08-23 06:37:38 +02:00
Matthias
1b0f37a93c Fix documentation typo 2022-08-23 06:37:38 +02:00
Matthias
5f38a574ce Add okx broker id 2022-08-23 06:37:38 +02:00
th0rntwig
5ce1c69803 Improve DBSCAN epsilon identification (#7269)
* Improve DBSCAN epsilon identification
2022-08-22 19:57:20 +02:00
Matthias
60ba921f56 Merge pull request #7282 from freqtrade/mem-leak-fix
Plug mem leak, add training timer
2022-08-22 19:36:52 +02:00
robcaulk
ac42c0153d deprecate indicator_max_period_candles, automatically compute startup candles for FreqAI backtesting. 2022-08-22 18:19:07 +02:00
robcaulk
96d8882f1e Plug mem leak, add training timer 2022-08-22 13:30:30 +02:00
Matthias
f55d5ffd8c Don't fail when --strategy-path is not a valid directory.
closes #7264
2022-08-22 09:20:14 +00:00
Matthias
914b6247e4 Merge pull request #7278 from freqtrade/dependabot/pip/develop/ccxt-1.92.52
Bump ccxt from 1.92.20 to 1.92.52
2022-08-22 08:41:52 +02:00
Matthias
da87e9cbb3 Merge pull request #7275 from freqtrade/dependabot/pip/develop/types-requests-2.28.9
Bump types-requests from 2.28.8 to 2.28.9
2022-08-22 08:41:34 +02:00
Matthias
484b147a89 Merge pull request #7277 from freqtrade/dependabot/pip/develop/time-machine-2.8.1
Bump time-machine from 2.7.1 to 2.8.1
2022-08-22 07:13:05 +02:00
Timothy Pogue
4fa01548f6 Remove old var from strategy interface 2022-08-21 22:49:42 -06:00
Timothy Pogue
6f5478cc02 DataFrame transmission, strategy follower logic 2022-08-21 22:45:36 -06:00
Matthias
015be770c3 ccxt now defaults to base volume for all markets 2022-08-22 06:42:14 +02:00
Matthias
93d2f7fc85 types-requests bump pre-commit 2022-08-22 06:37:26 +02:00
Matthias
7844157a90 Merge pull request #7276 from freqtrade/dependabot/pip/develop/jsonschema-4.14.0
Bump jsonschema from 4.9.1 to 4.14.0
2022-08-22 06:31:29 +02:00
Matthias
6e046884af Merge pull request #7273 from freqtrade/dependabot/pip/develop/fastapi-0.79.1
Bump fastapi from 0.79.0 to 0.79.1
2022-08-22 06:25:35 +02:00
Matthias
a784f63e9a Merge pull request #7274 from freqtrade/dependabot/pip/develop/mkdocs-material-8.4.1
Bump mkdocs-material from 8.4.0 to 8.4.1
2022-08-22 06:24:22 +02:00
dependabot[bot]
ff9ed1abad Bump ccxt from 1.92.20 to 1.92.52
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.92.20 to 1.92.52.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/exchanges.cfg)
- [Commits](https://github.com/ccxt/ccxt/compare/1.92.20...1.92.52)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-22 03:02:25 +00:00
dependabot[bot]
354d3c0cda Bump time-machine from 2.7.1 to 2.8.1
Bumps [time-machine](https://github.com/adamchainz/time-machine) from 2.7.1 to 2.8.1.
- [Release notes](https://github.com/adamchainz/time-machine/releases)
- [Changelog](https://github.com/adamchainz/time-machine/blob/main/HISTORY.rst)
- [Commits](https://github.com/adamchainz/time-machine/compare/2.7.1...2.8.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-22 03:02:10 +00:00
dependabot[bot]
eeb177110e Bump jsonschema from 4.9.1 to 4.14.0
Bumps [jsonschema](https://github.com/python-jsonschema/jsonschema) from 4.9.1 to 4.14.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.9.1...v4.14.0)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-22 03:02:03 +00:00
dependabot[bot]
70848a258d Bump types-requests from 2.28.8 to 2.28.9
Bumps [types-requests](https://github.com/python/typeshed) from 2.28.8 to 2.28.9.
- [Release notes](https://github.com/python/typeshed/releases)
- [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>
2022-08-22 03:01:59 +00:00
dependabot[bot]
3958e53aaa Bump mkdocs-material from 8.4.0 to 8.4.1
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 8.4.0 to 8.4.1.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/8.4.0...8.4.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-22 03:01:55 +00:00
dependabot[bot]
dfa7d1fc27 Bump fastapi from 0.79.0 to 0.79.1
Bumps [fastapi](https://github.com/tiangolo/fastapi) from 0.79.0 to 0.79.1.
- [Release notes](https://github.com/tiangolo/fastapi/releases)
- [Commits](https://github.com/tiangolo/fastapi/compare/0.79.0...0.79.1)

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

Signed-off-by: dependabot[bot] <support@github.com>
2022-08-22 03:01:51 +00:00
Matthias
2dc34779d5 Fix line length 2022-08-21 18:07:41 +02:00
Matthias
f6d832c6d9 Add get_option to expose ft_has via method 2022-08-21 17:51:46 +02:00
Matthias
87a3115073 Add get_open_trade_count() to simplify getting open trade count. 2022-08-21 17:08:27 +02:00
Matthias
085f81ec9e Fix header indent on stake-size-management 2022-08-21 08:24:14 +02:00
Matthias
0ec38e0cfd Fix typo in docs 2022-08-21 08:23:07 +02:00
Matthias
6189aa817c Fix HybridExample formatting 2022-08-20 19:50:18 +02:00
robcaulk
64b0834437 add credit in docstring 2022-08-20 17:04:38 +02:00
robcaulk
90c03178b1 provide user directions, clean up strategy, remove unnecessary code. 2022-08-20 17:02:18 +02:00
Matthias
cdd4745693 Merge pull request #7263 from freqtrade/okx_cache_tiers
Okx cache tiers
2022-08-20 15:18:13 +02:00
Matthias
1fb2e9558f Disable caching of leverage tiers in ccxt compat methods 2022-08-20 14:39:11 +02:00
Matthias
5b3f031590 Use hyperopt safe amount precision method 2022-08-20 14:13:15 +02:00
Matthias
4511634f3a improve test coverage 2022-08-20 14:03:47 +02:00
Matthias
738e95b875 Add tests for leverage tiers caching 2022-08-20 13:54:54 +02:00
Matthias
b6e8b9df35 Use cached leverage tiers 2022-08-20 13:01:58 +02:00
Matthias
52ec0d1046 Update binance Leverage tiers 2022-08-20 11:53:15 +02:00
Matthias
7563050f17 Realign tests to precision backtesting 2022-08-20 11:47:15 +02:00
Matthias
0da0600836 Have backtesting respect tradable size
closes #7161
2022-08-20 11:41:11 +02:00
Matthias
54ddc1a4c2 Add --tradingmode alias 2022-08-20 11:24:20 +02:00
Matthias
aa3da092a0 Dont' use classProperty - that's not supported on 3.8 2022-08-20 10:55:52 +02:00
Matthias
63efb3ff3e Merge pull request #7245 from th0rntwig/improve-doc
Restructure and improve doc, add fig
2022-08-20 09:48:50 +02:00
Matthias
665cf4431d Add explicit test cov. for .range behavior 2022-08-20 08:41:31 +02:00
Matthias
01d45ed12e Merge pull request #7257 from freqtrade/feat/list-pair-time
Get min/max data in list-data command
2022-08-20 08:16:52 +02:00
Matthias
7b8b73e651 Merge pull request #7243 from lolongcovas/newbranch_test
Improve PCA and pairwise distance calcs
2022-08-20 08:13:40 +02:00
Timothy Pogue
739b68f8fd ExternalPairList plugin 2022-08-19 22:40:01 -06:00
elintornquist
692c6bf1fd Added and updated figs and fig descriptions 2022-08-19 22:23:26 +02:00
robcaulk
88d6a7fbff additional edits 2022-08-19 22:23:26 +02:00
elintornquist
9c6b745f06 Restructure and improve doc, add fiq 2022-08-19 22:23:26 +02:00
Johan van der Vlugt
b44bd0171c Example Classifier strat 2022-08-19 19:10:37 +02:00
Matthias
b9d48c3278 use numbers in HyperoptState properly ... 2022-08-19 15:40:06 +02:00
Matthias
1389c8f5b6 Update documentation with show-timerange option 2022-08-19 15:34:10 +02:00
Matthias
733f716819 Update documentation 2022-08-19 15:22:43 +02:00
Matthias
bc359675a2 Add --analyze-per-epoch - moving populate_analysis to the epoch process 2022-08-19 15:19:43 +02:00
Matthias
09f8904545 Extract analysis to separate method 2022-08-19 15:12:55 +02:00
Matthias
08ef5ad2d8 Add HyperoptState enum and container class 2022-08-19 15:11:43 +02:00
Matthias
1c6f966579 Hyperopt: simplify parameter "can_optimize" handling 2022-08-19 15:03:03 +02:00
Matthias
b7553d20d4 Get min/max data in list-data command 2022-08-19 13:45:55 +02:00
longyu
521381ebf0 undo example strategy newline 2022-08-19 12:40:03 +02:00
longyu
790534e0f8 Merge branch 'newbranch_test' of github.com:lolongcovas/freqtrade into newbranch_test 2022-08-19 12:39:19 +02:00
longyu
cfa5b3f12c add new line 2022-08-19 12:39:08 +02:00
longyu
277245c69d remove line 2022-08-19 12:39:00 +02:00
Timothy Pogue
6834db11f3 minor improvements and pairlist data transmission 2022-08-19 00:06:19 -06:00
Timothy Pogue
9f6bba40af initial concept for replicate, basic leader and follower logic 2022-08-18 20:10:58 +02:00
longyu
1fada53ddd remove newline 2022-08-18 19:40:00 +02:00
longyu
f70b0bab80 remove line 2022-08-17 23:49:20 +02:00
robcaulk
88dd9920ea sort imports for isort 2022-08-17 16:38:09 +02:00
robcaulk
5155afb4e7 clean up code remnants 2022-08-17 15:22:48 +02:00
robcaulk
0c34104e45 extract download-data from freqai to prepare for future async changes 2022-08-17 15:18:44 +02:00
longyu
9c38c27eed ignore sample itself distance for avg_mean_dist computation 2022-08-17 15:09:57 +02:00
longyu
72c34291e3 newline 2022-08-17 15:09:10 +02:00
Matthias
987bbb8e12 Merge pull request #7176 from Jetsukda/patch-1
Edit Typo Custom-stake-amount
2022-08-05 06:23:00 +02:00
OGSK
c3d06257be Edit index of custom_stake_amount 2022-08-05 09:36:26 +07:00
OGSK
8bf056ca39 Edit Typo Custom-stake-amount
Edit Custom-stake-amount to `custom_stake_amount`
2022-08-05 00:28:28 +07:00
Matthias
046ae18411 Merge pull request #7144 from freqtrade/new_release
New release 2022.7
2022-07-30 16:06:37 +02:00
Matthias
28b4773083 Version bump 2022.7 2022-07-30 09:21:29 +02:00
Matthias
d4e8ab1cac Merge branch 'stable' into new_release 2022-07-30 09:21:05 +02:00
Matthias
2db5cc177d Merge pull request #7029 from freqtrade/new_release
New release 2022.6
2022-07-03 19:42:24 +02:00
Matthias
c1d4078518 Version bump to 2022.6 2022-07-03 15:04:38 +02:00
Matthias
d25ec6d0b8 Merge branch 'stable' into new_release 2022-07-03 15:04:16 +02:00
Matthias
c57db0a330 Version bump 2022.5.1 2022-06-01 06:34:28 +02:00
Matthias
f5087a82dc Merge branch 'stable' into new_release 2022-06-01 06:33:42 +02:00
Matthias
eed0d67005 Merge pull request #6893 from freqtrade/new_release
New release 2022.5
2022-05-28 13:46:24 +02:00
Matthias
a1d54f5ae0 Version bump 2022.5 2022-05-28 09:49:58 +02:00
Matthias
a4a7c6536d Merge branch 'stable' into new_release 2022-05-28 09:49:46 +02:00
Matthias
340a97d1df Merge pull request #6811 from DJCrashdummy/patch-1
corrected minor "typo" in formatting
2022-05-10 19:16:40 +02:00
DJCrashdummy
fab197edf2 corrected minor "typo" in formatting 2022-05-10 10:33:04 +00:00
Matthias
851c5dad30 Version bump 2022.4.2 2022-05-03 20:37:29 +02:00
Matthias
5b76ae452f Fix fee handling for futures trades 2022-05-03 20:35:30 +02:00
Matthias
2c750fdb09 Reduce no stake amount verbosity
closes #6768
2022-05-03 20:35:22 +02:00
Matthias
e7f5252074 Version bump 2022.4.1 2022-05-01 16:49:11 +02:00
Matthias
dfbd1c34c4 Merge pull request #6755 from freqtrade/new_release
New release 2022.4
2022-05-01 14:51:39 +02:00
Matthias
7615c4e904 Version bump 2022.4 2022-05-01 11:19:32 +02:00
Matthias
e9b78bf3ae Merge branch 'stable' into new_release 2022-05-01 11:19:17 +02:00
Matthias
2e397a88e1 Merge pull request #6592 from freqtrade/new_release
New release 2022.3
2022-03-27 15:51:58 +02:00
Matthias
fe6c62e144 Version bump 2022.3 2022-03-27 15:27:16 +02:00
Matthias
f0db721f05 Merge branch 'stable' into new_release 2022-03-27 15:09:06 +02:00
Matthias
4d8d30ea39 Version bump to 2022.2.2 2022-03-21 06:34:33 +01:00
Matthias
e90e3cead0 Map usdt fiat to correct coingecko fiat 2022-03-21 06:34:20 +01:00
Matthias
a568548192 Merge pull request #6464 from freqtrade/new_release
New release 2022.2.1
2022-02-26 08:57:42 +01:00
Matthias
f9d10a7fad Version bump 2022.2.1 2022-02-26 08:35:50 +01:00
Matthias
cbc2b00ee6 Merge branch 'stable' into new_release 2022-02-26 08:35:31 +01:00
Matthias
8f7b857ae9 Merge pull request #6459 from freqtrade/new_release
New release 2022.2
2022-02-25 15:14:27 +01:00
Matthias
e88b022cd4 Version bump 2022.2 2022-02-25 12:07:09 +01:00
Matthias
bfb738f69f Merge branch 'stable' into new_release 2022-02-25 12:06:11 +01:00
Matthias
00dd8e76ee Merge pull request #6416 from froggleston/patch-2
Update windows_installation.md
2022-02-25 11:44:40 +01:00
236 changed files with 10068 additions and 3432 deletions

View File

@@ -24,7 +24,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ ubuntu-18.04, ubuntu-20.04, ubuntu-22.04 ] os: [ ubuntu-18.04, ubuntu-20.04, ubuntu-22.04 ]
python-version: ["3.8", "3.9", "3.10"] python-version: ["3.8", "3.9", "3.10.6"]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -121,7 +121,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ macos-latest ] os: [ macos-latest ]
python-version: ["3.8", "3.9", "3.10"] python-version: ["3.8", "3.9", "3.10.6"]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -205,7 +205,7 @@ jobs:
strategy: strategy:
matrix: matrix:
os: [ windows-latest ] os: [ windows-latest ]
python-version: ["3.8", "3.9", "3.10"] python-version: ["3.8", "3.9", "3.10.6"]
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
@@ -272,6 +272,16 @@ jobs:
pip install pyaml pip install pyaml
python build_helpers/pre_commit_update.py python build_helpers/pre_commit_update.py
pre-commit:
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: "3.10"
- uses: pre-commit/action@v3.0.0
docs_check: docs_check:
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
@@ -302,7 +312,7 @@ jobs:
# Notify only once - when CI completes (and after deploy) in case it's successfull # Notify only once - when CI completes (and after deploy) in case it's successfull
notify-complete: notify-complete:
needs: [ build_linux, build_macos, build_windows, docs_check, mypy_version_check ] needs: [ build_linux, build_macos, build_windows, docs_check, mypy_version_check, pre-commit ]
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
# Discord notification can't handle schedule events # Discord notification can't handle schedule events
if: (github.event_name != 'schedule') if: (github.event_name != 'schedule')
@@ -327,7 +337,7 @@ jobs:
webhookUrl: ${{ secrets.DISCORD_WEBHOOK }} webhookUrl: ${{ secrets.DISCORD_WEBHOOK }}
deploy: deploy:
needs: [ build_linux, build_macos, build_windows, docs_check, mypy_version_check ] needs: [ build_linux, build_macos, build_windows, docs_check, mypy_version_check, pre-commit ]
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade' if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade'
@@ -397,15 +407,6 @@ jobs:
run: | run: |
build_helpers/publish_docker_multi.sh build_helpers/publish_docker_multi.sh
- name: Discord notification
uses: rjstone/discord-webhook-notify@v1
if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) && (github.event_name != 'schedule')
with:
severity: info
details: Deploy Succeeded!
webhookUrl: ${{ secrets.DISCORD_WEBHOOK }}
deploy_arm: deploy_arm:
needs: [ deploy ] needs: [ deploy ]
# Only run on 64bit machines # Only run on 64bit machines
@@ -433,3 +434,11 @@ jobs:
BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }} BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }}
run: | run: |
build_helpers/publish_docker_arm64.sh build_helpers/publish_docker_arm64.sh
- name: Discord notification
uses: rjstone/discord-webhook-notify@v1
if: always() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false) && (github.event_name != 'schedule')
with:
severity: info
details: Deploy Succeeded!
webhookUrl: ${{ secrets.DISCORD_WEBHOOK }}

View File

@@ -15,7 +15,7 @@ repos:
additional_dependencies: additional_dependencies:
- types-cachetools==5.2.1 - types-cachetools==5.2.1
- types-filelock==3.2.7 - types-filelock==3.2.7
- types-requests==2.28.8 - types-requests==2.28.11
- types-tabulate==0.8.11 - types-tabulate==0.8.11
- types-python-dateutil==2.8.19 - types-python-dateutil==2.8.19
# stages: [push] # stages: [push]
@@ -34,7 +34,9 @@ repos:
exclude: | exclude: |
(?x)^( (?x)^(
tests/.*| tests/.*|
.*\.svg .*\.svg|
.*\.yml|
.*\.json
)$ )$
- id: mixed-line-ending - id: mixed-line-ending
- id: debug-statements - id: debug-statements

View File

@@ -1,4 +1,4 @@
FROM python:3.10.6-slim-bullseye as base FROM python:3.10.7-slim-bullseye as base
# Setup env # Setup env
ENV LANG C.UTF-8 ENV LANG C.UTF-8
@@ -11,7 +11,7 @@ ENV FT_APP_ENV="docker"
# Prepare environment # Prepare environment
RUN mkdir /freqtrade \ RUN mkdir /freqtrade \
&& apt-get update \ && apt-get update \
&& apt-get -y install sudo libatlas3-base curl sqlite3 libhdf5-serial-dev \ && apt-get -y install sudo libatlas3-base curl sqlite3 libhdf5-serial-dev libgomp1 \
&& apt-get clean \ && apt-get clean \
&& useradd -u 1000 -G sudo -U -m -s /bin/bash ftuser \ && useradd -u 1000 -G sudo -U -m -s /bin/bash ftuser \
&& chown ftuser:ftuser /freqtrade \ && chown ftuser:ftuser /freqtrade \

View File

@@ -130,7 +130,7 @@ Telegram is not mandatory. However, this is a great way to control your bot. Mor
- `/start`: Starts the trader. - `/start`: Starts the trader.
- `/stop`: Stops the trader. - `/stop`: Stops the trader.
- `/stopbuy`: Stop entering new trades. - `/stopentry`: Stop entering new trades.
- `/status <trade_id>|[table]`: Lists all or specific open trades. - `/status <trade_id>|[table]`: Lists all or specific open trades.
- `/profit [<n>]`: Lists cumulative profit from all finished trades, over the last n days. - `/profit [<n>]`: Lists cumulative profit from all finished trades, over the last n days.
- `/forceexit <trade_id>|all`: Instantly exits the given trade (Ignoring `minimum_roi`). - `/forceexit <trade_id>|all`: Instantly exits the given trade (Ignoring `minimum_roi`).

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -6,13 +6,13 @@ python -m pip install --upgrade pip wheel
$pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')" $pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
if ($pyv -eq '3.8') { if ($pyv -eq '3.8') {
pip install build_helpers\TA_Lib-0.4.24-cp38-cp38-win_amd64.whl pip install build_helpers\TA_Lib-0.4.25-cp38-cp38-win_amd64.whl
} }
if ($pyv -eq '3.9') { if ($pyv -eq '3.9') {
pip install build_helpers\TA_Lib-0.4.24-cp39-cp39-win_amd64.whl pip install build_helpers\TA_Lib-0.4.25-cp39-cp39-win_amd64.whl
} }
if ($pyv -eq '3.10') { if ($pyv -eq '3.10') {
pip install build_helpers\TA_Lib-0.4.24-cp310-cp310-win_amd64.whl pip install build_helpers\TA_Lib-0.4.25-cp310-cp310-win_amd64.whl
} }
pip install -r requirements-dev.txt pip install -r requirements-dev.txt
pip install -e . pip install -e .

View File

@@ -53,7 +53,6 @@
], ],
"freqai": { "freqai": {
"enabled": true, "enabled": true,
"startup_candles": 10000,
"purge_old_models": true, "purge_old_models": true,
"train_period_days": 15, "train_period_days": 15,
"backtest_period_days": 7, "backtest_period_days": 7,
@@ -75,9 +74,11 @@
"weight_factor": 0.9, "weight_factor": 0.9,
"principal_component_analysis": false, "principal_component_analysis": false,
"use_SVM_to_remove_outliers": true, "use_SVM_to_remove_outliers": true,
"stratify_training_data": 0, "indicator_periods_candles": [
"indicator_max_period_candles": 20, 10,
"indicator_periods_candles": [10, 20] 20
],
"plot_feature_importances": 0
}, },
"data_split_parameters": { "data_split_parameters": {
"test_size": 0.33, "test_size": 0.33,

View File

@@ -64,8 +64,8 @@
"stoploss_on_exchange_limit_ratio": 0.99 "stoploss_on_exchange_limit_ratio": 0.99
}, },
"order_time_in_force": { "order_time_in_force": {
"entry": "gtc", "entry": "GTC",
"exit": "gtc" "exit": "GTC"
}, },
"pairlists": [ "pairlists": [
{"method": "StaticPairList"}, {"method": "StaticPairList"},
@@ -172,7 +172,24 @@
"jwt_secret_key": "somethingrandom", "jwt_secret_key": "somethingrandom",
"CORS_origins": [], "CORS_origins": [],
"username": "freqtrader", "username": "freqtrader",
"password": "SuperSecurePassword" "password": "SuperSecurePassword",
"ws_token": "secret_ws_t0ken."
},
"external_message_consumer": {
"enabled": false,
"producers": [
{
"name": "default",
"host": "127.0.0.2",
"port": 8080,
"ws_token": "secret_ws_t0ken."
}
],
"wait_timeout": 300,
"ping_timeout": 10,
"sleep_time": 10,
"remove_entry_exit_signals": false,
"message_size_limit": 8
}, },
"bot_name": "freqtrade", "bot_name": "freqtrade",
"db_url": "sqlite:///tradesv3.sqlite", "db_url": "sqlite:///tradesv3.sqlite",

View File

@@ -6,4 +6,3 @@ FROM ${sourceimage}:${sourcetag}
COPY requirements-freqai.txt /freqtrade/ COPY requirements-freqai.txt /freqtrade/
RUN pip install -r requirements-freqai.txt --user --no-cache-dir RUN pip install -r requirements-freqai.txt --user --no-cache-dir

View File

@@ -1,7 +1,8 @@
FROM freqtradeorg/freqtrade:develop_plot FROM freqtradeorg/freqtrade:develop_plot
RUN pip install jupyterlab --user --no-cache-dir # Pin jupyter-client to avoid tornado version conflict
RUN pip install jupyterlab jupyter-client==7.3.4 --user --no-cache-dir
# Empty the ENTRYPOINT to allow all commands # Empty the ENTRYPOINT to allow all commands
ENTRYPOINT [] ENTRYPOINT []

View File

@@ -10,7 +10,7 @@ services:
ports: ports:
- "127.0.0.1:8888:8888" - "127.0.0.1:8888:8888"
volumes: volumes:
- "./user_data:/freqtrade/user_data" - "../user_data:/freqtrade/user_data"
# Default command used when running `docker compose up` # Default command used when running `docker compose up`
command: > command: >
jupyter lab --port=8888 --ip 0.0.0.0 --allow-root jupyter lab --port=8888 --ip 0.0.0.0 --allow-root

View File

@@ -17,6 +17,7 @@ from typing import Any, Dict
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import Config
from freqtrade.optimize.hyperopt import IHyperOptLoss from freqtrade.optimize.hyperopt import IHyperOptLoss
TARGET_TRADES = 600 TARGET_TRADES = 600
@@ -31,7 +32,7 @@ class SuperDuperHyperOptLoss(IHyperOptLoss):
@staticmethod @staticmethod
def hyperopt_loss_function(results: DataFrame, trade_count: int, def hyperopt_loss_function(results: DataFrame, trade_count: int,
min_date: datetime, max_date: datetime, min_date: datetime, max_date: datetime,
config: Dict, processed: Dict[str, DataFrame], config: Config, processed: Dict[str, DataFrame],
backtest_stats: Dict[str, Any], backtest_stats: Dict[str, Any],
*args, **kwargs) -> float: *args, **kwargs) -> float:
""" """

BIN
docs/assets/freqai_DI.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

BIN
docs/assets/freqai_algo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 345 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 995 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 490 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 458 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 185 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

View File

@@ -107,7 +107,7 @@ Strategy arguments:
## Test your strategy with Backtesting ## Test your strategy with Backtesting
Now you have good Buy and Sell strategies and some historic data, you want to test it against Now you have good Entry and exit strategies and some historic data, you want to test it against
real data. This is what we call [backtesting](https://en.wikipedia.org/wiki/Backtesting). real data. This is what we call [backtesting](https://en.wikipedia.org/wiki/Backtesting).
Backtesting will use the crypto-currencies (pairs) from your config file and load historical candle (OHLCV) data from `user_data/data/<exchange>` by default. Backtesting will use the crypto-currencies (pairs) from your config file and load historical candle (OHLCV) data from `user_data/data/<exchange>` by default.
@@ -215,7 +215,7 @@ Sometimes your account has certain fee rebates (fee reductions starting with a c
To account for this in backtesting, you can use the `--fee` command line option to supply this value to backtesting. To account for this in backtesting, you can use the `--fee` command line option to supply this value to backtesting.
This fee must be a ratio, and will be applied twice (once for trade entry, and once for trade exit). This fee must be a ratio, and will be applied twice (once for trade entry, and once for trade exit).
For example, if the buying and selling commission fee is 0.1% (i.e., 0.001 written as ratio), then you would run backtesting as the following: For example, if the commission fee per order is 0.1% (i.e., 0.001 written as ratio), then you would run backtesting as the following:
```bash ```bash
freqtrade backtesting --fee 0.001 freqtrade backtesting --fee 0.001
@@ -252,9 +252,9 @@ The most important in the backtesting is to understand the result.
A backtesting result will look like that: A backtesting result will look like that:
``` ```
========================================================= BACKTESTING REPORT ========================================================== ========================================================= BACKTESTING REPORT =========================================================
| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins Draws Loss Win% | | Pair | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins Draws Loss Win% |
|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:-------------|-------------------------:| |:---------|--------:|---------------:|---------------:|-----------------:|---------------:|:-------------|-------------------------:|
| ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 0 21 40.0 | | ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 0 21 40.0 |
| ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 0 8 27.3 | | ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 0 8 27.3 |
| BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 0 14 56.2 | | BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 0 14 56.2 |
@@ -275,15 +275,15 @@ A backtesting result will look like that:
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 | | ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 |
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 | | TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 |
========================================================= EXIT REASON STATS ========================================================== ========================================================= EXIT REASON STATS ==========================================================
| Exit Reason | Sells | Wins | Draws | Losses | | Exit Reason | Exits | Wins | Draws | Losses |
|:-------------------|--------:|------:|-------:|--------:| |:-------------------|--------:|------:|-------:|--------:|
| trailing_stop_loss | 205 | 150 | 0 | 55 | | trailing_stop_loss | 205 | 150 | 0 | 55 |
| stop_loss | 166 | 0 | 0 | 166 | | stop_loss | 166 | 0 | 0 | 166 |
| exit_signal | 56 | 36 | 0 | 20 | | exit_signal | 56 | 36 | 0 | 20 |
| force_exit | 2 | 0 | 0 | 2 | | force_exit | 2 | 0 | 0 | 2 |
====================================================== LEFT OPEN TRADES REPORT ====================================================== ====================================================== LEFT OPEN TRADES REPORT ======================================================
| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% | | Pair | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% |
|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|--------------------:| |:---------|---------:|---------------:|---------------:|-----------------:|---------------:|:---------------|--------------------:|
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 | | ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 |
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 | | LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 |
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 | | TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 |
@@ -356,7 +356,7 @@ The column `Avg Profit %` shows the average profit for all trades made while the
The column `Tot Profit %` shows instead the total profit % in relation to the starting balance. The column `Tot Profit %` shows instead the total profit % in relation to the starting balance.
In the above results, we have a starting balance of 0.01 BTC and an absolute profit of 0.00762792 BTC - so the `Tot Profit %` will be `(0.00762792 / 0.01) * 100 ~= 76.2%`. In the above results, we have a starting balance of 0.01 BTC and an absolute profit of 0.00762792 BTC - so the `Tot Profit %` will be `(0.00762792 / 0.01) * 100 ~= 76.2%`.
Your strategy performance is influenced by your buy strategy, your exit strategy, and also by the `minimal_roi` and `stop_loss` you have set. Your strategy performance is influenced by your entry strategy, your exit strategy, and also by the `minimal_roi` and `stop_loss` you have set.
For example, if your `minimal_roi` is only `"0": 0.01` you cannot expect the bot to make more profit than 1% (because it will exit every time a trade reaches 1%). For example, if your `minimal_roi` is only `"0": 0.01` you cannot expect the bot to make more profit than 1% (because it will exit every time a trade reaches 1%).
@@ -515,7 +515,7 @@ You can then load the trades to perform further analysis as shown in the [data a
Since backtesting lacks some detailed information about what happens within a candle, it needs to take a few assumptions: Since backtesting lacks some detailed information about what happens within a candle, it needs to take a few assumptions:
- Exchange [trading limits](#trading-limits-in-backtesting) are respected - Exchange [trading limits](#trading-limits-in-backtesting) are respected
- Buys happen at open-price - Entries happen at open-price
- All orders are filled at the requested price (no slippage, no unfilled orders) - All orders are filled at the requested price (no slippage, no unfilled orders)
- Exit-signal exits happen at open-price of the consecutive candle - Exit-signal exits happen at open-price of the consecutive candle
- Exit-signal is favored over Stoploss, because exit-signals are assumed to trigger on candle's open - Exit-signal is favored over Stoploss, because exit-signals are assumed to trigger on candle's open
@@ -561,6 +561,14 @@ BTC trades at 22.000\$ today (0.001 BTC is related to this) - but the backtestin
Today's minimum would be `0.001 * 22_000` - or 22\$. Today's minimum would be `0.001 * 22_000` - or 22\$.
However the limit could also be 50$ - based on `0.001 * 50_000` in some historic setting. However the limit could also be 50$ - based on `0.001 * 50_000` in some historic setting.
#### Trading precision limits
Most exchanges pose precision limits on both price and amounts, so you cannot buy 1.0020401 of a pair, or at a price of 1.24567123123.
Instead, these prices and amounts will be rounded or truncated (based on the exchange definition) to the defined trading precision.
The above values may for example be rounded to an amount of 1.002, and a price of 1.24567.
These precision values are based on current exchange limits (as described in the [above section](#trading-limits-in-backtesting)), as historic precision limits are not available.
## Improved backtest accuracy ## Improved backtest accuracy
One big limitation of backtesting is it's inability to know how prices moved intra-candle (was high before close, or viceversa?). One big limitation of backtesting is it's inability to know how prices moved intra-candle (was high before close, or viceversa?).
@@ -604,9 +612,9 @@ There will be an additional table comparing win/losses of the different strategi
Detailed output for all strategies one after the other will be available, so make sure to scroll up to see the details per strategy. Detailed output for all strategies one after the other will be available, so make sure to scroll up to see the details per strategy.
``` ```
=========================================================== STRATEGY SUMMARY ========================================================================= =========================================================== STRATEGY SUMMARY ===========================================================================
| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses | Drawdown % | | Strategy | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses | Drawdown % |
|:------------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|-------:|-----------:| |:------------|---------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|-------:|-----------:|
| Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 | 45.2 | | Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 | 45.2 |
| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 | 241.68 | | Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 | 241.68 |
``` ```

View File

@@ -70,7 +70,7 @@ This loop will be repeated again and again until the bot is stopped.
* Determine stake size by calling the `custom_stake_amount()` callback. * Determine stake size by calling the `custom_stake_amount()` callback.
* Check position adjustments for open trades if enabled and call `adjust_trade_position()` to determine if an additional order is requested. * Check position adjustments for open trades if enabled and call `adjust_trade_position()` to determine if an additional order is requested.
* Call `custom_stoploss()` and `custom_exit()` to find custom exit points. * Call `custom_stoploss()` and `custom_exit()` to find custom exit points.
* For exits based on exit-signal and custom-exit: Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle). * For exits based on exit-signal, custom-exit and partial exits: Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle).
* Generate backtest report output * Generate backtest report output
!!! Note !!! Note

View File

@@ -58,9 +58,20 @@ This is similar to using multiple `--config` parameters, but simpler in usage as
!!! Tip "Use multiple configuration files to keep secrets secret" !!! Tip "Use multiple configuration files to keep secrets secret"
You can use a 2nd configuration file containing your secrets. That way you can share your "primary" configuration file, while still keeping your API keys for yourself. You can use a 2nd configuration file containing your secrets. That way you can share your "primary" configuration file, while still keeping your API keys for yourself.
The 2nd file should only specify what you intend to override.
If a key is in more than one of the configurations, then the "last specified configuration" wins (in the above example, `config-private.json`).
For one-off commands, you can also use the below syntax by specifying multiple "--config" parameters.
``` bash
freqtrade trade --config user_data/config1.json --config user_data/config-private.json <...>
```
The below is equivalent to the example above - but having 2 configuration files in the configuration, for easier reuse.
``` json title="user_data/config.json" ``` json title="user_data/config.json"
"add_config_files": [ "add_config_files": [
"config1.json",
"config-private.json" "config-private.json"
] ]
``` ```
@@ -69,17 +80,6 @@ This is similar to using multiple `--config` parameters, but simpler in usage as
freqtrade trade --config user_data/config.json <...> freqtrade trade --config user_data/config.json <...>
``` ```
The 2nd file should only specify what you intend to override.
If a key is in more than one of the configurations, then the "last specified configuration" wins (in the above example, `config-private.json`).
For one-off commands, you can also use the below syntax by specifying multiple "--config" parameters.
``` bash
freqtrade trade --config user_data/config.json --config user_data/config-private.json <...>
```
This is equivalent to the example above - but `config-private.json` is specified as cli argument.
??? Note "config collision handling" ??? Note "config collision handling"
If the same configuration setting takes place in both `config.json` and `config-import.json`, then the parent configuration wins. If the same configuration setting takes place in both `config.json` and `config-import.json`, then the parent configuration wins.
In the below case, `max_open_trades` would be 3 after the merging - as the reusable "import" configuration has this key overwritten. In the below case, `max_open_trades` would be 3 after the merging - as the reusable "import" configuration has this key overwritten.
@@ -111,6 +111,8 @@ This is similar to using multiple `--config` parameters, but simpler in usage as
} }
``` ```
If multiple files are in the `add_config_files` section, then they will be assumed to be at identical levels, having the last occurrence override the earlier config (unless a parent already defined such a key).
## Configuration parameters ## Configuration parameters
The table below will list all configuration parameters available. The table below will list all configuration parameters available.
@@ -223,14 +225,16 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `webhook.webhookexitcancel` | Payload to send on exit order cancel. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String | `webhook.webhookexitcancel` | Payload to send on exit order cancel. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
| `webhook.webhookexitfill` | Payload to send on exit order filled. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String | `webhook.webhookexitfill` | Payload to send on exit order filled. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
| `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String | `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
| | **Rest API / FreqUI** | | **Rest API / FreqUI / Producer-Consumer**
| `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** Boolean | `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** Boolean
| `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** IPv4 | `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** IPv4
| `api_server.listen_port` | Bind Port. See the [API Server documentation](rest-api.md) for more details. <br>**Datatype:** Integer between 1024 and 65535 | `api_server.listen_port` | Bind Port. See the [API Server documentation](rest-api.md) for more details. <br>**Datatype:** Integer between 1024 and 65535
| `api_server.verbosity` | Logging verbosity. `info` will print all RPC Calls, while "error" will only display errors. <br>**Datatype:** Enum, either `info` or `error`. Defaults to `info`. | `api_server.verbosity` | Logging verbosity. `info` will print all RPC Calls, while "error" will only display errors. <br>**Datatype:** Enum, either `info` or `error`. Defaults to `info`.
| `api_server.username` | Username for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> **Datatype:** String | `api_server.username` | Username for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> **Datatype:** String
| `api_server.password` | Password for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> **Datatype:** String | `api_server.password` | Password for API server. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.**<br> **Datatype:** String
| `api_server.ws_token` | API token for the Message WebSocket. See the [API Server documentation](rest-api.md) for more details. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `bot_name` | Name of the bot. Passed via API to a client - can be shown to distinguish / name bots.<br> *Defaults to `freqtrade`*<br> **Datatype:** String | `bot_name` | Name of the bot. Passed via API to a client - can be shown to distinguish / name bots.<br> *Defaults to `freqtrade`*<br> **Datatype:** String
| `external_message_consumer` | Enable [Producer/Consumer mode](producer-consumer.md) for more details. <br> **Datatype:** Dict
| | **Other** | | **Other**
| `initial_state` | Defines the initial application state. If set to stopped, then the bot has to be explicitly started via `/start` RPC command. <br>*Defaults to `stopped`.* <br> **Datatype:** Enum, either `stopped` or `running` | `initial_state` | Defines the initial application state. If set to stopped, then the bot has to be explicitly started via `/start` RPC command. <br>*Defaults to `stopped`.* <br> **Datatype:** Enum, either `stopped` or `running`
| `force_entry_enable` | Enables the RPC Commands to force a Trade entry. More information below. <br> **Datatype:** Boolean | `force_entry_enable` | Enables the RPC Commands to force a Trade entry. More information below. <br> **Datatype:** Boolean
@@ -525,21 +529,28 @@ It means if the order is not executed immediately AND fully then it is cancelled
It is the same as FOK (above) except it can be partially fulfilled. The remaining part It is the same as FOK (above) except it can be partially fulfilled. The remaining part
is automatically cancelled by the exchange. is automatically cancelled by the exchange.
The `order_time_in_force` parameter contains a dict with buy and sell time in force policy values. **PO (Post only):**
Post only order. The order is either placed as a maker order, or it is canceled.
This means the order must be placed on orderbook for at at least time in an unfilled state.
#### time_in_force config
The `order_time_in_force` parameter contains a dict with entry and exit time in force policy values.
This can be set in the configuration file or in the strategy. This can be set in the configuration file or in the strategy.
Values set in the configuration file overwrites values set in the strategy. Values set in the configuration file overwrites values set in the strategy.
The possible values are: `gtc` (default), `fok` or `ioc`. The possible values are: `GTC` (default), `FOK` or `IOC`.
``` python ``` python
"order_time_in_force": { "order_time_in_force": {
"entry": "gtc", "entry": "GTC",
"exit": "gtc" "exit": "GTC"
}, },
``` ```
!!! Warning !!! Warning
This is ongoing work. For now, it is supported only for binance and kucoin. This is ongoing work. For now, it is supported only for binance, gate, ftx and kucoin.
Please don't change the default value unless you know what you are doing and have researched the impact of using different values for your particular exchange. Please don't change the default value unless you know what you are doing and have researched the impact of using different values for your particular exchange.
### What values can be used for fiat_display_currency? ### What values can be used for fiat_display_currency?
@@ -650,17 +661,7 @@ You should also make sure to read the [Exchanges](exchanges.md) section of the d
### Using proxy with Freqtrade ### Using proxy with Freqtrade
To use a proxy with freqtrade, add the kwarg `"aiohttp_trust_env"=true` to the `"ccxt_async_kwargs"` dict in the exchange section of the configuration. To use a proxy with freqtrade, export your proxy settings using the variables `"HTTP_PROXY"` and `"HTTPS_PROXY"` set to the appropriate values.
An example for this can be found in `config_examples/config_full.example.json`
``` json
"ccxt_async_config": {
"aiohttp_trust_env": true
}
```
Then, export your proxy settings using the variables `"HTTP_PROXY"` and `"HTTPS_PROXY"` set to the appropriate values
``` bash ``` bash
export HTTP_PROXY="http://addr:port" export HTTP_PROXY="http://addr:port"
@@ -668,6 +669,20 @@ export HTTPS_PROXY="http://addr:port"
freqtrade freqtrade
``` ```
#### Proxy just exchange requests
To use a proxy just for exchange connections (skips/ignores telegram and coingecko) - you can also define the proxies as part of the ccxt configuration.
``` json
"ccxt_config": {
"aiohttp_proxy": "http://addr:port",
"proxies": {
"http": "http://addr:port",
"https": "http://addr:port"
},
}
```
## Next step ## Next step
Now you have configured your config.json, the next step is to [start your bot](bot-usage.md). Now you have configured your config.json, the next step is to [start your bot](bot-usage.md).

View File

@@ -25,9 +25,8 @@ usage: freqtrade download-data [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[--include-inactive-pairs] [--include-inactive-pairs]
[--timerange TIMERANGE] [--dl-trades] [--timerange TIMERANGE] [--dl-trades]
[--exchange EXCHANGE] [--exchange EXCHANGE]
[-t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...]] [-t TIMEFRAMES [TIMEFRAMES ...]] [--erase]
[--erase] [--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}]
[--data-format-ohlcv {json,jsongz,hdf5}]
[--data-format-trades {json,jsongz,hdf5}] [--data-format-trades {json,jsongz,hdf5}]
[--trading-mode {spot,margin,futures}] [--trading-mode {spot,margin,futures}]
[--prepend] [--prepend]
@@ -37,7 +36,8 @@ optional arguments:
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...] -p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Limit command to these pairs. Pairs are space- Limit command to these pairs. Pairs are space-
separated. separated.
--pairs-file FILE File containing a list of pairs to download. --pairs-file FILE File containing a list of pairs. Takes precedence over
--pairs or pairs configured in the configuration.
--days INT Download data for given number of days. --days INT Download data for given number of days.
--new-pairs-days INT Download data of new pairs for given number of days. --new-pairs-days INT Download data of new pairs for given number of days.
Default: `None`. Default: `None`.
@@ -50,20 +50,20 @@ optional arguments:
as --timeframes/-t. as --timeframes/-t.
--exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no --exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no
config is provided. config is provided.
-t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...], --timeframes {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...] -t TIMEFRAMES [TIMEFRAMES ...], --timeframes TIMEFRAMES [TIMEFRAMES ...]
Specify which tickers to download. Space-separated Specify which tickers to download. Space-separated
list. Default: `1m 5m`. list. Default: `1m 5m`.
--erase Clean all existing data for the selected --erase Clean all existing data for the selected
exchange/pairs/timeframes. exchange/pairs/timeframes.
--data-format-ohlcv {json,jsongz,hdf5} --data-format-ohlcv {json,jsongz,hdf5,feather,parquet}
Storage format for downloaded candle (OHLCV) data. Storage format for downloaded candle (OHLCV) data.
(default: `json`). (default: `json`).
--data-format-trades {json,jsongz,hdf5} --data-format-trades {json,jsongz,hdf5}
Storage format for downloaded trades data. (default: Storage format for downloaded trades data. (default:
`jsongz`). `jsongz`).
--trading-mode {spot,margin,futures} --trading-mode {spot,margin,futures}, --tradingmode {spot,margin,futures}
Select Trading mode Select Trading mode
--prepend Allow data prepending. --prepend Allow data prepending. (Data-appending is disabled)
Common arguments: Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages). -v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
@@ -76,7 +76,7 @@ Common arguments:
`userdir/config.json` or `config.json` whichever `userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin. set to `-` to read config from stdin.
-d PATH, --datadir PATH -d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data. Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH --userdir PATH, --user-data-dir PATH
Path to userdata directory. Path to userdata directory.
@@ -179,14 +179,16 @@ freqtrade download-data --exchange binance --pairs ETH/USDT XRP/USDT BTC/USDT --
Freqtrade currently supports 3 data-formats for both OHLCV and trades data: Freqtrade currently supports 3 data-formats for both OHLCV and trades data:
* `json` (plain "text" json files) * `json` - plain "text" json files
* `jsongz` (a gzip-zipped version of json files) * `jsongz` - a gzip-zipped version of json files
* `hdf5` (a high performance datastore) * `hdf5` - a high performance datastore
* `feather` - a dataformat based on Apache Arrow
* `parquet` - columnar datastore
By default, OHLCV data is stored as `json` data, while trades data is stored as `jsongz` data. By default, OHLCV data is stored as `json` data, while trades data is stored as `jsongz` data.
This can be changed via the `--data-format-ohlcv` and `--data-format-trades` command line arguments respectively. This can be changed via the `--data-format-ohlcv` and `--data-format-trades` command line arguments respectively.
To persist this change, you can should also add the following snippet to your configuration, so you don't have to insert the above arguments each time: To persist this change, you should also add the following snippet to your configuration, so you don't have to insert the above arguments each time:
``` jsonc ``` jsonc
// ... // ...
@@ -200,38 +202,74 @@ If the default data-format has been changed during download, then the keys `data
!!! Note !!! Note
You can convert between data-formats using the [convert-data](#sub-command-convert-data) and [convert-trade-data](#sub-command-convert-trade-data) methods. You can convert between data-formats using the [convert-data](#sub-command-convert-data) and [convert-trade-data](#sub-command-convert-trade-data) methods.
#### Dataformat comparison
The following comparisons have been made with the following data, and by using the linux `time` command.
```
Found 6 pair / timeframe combinations.
+----------+-------------+--------+---------------------+---------------------+
| Pair | Timeframe | Type | From | To |
|----------+-------------+--------+---------------------+---------------------|
| BTC/USDT | 5m | spot | 2017-08-17 04:00:00 | 2022-09-13 19:25:00 |
| ETH/USDT | 1m | spot | 2017-08-17 04:00:00 | 2022-09-13 19:26:00 |
| BTC/USDT | 1m | spot | 2017-08-17 04:00:00 | 2022-09-13 19:30:00 |
| XRP/USDT | 5m | spot | 2018-05-04 08:10:00 | 2022-09-13 19:15:00 |
| XRP/USDT | 1m | spot | 2018-05-04 08:11:00 | 2022-09-13 19:22:00 |
| ETH/USDT | 5m | spot | 2017-08-17 04:00:00 | 2022-09-13 19:20:00 |
+----------+-------------+--------+---------------------+---------------------+
```
Timings have been taken in a not very scientific way with the following command, which forces reading the data into memory.
``` bash
time freqtrade list-data --show-timerange --data-format-ohlcv <dataformat>
```
| Format | Size | timing |
|------------|-------------|-------------|
| `json` | 149Mb | 25.6s |
| `jsongz` | 39Mb | 27s |
| `hdf5` | 145Mb | 3.9s |
| `feather` | 72Mb | 3.5s |
| `parquet` | 83Mb | 3.8s |
Size has been taken from the BTC/USDT 1m spot combination for the timerange specified above.
To have a best performance/size mix, we recommend the use of either feather or parquet.
#### Sub-command convert data #### Sub-command convert data
``` ```
usage: freqtrade convert-data [-h] [-v] [--logfile FILE] [-V] [-c PATH] usage: freqtrade convert-data [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [-d PATH] [--userdir PATH]
[-p PAIRS [PAIRS ...]] --format-from [-p PAIRS [PAIRS ...]] --format-from
{json,jsongz,hdf5} --format-to {json,jsongz,hdf5,feather,parquet} --format-to
{json,jsongz,hdf5} [--erase] {json,jsongz,hdf5,feather,parquet} [--erase]
[-t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...]]
[--exchange EXCHANGE] [--exchange EXCHANGE]
[-t TIMEFRAMES [TIMEFRAMES ...]]
[--trading-mode {spot,margin,futures}] [--trading-mode {spot,margin,futures}]
[--candle-types {spot,,futures,mark,index,premiumIndex,funding_rate} [{spot,,futures,mark,index,premiumIndex,funding_rate} ...]] [--candle-types {spot,futures,mark,index,premiumIndex,funding_rate} [{spot,futures,mark,index,premiumIndex,funding_rate} ...]]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...] -p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Limit command to these pairs. Pairs are space- Limit command to these pairs. Pairs are space-
separated. separated.
--format-from {json,jsongz,hdf5} --format-from {json,jsongz,hdf5,feather,parquet}
Source format for data conversion. Source format for data conversion.
--format-to {json,jsongz,hdf5} --format-to {json,jsongz,hdf5,feather,parquet}
Destination format for data conversion. Destination format for data conversion.
--erase Clean all existing data for the selected --erase Clean all existing data for the selected
exchange/pairs/timeframes. exchange/pairs/timeframes.
-t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...], --timeframes {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...]
Specify which tickers to download. Space-separated
list. Default: `1m 5m`.
--exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no --exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no
config is provided. config is provided.
--trading-mode {spot,margin,futures} -t TIMEFRAMES [TIMEFRAMES ...], --timeframes TIMEFRAMES [TIMEFRAMES ...]
Specify which tickers to download. Space-separated
list. Default: `1m 5m`.
--trading-mode {spot,margin,futures}, --tradingmode {spot,margin,futures}
Select Trading mode Select Trading mode
--candle-types {spot,,futures,mark,index,premiumIndex,funding_rate} [{spot,,futures,mark,index,premiumIndex,funding_rate} ...] --candle-types {spot,futures,mark,index,premiumIndex,funding_rate} [{spot,futures,mark,index,premiumIndex,funding_rate} ...]
Select candle type to use Select candle type to use
Common arguments: Common arguments:
@@ -245,7 +283,7 @@ Common arguments:
`userdir/config.json` or `config.json` whichever `userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin. set to `-` to read config from stdin.
-d PATH, --datadir PATH -d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data. Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH --userdir PATH, --user-data-dir PATH
Path to userdata directory. Path to userdata directory.
@@ -267,20 +305,24 @@ freqtrade convert-data --format-from json --format-to jsongz --datadir ~/.freqtr
usage: freqtrade convert-trade-data [-h] [-v] [--logfile FILE] [-V] [-c PATH] usage: freqtrade convert-trade-data [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [-d PATH] [--userdir PATH]
[-p PAIRS [PAIRS ...]] --format-from [-p PAIRS [PAIRS ...]] --format-from
{json,jsongz,hdf5} --format-to {json,jsongz,hdf5,feather,parquet}
{json,jsongz,hdf5} [--erase] --format-to
{json,jsongz,hdf5,feather,parquet}
[--erase] [--exchange EXCHANGE]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...] -p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Show profits for only these pairs. Pairs are space- Limit command to these pairs. Pairs are space-
separated. separated.
--format-from {json,jsongz,hdf5} --format-from {json,jsongz,hdf5,feather,parquet}
Source format for data conversion. Source format for data conversion.
--format-to {json,jsongz,hdf5} --format-to {json,jsongz,hdf5,feather,parquet}
Destination format for data conversion. Destination format for data conversion.
--erase Clean all existing data for the selected --erase Clean all existing data for the selected
exchange/pairs/timeframes. exchange/pairs/timeframes.
--exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no
config is provided.
Common arguments: Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages). -v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
@@ -293,7 +335,7 @@ Common arguments:
`userdir/config.json` or `config.json` whichever `userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin. set to `-` to read config from stdin.
-d PATH, --datadir PATH -d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data. Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH --userdir PATH, --user-data-dir PATH
Path to userdata directory. Path to userdata directory.
@@ -318,9 +360,9 @@ This command will allow you to repeat this last step for additional timeframes w
usage: freqtrade trades-to-ohlcv [-h] [-v] [--logfile FILE] [-V] [-c PATH] usage: freqtrade trades-to-ohlcv [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [-d PATH] [--userdir PATH]
[-p PAIRS [PAIRS ...]] [-p PAIRS [PAIRS ...]]
[-t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...]] [-t TIMEFRAMES [TIMEFRAMES ...]]
[--exchange EXCHANGE] [--exchange EXCHANGE]
[--data-format-ohlcv {json,jsongz,hdf5}] [--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}]
[--data-format-trades {json,jsongz,hdf5}] [--data-format-trades {json,jsongz,hdf5}]
optional arguments: optional arguments:
@@ -328,12 +370,12 @@ optional arguments:
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...] -p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Limit command to these pairs. Pairs are space- Limit command to these pairs. Pairs are space-
separated. separated.
-t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...], --timeframes {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w,2w,1M,1y} ...] -t TIMEFRAMES [TIMEFRAMES ...], --timeframes TIMEFRAMES [TIMEFRAMES ...]
Specify which tickers to download. Space-separated Specify which tickers to download. Space-separated
list. Default: `1m 5m`. list. Default: `1m 5m`.
--exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no --exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no
config is provided. config is provided.
--data-format-ohlcv {json,jsongz,hdf5} --data-format-ohlcv {json,jsongz,hdf5,feather,parquet}
Storage format for downloaded candle (OHLCV) data. Storage format for downloaded candle (OHLCV) data.
(default: `json`). (default: `json`).
--data-format-trades {json,jsongz,hdf5} --data-format-trades {json,jsongz,hdf5}
@@ -351,7 +393,7 @@ Common arguments:
`userdir/config.json` or `config.json` whichever `userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin. set to `-` to read config from stdin.
-d PATH, --datadir PATH -d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data. Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH --userdir PATH, --user-data-dir PATH
Path to userdata directory. Path to userdata directory.
@@ -371,22 +413,25 @@ You can get a list of downloaded data using the `list-data` sub-command.
``` ```
usage: freqtrade list-data [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] usage: freqtrade list-data [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [--exchange EXCHANGE] [--userdir PATH] [--exchange EXCHANGE]
[--data-format-ohlcv {json,jsongz,hdf5}] [--data-format-ohlcv {json,jsongz,hdf5,feather,parquet}]
[-p PAIRS [PAIRS ...]] [-p PAIRS [PAIRS ...]]
[--trading-mode {spot,margin,futures}] [--trading-mode {spot,margin,futures}]
[--show-timerange]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
--exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no --exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no
config is provided. config is provided.
--data-format-ohlcv {json,jsongz,hdf5} --data-format-ohlcv {json,jsongz,hdf5,feather,parquet}
Storage format for downloaded candle (OHLCV) data. Storage format for downloaded candle (OHLCV) data.
(default: `json`). (default: `json`).
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...] -p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Limit command to these pairs. Pairs are space- Limit command to these pairs. Pairs are space-
separated. separated.
--trading-mode {spot,margin,futures} --trading-mode {spot,margin,futures}, --tradingmode {spot,margin,futures}
Select Trading mode Select Trading mode
--show-timerange Show timerange available for available data. (May take
a while to calculate).
Common arguments: Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages). -v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
@@ -399,7 +444,7 @@ Common arguments:
`userdir/config.json` or `config.json` whichever `userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin. set to `-` to read config from stdin.
-d PATH, --datadir PATH -d PATH, --datadir PATH, --data-dir PATH
Path to directory with historical backtesting data. Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH --userdir PATH, --user-data-dir PATH
Path to userdata directory. Path to userdata directory.

View File

@@ -409,8 +409,9 @@ Determine if crucial bugfixes have been made between this commit and the current
* Merge the release branch (stable) into this branch. * Merge the release branch (stable) into this branch.
* Edit `freqtrade/__init__.py` and add the version matching the current date (for example `2019.7` for July 2019). Minor versions can be `2019.7.1` should we need to do a second release that month. Version numbers must follow allowed versions from PEP0440 to avoid failures pushing to pypi. * Edit `freqtrade/__init__.py` and add the version matching the current date (for example `2019.7` for July 2019). Minor versions can be `2019.7.1` should we need to do a second release that month. Version numbers must follow allowed versions from PEP0440 to avoid failures pushing to pypi.
* Commit this part * Commit this part.
* push that branch to the remote and create a PR against the stable branch * push that branch to the remote and create a PR against the stable branch.
* Update develop version to next version following the pattern `2019.8-dev`.
### Create changelog from git commits ### Create changelog from git commits

View File

@@ -57,12 +57,13 @@ This configuration enables kraken, as well as rate-limiting to avoid bans from t
Binance supports [time_in_force](configuration.md#understand-order_time_in_force). Binance supports [time_in_force](configuration.md#understand-order_time_in_force).
!!! Tip "Stoploss on Exchange" !!! Tip "Stoploss on Exchange"
Binance 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.. Binance 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, Binance 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.
### Binance Blacklist ### Binance Blacklist
For Binance, please add `"BNB/<STAKE>"` to your blacklist to avoid issues. For Binance, it is suggested to add `"BNB/<STAKE>"` to your blacklist to avoid issues, unless you are willing to maintain enough extra `BNB` on the account or unless you're willing to disable using `BNB` for fees.
Accounts having BNB accounts use this to pay for fees - if your first trade happens to be on `BNB`, further trades will consume this position and make the initial BNB trade unsellable as the expected amount is not there anymore. Binance accounts may use `BNB` for fees, and if a trade happens to be on `BNB`, further trades may consume this position and make the initial BNB trade unsellable as the expected amount is not there anymore.
### Binance Futures ### Binance Futures
@@ -205,8 +206,8 @@ Kucoin supports [time_in_force](configuration.md#understand-order_time_in_force)
### Kucoin Blacklists ### Kucoin Blacklists
For Kucoin, please add `"KCS/<STAKE>"` to your blacklist to avoid issues. For Kucoin, it is suggested to add `"KCS/<STAKE>"` to your blacklist to avoid issues, unless you are willing to maintain enough extra `KCS` on the account or unless you're willing to disable using `KCS` for fees.
Accounts having KCS accounts use this to pay for fees - if your first trade happens to be on `KCS`, further trades will consume this position and make the initial KCS trade unsellable as the expected amount is not there anymore. Kucoin accounts may use `KCS` for fees, and if a trade happens to be on `KCS`, further trades may consume this position and make the initial `KCS` trade unsellable as the expected amount is not there anymore.
## Huobi ## Huobi
@@ -232,7 +233,7 @@ OKX requires a passphrase for each api key, you will therefore need to add this
!!! Warning "Futures" !!! Warning "Futures"
OKX Futures has the concept of "position mode" - which can be Net or long/short (hedge mode). OKX Futures has the concept of "position mode" - which can be Net or long/short (hedge mode).
Freqtrade supports both modes - but changing the mode mid-trading is not supported and will lead to exceptions and failures to place trades. Freqtrade supports both modes (we recommend to use net mode) - but changing the mode mid-trading is not supported and will lead to exceptions and failures to place trades.
OKX also only provides MARK candles for the past ~3 months. Backtesting futures prior to that date will therefore lead to slight deviations, as funding-fees cannot be calculated correctly without this data. OKX also only provides MARK candles for the past ~3 months. Backtesting futures prior to that date will therefore lead to slight deviations, as funding-fees cannot be calculated correctly without this data.
## Gate.io ## Gate.io
@@ -278,7 +279,7 @@ For example, to test the order type `FOK` with Kraken, and modify candle limit t
"exchange": { "exchange": {
"name": "kraken", "name": "kraken",
"_ft_has_params": { "_ft_has_params": {
"order_time_in_force": ["gtc", "fok"], "order_time_in_force": ["GTC", "FOK"],
"ohlcv_candle_limit": 200 "ohlcv_candle_limit": 200
} }
//... //...

View File

@@ -4,7 +4,7 @@
Freqtrade supports spot trading only. Freqtrade supports spot trading only.
### Can I open short positions? ### Can my bot open short positions?
Freqtrade can open short positions in futures markets. Freqtrade can open short positions in futures markets.
This requires the strategy to be made for this - and `"trading_mode": "futures"` in the configuration. This requires the strategy to be made for this - and `"trading_mode": "futures"` in the configuration.
@@ -12,9 +12,9 @@ Please make sure to read the [relevant documentation page](leverage.md) first.
In spot markets, you can in some cases use leveraged spot tokens, which reflect an inverted pair (eg. BTCUP/USD, BTCDOWN/USD, ETHBULL/USD, ETHBEAR/USD,...) which can be traded with Freqtrade. In spot markets, you can in some cases use leveraged spot tokens, which reflect an inverted pair (eg. BTCUP/USD, BTCDOWN/USD, ETHBULL/USD, ETHBEAR/USD,...) which can be traded with Freqtrade.
### Can I trade options or futures? ### Can my bot trade options or futures?
Futures trading is supported for selected exchanges. Futures trading is supported for selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges-experimental) for an uptodate list of supported exchanges.
## Beginner Tips & Tricks ## Beginner Tips & Tricks
@@ -22,6 +22,13 @@ Futures trading is supported for selected exchanges.
## Freqtrade common issues ## Freqtrade common issues
### Can freqtrade open multiple positions on the same pair in parallel?
No. Freqtrade will only open one position per pair at a time.
You can however use the [`adjust_trade_position()` callback](strategy-callbacks.md#adjust-trade-position) to adjust an open position.
Backtesting provides an option for this in `--eps` - however this is only there to highlight "hidden" signals, and will not work in live.
### The bot does not start ### The bot does not start
Running the bot with `freqtrade trade --config config.json` shows the output `freqtrade: command not found`. Running the bot with `freqtrade trade --config config.json` shows the output `freqtrade: command not found`.
@@ -30,7 +37,7 @@ This could be caused by the following reasons:
* The virtual environment is not active. * The virtual environment is not active.
* Run `source .env/bin/activate` to activate the virtual environment. * Run `source .env/bin/activate` to activate the virtual environment.
* The installation did not work correctly. * The installation did not complete successfully.
* Please check the [Installation documentation](installation.md). * Please check the [Installation documentation](installation.md).
### I have waited 5 minutes, why hasn't the bot made any trades yet? ### I have waited 5 minutes, why hasn't the bot made any trades yet?
@@ -77,9 +84,9 @@ Freqtrade will not provide incomplete candles to strategies. Using incomplete ca
You can use "current" market data by using the [dataprovider](strategy-customization.md#orderbookpair-maximum)'s orderbook or ticker methods - which however cannot be used during backtesting. You can use "current" market data by using the [dataprovider](strategy-customization.md#orderbookpair-maximum)'s orderbook or ticker methods - which however cannot be used during backtesting.
### Is there a setting to only SELL the coins being held and not perform anymore BUYS? ### Is there a setting to only Exit the trades being held and not perform any new Entries?
You can use the `/stopbuy` command in Telegram to prevent future buys, followed by `/forceexit all` (sell all open trades). You can use the `/stopentry` command in Telegram to prevent future trade entry, followed by `/forceexit all` (sell all open trades).
### I want to run multiple bots on the same machine ### I want to run multiple bots on the same machine

View File

@@ -0,0 +1,217 @@
# Configuration
`FreqAI` is configured through the typical [Freqtrade config file](configuration.md) and the standard [Freqtrade strategy](strategy-customization.md). Examples of `FreqAI` config and strategy files can be found in `config_examples/config_freqai.example.json` and `freqtrade/templates/FreqaiExampleStrategy.py`, respectively.
## Setting up the configuration file
Although there are plenty of additional parameters to choose from, as highlighted in the [parameter table](freqai-parameter-table.md#parameter-table), a `FreqAI` config must at minimum include the following parameters (the parameter values are only examples):
```json
"freqai": {
"enabled": true,
"purge_old_models": true,
"train_period_days": 30,
"backtest_period_days": 7,
"identifier" : "unique-id",
"feature_parameters" : {
"include_timeframes": ["5m","15m","4h"],
"include_corr_pairlist": [
"ETH/USD",
"LINK/USD",
"BNB/USD"
],
"label_period_candles": 24,
"include_shifted_candles": 2,
"indicator_periods_candles": [10, 20]
},
"data_split_parameters" : {
"test_size": 0.25
},
"model_training_parameters" : {
"n_estimators": 100
},
}
```
A full example config is available in `config_examples/config_freqai.example.json`.
## Building a `FreqAI` strategy
The `FreqAI` strategy requires including the following lines of code in the standard [Freqtrade strategy](strategy-customization.md):
```python
# user should define the maximum startup candle count (the largest number of candles
# passed to any single indicator)
startup_candle_count: int = 20
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# the model will return all labels created by user in `populate_any_indicators`
# (& appended targets), an indication of whether or not the prediction should be accepted,
# the target mean/std values for each of the labels created by user in
# `populate_any_indicators()` for each training period.
dataframe = self.freqai.start(dataframe, metadata, self)
return dataframe
def populate_any_indicators(
self, pair, df, tf, informative=None, set_generalized_indicators=False
):
"""
Function designed to automatically generate, name and merge features
from user indicated timeframes in the configuration file. User controls the indicators
passed to the training/prediction by prepending indicators with `'%-' + coin `
(see convention below). I.e. user should not prepend any supporting metrics
(e.g. bb_lowerband below) with % unless they explicitly want to pass that metric to the
model.
:param pair: pair to be used as informative
:param df: strategy dataframe which will receive merges from informatives
:param tf: timeframe of the dataframe which will modify the feature names
:param informative: the dataframe associated with the informative pair
:param coin: the name of the coin which will modify the feature names.
"""
coin = pair.split('/')[0]
if informative is None:
informative = self.dp.get_pair_dataframe(pair, tf)
# first loop is automatically duplicating indicators for time periods
for t in self.freqai_info["feature_parameters"]["indicator_periods_candles"]:
t = int(t)
informative[f"%-{coin}rsi-period_{t}"] = ta.RSI(informative, timeperiod=t)
informative[f"%-{coin}mfi-period_{t}"] = ta.MFI(informative, timeperiod=t)
informative[f"%-{coin}adx-period_{t}"] = ta.ADX(informative, window=t)
indicators = [col for col in informative if col.startswith("%")]
# This loop duplicates and shifts all indicators to add a sense of recency to data
for n in range(self.freqai_info["feature_parameters"]["include_shifted_candles"] + 1):
if n == 0:
continue
informative_shift = informative[indicators].shift(n)
informative_shift = informative_shift.add_suffix("_shift-" + str(n))
informative = pd.concat((informative, informative_shift), axis=1)
df = merge_informative_pair(df, informative, self.config["timeframe"], tf, ffill=True)
skip_columns = [
(s + "_" + tf) for s in ["date", "open", "high", "low", "close", "volume"]
]
df = df.drop(columns=skip_columns)
# Add generalized indicators here (because in live, it will call this
# function to populate indicators during training). Notice how we ensure not to
# add them multiple times
if set_generalized_indicators:
# user adds targets here by prepending them with &- (see convention below)
# If user wishes to use multiple targets, a multioutput prediction model
# needs to be used such as templates/CatboostPredictionMultiModel.py
df["&-s_close"] = (
df["close"]
.shift(-self.freqai_info["feature_parameters"]["label_period_candles"])
.rolling(self.freqai_info["feature_parameters"]["label_period_candles"])
.mean()
/ df["close"]
- 1
)
return df
```
Notice how the `populate_any_indicators()` is where [features](freqai-feature-engineering.md#feature-engineering) and labels/targets are added. A full example strategy is available in `templates/FreqaiExampleStrategy.py`.
Notice also the location of the labels under `if set_generalized_indicators:` at the bottom of the example. This is where single features and labels/targets should be added to the feature set to avoid duplication of them from various configuration parameters that multiply the feature set, such as `include_timeframes`.
!!! Note
The `self.freqai.start()` function cannot be called outside the `populate_indicators()`.
!!! Note
Features **must** be defined in `populate_any_indicators()`. Defining `FreqAI` features in `populate_indicators()`
will cause the algorithm to fail in live/dry mode. In order to add generalized features that are not associated with a specific pair or timeframe, the following structure inside `populate_any_indicators()` should be used
(as exemplified in `freqtrade/templates/FreqaiExampleStrategy.py`):
```python
def populate_any_indicators(self, metadata, pair, df, tf, informative=None, coin="", set_generalized_indicators=False):
...
# Add generalized indicators here (because in live, it will call only this function to populate
# indicators for retraining). Notice how we ensure not to add them multiple times by associating
# these generalized indicators to the basepair/timeframe
if set_generalized_indicators:
df['%-day_of_week'] = (df["date"].dt.dayofweek + 1) / 7
df['%-hour_of_day'] = (df['date'].dt.hour + 1) / 25
# user adds targets here by prepending them with &- (see convention below)
# If user wishes to use multiple targets, a multioutput prediction model
# needs to be used such as templates/CatboostPredictionMultiModel.py
df["&-s_close"] = (
df["close"]
.shift(-self.freqai_info["feature_parameters"]["label_period_candles"])
.rolling(self.freqai_info["feature_parameters"]["label_period_candles"])
.mean()
/ df["close"]
- 1
)
```
Please see the example script located in `freqtrade/templates/FreqaiExampleStrategy.py` for a full example of `populate_any_indicators()`.
## Important dataframe key patterns
Below are the values you can expect to include/use inside a typical strategy dataframe (`df[]`):
| DataFrame Key | Description |
|------------|-------------|
| `df['&*']` | Any dataframe column prepended with `&` in `populate_any_indicators()` is treated as a training target (label) inside `FreqAI` (typically following the naming convention `&-s*`). The names of these dataframe columns are fed back as the predictions. For example, to predict the price change in the next 40 candles (similar to `templates/FreqaiExampleStrategy.py`), you would set `df['&-s_close']`. `FreqAI` makes the predictions and gives them back under the same key (`df['&-s_close']`) to be used in `populate_entry/exit_trend()`. <br> **Datatype:** Depends on the output of the model.
| `df['&*_std/mean']` | Standard deviation and mean values of the defined labels during training (or live tracking with `fit_live_predictions_candles`). Commonly used to understand the rarity of a prediction (use the z-score as shown in `templates/FreqaiExampleStrategy.py` and explained [here](#creating-a-dynamic-target-threshold) to evaluate how often a particular prediction was observed during training or historically with `fit_live_predictions_candles`). <br> **Datatype:** Float.
| `df['do_predict']` | Indication of an outlier data point. The return value is integer between -1 and 2, which lets you know if the prediction is trustworthy or not. `do_predict==1` means that the prediction is trustworthy. If the Dissimilarity Index (DI, see details [here](freqai-feature-engineering.md#identifying-outliers-with-the-dissimilarity-index-di)) of the input data point is above the threshold defined in the config, `FreqAI` will subtract 1 from `do_predict`, resulting in `do_predict==0`. If `use_SVM_to_remove_outliers()` is active, the Support Vector Machine (SVM, see details [here](freqai-feature-engineering.md#identifying-outliers-using-a-support-vector-machine-svm)) may also detect outliers in training and prediction data. In this case, the SVM will also subtract 1 from `do_predict`. If the input data point was considered an outlier by the SVM but not by the DI, or vice versa, the result will be `do_predict==0`. If both the DI and the SVM considers the input data point to be an outlier, the result will be `do_predict==-1`. A particular case is when `do_predict == 2`, which means that the model has expired due to exceeding `expired_hours`. <br> **Datatype:** Integer between -1 and 2.
| `df['DI_values']` | Dissimilarity Index (DI) values are proxies for the level of confidence `FreqAI` has in the prediction. A lower DI means the prediction is close to the training data, i.e., higher prediction confidence. See details about the DI [here](freqai-feature-engineering.md#identifying-outliers-with-the-dissimilarity-index-di). <br> **Datatype:** Float.
| `df['%*']` | Any dataframe column prepended with `%` in `populate_any_indicators()` is treated as a training feature. For example, you can include the RSI in the training feature set (similar to in `templates/FreqaiExampleStrategy.py`) by setting `df['%-rsi']`. See more details on how this is done [here](freqai-feature-engineering.md). <br> **Note:** Since the number of features prepended with `%` can multiply very quickly (10s of thousands of features is easily engineered using the multiplictative functionality described in the `feature_parameters` table shown above), these features are removed from the dataframe upon return from `FreqAI`. To keep a particular type of feature for plotting purposes, you would prepend it with `%%`. <br> **Datatype:** Depends on the output of the model.
## Setting the `startup_candle_count`
The `startup_candle_count` in the `FreqAI` strategy needs to be set up in the same way as in the standard Freqtrade strategy (see details [here](strategy-customization.md#strategy-startup-period)). This value is used by Freqtrade to ensure that a sufficient amount of data is provided when calling the `dataprovider`, to avoid any NaNs at the beginning of the first training. You can easily set this value by identifying the longest period (in candle units) which is passed to the indicator creation functions (e.g., Ta-Lib functions). In the presented example, `startup_candle_count` is 20 since this is the maximum value in `indicators_periods_candles`.
!!! Note
There are instances where the Ta-Lib functions actually require more data than just the passed `period` or else the feature dataset gets populated with NaNs. Anecdotally, multiplying the `startup_candle_count` by 2 always leads to a fully NaN free training dataset. Hence, it is typically safest to multiply the expected `startup_candle_count` by 2. Look out for this log message to confirm that the data is clean:
```
2022-08-31 15:14:04 - freqtrade.freqai.data_kitchen - INFO - dropped 0 training points due to NaNs in populated dataset 4319.
```
## Creating a dynamic target threshold
Deciding when to enter or exit a trade can be done in a dynamic way to reflect current market conditions. `FreqAI` allows you to return additional information from the training of a model (more info [here](freqai-feature-engineering.md#returning-additional-info-from-training)). For example, the `&*_std/mean` return values describe the statistical distribution of the target/label *during the most recent training*. Comparing a given prediction to these values allows you to know the rarity of the prediction. In `templates/FreqaiExampleStrategy.py`, the `target_roi` and `sell_roi` are defined to be 1.25 z-scores away from the mean which causes predictions that are closer to the mean to be filtered out.
```python
dataframe["target_roi"] = dataframe["&-s_close_mean"] + dataframe["&-s_close_std"] * 1.25
dataframe["sell_roi"] = dataframe["&-s_close_mean"] - dataframe["&-s_close_std"] * 1.25
```
To consider the population of *historical predictions* for creating the dynamic target instead of information from the training as discussed above, you would set `fit_live_prediction_candles` in the config to the number of historical prediction candles you wish to use to generate target statistics.
```json
"freqai": {
"fit_live_prediction_candles": 300,
}
```
If this value is set, `FreqAI` will initially use the predictions from the training data and subsequently begin introducing real prediction data as it is generated. `FreqAI` will save this historical data to be reloaded if you stop and restart a model with the same `identifier`.
## Using different prediction models
`FreqAI` has multiple example prediction model libraries that are ready to be used as is via the flag `--freqaimodel`. These libraries include `Catboost`, `LightGBM`, and `XGBoost` regression, classification, and multi-target models, and can be found in `freqai/prediction_models/`. However, it is possible to customize and create your own prediction models using the `IFreqaiModel` class. You are encouraged to inherit `fit()`, `train()`, and `predict()` to let these customize various aspects of the training procedures.
### Setting classifier targets
`FreqAI` includes a variety of classifiers, such as the `CatboostClassifier` via the flag `--freqaimodel CatboostClassifier`. If you elects to use a classifier, the classes need to be set using strings. For example:
```python
df['&s-up_or_down'] = np.where( df["close"].shift(-100) > df["close"], 'up', 'down')
```
Additionally, the example classifier models do not accommodate multiple labels, but they do allow multi-class classification within a single label column.

78
docs/freqai-developers.md Normal file
View File

@@ -0,0 +1,78 @@
# Development
## Project architecture
The architecture and functions of `FreqAI` are generalized to encourages development of unique features, functions, models, etc.
The class structure and a detailed algorithmic overview is depicted in the following diagram:
![image](assets/freqai_algorithm-diagram.jpg)
As shown, there are three distinct objects comprising `FreqAI`:
* **IFreqaiModel** - A singular persistent object containing all the necessary logic to collect, store, and process data, engineer features, run training, and inference models.
* **FreqaiDataKitchen** - A non-persistent object which is created uniquely for each unique asset/model. Beyond metadata, it also contains a variety of data processing tools.
* **FreqaiDataDrawer** - A singular persistent object containing all the historical predictions, models, and save/load methods.
There are a variety of built-in [prediction models](freqai-configuration.md#using-different-prediction-models) which inherit directly from `IFreqaiModel`. Each of these models have full access to all methods in `IFreqaiModel` and can therefore override any of those functions at will. However, advanced users will likely stick to overriding `fit()`, `train()`, `predict()`, and `data_cleaning_train/predict()`.
## Data handling
`FreqAI` aims to organize model files, prediction data, and meta data in a way that simplifies post-processing and enhances crash resilience by automatic data reloading. The data is saved in a file structure,`user_data_dir/models/`, which contains all the data associated with the trainings and backtests. The `FreqaiDataKitchen()` relies heavily on the file structure for proper training and inferencing and should therefore not be manually modified.
### File structure
The file structure is automatically generated based on the model `identifier` set in the [config](freqai-configuration.md#setting-up-the-configuration-file). The following structure shows where the data is stored for post processing:
| Structure | Description |
|-----------|-------------|
| `config_*.json` | A copy of the model specific configuration file. |
| `historic_predictions.pkl` | A file containing all historic predictions generated during the lifetime of the `identifier` model during live deployment. `historic_predictions.pkl` is used to reload the model after a crash or a config change. A backup file is always held incase of corruption on the main file. **`FreqAI` automatically detects corruption and replaces the corrupted file with the backup**. |
| `pair_dictionary.json` | A file containing the training queue as well as the on disk location of the most recently trained model. |
| `sub-train-*_TIMESTAMP` | A folder containing all the files associated with a single model, such as: <br>
|| `*_metadata.json` - Metadata for the model, such as normalization max/mins, expected training feature list, etc. <br>
|| `*_model.*` - The model file saved to disk for reloading from a crash. Can be `joblib` (typical boosting libs), `zip` (stable_baselines), `hd5` (keras type), etc. <br>
|| `*_pca_object.pkl` - The [Principal component analysis (PCA)](freqai-feature-engineering.md#data-dimensionality-reduction-with-principal-component-analysis) transform (if `principal_component_analysis: true` is set in the config) which will be used to transform unseen prediction features. <br>
|| `*_svm_model.pkl` - The [Support Vector Machine (SVM)](freqai-feature-engineering.md#identifying-outliers-using-a-support-vector-machine-svm) model which is used to detect outliers in unseen prediction features. <br>
|| `*_trained_df.pkl` - The dataframe containing all the training features used to train the `identifier` model. This is used for computing the [Dissimilarity Index (DI)](freqai-feature-engineering.md#identifying-outliers-with-the-dissimilarity-index-di) and can also be used for post-processing. <br>
|| `*_trained_dates.df.pkl` - The dates associated with the `trained_df.pkl`, which is useful for post-processing. |
The example file structure would look like this:
```
├── models
│   └── unique-id
│   ├── config_freqai.example.json
│   ├── historic_predictions.backup.pkl
│   ├── historic_predictions.pkl
│   ├── pair_dictionary.json
│   ├── sub-train-1INCH_1662821319
│   │   ├── cb_1inch_1662821319_metadata.json
│   │   ├── cb_1inch_1662821319_model.joblib
│   │   ├── cb_1inch_1662821319_pca_object.pkl
│   │   ├── cb_1inch_1662821319_svm_model.joblib
│   │   ├── cb_1inch_1662821319_trained_dates_df.pkl
│   │   └── cb_1inch_1662821319_trained_df.pkl
│   ├── sub-train-1INCH_1662821371
│   │   ├── cb_1inch_1662821371_metadata.json
│   │   ├── cb_1inch_1662821371_model.joblib
│   │   ├── cb_1inch_1662821371_pca_object.pkl
│   │   ├── cb_1inch_1662821371_svm_model.joblib
│   │   ├── cb_1inch_1662821371_trained_dates_df.pkl
│   │   └── cb_1inch_1662821371_trained_df.pkl
│   ├── sub-train-ADA_1662821344
│   │   ├── cb_ada_1662821344_metadata.json
│   │   ├── cb_ada_1662821344_model.joblib
│   │   ├── cb_ada_1662821344_pca_object.pkl
│   │   ├── cb_ada_1662821344_svm_model.joblib
│   │   ├── cb_ada_1662821344_trained_dates_df.pkl
│   │   └── cb_ada_1662821344_trained_df.pkl
│   └── sub-train-ADA_1662821399
│   ├── cb_ada_1662821399_metadata.json
│   ├── cb_ada_1662821399_model.joblib
│   ├── cb_ada_1662821399_pca_object.pkl
│   ├── cb_ada_1662821399_svm_model.joblib
│   ├── cb_ada_1662821399_trained_dates_df.pkl
│   └── cb_ada_1662821399_trained_df.pkl
```

View File

@@ -0,0 +1,268 @@
# Feature engineering
## Defining the features
Low level feature engineering is performed in the user strategy within a function called `populate_any_indicators()`. That function sets the `base features` such as, `RSI`, `MFI`, `EMA`, `SMA`, time of day, volume, etc. The `base features` can be custom indicators or they can be imported from any technical-analysis library that you can find. One important syntax rule is that all `base features` string names are prepended with `%`, while labels/targets are prepended with `&`.
Meanwhile, high level feature engineering is handled within `"feature_parameters":{}` in the `FreqAI` config. Within this file, it is possible to decide large scale feature expansions on top of the `base_features` such as "including correlated pairs" or "including informative timeframes" or even "including recent candles."
It is advisable to start from the template `populate_any_indicators()` in the source provided example strategy (found in `templates/FreqaiExampleStrategy.py`) to ensure that the feature definitions are following the correct conventions. Here is an example of how to set the indicators and labels in the strategy:
```python
def populate_any_indicators(
self, pair, df, tf, informative=None, set_generalized_indicators=False
):
"""
Function designed to automatically generate, name, and merge features
from user-indicated timeframes in the configuration file. The user controls the indicators
passed to the training/prediction by prepending indicators with `'%-' + coin `
(see convention below). I.e., the user should not prepend any supporting metrics
(e.g., bb_lowerband below) with % unless they explicitly want to pass that metric to the
model.
:param pair: pair to be used as informative
:param df: strategy dataframe which will receive merges from informatives
:param tf: timeframe of the dataframe which will modify the feature names
:param informative: the dataframe associated with the informative pair
:param coin: the name of the coin which will modify the feature names.
"""
coin = pair.split('/')[0]
if informative is None:
informative = self.dp.get_pair_dataframe(pair, tf)
# first loop is automatically duplicating indicators for time periods
for t in self.freqai_info["feature_parameters"]["indicator_periods_candles"]:
t = int(t)
informative[f"%-{coin}rsi-period_{t}"] = ta.RSI(informative, timeperiod=t)
informative[f"%-{coin}mfi-period_{t}"] = ta.MFI(informative, timeperiod=t)
informative[f"%-{coin}adx-period_{t}"] = ta.ADX(informative, window=t)
bollinger = qtpylib.bollinger_bands(
qtpylib.typical_price(informative), window=t, stds=2.2
)
informative[f"{coin}bb_lowerband-period_{t}"] = bollinger["lower"]
informative[f"{coin}bb_middleband-period_{t}"] = bollinger["mid"]
informative[f"{coin}bb_upperband-period_{t}"] = bollinger["upper"]
informative[f"%-{coin}bb_width-period_{t}"] = (
informative[f"{coin}bb_upperband-period_{t}"]
- informative[f"{coin}bb_lowerband-period_{t}"]
) / informative[f"{coin}bb_middleband-period_{t}"]
informative[f"%-{coin}close-bb_lower-period_{t}"] = (
informative["close"] / informative[f"{coin}bb_lowerband-period_{t}"]
)
informative[f"%-{coin}relative_volume-period_{t}"] = (
informative["volume"] / informative["volume"].rolling(t).mean()
)
indicators = [col for col in informative if col.startswith("%")]
# This loop duplicates and shifts all indicators to add a sense of recency to data
for n in range(self.freqai_info["feature_parameters"]["include_shifted_candles"] + 1):
if n == 0:
continue
informative_shift = informative[indicators].shift(n)
informative_shift = informative_shift.add_suffix("_shift-" + str(n))
informative = pd.concat((informative, informative_shift), axis=1)
df = merge_informative_pair(df, informative, self.config["timeframe"], tf, ffill=True)
skip_columns = [
(s + "_" + tf) for s in ["date", "open", "high", "low", "close", "volume"]
]
df = df.drop(columns=skip_columns)
# Add generalized indicators here (because in live, it will call this
# function to populate indicators during training). Notice how we ensure not to
# add them multiple times
if set_generalized_indicators:
df["%-day_of_week"] = (df["date"].dt.dayofweek + 1) / 7
df["%-hour_of_day"] = (df["date"].dt.hour + 1) / 25
# user adds targets here by prepending them with &- (see convention below)
# If user wishes to use multiple targets, a multioutput prediction model
# needs to be used such as templates/CatboostPredictionMultiModel.py
df["&-s_close"] = (
df["close"]
.shift(-self.freqai_info["feature_parameters"]["label_period_candles"])
.rolling(self.freqai_info["feature_parameters"]["label_period_candles"])
.mean()
/ df["close"]
- 1
)
return df
```
In the presented example, the user does not wish to pass the `bb_lowerband` as a feature to the model,
and has therefore not prepended it with `%`. The user does, however, wish to pass `bb_width` to the
model for training/prediction and has therefore prepended it with `%`.
After having defined the `base features`, the next step is to expand upon them using the powerful `feature_parameters` in the configuration file:
```json
"freqai": {
//...
"feature_parameters" : {
"include_timeframes": ["5m","15m","4h"],
"include_corr_pairlist": [
"ETH/USD",
"LINK/USD",
"BNB/USD"
],
"label_period_candles": 24,
"include_shifted_candles": 2,
"indicator_periods_candles": [10, 20]
},
//...
}
```
The `include_timeframes` in the config above are the timeframes (`tf`) of each call to `populate_any_indicators()` in the strategy. In the presented case, the user is asking for the `5m`, `15m`, and `4h` timeframes of the `rsi`, `mfi`, `roc`, and `bb_width` to be included in the feature set.
You can ask for each of the defined features to be included also for informative pairs using the `include_corr_pairlist`. This means that the feature set will include all the features from `populate_any_indicators` on all the `include_timeframes` for each of the correlated pairs defined in the config (`ETH/USD`, `LINK/USD`, and `BNB/USD` in the presented example).
`include_shifted_candles` indicates the number of previous candles to include in the feature set. For example, `include_shifted_candles: 2` tells `FreqAI` to include the past 2 candles for each of the features in the feature set.
In total, the number of features the user of the presented example strat has created is: length of `include_timeframes` * no. features in `populate_any_indicators()` * length of `include_corr_pairlist` * no. `include_shifted_candles` * length of `indicator_periods_candles`
$= 3 * 3 * 3 * 2 * 2 = 108$.
### Returning additional info from training
Important metrics can be returned to the strategy at the end of each model training by assigning them to `dk.data['extra_returns_per_train']['my_new_value'] = XYZ` inside the custom prediction model class.
`FreqAI` takes the `my_new_value` assigned in this dictionary and expands it to fit the dataframe that is returned to the strategy. You can then use the returned metrics in your strategy through `dataframe['my_new_value']`. An example of how return values can be used in `FreqAI` are the `&*_mean` and `&*_std` values that are used to [created a dynamic target threshold](freqai-configuration.md#creating-a-dynamic-target-threshold).
Another example, where the user wants to use live metrics from the trade database, is shown below:
```json
"freqai": {
"extra_returns_per_train": {"total_profit": 4}
}
```
You need to set the standard dictionary in the config so that `FreqAI` can return proper dataframe shapes. These values will likely be overridden by the prediction model, but in the case where the model has yet to set them, or needs a default initial value, the preset values are what will be returned.
## Feature normalization
`FreqAI` is strict when it comes to data normalization. The train features, $X^{train}$, are always normalized to [-1, 1] using a shifted min-max normalization:
$$X^{train}_{norm} = 2 * \frac{X^{train} - X^{train}.min()}{X^{train}.max() - X^{train}.min()} - 1$$
All other data (test data and unseen prediction data in dry/live/backtest) is always automatically normalized to the training feature space according to industry standards. `FreqAI` stores all the metadata required to ensure that test and prediction features will be properly normalized and that predictions are properly denormalized. For this reason, it is not recommended to eschew industry standards and modify `FreqAI` internals - however - advanced users can do so by inheriting `train()` in their custom `IFreqaiModel` and using their own normalization functions.
## Data dimensionality reduction with Principal Component Analysis
You can reduce the dimensionality of your features by activating the `principal_component_analysis` in the config:
```json
"freqai": {
"feature_parameters" : {
"principal_component_analysis": true
}
}
```
This will perform PCA on the features and reduce their dimensionality so that the explained variance of the data set is >= 0.999. Reducing data dimensionality makes training the model faster and hence allows for more up-to-date models.
## Inlier metric
The `inlier_metric` is a metric aimed at quantifying how similar a the features of a data point are to the most recent historic data points.
You define the lookback window by setting `inlier_metric_window` and `FreqAI` computes the distance between the present time point and each of the previous `inlier_metric_window` lookback points. A Weibull function is fit to each of the lookback distributions and its cumulative distribution function (CDF) is used to produce a quantile for each lookback point. The `inlier_metric` is then computed for each time point as the average of the corresponding lookback quantiles. The figure below explains the concept for an `inlier_metric_window` of 5.
![inlier-metric](assets/freqai_inlier-metric.jpg)
`FreqAI` adds the `inlier_metric` to the training features and hence gives the model access to a novel type of temporal information.
This function does **not** remove outliers from the data set.
## Weighting features for temporal importance
`FreqAI` allows you to set a `weight_factor` to weight recent data more strongly than past data via an exponential function:
$$ W_i = \exp(\frac{-i}{\alpha*n}) $$
where $W_i$ is the weight of data point $i$ in a total set of $n$ data points. Below is a figure showing the effect of different weight factors on the data points in a feature set.
![weight-factor](assets/freqai_weight-factor.jpg)
## Outlier detection
Equity and crypto markets suffer from a high level of non-patterned noise in the form of outlier data points. `FreqAI` implements a variety of methods to identify such outliers and hence mitigate risk.
### Identifying outliers with the Dissimilarity Index (DI)
The Dissimilarity Index (DI) aims to quantify the uncertainty associated with each prediction made by the model.
You can tell `FreqAI` to remove outlier data points from the training/test data sets using the DI by including the following statement in the config:
```json
"freqai": {
"feature_parameters" : {
"DI_threshold": 1
}
}
```
The DI allows predictions which are outliers (not existent in the model feature space) to be thrown out due to low levels of certainty. To do so, `FreqAI` measures the distance between each training data point (feature vector), $X_{a}$, and all other training data points:
$$ d_{ab} = \sqrt{\sum_{j=1}^p(X_{a,j}-X_{b,j})^2} $$
where $d_{ab}$ is the distance between the normalized points $a$ and $b$, and $p$ is the number of features, i.e., the length of the vector $X$. The characteristic distance, $\overline{d}$, for a set of training data points is simply the mean of the average distances:
$$ \overline{d} = \sum_{a=1}^n(\sum_{b=1}^n(d_{ab}/n)/n) $$
$\overline{d}$ quantifies the spread of the training data, which is compared to the distance between a new prediction feature vectors, $X_k$ and all the training data:
$$ d_k = \arg \min d_{k,i} $$
This enables the estimation of the Dissimilarity Index as:
$$ DI_k = d_k/\overline{d} $$
You can tweak the DI through the `DI_threshold` to increase or decrease the extrapolation of the trained model. A higher `DI_threshold` means that the DI is more lenient and allows predictions further away from the training data to be used whilst a lower `DI_threshold` has the opposite effect and hence discards more predictions.
Below is a figure that describes the DI for a 3D data set.
![DI](assets/freqai_DI.jpg)
### Identifying outliers using a Support Vector Machine (SVM)
You can tell `FreqAI` to remove outlier data points from the training/test data sets using a Support Vector Machine (SVM) by including the following statement in the config:
```json
"freqai": {
"feature_parameters" : {
"use_SVM_to_remove_outliers": true
}
}
```
The SVM will be trained on the training data and any data point that the SVM deems to be beyond the feature space will be removed.
`FreqAI` uses `sklearn.linear_model.SGDOneClassSVM` (details are available on scikit-learn's webpage [here](https://scikit-learn.org/stable/modules/generated/sklearn.linear_model.SGDOneClassSVM.html) (external website)) and you can elect to provide additional parameters for the SVM, such as `shuffle`, and `nu`.
The parameter `shuffle` is by default set to `False` to ensure consistent results. If it is set to `True`, running the SVM multiple times on the same data set might result in different outcomes due to `max_iter` being to low for the algorithm to reach the demanded `tol`. Increasing `max_iter` solves this issue but causes the procedure to take longer time.
The parameter `nu`, *very* broadly, is the amount of data points that should be considered outliers and should be between 0 and 1.
### Identifying outliers with DBSCAN
You can configure `FreqAI` to use DBSCAN to cluster and remove outliers from the training/test data set or incoming outliers from predictions, by activating `use_DBSCAN_to_remove_outliers` in the config:
```json
"freqai": {
"feature_parameters" : {
"use_DBSCAN_to_remove_outliers": true
}
}
```
DBSCAN is an unsupervised machine learning algorithm that clusters data without needing to know how many clusters there should be.
Given a number of data points $N$, and a distance $\varepsilon$, DBSCAN clusters the data set by setting all data points that have $N-1$ other data points within a distance of $\varepsilon$ as *core points*. A data point that is within a distance of $\varepsilon$ from a *core point* but that does not have $N-1$ other data points within a distance of $\varepsilon$ from itself is considered an *edge point*. A cluster is then the collection of *core points* and *edge points*. Data points that have no other data points at a distance $<\varepsilon$ are considered outliers. The figure below shows a cluster with $N = 3$.
![dbscan](assets/freqai_dbscan.jpg)
`FreqAI` uses `sklearn.cluster.DBSCAN` (details are available on scikit-learn's webpage [here](https://scikit-learn.org/stable/modules/generated/sklearn.cluster.DBSCAN.html) (external website)) with `min_samples` ($N$) taken as 1/4 of the no. of time points in the feature set. `eps` ($\varepsilon$) is computed automatically as the elbow point in the *k-distance graph* computed from the nearest neighbors in the pairwise distances of all data points in the feature set.

View File

@@ -0,0 +1,52 @@
# Parameter table
The table below will list all configuration parameters available for `FreqAI`. Some of the parameters are exemplified in `config_examples/config_freqai.example.json`.
Mandatory parameters are marked as **Required** and have to be set in one of the suggested ways.
| Parameter | Description |
|------------|-------------|
| | **General configuration parameters**
| `freqai` | **Required.** <br> The parent dictionary containing all the parameters for controlling `FreqAI`. <br> **Datatype:** Dictionary.
| `train_period_days` | **Required.** <br> Number of days to use for the training data (width of the sliding window). <br> **Datatype:** Positive integer.
| `backtest_period_days` | **Required.** <br> Number of days to inference from the trained model before sliding the `train_period_days` window defined above, and retraining the model during backtesting (more info [here](freqai-running.md#backtesting)). This can be fractional days, but beware that the provided `timerange` will be divided by this number to yield the number of trainings necessary to complete the backtest. <br> **Datatype:** Float.
| `identifier` | **Required.** <br> A unique ID for the current model. If models are saved to disk, the `identifier` allows for reloading specific pre-trained models/data. <br> **Datatype:** String.
| `live_retrain_hours` | Frequency of retraining during dry/live runs. <br> **Datatype:** Float > 0. <br> Default: 0 (models retrain as often as possible).
| `expiration_hours` | Avoid making predictions if a model is more than `expiration_hours` old. <br> **Datatype:** Positive integer. <br> Default: 0 (models never expire).
| `purge_old_models` | Delete obsolete models. <br> **Datatype:** Boolean. <br> Default: `False` (all historic models remain on disk).
| `save_backtest_models` | Save models to disk when running backtesting. Backtesting operates most efficiently by saving the prediction data and reusing them directly for subsequent runs (when you wish to tune entry/exit parameters). Saving backtesting models to disk also allows to use the same model files for starting a dry/live instance with the same model `identifier`. <br> **Datatype:** Boolean. <br> Default: `False` (no models are saved).
| `fit_live_predictions_candles` | Number of historical candles to use for computing target (label) statistics from prediction data, instead of from the training dataset (more information can be found [here](freqai-configuration.md#creating-a-dynamic-target-threshold)). <br> **Datatype:** Positive integer.
| `follow_mode` | Use a `follower` that will look for models associated with a specific `identifier` and load those for inferencing. A `follower` will **not** train new models. <br> **Datatype:** Boolean. <br> Default: `False`.
| `continual_learning` | Use the final state of the most recently trained model as starting point for the new model, allowing for incremental learning (more information can be found [here](freqai-running.md#continual-learning)). <br> **Datatype:** Boolean. <br> Default: `False`.
| | **Feature parameters**
| `feature_parameters` | A dictionary containing the parameters used to engineer the feature set. Details and examples are shown [here](freqai-feature-engineering.md). <br> **Datatype:** Dictionary.
| `include_timeframes` | A list of timeframes that all indicators in `populate_any_indicators` will be created for. The list is added as features to the base indicators dataset. <br> **Datatype:** List of timeframes (strings).
| `include_corr_pairlist` | A list of correlated coins that `FreqAI` will add as additional features to all `pair_whitelist` coins. All indicators set in `populate_any_indicators` during feature engineering (see details [here](freqai-feature-engineering.md)) will be created for each correlated coin. The correlated coins features are added to the base indicators dataset. <br> **Datatype:** List of assets (strings).
| `label_period_candles` | Number of candles into the future that the labels are created for. This is used in `populate_any_indicators` (see `templates/FreqaiExampleStrategy.py` for detailed usage). You can create custom labels and choose whether to make use of this parameter or not. <br> **Datatype:** Positive integer.
| `include_shifted_candles` | Add features from previous candles to subsequent candles with the intent of adding historical information. If used, `FreqAI` will duplicate and shift all features from the `include_shifted_candles` previous candles so that the information is available for the subsequent candle. <br> **Datatype:** Positive integer.
| `weight_factor` | Weight training data points according to their recency (see details [here](freqai-feature-engineering.md#weighting-features-for-temporal-importance)). <br> **Datatype:** Positive float (typically < 1).
| `indicator_max_period_candles` | **No longer used (#7325)**. Replaced by `startup_candle_count` which is set in the [strategy](freqai-configuration.md#building-a-freqai-strategy). `startup_candle_count` is timeframe independent and defines the maximum *period* used in `populate_any_indicators()` for indicator creation. `FreqAI` uses this parameter together with the maximum timeframe in `include_time_frames` to calculate how many data points to download such that the first data point does not include a NaN <br> **Datatype:** Positive integer.
| `indicator_periods_candles` | Time periods to calculate indicators for. The indicators are added to the base indicator dataset. <br> **Datatype:** List of positive integers.
| `stratify_training_data` | Split the feature set into training and testing datasets. For example, `stratify_training_data: 2` would set every 2nd data point into a separate dataset to be pulled from during training/testing. See details about how it works [here](freqai-running.md#data-stratification-for-training-and-testing-the-model). <br> **Datatype:** Positive integer.
| `principal_component_analysis` | Automatically reduce the dimensionality of the data set using Principal Component Analysis. See details about how it works [here](#reducing-data-dimensionality-with-principal-component-analysis) <br> **Datatype:** Boolean. defaults to `false`.
| `plot_feature_importances` | Create a feature importance plot for each model for the top/bottom `plot_feature_importances` number of features.<br> **Datatype:** Integer, defaults to `0`.
| `DI_threshold` | Activates the use of the Dissimilarity Index for outlier detection when set to > 0. See details about how it works [here](freqai-feature-engineering.md#identifying-outliers-with-the-dissimilarity-index-di). <br> **Datatype:** Positive float (typically < 1).
| `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).
| | **Data split parameters**
| `data_split_parameters` | Include any additional parameters available from Scikit-learn `test_train_split()`, which are shown [here](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html) (external website). <br> **Datatype:** Dictionary.
| `test_size` | The fraction of data that should be used for testing instead of training. <br> **Datatype:** Positive float < 1.
| `shuffle` | Shuffle the training data points during training. Typically, for time-series forecasting, this is set to `False`. <br> **Datatype:** Boolean.
| | **Model training parameters**
| `model_training_parameters` | A flexible dictionary that includes all parameters available by the selected model library. For example, if you use `LightGBMRegressor`, this dictionary can contain any parameter available by the `LightGBMRegressor` [here](https://lightgbm.readthedocs.io/en/latest/pythonapi/lightgbm.LGBMRegressor.html) (external website). If you select a different model, this dictionary can contain any parameter from that model. <br> **Datatype:** Dictionary.
| `n_estimators` | The number of boosted trees to fit in regression. <br> **Datatype:** Integer.
| `learning_rate` | Boosting learning rate during regression. <br> **Datatype:** Float.
| `n_jobs`, `thread_count`, `task_type` | Set the number of threads for parallel processing and the `task_type` (`gpu` or `cpu`). Different model libraries use different parameter names. <br> **Datatype:** Float.
| | **Extraneous parameters**
| `keras` | If the selected model makes use of Keras (typical for Tensorflow-based prediction models), this flag needs to be activated so that the model save/loading follows Keras standards. <br> **Datatype:** Boolean. <br> Default: `False`.
| `conv_width` | The width of a convolutional neural network input tensor. This replaces the need for shifting candles (`include_shifted_candles`) by feeding in historical data points as the second dimension of the tensor. Technically, this parameter can also be used for regressors, but it only adds computational overhead and does not change the model training/prediction. <br> **Datatype:** Integer. <br> Default: 2.

173
docs/freqai-running.md Normal file
View File

@@ -0,0 +1,173 @@
# Running FreqAI
There are two ways to train and deploy an adaptive machine learning model - live deployment and historical backtesting. In both cases, `FreqAI` runs/simulates periodic retraining of models as shown in the following figure:
![freqai-window](assets/freqai_moving-window.jpg)
## Live deployments
FreqAI can be run dry/live using the following command:
```bash
freqtrade trade --strategy FreqaiExampleStrategy --config config_freqai.example.json --freqaimodel LightGBMRegressor
```
When launched, FreqAI will start training a new model, with a new `identifier`, based on the config settings. Following training, the model will be used to make predictions on incoming candles until a new model is available. New models are typically generated as often as possible, with FreqAI managing an internal queue of the coin pairs to try to keep all models equally up to date. FreqAI will always use the most recently trained model to make predictions on incoming live data. If you do not want FreqAI to retrain new models as often as possible, you can set `live_retrain_hours` to tell FreqAI to wait at least that number of hours before training a new model. Additionally, you can set `expired_hours` to tell FreqAI to avoid making predictions on models that are older than that number of hours.
Trained models are by default saved to disk to allow for reuse during backtesting or after a crash. You can opt to [purge old models](#purging-old-model-data) to save disk space by setting `"purge_old_models": true` in the config.
To start a dry/live run from a saved backtest model (or from a previously crashed dry/live session), you only need to specify the `identifier` of the specific model:
```json
"freqai": {
"identifier": "example",
"live_retrain_hours": 0.5
}
```
In this case, although FreqAI will initiate with a pre-trained model, it will still check to see how much time has elapsed since the model was trained. If a full `live_retrain_hours` has elapsed since the end of the loaded model, FreqAI will start training a new model.
### Automatic data download
FreqAI automatically downloads the proper amount of data needed to ensure training of a model through the defined `train_period_days` and `startup_candle_count` (see the [parameter table](freqai-parameter-table.md) for detailed descriptions of these parameters).
### Saving prediction data
All predictions made during the lifetime of a specific `identifier` model are stored in `historical_predictions.pkl` to allow for reloading after a crash or changes made to the config.
### Purging old model data
FreqAI stores new model files after each successful training. These files become obsolete as new models are generated to adapt to new market conditions. If you are planning to leave FreqAI running for extended periods of time with high frequency retraining, you should enable `purge_old_models` in the config:
```json
"freqai": {
"purge_old_models": true,
}
```
This will automatically purge all models older than the two most recently trained ones to save disk space.
## Backtesting
The FreqAI backtesting module can be executed with the following command:
```bash
freqtrade backtesting --strategy FreqaiExampleStrategy --strategy-path freqtrade/templates --config config_examples/config_freqai.example.json --freqaimodel LightGBMRegressor --timerange 20210501-20210701
```
If this command has never been executed with the existing config file, FreqAI will train a new model
for each pair, for each backtesting window within the expanded `--timerange`.
Backtesting mode requires [downloading the necessary data](#downloading-data-to-cover-the-full-backtest-period) before deployment (unlike in dry/live mode where FreqAI handles the data downloading automatically). You should be careful to consider that the time range of the downloaded data is more than the backtesting time range. This is because FreqAI needs data prior to the desired backtesting time range in order to train a model to be ready to make predictions on the first candle of the set backtesting time range. More details on how to calculate the data to download can be found [here](#deciding-the-size-of-the-sliding-training-window-and-backtesting-duration).
!!! Note "Model reuse"
Once the training is completed, you can execute the backtesting again with the same config file and
FreqAI will find the trained models and load them instead of spending time training. This is useful
if you want to tweak (or even hyperopt) buy and sell criteria inside the strategy. If you
*want* to retrain a new model with the same config file, you should simply change the `identifier`.
This way, you can return to using any model you wish by simply specifying the `identifier`.
---
### Saving prediction data
To allow for tweaking your strategy (**not** the features!), FreqAI will automatically save the predictions during backtesting so that they can be reused for future backtests and live runs using the same `identifier` model. This provides a performance enhancement geared towards enabling **high-level hyperopting** of entry/exit criteria.
An additional directory called `predictions`, which contains all the predictions stored in `hdf` format, will be created in the `unique-id` folder.
To change your **features**, you **must** set a new `identifier` in the config to signal to `FreqAI` to train new models.
To save the models generated during a particular backtest so that you can start a live deployment from one of them instead of training a new model, you must set `save_backtest_models` to `True` in the config.
### Downloading data to cover the full backtest period
For live/dry deployments, FreqAI will download the necessary data automatically. However, to use backtesting functionality, you need to download the necessary data using `download-data` (details [here](data-download.md#data-downloading)). You need to pay careful attention to understanding how much *additional* data needs to be downloaded to ensure that there is a sufficient amount of training data *before* the start of the backtesting timerange. The amount of additional data can be roughly estimated by moving the start date of the timerange backwards by `train_period_days` and the `startup_candle_count` (see the [parameter table](freqai-parameter-table.md) for detailed descriptions of these parameters) from the beginning of the desired backtesting timerange.
As an example, to backtest the `--timerange 20210501-20210701` using the [example config](freqai-configuration.md#setting-up-the-configuration-file) which sets `train_period_days` to 30, together with `startup_candle_count: 40` on a maximum `include_timeframes` of 1h, the start date for the downloaded data needs to be `20210501` - 30 days - 40 * 1h / 24 hours = 20210330 (31.7 days earlier than the start of the desired training timerange).
### Deciding the size of the sliding training window and backtesting duration
The backtesting timerange is defined with the typical `--timerange` parameter in the configuration file. The duration of the sliding training window is set by `train_period_days`, whilst `backtest_period_days` is the sliding backtesting window, both in number of days (`backtest_period_days` can be
a float to indicate sub-daily retraining in live/dry mode). In the presented [example config](freqai-configuration.md#setting-up-the-configuration-file) (found in `config_examples/config_freqai.example.json`), the user is asking FreqAI to use a training period of 30 days and backtest on the subsequent 7 days. After the training of the model, FreqAI will backtest the subsequent 7 days. The "sliding window" then moves one week forward (emulating FreqAI retraining once per week in live mode) and the new model uses the previous 30 days (including the 7 days used for backtesting by the previous model) to train. This is repeated until the end of `--timerange`. This means that if you set `--timerange 20210501-20210701`, FreqAI will have trained 8 separate models at the end of `--timerange` (because the full range comprises 8 weeks).
!!! Note
Although fractional `backtest_period_days` is allowed, you should be aware that the `--timerange` is divided by this value to determine the number of models that FreqAI will need to train in order to backtest the full range. For example, by setting a `--timerange` of 10 days, and a `backtest_period_days` of 0.1, FreqAI will need to train 100 models per pair to complete the full backtest. Because of this, a true backtest of FreqAI adaptive training would take a *very* long time. The best way to fully test a model is to run it dry and let it train constantly. In this case, backtesting would take the exact same amount of time as a dry run.
## Defining model expirations
During dry/live mode, FreqAI trains each coin pair sequentially (on separate threads/GPU from the main Freqtrade bot). This means that there is always an age discrepancy between models. If you are training on 50 pairs, and each pair requires 5 minutes to train, the oldest model will be over 4 hours old. This may be undesirable if the characteristic time scale (the trade duration target) for a strategy is less than 4 hours. You can decide to only make trade entries if the model is less than a certain number of hours old by setting the `expiration_hours` in the config file:
```json
"freqai": {
"expiration_hours": 0.5,
}
```
In the presented example config, the user will only allow predictions on models that are less than 1/2 hours old.
## Data stratification for training and testing the model
You can stratify (group) the training/testing data using:
```json
"freqai": {
"feature_parameters" : {
"stratify_training_data": 3
}
}
```
This will split the data chronologically so that every Xth data point is used to test the model after training. In the example above, the user is asking for every third data point in the dataframe to be used for
testing; the other points are used for training.
The test data is used to evaluate the performance of the model after training. If the test score is high, the model is able to capture the behavior of the data well. If the test score is low, either the model does not capture the complexity of the data, the test data is significantly different from the train data, or a different type of model should be used.
## Controlling the model learning process
Model training parameters are unique to the selected machine learning library. FreqAI allows you to set any parameter for any library using the `model_training_parameters` dictionary in the config. The example config (found in `config_examples/config_freqai.example.json`) shows some of the example parameters associated with `Catboost` and `LightGBM`, but you can add any parameters available in those libraries or any other machine learning library you choose to implement.
Data split parameters are defined in `data_split_parameters` which can be any parameters associated with Scikit-learn's `train_test_split()` function. `train_test_split()` has a parameters called `shuffle` which allows to shuffle the data or keep it unshuffled. This is particularly useful to avoid biasing training with temporally auto-correlated data. More details about these parameters can be found the [Scikit-learn website](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html) (external website).
The FreqAI specific parameter `label_period_candles` defines the offset (number of candles into the future) used for the `labels`. In the presented [example config](freqai-configuration.md#setting-up-the-configuration-file), the user is asking for `labels` that are 24 candles in the future.
## Continual learning
You can choose to adopt a continual learning scheme by setting `"continual_learning": true` in the config. By enabling `continual_learning`, after training an initial model from scratch, subsequent trainings will start from the final model state of the preceding training. This gives the new model a "memory" of the previous state. By default, this is set to `false` which means that all new models are trained from scratch, without input from previous models.
## Hyperopt
You can hyperopt using the same command as for [typical Freqtrade hyperopt](hyperopt.md):
```bash
freqtrade hyperopt --hyperopt-loss SharpeHyperOptLoss --strategy FreqaiExampleStrategy --freqaimodel LightGBMRegressor --strategy-path freqtrade/templates --config config_examples/config_freqai.example.json --timerange 20220428-20220507
```
`hyperopt` requires you to have the data pre-downloaded in the same fashion as if you were doing [backtesting](#backtesting). In addition, you must consider some restrictions when trying to hyperopt FreqAI strategies:
- The `--analyze-per-epoch` hyperopt parameter is not compatible with FreqAI.
- It's not possible to hyperopt indicators in the `populate_any_indicators()` function. This means that you cannot optimize model parameters using hyperopt. Apart from this exception, it is possible to optimize all other [spaces](hyperopt.md#running-hyperopt-with-smaller-search-space).
- The backtesting instructions also apply to hyperopt.
The best method for combining hyperopt and FreqAI is to focus on hyperopting entry/exit thresholds/criteria. You need to focus on hyperopting parameters that are not used in your features. For example, you should not try to hyperopt rolling window lengths in the feature creation, or any part of the FreqAI config which changes predictions. In order to efficiently hyperopt the FreqAI strategy, FreqAI stores predictions as dataframes and reuses them. Hence the requirement to hyperopt entry/exit thresholds/criteria only.
A good example of a hyperoptable parameter in FreqAI is a threshold for the [Dissimilarity Index (DI)](freqai-feature-engineering.md#identifying-outliers-with-the-dissimilarity-index-di) `DI_values` beyond which we consider data points as outliers:
```python
di_max = IntParameter(low=1, high=20, default=10, space='buy', optimize=True, load=True)
dataframe['outlier'] = np.where(dataframe['DI_values'] > self.di_max.value/10, 1, 0)
```
This specific hyperopt would help you understand the appropriate `DI_values` for your particular parameter space.
## Setting up a follower
You can indicate to the bot that it should not train models, but instead should look for models trained by a leader with a specific `identifier` by defining:
```json
"freqai": {
"follow_mode": true,
"identifier": "example"
}
```
In this example, the user has a leader bot with the `"identifier": "example"`. The leader bot is already running or is launched simultaneously with the follower. The follower will load models created by the leader and inference them to obtain predictions instead of training its own models.

View File

@@ -1,769 +1,100 @@
![freqai-logo](assets/freqai_doc_logo.svg) ![freqai-logo](assets/freqai_doc_logo.svg)
# FreqAI # `FreqAI`
FreqAI is a module designed to automate a variety of tasks associated with training a predictive model to generate market forecasts given a set of input features. ## Introduction
Among the the features included: `FreqAI` is a software designed to automate a variety of tasks associated with training a predictive machine learning model to generate market forecasts given a set of input features.
* **Self-adaptive retraining**: retrain models during live deployments to self-adapt to the market in an unsupervised manner. Features include:
* **Rapid feature engineering**: create large rich feature sets (10k+ features) based on simple user created strategies.
* **High performance**: adaptive retraining occurs on separate thread (or on GPU if available) from inferencing and bot trade operations. Keep newest models and data in memory for rapid inferencing. * **Self-adaptive retraining** - Retrain models during [live deployments](freqai-running.md#live-deployments) to self-adapt to the market in a supervised manner
* **Realistic backtesting**: emulate self-adaptive retraining with backtesting module that automates past retraining. * **Rapid feature engineering** - Create large rich [feature sets](freqai-feature-engineering.md#feature-engineering) (10k+ features) based on simple user-created strategies
* **Modifiable**: use the generalized and robust architecture for incorporating any machine learning library/method available in Python. Seven examples available. * **High performance** - Threading allows for adaptive model retraining on a separate thread (or on GPU if available) from model inferencing (prediction) and bot trade operations. Newest models and data are kept in RAM for rapid inferencing
* **Smart outlier removal**: remove outliers from training and prediction sets using a variety of outlier detection techniques. * **Realistic backtesting** - Emulate self-adaptive training on historic data with a [backtesting module](freqai-running.md#backtesting) that automates retraining
* **Crash resilience**: model storage to disk to make reloading from a crash fast and easy (and purge obsolete files for sustained dry/live runs). * **Extensibility** - The generalized and robust architecture allows for incorporating any [machine learning library/method](freqai-configuration.md#using-different-prediction-models) available in Python. Eight examples are currently available, including classifiers, regressors, and a convolutional neural network
* **Automated data normalization**: normalize the data in a smart and statistically safe way. * **Smart outlier removal** - Remove outliers from training and prediction data sets using a variety of [outlier detection techniques](freqai-feature-engineering.md#outlier-detection)
* **Automatic data download**: compute the data download timerange and update historic data (in live deployments). * **Crash resilience** - Store trained models to disk to make reloading from a crash fast and easy, and [purge obsolete files](freqai-running.md#purging-old-model-data) for sustained dry/live runs
* **Clean incoming data** safe NaN handling before training and prediction. * **Automatic data normalization** - [Normalize the data](freqai-feature-engineering.md#feature-normalization) in a smart and statistically safe way
* **Dimensionality reduction**: reduce the size of the training data via Principal Component Analysis. * **Automatic data download** - Compute timeranges for data downloads and update historic data (in live deployments)
* **Deploy bot fleets**: set one bot to train models while a fleet of other bots inference into the models and handle trades. * **Cleaning of incoming data** - Handle NaNs safely before training and model inferencing
* **Dimensionality reduction** - Reduce the size of the training data via [Principal Component Analysis](freqai-feature-engineering.md#data-dimensionality-reduction-with-principal-component-analysis)
* **Deploying bot fleets** - Set one bot to train models while a fleet of [follower bots](freqai-running.md#setting-up-a-follower) inference the models and handle trades
## Quick start ## Quick start
The easiest way to quickly test FreqAI is to run it in dry run with the following command The easiest way to quickly test `FreqAI` is to run it in dry mode with the following command:
```bash ```bash
freqtrade trade --config config_examples/config_freqai.example.json --strategy FreqaiExampleStrategy --freqaimodel LightGBMRegressor --strategy-path freqtrade/templates freqtrade trade --config config_examples/config_freqai.example.json --strategy FreqaiExampleStrategy --freqaimodel LightGBMRegressor --strategy-path freqtrade/templates
``` ```
where the user will see the boot-up process of auto-data downloading, followed by simultaneous training and trading. You will see the boot-up process of automatic data downloading, followed by simultaneous training and trading.
The example strategy, example prediction model, and example config can all be found in An example strategy, prediction model, and config to use as a starting points can be found in
`freqtrade/templates/FreqaiExampleStrategy.py`, `freqtrade/freqai/prediction_models/LightGBMRegressor.py`, `freqtrade/templates/FreqaiExampleStrategy.py`, `freqtrade/freqai/prediction_models/LightGBMRegressor.py`, and
`config_examples/config_freqai.example.json`, respectively. `config_examples/config_freqai.example.json`, respectively.
## General approach ## General approach
The user provides FreqAI with a set of custom *base* indicators (created inside the strategy the same way You provide `FreqAI` with a set of custom *base indicators* (the same way as in a [typical Freqtrade strategy](strategy-customization.md)) as well as target values (*labels*). For each pair in the whitelist, `FreqAI` trains a model to predict the target values based on the input of custom indicators. The models are then consistently retrained, with a predetermined frequency, to adapt to market conditions. `FreqAI` offers the ability to both backtest strategies (emulating reality with periodic retraining on historic data) and deploy dry/live runs. In dry/live conditions, `FreqAI` can be set to constant retraining in a background thread to keep models as up to date as possible.
a typical Freqtrade strategy is created) as well as target values which look into the future.
FreqAI trains a model to predict the target value based on the input of custom indicators for each pair in the whitelist. These models are consistently retrained to adapt to market conditions. FreqAI offers the ability to both backtest strategies (emulating reality with periodic retraining) and deploy dry/live. In dry/live conditions, FreqAI can be set to constant retraining in a background thread in an effort to keep models as young as possible.
An overview of the algorithm is shown here to help users understand the data processing pipeline and the model usage. An overview of the algorithm, explaining the data processing pipeline and model usage, is shown below.
![freqai-algo](assets/freqai_algo.png) ![freqai-algo](assets/freqai_algo.jpg)
## Background and vocabulary ### Important machine learning vocabulary
**Features** are the quantities with which a model is trained. $X_i$ represents the **Features** - the parameters, based on historic data, on which a model is trained. All features for a single candle is stored as a vector. In `FreqAI`, you build a feature data sets from anything you can construct in the strategy.
vector of all features for a single candle. In FreqAI, the user
builds the features from anything they can construct in the strategy.
**Labels** are the target values with which the weights inside a model are trained **Labels** - the target values that a model is trained toward. Each feature vector is associated with a single label that is defined by you within the strategy. These labels intentionally look into the future, and are not available to the model during dry/live/backtesting.
toward. Each set of features is associated with a single label, which is also
defined within the strategy by the user. These labels intentionally look into the
future, and are not available to the model during dryrun/live/backtesting.
**Training** refers to the process of feeding individual feature sets into the **Training** - the process of "teaching" the model to match the feature sets to the associated labels. Different types of models "learn" in different ways. More information about the different models can be found [here](freqai-configuration.md#using-different-prediction-models).
model with associated labels with the goal of matching input feature sets to associated labels.
**Train data** is a subset of the historic data which is fed to the model during **Train data** - a subset of the feature data set that is fed to the model during training. This data directly influences weight connections in the model.
training to adjust weights. This data directly influences weight connections in the model.
**Test data** is a subset of the historic data which is used to evaluate the **Test data** - a subset of the feature data set that is used to evaluate the performance of the model after training. This data does not influence nodal weights within the model.
intermediate performance of the model during training. This data does not
directly influence nodal weights within the model. **Inferencing** - the process of feeding a trained model new data on which it will make a prediction.
## Install prerequisites ## Install prerequisites
The normal Freqtrade install process will ask the user if they wish to install FreqAI dependencies. The user should reply "yes" to this question if they wish to use FreqAI. If the user did not reply yes, they can manually install these dependencies after the install with: The normal Freqtrade install process will ask if you wish to install `FreqAI` dependencies. You should reply "yes" to this question if you wish to use `FreqAI`. If you did not reply yes, you can manually install these dependencies after the install with:
``` bash ``` bash
pip install -r requirements-freqai.txt pip install -r requirements-freqai.txt
``` ```
!!! Note !!! Note
Catboost will not be installed on arm devices (raspberry, Mac M1, ARM based VPS, ...), since Catboost does not provide wheels for this platform. Catboost will not be installed on arm devices (raspberry, Mac M1, ARM based VPS, ...), since it does not provide wheels for this platform.
### Usage with docker ### Usage with docker
For docker users, a dedicated tag with freqAI dependencies is available as `:freqai`. If you are using docker, a dedicated tag with `FreqAI` dependencies is available as `:freqai`. As such - you can replace the image line in your docker-compose file with `image: freqtradeorg/freqtrade:develop_freqai`. This image contains the regular `FreqAI` dependencies. Similar to native installs, Catboost will not be available on ARM based devices.
As such - you can replace the image line in your docker-compose file with `image: freqtradeorg/freqtrade:develop_freqai`.
This image contains the regular freqAI dependencies. Similar to native installs, Catboost will not be available on ARM based devices.
## Configuring FreqAI ## Common pitfalls
### Parameter table `FreqAI` cannot be combined with dynamic `VolumePairlists` (or any pairlist filter that adds and removes pairs dynamically).
This is for performance reasons - `FreqAI` relies on making quick predictions/retrains. To do this effectively,
The table below will list all configuration parameters available for FreqAI. it needs to download all the training data at the beginning of a dry/live instance. `FreqAI` stores and appends
new candles automatically for future retrains. This means that if new pairs arrive later in the dry run due to a volume pairlist, it will not have the data ready. However, `FreqAI` does work with the `ShufflePairlist` or a `VolumePairlist` which keeps the total pairlist constant (but reorders the pairs according to volume).
Mandatory parameters are marked as **Required**, which means that they are required to be set in one of the possible ways.
| Parameter | Description |
|------------|-------------|
| `freqai` | **Required.** The parent dictionary containing all the parameters below for controlling FreqAI. <br> **Datatype:** dictionary.
| `identifier` | **Required.** A unique name for the current model. This can be reused to reload pre-trained models/data. <br> **Datatype:** string.
| `train_period_days` | **Required.** Number of days to use for the training data (width of the sliding window). <br> **Datatype:** positive integer.
| `backtest_period_days` | **Required.** Number of days to inference into the trained model before sliding the window and retraining. This can be fractional days, but beware that the user provided `timerange` will be divided by this number to yield the number of trainings necessary to complete the backtest. <br> **Datatype:** Float.
| `live_retrain_hours` | Frequency of retraining during dry/live runs. Default set to 0, which means it will retrain as often as possible. <br> **Datatype:** Float > 0.
| `follow_mode` | If true, this instance of FreqAI will look for models associated with `identifier` and load those for inferencing. A `follower` will **not** train new models. `False` by default. <br> **Datatype:** boolean.
| `startup_candles` | Number of candles needed for *backtesting only* to ensure all indicators are non NaNs at the start of the first train period. <br> **Datatype:** positive integer.
| `fit_live_predictions_candles` | Computes target (label) statistics from prediction data, instead of from the training data set. Number of candles is the number of historical candles it uses to generate the statistics. <br> **Datatype:** positive integer.
| `purge_old_models` | Tell FreqAI to delete obsolete models. Otherwise, all historic models will remain on disk. Defaults to `False`. <br> **Datatype:** boolean.
| `expiration_hours` | Ask FreqAI to avoid making predictions if a model is more than `expiration_hours` old. Defaults to 0 which means models never expire. <br> **Datatype:** positive integer.
| | **Feature Parameters**
| `feature_parameters` | A dictionary containing the parameters used to engineer the feature set. Details and examples shown [here](#feature-engineering) <br> **Datatype:** dictionary.
| `include_corr_pairlist` | A list of correlated coins that FreqAI will add as additional features to all `pair_whitelist` coins. All indicators set in `populate_any_indicators` will be created for each coin in this list, and that set of features is added to the base asset feature set. <br> **Datatype:** list of assets (strings).
| `include_timeframes` | A list of timeframes that all indicators in `populate_any_indicators` will be created for and added as features to the base asset feature set. <br> **Datatype:** list of timeframes (strings).
| `label_period_candles` | Number of candles into the future that the labels are created for. This is used in `populate_any_indicators`, refer to `templates/FreqaiExampleStrategy.py` for detailed usage. The user can create custom labels, making use of this parameter not. <br> **Datatype:** positive integer.
| `include_shifted_candles` | Parameter used to add a sense of temporal recency to flattened regression type input data. `include_shifted_candles` takes all features, duplicates and shifts them by the number indicated by user. <br> **Datatype:** positive integer.
| `DI_threshold` | Activates the Dissimilarity Index for outlier detection when above 0, explained in detail [here](#removing-outliers-with-the-dissimilarity-index). <br> **Datatype:** positive float (typically below 1).
| `weight_factor` | Used to set weights for training data points according to their recency, see details and a figure of how it works [here](#controlling-the-model-learning-process). <br> **Datatype:** positive float (typically below 1).
| `principal_component_analysis` | Ask FreqAI to automatically reduce the dimensionality of the data set using PCA. <br> **Datatype:** boolean.
| `use_SVM_to_remove_outliers` | Ask FreqAI to train a support vector machine to detect and remove outliers from the training data set as well as from incoming data points. <br> **Datatype:** boolean.
| `svm_params` | All parameters available in Sklearn's `SGDOneClassSVM()`. E.g. `nu` *Very* broadly, is the percentage of data points that should be considered outliers. `shuffle` is by default false to maintain reproducibility. But these and all others can be added/changed in this dictionary. <br> **Datatype:** dictionary.
| `stratify_training_data` | This value is used to indicate the stratification of the data. e.g. 2 would set every 2nd data point into a separate dataset to be pulled from during training/testing. <br> **Datatype:** positive integer.
| `indicator_max_period_candles` | The maximum *period* used in `populate_any_indicators()` for indicator creation. FreqAI uses this information in combination with the maximum timeframe to calculate how many data points it should download so that the first data point does not have a NaN <br> **Datatype:** positive integer.
| `indicator_periods_candles` | A list of integers used to duplicate all indicators according to a set of periods and add them to the feature set. <br> **Datatype:** list of positive integers.
| `use_DBSCAN_to_remove_outliers` | Inactive by default. If true, FreqAI clusters data using DBSCAN to identify and remove outliers from training and prediction data. <br> **Datatype:** float (fraction of 1).
| | **Data split parameters**
| `data_split_parameters` | Include any additional parameters available from Scikit-learn `test_train_split()`, which are shown [here](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html) <br> **Datatype:** dictionary.
| `test_size` | Fraction of data that should be used for testing instead of training. <br> **Datatype:** positive float below 1.
| `shuffle` | Shuffle the training data points during training. Typically for time-series forecasting, this is set to False. <br> **Datatype:** boolean.
| | **Model training parameters**
| `model_training_parameters` | A flexible dictionary that includes all parameters available by the user selected library. For example, if the user uses `LightGBMRegressor`, then this dictionary can contain any parameter available by the `LightGBMRegressor` [here](https://lightgbm.readthedocs.io/en/latest/pythonapi/lightgbm.LGBMRegressor.html). If the user selects a different model, then this dictionary can contain any parameter from that different model. <br> **Datatype:** dictionary.
| `n_estimators` | A common parameter among regressors which sets the number of boosted trees to fit <br> **Datatype:** integer.
| `learning_rate` | A common parameter among regressors which sets the boosting learning rate. <br> **Datatype:** float.
| `n_jobs`, `thread_count`, `task_type` | Different libraries use different parameter names to control the number of threads used for parallel processing or whether or not it is a `task_type` of `gpu` or `cpu`. <br> **Datatype:** float.
| | **Extraneous parameters**
| `keras` | If your model makes use of keras (typical of Tensorflow based prediction models), activate this flag so that the model save/loading follows keras standards. Default value `false` <br> **Datatype:** boolean.
| `conv_width` | The width of a convolutional neural network input tensor. This replaces the need for `shift` by feeding in historical data points as the second dimension of the tensor. Technically, this parameter can also be used for regressors, but it only adds computational overhead and does not change the model training/prediction. Default value, 2 <br> **Datatype:** integer.
### Important FreqAI dataframe key patterns
Here are the values the user can expect to include/use inside the typical strategy dataframe (`df[]`):
| DataFrame Key | Description |
|------------|-------------|
| `df['&*']` | Any dataframe column prepended with `&` in `populate_any_indicators()` is treated as a training target inside FreqAI (typically following the naming convention `&-s*`). These same dataframe columns names are fed back to the user as the predictions. For example, the user wishes to predict the price change in the next 40 candles (similar to `templates/FreqaiExampleStrategy.py`) by setting `df['&-s_close']`. FreqAI makes the predictions and gives them back to the user under the same key (`df['&-s_close']`) to be used in `populate_entry/exit_trend()`. <br> **Datatype:** depends on the output of the model.
| `df['&*_std/mean']` | The standard deviation and mean values of the user defined labels during training (or live tracking with `fit_live_predictions_candles`). Commonly used to understand rarity of prediction (use the z-score as shown in `templates/FreqaiExampleStrategy.py` to evaluate how often a particular prediction was observed during training (or historically with `fit_live_predictions_candles`)<br> **Datatype:** float.
| `df['do_predict']` | An indication of an outlier, this return value is integer between -1 and 2 which lets the user understand if the prediction is trustworthy or not. `do_predict==1` means the prediction is trustworthy. If the [Dissimilarity Index](#removing-outliers-with-the-dissimilarity-index) is above the user defined threshold, it will subtract 1 from `do_predict`. If `use_SVM_to_remove_outliers()` is active, then the Support Vector Machine (SVM) may also detect outliers in training and prediction data. In this case, the SVM will also subtract one from `do_predict`. A particular case is when `do_predict == 2`, it means that the model has expired due to `expired_hours`. <br> **Datatype:** integer between -1 and 2.
| `df['DI_values']` | The raw Dissimilarity Index values to give the user a sense of confidence in the prediction. Lower DI means the data point is closer to the trained parameter space. <br> **Datatype:** float.
| `df['%*']` | Any dataframe column prepended with `%` in `populate_any_indicators()` is treated as a training feature inside FreqAI. For example, the user can include the rsi in the training feature set (similar to `templates/FreqaiExampleStrategy.py`) by setting `df['%-rsi']`. See more details on how this is done [here](#building-the-feature-set). <br>**Note**: since the number of features prepended with `%` can multiply very quickly (10s of thousands of features is easily engineered using the multiplictative functionality described in the `feature_parameters` table.) these features are removed from the dataframe upon return from FreqAI. If the user wishes to keep a particular type of feature for plotting purposes, you can prepend it with `%%`. <br> **Datatype:** depends on the output of the model.
### Example config file
The user interface is isolated to the typical config file. A typical FreqAI config setup could include:
```json
"freqai": {
"startup_candles": 10000,
"purge_old_models": true,
"train_period_days": 30,
"backtest_period_days": 7,
"identifier" : "unique-id",
"feature_parameters" : {
"include_timeframes": ["5m","15m","4h"],
"include_corr_pairlist": [
"ETH/USD",
"LINK/USD",
"BNB/USD"
],
"label_period_candles": 24,
"include_shifted_candles": 2,
"weight_factor": 0,
"indicator_max_period_candles": 20,
"indicator_periods_candles": [10, 20]
},
"data_split_parameters" : {
"test_size": 0.25,
"random_state": 42
},
"model_training_parameters" : {
"n_estimators": 100,
"random_state": 42,
"learning_rate": 0.02,
"task_type": "CPU",
},
}
```
### Feature engineering
Features are added by the user inside the `populate_any_indicators()` method of the strategy
by prepending indicators with `%` and labels are added by prepending `&`.
There are some important components/structures that the user *must* include when building their feature set.
Another structure to consider is the location of the labels at the bottom of the example function (below `if set_generalized_indicators:`).
This is where the user will add single features and labels to their feature set to avoid duplication from
various configuration parameters which multiply the feature set such as `include_timeframes`.
```python
def populate_any_indicators(
self, pair, df, tf, informative=None, set_generalized_indicators=False
):
"""
Function designed to automatically generate, name and merge features
from user indicated timeframes in the configuration file. User controls the indicators
passed to the training/prediction by prepending indicators with `'%-' + coin `
(see convention below). I.e. user should not prepend any supporting metrics
(e.g. bb_lowerband below) with % unless they explicitly want to pass that metric to the
model.
:param pair: pair to be used as informative
:param df: strategy dataframe which will receive merges from informatives
:param tf: timeframe of the dataframe which will modify the feature names
:param informative: the dataframe associated with the informative pair
:param coin: the name of the coin which will modify the feature names.
"""
coint = pair.split('/')[0]
if informative is None:
informative = self.dp.get_pair_dataframe(pair, tf)
# first loop is automatically duplicating indicators for time periods
for t in self.freqai_info["feature_parameters"]["indicator_periods_candles"]:
t = int(t)
informative[f"%-{coin}rsi-period_{t}"] = ta.RSI(informative, timeperiod=t)
informative[f"%-{coin}mfi-period_{t}"] = ta.MFI(informative, timeperiod=t)
informative[f"%-{coin}adx-period_{t}"] = ta.ADX(informative, window=t)
bollinger = qtpylib.bollinger_bands(
qtpylib.typical_price(informative), window=t, stds=2.2
)
informative[f"{coin}bb_lowerband-period_{t}"] = bollinger["lower"]
informative[f"{coin}bb_middleband-period_{t}"] = bollinger["mid"]
informative[f"{coin}bb_upperband-period_{t}"] = bollinger["upper"]
informative[f"%-{coin}bb_width-period_{t}"] = (
informative[f"{coin}bb_upperband-period_{t}"]
- informative[f"{coin}bb_lowerband-period_{t}"]
) / informative[f"{coin}bb_middleband-period_{t}"]
informative[f"%-{coin}close-bb_lower-period_{t}"] = (
informative["close"] / informative[f"{coin}bb_lowerband-period_{t}"]
)
informative[f"%-{coin}relative_volume-period_{t}"] = (
informative["volume"] / informative["volume"].rolling(t).mean()
)
indicators = [col for col in informative if col.startswith("%")]
# This loop duplicates and shifts all indicators to add a sense of recency to data
for n in range(self.freqai_info["feature_parameters"]["include_shifted_candles"] + 1):
if n == 0:
continue
informative_shift = informative[indicators].shift(n)
informative_shift = informative_shift.add_suffix("_shift-" + str(n))
informative = pd.concat((informative, informative_shift), axis=1)
df = merge_informative_pair(df, informative, self.config["timeframe"], tf, ffill=True)
skip_columns = [
(s + "_" + tf) for s in ["date", "open", "high", "low", "close", "volume"]
]
df = df.drop(columns=skip_columns)
# Add generalized indicators here (because in live, it will call this
# function to populate indicators during training). Notice how we ensure not to
# add them multiple times
if set_generalized_indicators:
df["%-day_of_week"] = (df["date"].dt.dayofweek + 1) / 7
df["%-hour_of_day"] = (df["date"].dt.hour + 1) / 25
# user adds targets here by prepending them with &- (see convention below)
# If user wishes to use multiple targets, a multioutput prediction model
# needs to be used such as templates/CatboostPredictionMultiModel.py
df["&-s_close"] = (
df["close"]
.shift(-self.freqai_info["feature_parameters"]["label_period_candles"])
.rolling(self.freqai_info["feature_parameters"]["label_period_candles"])
.mean()
/ df["close"]
- 1
)
return df
```
The user of the present example does not wish to pass the `bb_lowerband` as a feature to the model,
and has therefore not prepended it with `%`. The user does, however, wish to pass `bb_width` to the
model for training/prediction and has therefore prepended it with `%`.
The `include_timeframes` from the example config above are the timeframes (`tf`) of each call to `populate_any_indicators()`
included metric for inclusion in the feature set. In the present case, the user is asking for the
`5m`, `15m`, and `4h` timeframes of the `rsi`, `mfi`, `roc`, and `bb_width` to be included in the feature set.
In addition, the user can ask for each of these features to be included from
informative pairs using the `include_corr_pairlist`. This means that the present feature
set will include all the features from `populate_any_indicators` on all the `include_timeframes` for each of
`ETH/USD`, `LINK/USD`, and `BNB/USD`.
`include_shifted_candles` is another user controlled parameter which indicates the number of previous
candles to include in the present feature set. In other words, `include_shifted_candles: 2`, tells
FreqAI to include the the past 2 candles for each of the features included in the dataset.
In total, the number of features the present user has created is:
length of `include_timeframes` * no. features in `populate_any_indicators()` * length of `include_corr_pairlist` * no. `include_shifted_candles` * length of `indicator_periods_candles`
$3 * 3 * 3 * 2 * 2 = 108$.
!!! Note
Features **must** be defined in `populate_any_indicators()`. Making features in `populate_indicators()`
will fail in live/dry mode. If the user wishes to add generalized features that are not associated with
a specific pair or timeframe, they should use the following structure inside `populate_any_indicators()`
(as exemplified in `freqtrade/templates/FreqaiExampleStrategy.py`:
```python
def populate_any_indicators(self, metadata, pair, df, tf, informative=None, coin="", set_generalized_indicators=False):
...
# Add generalized indicators here (because in live, it will call only this function to populate
# indicators for retraining). Notice how we ensure not to add them multiple times by associating
# these generalized indicators to the basepair/timeframe
if set_generalized_indicators:
df['%-day_of_week'] = (df["date"].dt.dayofweek + 1) / 7
df['%-hour_of_day'] = (df['date'].dt.hour + 1) / 25
# user adds targets here by prepending them with &- (see convention below)
# If user wishes to use multiple targets, a multioutput prediction model
# needs to be used such as templates/CatboostPredictionMultiModel.py
df["&-s_close"] = (
df["close"]
.shift(-self.freqai_info["feature_parameters"]["label_period_candles"])
.rolling(self.freqai_info["feature_parameters"]["label_period_candles"])
.mean()
/ df["close"]
- 1
)
```
(Please see the example script located in `freqtrade/templates/FreqaiExampleStrategy.py` for a full example of `populate_any_indicators()`)
### Deciding the sliding training window and backtesting duration
Users define the backtesting timerange with the typical `--timerange` parameter in the user
configuration file. `train_period_days` is the duration of the sliding training window, while
`backtest_period_days` is the sliding backtesting window, both in number of days (`backtest_period_days` can be
a float to indicate sub daily retraining in live/dry mode). In the present example,
the user is asking FreqAI to use a training period of 30 days and backtest the subsequent 7 days.
This means that if the user sets `--timerange 20210501-20210701`,
FreqAI will train 8 separate models (because the full range comprises 8 weeks),
and then backtest the subsequent week associated with each of the 8 training
data set timerange months. Users can think of this as a "sliding window" which
emulates FreqAI retraining itself once per week in live using the previous
month of data.
In live, the required training data is automatically computed and downloaded. However, in backtesting
the user must manually enter the required number of `startup_candles` in the config. This value
is used to increase the available data to FreqAI and should be sufficient to enable all indicators
to be NaN free at the beginning of the first training timerange. This boils down to identifying the
highest timeframe (`4h` in present example) and the longest indicator period (25 in present example)
and adding this to the `train_period_days`. The units need to be in the base candle time frame:
`startup_candles` = ( 4 hours * 25 max period * 60 minutes/hour + 30 day train_period_days * 1440 minutes per day ) / 5 min (base time frame) = 1488.
!!! Note
In dry/live, this is all precomputed and handled automatically. Thus, `startup_candle` has no influence on dry/live.
!!! Note
Although fractional `backtest_period_days` is allowed, the user should be ware that the `--timerange` is divided by this value to determine the number of models that FreqAI will need to train in order to backtest the full range. For example, if the user wants to set a `--timerange` of 10 days, and asks for a `backtest_period_days` of 0.1, FreqAI will need to train 100 models per pair to complete the full backtest. This is why it is physically impossible to truly backtest FreqAI adaptive training. The best way to fully test a model is to run it dry and let it constantly train. In this case, backtesting would take the exact same amount of time as a dry run.
## Running FreqAI
### Backtesting
The FreqAI backtesting module can be executed with the following command:
```bash
freqtrade backtesting --strategy FreqaiExampleStrategy --config config_freqai.example.json --freqaimodel LightGBMRegressor --timerange 20210501-20210701
```
Backtesting mode requires the user to have the data pre-downloaded (unlike dry/live, where FreqAI automatically downloads the necessary data). The user should be careful to consider that the range of the downloaded data is more than the backtesting range. This is because FreqAI needs data prior to the desired backtesting range in order to train a model to be ready to make predictions on the first candle of the user set backtesting range. More details on how to calculate the data download timerange can be found [here](#deciding-the-sliding-training-window-and-backtesting-duration).
If this command has never been executed with the existing config file, then it will train a new model
for each pair, for each backtesting window within the bigger `--timerange`.
!!! Note "Model reuse"
Once the training is completed, the user can execute this again with the same config file and
FreqAI will find the trained models and load them instead of spending time training. This is useful
if the user wants to tweak (or even hyperopt) buy and sell criteria inside the strategy. IF the user
*wants* to retrain a new model with the same config file, then he/she should simply change the `identifier`.
This way, the user can return to using any model they wish by simply changing the `identifier`.
---
### Building a freqai strategy
The FreqAI strategy requires the user to include the following lines of code in the strategy:
```python
def informative_pairs(self):
whitelist_pairs = self.dp.current_whitelist()
corr_pairs = self.config["freqai"]["feature_parameters"]["include_corr_pairlist"]
informative_pairs = []
for tf in self.config["freqai"]["feature_parameters"]["include_timeframes"]:
for pair in whitelist_pairs:
informative_pairs.append((pair, tf))
for pair in corr_pairs:
if pair in whitelist_pairs:
continue # avoid duplication
informative_pairs.append((pair, tf))
return informative_pairs
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# All indicators must be populated by populate_any_indicators() for live functionality
# to work correctly.
# the model will return all labels created by user in `populate_any_indicators`
# (& appended targets), an indication of whether or not the prediction should be accepted,
# the target mean/std values for each of the labels created by user in
# `populate_any_indicators()` for each training period.
dataframe = self.freqai.start(dataframe, metadata, self)
return dataframe
def populate_any_indicators(
self, pair, df, tf, informative=None, set_generalized_indicators=False
):
"""
Function designed to automatically generate, name and merge features
from user indicated timeframes in the configuration file. User controls the indicators
passed to the training/prediction by prepending indicators with `'%-' + coin `
(see convention below). I.e. user should not prepend any supporting metrics
(e.g. bb_lowerband below) with % unless they explicitly want to pass that metric to the
model.
:param pair: pair to be used as informative
:param df: strategy dataframe which will receive merges from informatives
:param tf: timeframe of the dataframe which will modify the feature names
:param informative: the dataframe associated with the informative pair
:param coin: the name of the coin which will modify the feature names.
"""
coin = pair.split('/')[0]
if informative is None:
informative = self.dp.get_pair_dataframe(pair, tf)
# first loop is automatically duplicating indicators for time periods
for t in self.freqai_info["feature_parameters"]["indicator_periods_candles"]:
t = int(t)
informative[f"%-{coin}rsi-period_{t}"] = ta.RSI(informative, timeperiod=t)
informative[f"%-{coin}mfi-period_{t}"] = ta.MFI(informative, timeperiod=t)
informative[f"%-{coin}adx-period_{t}"] = ta.ADX(informative, window=t)
indicators = [col for col in informative if col.startswith("%")]
# This loop duplicates and shifts all indicators to add a sense of recency to data
for n in range(self.freqai_info["feature_parameters"]["include_shifted_candles"] + 1):
if n == 0:
continue
informative_shift = informative[indicators].shift(n)
informative_shift = informative_shift.add_suffix("_shift-" + str(n))
informative = pd.concat((informative, informative_shift), axis=1)
df = merge_informative_pair(df, informative, self.config["timeframe"], tf, ffill=True)
skip_columns = [
(s + "_" + tf) for s in ["date", "open", "high", "low", "close", "volume"]
]
df = df.drop(columns=skip_columns)
# Add generalized indicators here (because in live, it will call this
# function to populate indicators during training). Notice how we ensure not to
# add them multiple times
if set_generalized_indicators:
# user adds targets here by prepending them with &- (see convention below)
# If user wishes to use multiple targets, a multioutput prediction model
# needs to be used such as templates/CatboostPredictionMultiModel.py
df["&-s_close"] = (
df["close"]
.shift(-self.freqai_info["feature_parameters"]["label_period_candles"])
.rolling(self.freqai_info["feature_parameters"]["label_period_candles"])
.mean()
/ df["close"]
- 1
)
return df
```
Notice how the `populate_any_indicators()` is where the user adds their own features and labels ([more information](#feature-engineering)). See a full example at `templates/FreqaiExampleStrategy.py`.
### Setting classifier targets
FreqAI includes a the `CatboostClassifier` via the flag `--freqaimodel CatboostClassifier`. Typically, the user would set the targets using strings:
```python
df['&s-up_or_down'] = np.where( df["close"].shift(-100) > df["close"], 'up', 'down')
```
### Running the model live
FreqAI can be run dry/live using the following command
```bash
freqtrade trade --strategy FreqaiExampleStrategy --config config_freqai.example.json --freqaimodel LightGBMRegressor
```
By default, FreqAI will not find any existing models and will start by training a new one
given the user configuration settings. Following training, it will use that model to make predictions on incoming candles until a new model is available. New models are typically generated as often as possible, with FreqAI managing an internal queue of the pairs to try and keep all models equally "young." FreqAI will always use the newest trained model to make predictions on incoming live data. If users do not want FreqAI to retrain new models as often as possible, they can set `live_retrain_hours` to tell FreqAI to wait at least that number of hours before retraining a new model. Additionally, users can set `expired_hours` to tell FreqAI to avoid making predictions on models aged over this number of hours.
If the user wishes to start dry/live from a backtested saved model, the user only needs to reuse
the same `identifier` parameter
```json
"freqai": {
"identifier": "example",
"live_retrain_hours": 1
}
```
In this case, although FreqAI will initiate with a
pre-trained model, it will still check to see how much time has elapsed since the model was trained,
and if a full `live_retrain_hours` has elapsed since the end of the loaded model, FreqAI will self retrain.
## Data analysis techniques
### Controlling the model learning process
Model training parameters are unique to the ML library used by the user. FreqAI allows users to set any parameter for any library using the `model_training_parameters` dictionary in the user configuration file. The example configuration files show some of the example parameters associated with `Catboost` and `LightGBM`, but users can add any parameters available in those libraries.
Data split parameters are defined in `data_split_parameters` which can be any parameters associated with `Sklearn`'s `train_test_split()` function. FreqAI includes some additional parameters such `weight_factor` which allows the user to weight more recent data more strongly
than past data via an exponential function:
$$ W_i = \exp(\frac{-i}{\alpha*n}) $$
where $W_i$ is the weight of data point $i$ in a total set of $n$ data points.
![weight-factor](assets/weights_factor.png)
`train_test_split()` has a parameters called `shuffle`, which users also have access to in FreqAI, that allows them to keep the data unshuffled. This is particularly useful to avoid biasing training with temporally auto-correlated data.
Finally, `label_period_candles` defines the offset used for the `labels`. In the present example,
the user is asking for `labels` that are 24 candles in the future.
### Removing outliers with the Dissimilarity Index
The Dissimilarity Index (DI) aims to quantify the uncertainty associated with each
prediction by the model. To do so, FreqAI measures the distance between each training
data point and all other training data points:
$$ d_{ab} = \sqrt{\sum_{j=1}^p(X_{a,j}-X_{b,j})^2} $$
where $d_{ab}$ is the distance between the normalized points $a$ and $b$. $p$
is the number of features i.e. the length of the vector $X$.
The characteristic distance, $\overline{d}$ for a set of training data points is simply the mean
of the average distances:
$$ \overline{d} = \sum_{a=1}^n(\sum_{b=1}^n(d_{ab}/n)/n) $$
$\overline{d}$ quantifies the spread of the training data, which is compared to
the distance between the new prediction feature vectors, $X_k$ and all the training
data:
$$ d_k = \arg \min d_{k,i} $$
which enables the estimation of a Dissimilarity Index:
$$ DI_k = d_k/\overline{d} $$
Equity and crypto markets suffer from a high level of non-patterned noise in the
form of outlier data points. The dissimilarity index allows predictions which
are outliers and not existent in the model feature space, to be thrown out due
to low levels of certainty. Activating the Dissimilarity Index can be achieved with:
```json
"freqai": {
"feature_parameters" : {
"DI_threshold": 1
}
}
```
The user can tweak the DI with `DI_threshold` to increase or decrease the extrapolation of the trained model.
### Reducing data dimensionality with Principal Component Analysis
Users can reduce the dimensionality of their features by activating the `principal_component_analysis`:
```json
"freqai": {
"feature_parameters" : {
"principal_component_analysis": true
}
}
```
Which will perform PCA on the features and reduce the dimensionality of the data so that the explained
variance of the data set is >= 0.999.
### Removing outliers using a Support Vector Machine (SVM)
The user can tell FreqAI to remove outlier data points from the training/test data sets by setting:
```json
"freqai": {
"feature_parameters" : {
"use_SVM_to_remove_outliers": true
}
}
```
FreqAI will train an SVM on the training data (or components if the user activated
`principal_component_analysis`) and remove any data point that it deems to be sitting beyond the feature space.
### Clustering the training data and removing outliers with DBSCAN
The user can configure FreqAI to use DBSCAN to cluster training data and remove outliers from the training data set. The user activates `use_DBSCAN_to_remove_outliers` to cluster training data for identification of outliers. Also used to detect incoming outliers for prediction data points.
```json
"freqai": {
"feature_parameters" : {
"use_DBSCAN_to_remove_outliers": true
}
}
```
### Stratifying the data
The user can stratify the training/testing data using:
```json
"freqai": {
"feature_parameters" : {
"stratify_training_data": 3
}
}
```
which will split the data chronologically so that every Xth data points is a testing data point. In the
present example, the user is asking for every third data point in the dataframe to be used for
testing, the other points are used for training.
## Setting up a follower
The user can define:
```json
"freqai": {
"follow_mode": true,
"identifier": "example"
}
```
to indicate to the bot that it should not train models, but instead should look for models trained
by a leader with the same `identifier`. In this example, the user has a leader bot with the
`identifier: "example"` already running or launching simultaneously as the present follower.
The follower will load models created by the leader and inference them to obtain predictions.
## Purging old model data
FreqAI stores new model files each time it retrains. These files become obsolete as new models
are trained and FreqAI adapts to the new market conditions. Users planning to leave FreqAI running
for extended periods of time with high frequency retraining should set `purge_old_models` in their
config:
```json
"freqai": {
"purge_old_models": true,
}
```
which will automatically purge all models older than the two most recently trained ones.
## Defining model expirations
During dry/live, FreqAI trains each pair sequentially (on separate threads/GPU from the main
Freqtrade bot). This means there is always an age discrepancy between models. If a user is training
on 50 pairs, and each pair requires 5 minutes to train, the oldest model will be over 4 hours old.
This may be undesirable if the characteristic time scale (read trade duration target) for a strategy
is much less than 4 hours. The user can decide to only make trade entries if the model is less than
a certain number of hours in age by setting the `expiration_hours` in the config file:
```json
"freqai": {
"expiration_hours": 0.5,
}
```
In the present example, the user will only allow predictions on models that are less than 1/2 hours
old.
## Choosing the calculation of the `target_roi`
As shown in `templates/FreqaiExampleStrategy.py`, the `target_roi` is based on two metrics computed
by FreqAI: `label_mean` and `label_std`. These are the statistics associated with the labels used
*during the most recent training*.
This allows the model to know what magnitude of a target to be expecting since it is directly stemming from the training data.
By default, FreqAI computes this based on training data and it assumes the labels are Gaussian distributed.
These are big assumptions that the user should consider when creating their labels. If the user wants to consider the population
of *historical predictions* for creating the dynamic target instead of the trained labels, the user
can do so by setting `fit_live_prediction_candles` to the number of historical prediction candles
the user wishes to use to generate target statistics.
```json
"freqai": {
"fit_live_prediction_candles": 300,
}
```
If the user sets this value, FreqAI will initially use the predictions from the training data set
and then subsequently begin introducing real prediction data as it is generated. FreqAI will save
this historical data to be reloaded if the user stops and restarts with the same `identifier`.
## Extra returns per train
Users may find that there are some important metrics that they'd like to return to the strategy at the end of each retrain.
Users can include these metrics by assigning them to `dk.data['extra_returns_per_train']['my_new_value'] = XYZ` inside their custom prediction
model class. FreqAI takes the `my_new_value` assigned in this dictionary and expands it to fit the return dataframe to the strategy.
The user can then use the value in the strategy with `dataframe['my_new_value']`. An example of how this is already used in FreqAI is
the `&*_mean` and `&*_std` values, which indicate the mean and standard deviation of that particular label during the most recent training.
Another example is shown below if the user wants to use live metrics from the trade database.
The user needs to set the standard dictionary in the config so FreqAI can return proper dataframe shapes:
```json
"freqai": {
"extra_returns_per_train": {"total_profit": 4}
}
```
These values will likely be overridden by the user prediction model, but in the case where the user model has yet to set them, or needs
a default initial value - this is the value that will be returned.
## Building an IFreqaiModel
FreqAI has multiple example prediction model based libraries such as `Catboost` regression (`freqai/prediction_models/CatboostRegressor.py`) and `LightGBM` regression.
However, users can customize and create their own prediction models using the `IFreqaiModel` class.
Users are encouraged to inherit `train()` and `predict()` to let them customize various aspects of their training procedures.
## Additional information
### Common pitfalls
FreqAI cannot be combined with `VolumePairlists` (or any pairlist filter that adds and removes pairs dynamically).
This is for performance reasons - FreqAI relies on making quick predictions/retrains. To do this effectively,
it needs to download all the training data at the beginning of a dry/live instance. FreqAI stores and appends
new candles automatically for future retrains. But this means that if new pairs arrive later in the dry run due
to a volume pairlist, it will not have the data ready. FreqAI does work, however, with the `ShufflePairlist`.
### Feature normalization
The feature set created by the user is automatically normalized to the training data only.
This includes all test data and unseen prediction data (dry/live/backtest).
### File structure
`user_data_dir/models/` contains all the data associated with the trainings and backtests.
This file structure is heavily controlled and read by the `FreqaiDataKitchen()`
and should therefore not be modified.
## Credits ## Credits
FreqAI was developed by a group of individuals who all contributed specific skillsets to the project. `FreqAI` is developed by a group of individuals who all contribute specific skillsets to the project.
Conception and software development: Conception and software development:
Robert Caulk @robcaulk Robert Caulk @robcaulk
Theoretical brainstorming: Theoretical brainstorming and data analysis:
Elin Törnquist @thorntwig Elin Törnquist @th0rntwig
Code review, software architecture brainstorming: Code review and software architecture brainstorming:
@xmatthias @xmatthias
Software development:
Wagner Costa @wagnercosta
Beta testing and bug reporting: Beta testing and bug reporting:
@bloodhunter4rc, Salah Lamkadem @ikonx, @ken11o2, @longyu, @paranoidandy, @smidelis, @smarm Stefan Gehring @bloodhunter4rc, @longyu, Andrew Robert Lawless @paranoidandy, Pascal Schmidt @smidelis, Ryan McMullan @smarmau,
Juha Nykänen @suikula, Wagner Costa @wagnercosta Juha Nykänen @suikula, Johan van der Vlugt @jooopiert, Richárd Józsa @richardjosza

View File

@@ -40,7 +40,8 @@ pip install -r requirements-hyperopt.txt
``` ```
usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [-s NAME] [--strategy-path PATH] [--userdir PATH] [-s NAME] [--strategy-path PATH]
[--recursive-strategy-search] [-i TIMEFRAME] [--recursive-strategy-search] [--freqaimodel NAME]
[--freqaimodel-path PATH] [-i TIMEFRAME]
[--timerange TIMERANGE] [--timerange TIMERANGE]
[--data-format-ohlcv {json,jsongz,hdf5}] [--data-format-ohlcv {json,jsongz,hdf5}]
[--max-open-trades INT] [--max-open-trades INT]
@@ -53,7 +54,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--print-all] [--no-color] [--print-json] [-j JOBS] [--print-all] [--no-color] [--print-json] [-j JOBS]
[--random-state INT] [--min-trades INT] [--random-state INT] [--min-trades INT]
[--hyperopt-loss NAME] [--disable-param-export] [--hyperopt-loss NAME] [--disable-param-export]
[--ignore-missing-spaces] [--ignore-missing-spaces] [--analyze-per-epoch]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
@@ -129,6 +130,7 @@ optional arguments:
--ignore-missing-spaces, --ignore-unparameterized-spaces --ignore-missing-spaces, --ignore-unparameterized-spaces
Suppress errors for any requested Hyperopt spaces that Suppress errors for any requested Hyperopt spaces that
do not contain any parameters. do not contain any parameters.
--analyze-per-epoch Run populate_indicators once per epoch.
Common arguments: Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages). -v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
@@ -154,6 +156,10 @@ Strategy arguments:
--recursive-strategy-search --recursive-strategy-search
Recursively search for a strategy in the strategies Recursively search for a strategy in the strategies
folder. folder.
--freqaimodel NAME Specify a custom freqaimodels.
--freqaimodel-path PATH
Specify additional lookup path for freqaimodels.
``` ```
### Hyperopt checklist ### Hyperopt checklist
@@ -185,7 +191,7 @@ Rarely you may also need to create a [nested class](advanced-hyperopt.md#overrid
### Hyperopt execution logic ### Hyperopt execution logic
Hyperopt will first load your data into memory and will then run `populate_indicators()` once per Pair to generate all indicators. Hyperopt will first load your data into memory and will then run `populate_indicators()` once per Pair to generate all indicators, unless `--analyze-per-epoch` is specified.
Hyperopt will then spawn into different processes (number of processors, or `-j <n>`), and run backtesting over and over again, changing the parameters that are part of the `--spaces` defined. Hyperopt will then spawn into different processes (number of processors, or `-j <n>`), and run backtesting over and over again, changing the parameters that are part of the `--spaces` defined.
@@ -426,9 +432,10 @@ While this strategy is most likely too simple to provide consistent profit, it s
`range` property may also be used with `DecimalParameter` and `CategoricalParameter`. `RealParameter` does not provide this property due to infinite search space. `range` property may also be used with `DecimalParameter` and `CategoricalParameter`. `RealParameter` does not provide this property due to infinite search space.
??? Hint "Performance tip" ??? Hint "Performance tip"
By doing the calculation of all possible indicators in `populate_indicators()`, the calculation of the indicator happens only once for every parameter. During normal hyperopting, indicators are calculated once and supplied to each epoch, linearly increasing RAM usage as a factor of increasing cores. As this also has performance implications, hyperopt provides `--analyze-per-epoch` which will move the execution of `populate_indicators()` to the epoch process, calculating a single value per parameter per epoch instead of using the `.range` functionality. In this case, `.range` functionality will only return the actually used value. This will reduce RAM usage, but increase CPU usage. However, your hyperopting run will be less likely to fail due to Out Of Memory (OOM) issues.
While this may slow down the hyperopt startup speed, the overall performance will increase as the Hyperopt execution itself may pick the same value for multiple epochs (changing other values).
You should however try to use space ranges as small as possible. Every new column will require more memory, and every possibility hyperopt can try will increase the search space. In either case, you should try to use space ranges as small as possible this will improve CPU/RAM usage in both scenarios.
## Optimizing protections ## Optimizing protections
@@ -879,6 +886,7 @@ To combat these, you have multiple options:
* Avoid using `--timeframe-detail` (this loads a lot of additional data into memory). * Avoid using `--timeframe-detail` (this loads a lot of additional data into memory).
* Reduce the number of parallel processes (`-j <n>`). * Reduce the number of parallel processes (`-j <n>`).
* Increase the memory of your machine. * Increase the memory of your machine.
* Use `--analyze-per-epoch` if you're using a lot of parameters with `.range` functionality.
## The objective has been evaluated at this point before. ## The objective has been evaluated at this point before.

View File

@@ -13,7 +13,7 @@
Please only use advanced trading modes when you know how freqtrade (and your strategy) works. Please only use advanced trading modes when you know how freqtrade (and your strategy) works.
Also, never risk more than what you can afford to lose. Also, never risk more than what you can afford to lose.
Please read the [strategy migration guide](strategy_migration.md#strategy-migration-between-v2-and-v3) to migrate your strategy from a freqtrade v2 strategy, to v3 strategy that can short and trade futures. If you already have an existing strategy, please read the [strategy migration guide](strategy_migration.md#strategy-migration-between-v2-and-v3) to migrate your strategy from a freqtrade v2 strategy, to strategy of version 3 which can short and trade futures.
## Shorting ## Shorting
@@ -62,6 +62,13 @@ You will also have to pick a "margin mode" (explanation below) - with freqtrade
"margin_mode": "isolated" "margin_mode": "isolated"
``` ```
##### Pair namings
Freqtrade follows the [ccxt naming conventions for futures](https://docs.ccxt.com/en/latest/manual.html?#perpetual-swap-perpetual-future).
A futures pair will therefore have the naming of `base/quote:settle` (e.g. `ETH/USDT:USDT`).
Binance is currently still an exception to this naming scheme, where pairs are named `ETH/USDT` also for futures markets, but will be aligned as soon as CCXT is ready.
### Margin mode ### Margin mode
On top of `trading_mode` - you will also have to configure your `margin_mode`. On top of `trading_mode` - you will also have to configure your `margin_mode`.

163
docs/producer-consumer.md Normal file
View File

@@ -0,0 +1,163 @@
# Producer / Consumer mode
freqtrade provides a mechanism whereby an instance (also called `consumer`) may listen to messages from an upstream freqtrade instance (also called `producer`) using the message websocket. Mainly, `analyzed_df` and `whitelist` messages. This allows the reuse of computed indicators (and signals) for pairs in multiple bots without needing to compute them multiple times.
See [Message Websocket](rest-api.md#message-websocket) in the Rest API docs for setting up the `api_server` configuration for your message websocket (this will be your producer).
!!! Note
We strongly recommend to set `ws_token` to something random and known only to yourself to avoid unauthorized access to your bot.
## Configuration
Enable subscribing to an instance by adding the `external_message_consumer` section to the consumer's config file.
```json
{
//...
"external_message_consumer": {
"enabled": true,
"producers": [
{
"name": "default", // This can be any name you'd like, default is "default"
"host": "127.0.0.1", // The host from your producer's api_server config
"port": 8080, // The port from your producer's api_server config
"ws_token": "sercet_Ws_t0ken" // The ws_token from your producer's api_server config
}
],
// The following configurations are optional, and usually not required
// "wait_timeout": 300,
// "ping_timeout": 10,
// "sleep_time": 10,
// "remove_entry_exit_signals": false,
// "message_size_limit": 8
}
//...
}
```
| Parameter | Description |
|------------|-------------|
| `enabled` | **Required.** Enable consumer mode. If set to false, all other settings in this section are ignored.<br>*Defaults to `false`.*<br> **Datatype:** boolean .
| `producers` | **Required.** List of producers <br> **Datatype:** Array.
| `producers.name` | **Required.** Name of this producer. This name must be used in calls to `get_producer_pairs()` and `get_producer_df()` if more than one producer is used.<br> **Datatype:** string
| `producers.host` | **Required.** The hostname or IP address from your producer.<br> **Datatype:** string
| `producers.port` | **Required.** The port matching the above host.<br> **Datatype:** string
| `producers.ws_token` | **Required.** `ws_token` as configured on the producer.<br> **Datatype:** string
| | **Optional settings**
| `wait_timeout` | Timeout until we ping again if no message is received. <br>*Defaults to `300`.*<br> **Datatype:** Integer - in seconds.
| `wait_timeout` | Ping timeout <br>*Defaults to `10`.*<br> **Datatype:** Integer - in seconds.
| `sleep_time` | Sleep time before retrying to connect.<br>*Defaults to `10`.*<br> **Datatype:** Integer - in seconds.
| `remove_entry_exit_signals` | Remove signal columns from the dataframe (set them to 0) on dataframe receipt.<br>*Defaults to `10`.*<br> **Datatype:** Integer - in seconds.
| `message_size_limit` | Size limit per message<br>*Defaults to `8`.*<br> **Datatype:** Integer - Megabytes.
Instead of (or as well as) calculating indicators in `populate_indicators()` the follower instance listens on the connection to a producer instance's messages (or multiple producer instances in advanced configurations) and requests the producer's most recently analyzed dataframes for each pair in the active whitelist.
A consumer instance will then have a full copy of the analyzed dataframes without the need to calculate them itself.
## Examples
### Example - Producer Strategy
A simple strategy with multiple indicators. No special considerations are required in the strategy itself.
```py
class ProducerStrategy(IStrategy):
#...
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Calculate indicators in the standard freqtrade way which can then be broadcast to other instances
"""
dataframe['rsi'] = ta.RSI(dataframe)
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_middleband'] = bollinger['mid']
dataframe['bb_upperband'] = bollinger['upper']
dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9)
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Populates the entry signal for the given dataframe
"""
dataframe.loc[
(
(qtpylib.crossed_above(dataframe['rsi'], self.buy_rsi.value)) &
(dataframe['tema'] <= dataframe['bb_middleband']) &
(dataframe['tema'] > dataframe['tema'].shift(1)) &
(dataframe['volume'] > 0)
),
'enter_long'] = 1
return dataframe
```
!!! Tip "FreqAI"
You can use this to setup [FreqAI](freqai.md) on a powerful machine, while you run consumers on simple machines like raspberries, which can interpret the signals generated from the producer in different ways.
### Example - Consumer Strategy
A logically equivalent strategy which calculates no indicators itself, but will have the same analyzed dataframes available to make trading decisions based on the indicators calculated in the producer. In this example the consumer has the same entry criteria, however this is not necessary. The consumer may use different logic to enter/exit trades, and only use the indicators as specified.
```py
class ConsumerStrategy(IStrategy):
#...
process_only_new_candles = False # required for consumers
_columns_to_expect = ['rsi_default', 'tema_default', 'bb_middleband_default']
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Use the websocket api to get pre-populated indicators from another freqtrade instance.
Use `self.dp.get_producer_df(pair)` to get the dataframe
"""
pair = metadata['pair']
timeframe = self.timeframe
producer_pairs = self.dp.get_producer_pairs()
# You can specify which producer to get pairs from via:
# self.dp.get_producer_pairs("my_other_producer")
# This func returns the analyzed dataframe, and when it was analyzed
producer_dataframe, _ = self.dp.get_producer_df(pair)
# You can get other data if the producer makes it available:
# self.dp.get_producer_df(
# pair,
# timeframe="1h",
# candle_type=CandleType.SPOT,
# producer_name="my_other_producer"
# )
if not producer_dataframe.empty:
# If you plan on passing the producer's entry/exit signal directly,
# specify ffill=False or it will have unintended results
merged_dataframe = merge_informative_pair(dataframe, producer_dataframe,
timeframe, timeframe,
append_timeframe=False,
suffix="default")
return merged_dataframe
else:
dataframe[self._columns_to_expect] = 0
return dataframe
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Populates the entry signal for the given dataframe
"""
# Use the dataframe columns as if we calculated them ourselves
dataframe.loc[
(
(qtpylib.crossed_above(dataframe['rsi_default'], self.buy_rsi.value)) &
(dataframe['tema_default'] <= dataframe['bb_middleband_default']) &
(dataframe['tema_default'] > dataframe['tema_default'].shift(1)) &
(dataframe['volume'] > 0)
),
'enter_long'] = 1
return dataframe
```
!!! Tip "Using upstream signals"
By setting `remove_entry_exit_signals=false`, you can also use the producer's signals directly. They should be available as `enter_long_default` (assuming `suffix="default"` was used) - and can be used as either signal directly, or as additional indicator.

View File

@@ -1,6 +1,6 @@
markdown==3.3.7 markdown==3.3.7
mkdocs==1.3.1 mkdocs==1.3.1
mkdocs-material==8.4.0 mkdocs-material==8.5.3
mdx_truly_sane_lists==1.3 mdx_truly_sane_lists==1.3
pymdown-extensions==9.5 pymdown-extensions==9.5
jinja2==3.1.2 jinja2==3.1.2

View File

@@ -31,7 +31,8 @@ Sample configuration:
"jwt_secret_key": "somethingrandom", "jwt_secret_key": "somethingrandom",
"CORS_origins": [], "CORS_origins": [],
"username": "Freqtrader", "username": "Freqtrader",
"password": "SuperSecret1!" "password": "SuperSecret1!",
"ws_token": "sercet_Ws_t0ken"
}, },
``` ```
@@ -93,7 +94,6 @@ Make sure that the following 2 lines are available in your docker-compose file:
!!! Danger "Security warning" !!! Danger "Security warning"
By using `8080:8080` in the docker port mapping, the API will be available to everyone connecting to the server under the correct port, so others may be able to control your bot. By using `8080:8080` in the docker port mapping, the API will be available to everyone connecting to the server under the correct port, so others may be able to control your bot.
## Rest API ## Rest API
### Consuming the API ### Consuming the API
@@ -163,6 +163,8 @@ python3 scripts/rest_client.py --config rest_config.json <command> [optional par
| `strategy <strategy>` | Get specific Strategy content. **Alpha** | `strategy <strategy>` | Get specific Strategy content. **Alpha**
| `available_pairs` | List available backtest data. **Alpha** | `available_pairs` | List available backtest data. **Alpha**
| `version` | Show version. | `version` | Show version.
| `sysinfo` | Show informations about the system load.
| `health` | Show bot health (last bot loop).
!!! Warning "Alpha status" !!! Warning "Alpha status"
Endpoints labeled with *Alpha status* above may change at any time without notice. Endpoints labeled with *Alpha status* above may change at any time without notice.
@@ -227,6 +229,11 @@ forceexit
Force-exit a trade. Force-exit a trade.
:param tradeid: Id of the trade (can be received via status command) :param tradeid: Id of the trade (can be received via status command)
:param ordertype: Order type to use (must be market or limit)
:param amount: Amount to sell. Full sell if not given
health
Provides a quick health check of the running bot.
locks locks
Return current locks Return current locks
@@ -312,12 +319,80 @@ version
whitelist whitelist
Show the current whitelist. Show the current whitelist.
```
### Message WebSocket
The API Server includes a websocket endpoint for subscribing to RPC messages from the freqtrade Bot.
This can be used to consume real-time data from your bot, such as entry/exit fill messages, whitelist changes, populated indicators for pairs, and more.
This is also used to setup [Producer/Consumer mode](producer-consumer.md) in Freqtrade.
Assuming your rest API is set to `127.0.0.1` on port `8080`, the endpoint is available at `http://localhost:8080/api/v1/message/ws`.
To access the websocket endpoint, the `ws_token` is required as a query parameter in the endpoint URL.
To generate a safe `ws_token` you can run the following code:
``` python
>>> import secrets
>>> secrets.token_urlsafe(25)
'hZ-y58LXyX_HZ8O1cJzVyN6ePWrLpNQv4Q'
```
You would then add that token under `ws_token` in your `api_server` config. Like so:
``` json
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "somethingrandom",
"CORS_origins": [],
"username": "Freqtrader",
"password": "SuperSecret1!",
"ws_token": "hZ-y58LXyX_HZ8O1cJzVyN6ePWrLpNQv4Q" // <-----
},
```
You can now connect to the endpoint at `http://localhost:8080/api/v1/message/ws?token=hZ-y58LXyX_HZ8O1cJzVyN6ePWrLpNQv4Q`.
!!! Danger "Reuse of example tokens"
Please do not use the above example token. To make sure you are secure, generate a completely new token.
#### Using the WebSocket
Once connected to the WebSocket, the bot will broadcast RPC messages to anyone who is subscribed to them. To subscribe to a list of messages, you must send a JSON request through the WebSocket like the one below. The `data` key must be a list of message type strings.
``` json
{
"type": "subscribe",
"data": ["whitelist", "analyzed_df"] // A list of string message types
}
```
For a list of message types, please refer to the RPCMessageType enum in `freqtrade/enums/rpcmessagetype.py`
Now anytime those types of RPC messages are sent in the bot, you will receive them through the WebSocket as long as the connection is active. They typically take the same form as the request:
``` json
{
"type": "analyzed_df",
"data": {
"key": ["NEO/BTC", "5m", "spot"],
"df": {}, // The dataframe
"la": "2022-09-08 22:14:41.457786+00:00"
}
}
``` ```
### OpenAPI interface ### OpenAPI interface
To enable the builtin openAPI interface (Swagger UI), specify `"enable_openapi": true` in the api_server configuration. To enable the builtin openAPI interface (Swagger UI), specify `"enable_openapi": true` in the api_server configuration.
This will enable the Swagger UI at the `/docs` endpoint. By default, that's running at http://localhost:8080/docs/ - but it'll depend on your settings. This will enable the Swagger UI at the `/docs` endpoint. By default, that's running at http://localhost:8080/docs - but it'll depend on your settings.
### Advanced API usage using JWT tokens ### Advanced API usage using JWT tokens

View File

@@ -106,6 +106,12 @@ def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_r
!!! Note !!! Note
`enter_tag` is limited to 100 characters, remaining data will be truncated. `enter_tag` is limited to 100 characters, remaining data will be truncated.
!!! Warning
There is only one `enter_tag` column, which is used for both long and short trades.
As a consequence, this column must be treated as "last write wins" (it's just a dataframe column after all).
In fancy situations, where multiple signals collide (or if signals are deactivated again based on different conditions), this can lead to odd results with the wrong tag applied to an entry signal.
These results are a consequence of the strategy overwriting prior tags - where the last tag will "stick" and will be the one freqtrade will use.
## Exit tag ## Exit tag
Similar to [Buy Tagging](#buy-tag), you can also specify a sell tag. Similar to [Buy Tagging](#buy-tag), you can also specify a sell tag.

View File

@@ -75,7 +75,7 @@ class AwesomeStrategy(IStrategy):
``` ```
### Stake size management ## Stake size management
Called before entering a trade, makes it possible to manage your position size when placing a new trade. Called before entering a trade, makes it possible to manage your position size when placing a new trade.
@@ -423,7 +423,7 @@ class AwesomeStrategy(IStrategy):
!!! Warning "Backtesting" !!! Warning "Backtesting"
Custom prices are supported in backtesting (starting with 2021.12), and orders will fill if the price falls within the candle's low/high range. Custom prices are supported in backtesting (starting with 2021.12), and orders will fill if the price falls within the candle's low/high range.
Orders that don't fill immediately are subject to regular timeout handling, which happens once per (detail) candle. Orders that don't fill immediately are subject to regular timeout handling, which happens once per (detail) candle.
`custom_exit_price()` is only called for sells of type exit_signal and Custom exit. All other exit-types will use regular backtesting prices. `custom_exit_price()` is only called for sells of type exit_signal, Custom exit and partial exits. All other exit-types will use regular backtesting prices.
## Custom order timeout rules ## Custom order timeout rules
@@ -654,7 +654,7 @@ Position adjustments will always be applied in the direction of the trade, so a
Stoploss is still calculated from the initial opening price, not averaged price. Stoploss is still calculated from the initial opening price, not averaged price.
Regular stoploss rules still apply (cannot move down). Regular stoploss rules still apply (cannot move down).
While `/stopbuy` command stops the bot from entering new trades, the position adjustment feature will continue buying new orders on existing trades. While `/stopentry` command stops the bot from entering new trades, the position adjustment feature will continue buying new orders on existing trades.
!!! Warning "Backtesting" !!! Warning "Backtesting"
During backtesting this callback is called for each candle in `timeframe` or `timeframe_detail`, so run-time performance will be affected. During backtesting this callback is called for each candle in `timeframe` or `timeframe_detail`, so run-time performance will be affected.

View File

@@ -166,7 +166,7 @@ Additional technical libraries can be installed as necessary, or custom indicato
Most indicators have an instable startup period, in which they are either not available (NaN), or the calculation is incorrect. This can lead to inconsistencies, since Freqtrade does not know how long this instable period should be. Most indicators have an instable startup period, in which they are either not available (NaN), or the calculation is incorrect. This can lead to inconsistencies, since Freqtrade does not know how long this instable period should be.
To account for this, the strategy can be assigned the `startup_candle_count` attribute. To account for this, the strategy can be assigned the `startup_candle_count` attribute.
This should be set to the maximum number of candles that the strategy requires to calculate stable indicators. This should be set to the maximum number of candles that the strategy requires to calculate stable indicators. In the case where a user includes higher timeframes with informative pairs, the `startup_candle_count` does not necessarily change. The value is the maximum period (in candles) that any of the informatives timeframes need to compute stable indicators.
In this example strategy, this should be set to 100 (`startup_candle_count = 100`), since the longest needed history is 100 candles. In this example strategy, this should be set to 100 (`startup_candle_count = 100`), since the longest needed history is 100 candles.
@@ -264,7 +264,8 @@ def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFram
### Exit signal rules ### Exit signal rules
Edit the method `populate_exit_trend()` into your strategy file to update your exit strategy. Edit the method `populate_exit_trend()` into your strategy file to update your exit strategy.
Please note that the exit-signal is only used if `use_exit_signal` is set to true in the configuration. The exit-signal is only used for exits if `use_exit_signal` is set to true in the configuration.
`use_exit_signal` will not influence [signal collision rules](#colliding-signals) - which will still apply and can prevent entries.
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected. It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected.
@@ -824,6 +825,8 @@ Options:
- Merge the dataframe without lookahead bias - Merge the dataframe without lookahead bias
- Forward-fill (optional) - Forward-fill (optional)
For a full sample, please refer to the [complete data provider example](#complete-data-provider-sample) below.
All columns of the informative dataframe will be available on the returning dataframe in a renamed fashion: All columns of the informative dataframe will be available on the returning dataframe in a renamed fashion:
!!! Example "Column renaming" !!! Example "Column renaming"

View File

@@ -332,8 +332,8 @@ After:
``` python hl_lines="2 3" ``` python hl_lines="2 3"
order_time_in_force: Dict = { order_time_in_force: Dict = {
"entry": "gtc", "entry": "GTC",
"exit": "gtc", "exit": "GTC",
} }
``` ```

View File

@@ -82,6 +82,8 @@ Example configuration showing the different settings:
"warning": "on", "warning": "on",
"startup": "off", "startup": "off",
"entry": "silent", "entry": "silent",
"entry_fill": "on",
"entry_cancel": "silent",
"exit": { "exit": {
"roi": "silent", "roi": "silent",
"emergency_exit": "on", "emergency_exit": "on",
@@ -90,11 +92,10 @@ Example configuration showing the different settings:
"trailing_stop_loss": "on", "trailing_stop_loss": "on",
"stop_loss": "on", "stop_loss": "on",
"stoploss_on_exchange": "on", "stoploss_on_exchange": "on",
"custom_exit": "silent" "custom_exit": "silent",
"partial_exit": "on"
}, },
"entry_cancel": "silent",
"exit_cancel": "on", "exit_cancel": "on",
"entry_fill": "off",
"exit_fill": "off", "exit_fill": "off",
"protection_trigger": "off", "protection_trigger": "off",
"protection_trigger_global": "on", "protection_trigger_global": "on",
@@ -149,7 +150,7 @@ You can create your own keyboard in `config.json`:
!!! Note "Supported Commands" !!! Note "Supported Commands"
Only the following commands are allowed. Command arguments are not supported! Only the following commands are allowed. Command arguments are not supported!
`/start`, `/stop`, `/status`, `/status table`, `/trades`, `/profit`, `/performance`, `/daily`, `/stats`, `/count`, `/locks`, `/balance`, `/stopbuy`, `/reload_config`, `/show_config`, `/logs`, `/whitelist`, `/blacklist`, `/edge`, `/help`, `/version` `/start`, `/stop`, `/status`, `/status table`, `/trades`, `/profit`, `/performance`, `/daily`, `/stats`, `/count`, `/locks`, `/balance`, `/stopentry`, `/reload_config`, `/show_config`, `/logs`, `/whitelist`, `/blacklist`, `/edge`, `/help`, `/version`
## Telegram commands ## Telegram commands
@@ -161,7 +162,7 @@ official commands. You can ask at any moment for help with `/help`.
|----------|-------------| |----------|-------------|
| `/start` | Starts the trader | `/start` | Starts the trader
| `/stop` | Stops the trader | `/stop` | Stops the trader
| `/stopbuy` | Stops the trader from opening new trades. Gracefully closes open trades according to their rules. | `/stopbuy | /stopentry` | Stops the trader from opening new trades. Gracefully closes open trades according to their rules.
| `/reload_config` | Reloads the configuration file | `/reload_config` | Reloads the configuration file
| `/show_config` | Shows part of the current configuration with relevant settings to operation | `/show_config` | Shows part of the current configuration with relevant settings to operation
| `/logs [limit]` | Show last log messages. | `/logs [limit]` | Show last log messages.

View File

@@ -525,12 +525,14 @@ Requires a configuration with specified `pairlists` attribute.
Can be used to generate static pairlists to be used during backtesting / hyperopt. Can be used to generate static pairlists to be used during backtesting / hyperopt.
``` ```
usage: freqtrade test-pairlist [-h] [-v] [-c PATH] usage: freqtrade test-pairlist [-h] [--userdir PATH] [-v] [-c PATH]
[--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] [--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]]
[-1] [--print-json] [--exchange EXCHANGE] [-1] [--print-json] [--exchange EXCHANGE]
optional arguments: optional arguments:
-h, --help show this help message and exit -h, --help show this help message and exit
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages). -v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
-c PATH, --config PATH -c PATH, --config PATH
Specify configuration file (default: Specify configuration file (default:

View File

@@ -23,7 +23,7 @@ git clone https://github.com/freqtrade/freqtrade.git
Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows). Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows).
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial pre-compiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which need to be downloaded and installed using `pip install TA_Lib-0.4.24-cp38-cp38-win_amd64.whl` (make sure to use the version matching your python version). As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial pre-compiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which need to be downloaded and installed using `pip install TA_Lib-0.4.25-cp38-cp38-win_amd64.whl` (make sure to use the version matching your python version).
Freqtrade provides these dependencies for the latest 3 Python versions (3.8, 3.9 and 3.10) and for 64bit Windows. Freqtrade provides these dependencies for the latest 3 Python versions (3.8, 3.9 and 3.10) and for 64bit Windows.
Other versions must be downloaded from the above link. Other versions must be downloaded from the above link.
@@ -34,7 +34,7 @@ python -m venv .env
.env\Scripts\activate.ps1 .env\Scripts\activate.ps1
# optionally install ta-lib from wheel # optionally install ta-lib from wheel
# Eventually adjust the below filename to match the downloaded wheel # Eventually adjust the below filename to match the downloaded wheel
pip install build_helpers/TA_Lib-0.4.19-cp38-cp38-win_amd64.whl pip install --find-links build_helpers\ TA-Lib
pip install -r requirements.txt pip install -r requirements.txt
pip install -e . pip install -e .
freqtrade freqtrade

View File

@@ -34,6 +34,7 @@ dependencies:
- schedule - schedule
- python-dateutil - python-dateutil
- joblib - joblib
- pyarrow
# ============================ # ============================

View File

@@ -1,5 +1,5 @@
""" Freqtrade bot """ """ Freqtrade bot """
__version__ = '2022.8.dev' __version__ = '2022.9.1'
if 'dev' in __version__: if 'dev' in __version__:
try: try:

View File

@@ -34,7 +34,7 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
"print_colorized", "print_json", "hyperopt_jobs", "print_colorized", "print_json", "hyperopt_jobs",
"hyperopt_random_state", "hyperopt_min_trades", "hyperopt_random_state", "hyperopt_min_trades",
"hyperopt_loss", "disableparamexport", "hyperopt_loss", "disableparamexport",
"hyperopt_ignore_missing_space"] "hyperopt_ignore_missing_space", "analyze_per_epoch"]
ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"] ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"]
@@ -53,8 +53,8 @@ ARGS_LIST_PAIRS = ["exchange", "print_list", "list_pairs_print_json", "print_one
"print_csv", "base_currencies", "quote_currencies", "list_pairs_all", "print_csv", "base_currencies", "quote_currencies", "list_pairs_all",
"trading_mode"] "trading_mode"]
ARGS_TEST_PAIRLIST = ["verbosity", "config", "quote_currencies", "print_one_column", ARGS_TEST_PAIRLIST = ["user_data_dir", "verbosity", "config", "quote_currencies",
"list_pairs_print_json", "exchange"] "print_one_column", "list_pairs_print_json", "exchange"]
ARGS_CREATE_USERDIR = ["user_data_dir", "reset"] ARGS_CREATE_USERDIR = ["user_data_dir", "reset"]
@@ -62,14 +62,14 @@ ARGS_BUILD_CONFIG = ["config"]
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"] ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"]
ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase"] ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase", "exchange"]
ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes", "exchange", "trading_mode", ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes", "trading_mode",
"candle_types"] "candle_types"]
ARGS_CONVERT_TRADES = ["pairs", "timeframes", "exchange", "dataformat_ohlcv", "dataformat_trades"] ARGS_CONVERT_TRADES = ["pairs", "timeframes", "exchange", "dataformat_ohlcv", "dataformat_trades"]
ARGS_LIST_DATA = ["exchange", "dataformat_ohlcv", "pairs", "trading_mode"] ARGS_LIST_DATA = ["exchange", "dataformat_ohlcv", "pairs", "trading_mode", "show_timerange"]
ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "new_pairs_days", "include_inactive", ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "new_pairs_days", "include_inactive",
"timerange", "download_trades", "exchange", "timeframes", "timerange", "download_trades", "exchange", "timeframes",

View File

@@ -211,6 +211,7 @@ def ask_user_config() -> Dict[str, Any]:
) )
# Force JWT token to be a random string # Force JWT token to be a random string
answers['api_server_jwt_key'] = secrets.token_hex() answers['api_server_jwt_key'] = secrets.token_hex()
answers['api_server_ws_token'] = secrets.token_urlsafe(25)
return answers return answers

View File

@@ -69,7 +69,7 @@ AVAILABLE_CLI_OPTIONS = {
metavar='PATH', metavar='PATH',
), ),
"datadir": Arg( "datadir": Arg(
'-d', '--datadir', '-d', '--datadir', '--data-dir',
help='Path to directory with historical backtesting data.', help='Path to directory with historical backtesting data.',
metavar='PATH', metavar='PATH',
), ),
@@ -255,6 +255,13 @@ AVAILABLE_CLI_OPTIONS = {
nargs='+', nargs='+',
default='default', default='default',
), ),
"analyze_per_epoch": Arg(
'--analyze-per-epoch',
help='Run populate_indicators once per epoch.',
action='store_true',
default=False,
),
"print_all": Arg( "print_all": Arg(
'--print-all', '--print-all',
help='Print all results, not only the best ones.', help='Print all results, not only the best ones.',
@@ -367,7 +374,7 @@ AVAILABLE_CLI_OPTIONS = {
metavar='BASE_CURRENCY', metavar='BASE_CURRENCY',
), ),
"trading_mode": Arg( "trading_mode": Arg(
'--trading-mode', '--trading-mode', '--tradingmode',
help='Select Trading mode', help='Select Trading mode',
choices=constants.TRADING_MODES, choices=constants.TRADING_MODES,
), ),
@@ -386,7 +393,8 @@ AVAILABLE_CLI_OPTIONS = {
# Download data # Download data
"pairs_file": Arg( "pairs_file": Arg(
'--pairs-file', '--pairs-file',
help='File containing a list of pairs to download.', help='File containing a list of pairs. '
'Takes precedence over --pairs or pairs configured in the configuration.',
metavar='FILE', metavar='FILE',
), ),
"days": Arg( "days": Arg(
@@ -432,7 +440,12 @@ AVAILABLE_CLI_OPTIONS = {
"dataformat_trades": Arg( "dataformat_trades": Arg(
'--data-format-trades', '--data-format-trades',
help='Storage format for downloaded trades data. (default: `jsongz`).', help='Storage format for downloaded trades data. (default: `jsongz`).',
choices=constants.AVAILABLE_DATAHANDLERS, choices=constants.AVAILABLE_DATAHANDLERS_TRADES,
),
"show_timerange": Arg(
'--show-timerange',
help='Show timerange available for available data. (May take a while to calculate).',
action='store_true',
), ),
"exchange": Arg( "exchange": Arg(
'--exchange', '--exchange',
@@ -443,14 +456,12 @@ AVAILABLE_CLI_OPTIONS = {
'-t', '--timeframes', '-t', '--timeframes',
help='Specify which tickers to download. Space-separated list. ' help='Specify which tickers to download. Space-separated list. '
'Default: `1m 5m`.', 'Default: `1m 5m`.',
choices=['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h',
'6h', '8h', '12h', '1d', '3d', '1w', '2w', '1M', '1y'],
default=['1m', '5m'], default=['1m', '5m'],
nargs='+', nargs='+',
), ),
"prepend_data": Arg( "prepend_data": Arg(
'--prepend', '--prepend',
help='Allow data prepending.', help='Allow data prepending. (Data-appending is disabled)',
action='store_true', action='store_true',
), ),
"erase": Arg( "erase": Arg(

View File

@@ -5,13 +5,13 @@ from datetime import datetime, timedelta
from typing import Any, Dict, List from typing import Any, Dict, List
from freqtrade.configuration import TimeRange, setup_utils_configuration from freqtrade.configuration import TimeRange, setup_utils_configuration
from freqtrade.constants import DATETIME_PRINT_FORMAT
from freqtrade.data.converter import convert_ohlcv_format, convert_trades_format from freqtrade.data.converter import convert_ohlcv_format, convert_trades_format
from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_ohlcv_data, from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_ohlcv_data,
refresh_backtest_trades_data) refresh_backtest_trades_data)
from freqtrade.enums import CandleType, RunMode, TradingMode from freqtrade.enums import CandleType, RunMode, TradingMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes from freqtrade.exchange import market_is_active, timeframe_to_minutes
from freqtrade.exchange.exchange import market_is_active
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist, expand_pairlist from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist, expand_pairlist
from freqtrade.resolvers import ExchangeResolver from freqtrade.resolvers import ExchangeResolver
@@ -80,7 +80,7 @@ def start_download_data(args: Dict[str, Any]) -> None:
data_format_trades=config['dataformat_trades'], data_format_trades=config['dataformat_trades'],
) )
else: else:
if not exchange._ft_has.get('ohlcv_has_history', True): if not exchange.get_option('ohlcv_has_history', True):
raise OperationalException( raise OperationalException(
f"Historic klines not available for {exchange.name}. " f"Historic klines not available for {exchange.name}. "
"Please use `--dl-trades` instead for this exchange " "Please use `--dl-trades` instead for this exchange "
@@ -177,6 +177,7 @@ def start_list_data(args: Dict[str, Any]) -> None:
paircombs = [comb for comb in paircombs if comb[0] in args['pairs']] paircombs = [comb for comb in paircombs if comb[0] in args['pairs']]
print(f"Found {len(paircombs)} pair / timeframe combinations.") print(f"Found {len(paircombs)} pair / timeframe combinations.")
if not config.get('show_timerange'):
groupedpair = defaultdict(list) groupedpair = defaultdict(list)
for pair, timeframe, candle_type in sorted( for pair, timeframe, candle_type in sorted(
paircombs, paircombs,
@@ -191,3 +192,16 @@ def start_list_data(args: Dict[str, Any]) -> None:
], ],
headers=("Pair", "Timeframe", "Type"), headers=("Pair", "Timeframe", "Type"),
tablefmt='psql', stralign='right')) tablefmt='psql', stralign='right'))
else:
paircombs1 = [(
pair, timeframe, candle_type,
*dhc.ohlcv_data_min_max(pair, timeframe, candle_type)
) for pair, timeframe, candle_type in paircombs]
print(tabulate([
(pair, timeframe, candle_type,
start.strftime(DATETIME_PRINT_FORMAT),
end.strftime(DATETIME_PRINT_FORMAT))
for pair, timeframe, candle_type, start, end in paircombs1
],
headers=("Pair", "Timeframe", "Type", 'From', 'To'),
tablefmt='psql', stralign='right'))

View File

@@ -4,7 +4,7 @@ from typing import Any, Dict
from sqlalchemy import func from sqlalchemy import func
from freqtrade.configuration.config_setup import setup_utils_configuration from freqtrade.configuration.config_setup import setup_utils_configuration
from freqtrade.enums.runmode import RunMode from freqtrade.enums import RunMode
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -36,24 +36,24 @@ def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: st
""" """
fallback = 'full' fallback = 'full'
indicators = render_template_with_fallback( indicators = render_template_with_fallback(
templatefile=f"subtemplates/indicators_{subtemplate}.j2", templatefile=f"strategy_subtemplates/indicators_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/indicators_{fallback}.j2", templatefallbackfile=f"strategy_subtemplates/indicators_{fallback}.j2",
) )
buy_trend = render_template_with_fallback( buy_trend = render_template_with_fallback(
templatefile=f"subtemplates/buy_trend_{subtemplate}.j2", templatefile=f"strategy_subtemplates/buy_trend_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/buy_trend_{fallback}.j2", templatefallbackfile=f"strategy_subtemplates/buy_trend_{fallback}.j2",
) )
sell_trend = render_template_with_fallback( sell_trend = render_template_with_fallback(
templatefile=f"subtemplates/sell_trend_{subtemplate}.j2", templatefile=f"strategy_subtemplates/sell_trend_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/sell_trend_{fallback}.j2", templatefallbackfile=f"strategy_subtemplates/sell_trend_{fallback}.j2",
) )
plot_config = render_template_with_fallback( plot_config = render_template_with_fallback(
templatefile=f"subtemplates/plot_config_{subtemplate}.j2", templatefile=f"strategy_subtemplates/plot_config_{subtemplate}.j2",
templatefallbackfile=f"subtemplates/plot_config_{fallback}.j2", templatefallbackfile=f"strategy_subtemplates/plot_config_{fallback}.j2",
) )
additional_methods = render_template_with_fallback( additional_methods = render_template_with_fallback(
templatefile=f"subtemplates/strategy_methods_{subtemplate}.j2", templatefile=f"strategy_subtemplates/strategy_methods_{subtemplate}.j2",
templatefallbackfile="subtemplates/strategy_methods_empty.j2", templatefallbackfile="strategy_subtemplates/strategy_methods_empty.j2",
) )
strategy_text = render_template(templatefile='base_strategy.py.j2', strategy_text = render_template(templatefile='base_strategy.py.j2',

View File

@@ -1,6 +1,6 @@
import logging import logging
from typing import Any, Dict
from freqtrade.constants import Config
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange import (available_exchanges, is_exchange_known_ccxt, from freqtrade.exchange import (available_exchanges, is_exchange_known_ccxt,
@@ -10,7 +10,7 @@ from freqtrade.exchange import (available_exchanges, is_exchange_known_ccxt,
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def check_exchange(config: Dict[str, Any], check_for_bad: bool = True) -> bool: def check_exchange(config: Config, check_for_bad: bool = True) -> bool:
""" """
Check if the exchange name in the config file is supported by Freqtrade Check if the exchange name in the config file is supported by Freqtrade
:param check_for_bad: if True, check the exchange against the list of known 'bad' :param check_for_bad: if True, check the exchange against the list of known 'bad'

View File

@@ -1,4 +1,5 @@
import logging import logging
from collections import Counter
from copy import deepcopy from copy import deepcopy
from typing import Any, Dict from typing import Any, Dict
@@ -84,6 +85,8 @@ def validate_config_consistency(conf: Dict[str, Any], preliminary: bool = False)
_validate_protections(conf) _validate_protections(conf)
_validate_unlimited_amount(conf) _validate_unlimited_amount(conf)
_validate_ask_orderbook(conf) _validate_ask_orderbook(conf)
_validate_freqai_hyperopt(conf)
_validate_consumers(conf)
validate_migrated_strategy_settings(conf) validate_migrated_strategy_settings(conf)
# validate configuration before returning # validate configuration before returning
@@ -323,6 +326,31 @@ def _validate_pricing_rules(conf: Dict[str, Any]) -> None:
del conf['ask_strategy'] del conf['ask_strategy']
def _validate_freqai_hyperopt(conf: Dict[str, Any]) -> None:
freqai_enabled = conf.get('freqai', {}).get('enabled', False)
analyze_per_epoch = conf.get('analyze_per_epoch', False)
if analyze_per_epoch and freqai_enabled:
raise OperationalException(
'Using analyze-per-epoch parameter is not supported with a FreqAI strategy.')
def _validate_consumers(conf: Dict[str, Any]) -> None:
emc_conf = conf.get('external_message_consumer', {})
if emc_conf.get('enabled', False):
if len(emc_conf.get('producers', [])) < 1:
raise OperationalException("You must specify at least 1 Producer to connect to.")
producer_names = [p['name'] for p in emc_conf.get('producers', [])]
duplicates = [item for item, count in Counter(producer_names).items() if count > 1]
if duplicates:
raise OperationalException(
f"Producer names must be unique. Duplicate: {', '.join(duplicates)}")
if conf.get('process_only_new_candles', True):
# Warning here or require it?
logger.warning("To receive best performance with external data, "
"please set `process_only_new_candles` to False")
def _strategy_settings(conf: Dict[str, Any]) -> None: def _strategy_settings(conf: Dict[str, Any]) -> None:
process_deprecated_setting(conf, None, 'use_sell_signal', None, 'use_exit_signal') process_deprecated_setting(conf, None, 'use_sell_signal', None, 'use_exit_signal')

View File

@@ -13,6 +13,7 @@ from freqtrade.configuration.deprecated_settings import process_temporary_deprec
from freqtrade.configuration.directory_operations import create_datadir, create_userdata_dir from freqtrade.configuration.directory_operations import create_datadir, create_userdata_dir
from freqtrade.configuration.environment_vars import enironment_vars_to_dict from freqtrade.configuration.environment_vars import enironment_vars_to_dict
from freqtrade.configuration.load_config import load_file, load_from_files from freqtrade.configuration.load_config import load_file, load_from_files
from freqtrade.constants import Config
from freqtrade.enums import NON_UTIL_MODES, TRADING_MODES, CandleType, RunMode, TradingMode from freqtrade.enums import NON_UTIL_MODES, TRADING_MODES, CandleType, RunMode, TradingMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.loggers import setup_logging from freqtrade.loggers import setup_logging
@@ -30,10 +31,10 @@ class Configuration:
def __init__(self, args: Dict[str, Any], runmode: RunMode = None) -> None: def __init__(self, args: Dict[str, Any], runmode: RunMode = None) -> None:
self.args = args self.args = args
self.config: Optional[Dict[str, Any]] = None self.config: Optional[Config] = None
self.runmode = runmode self.runmode = runmode
def get_config(self) -> Dict[str, Any]: def get_config(self) -> Config:
""" """
Return the config. Use this method to get the bot config Return the config. Use this method to get the bot config
:return: Dict: Bot config :return: Dict: Bot config
@@ -65,7 +66,7 @@ class Configuration:
:return: Configuration dictionary :return: Configuration dictionary
""" """
# Load all configs # Load all configs
config: Dict[str, Any] = load_from_files(self.args.get("config", [])) config: Config = load_from_files(self.args.get("config", []))
# Load environment variables # Load environment variables
env_data = enironment_vars_to_dict() env_data = enironment_vars_to_dict()
@@ -108,7 +109,7 @@ class Configuration:
return config return config
def _process_logging_options(self, config: Dict[str, Any]) -> None: def _process_logging_options(self, config: Config) -> None:
""" """
Extract information for sys.argv and load logging configuration: Extract information for sys.argv and load logging configuration:
the -v/--verbose, --logfile options the -v/--verbose, --logfile options
@@ -121,7 +122,7 @@ class Configuration:
setup_logging(config) setup_logging(config)
def _process_trading_options(self, config: Dict[str, Any]) -> None: def _process_trading_options(self, config: Config) -> None:
if config['runmode'] not in TRADING_MODES: if config['runmode'] not in TRADING_MODES:
return return
@@ -137,7 +138,7 @@ class Configuration:
logger.info(f'Using DB: "{parse_db_uri_for_logging(config["db_url"])}"') logger.info(f'Using DB: "{parse_db_uri_for_logging(config["db_url"])}"')
def _process_common_options(self, config: Dict[str, Any]) -> None: def _process_common_options(self, config: Config) -> None:
# Set strategy if not specified in config and or if it's non default # Set strategy if not specified in config and or if it's non default
if self.args.get('strategy') or not config.get('strategy'): if self.args.get('strategy') or not config.get('strategy'):
@@ -161,7 +162,7 @@ class Configuration:
if 'sd_notify' in self.args and self.args['sd_notify']: if 'sd_notify' in self.args and self.args['sd_notify']:
config['internals'].update({'sd_notify': True}) config['internals'].update({'sd_notify': True})
def _process_datadir_options(self, config: Dict[str, Any]) -> None: def _process_datadir_options(self, config: Config) -> None:
""" """
Extract information for sys.argv and load directory configurations Extract information for sys.argv and load directory configurations
--user-data, --datadir --user-data, --datadir
@@ -195,7 +196,7 @@ class Configuration:
config['exportfilename'] = (config['user_data_dir'] config['exportfilename'] = (config['user_data_dir']
/ 'backtest_results') / 'backtest_results')
def _process_optimize_options(self, config: Dict[str, Any]) -> None: def _process_optimize_options(self, config: Config) -> None:
# This will override the strategy configuration # This will override the strategy configuration
self._args_to_config(config, argname='timeframe', self._args_to_config(config, argname='timeframe',
@@ -302,6 +303,9 @@ class Configuration:
self._args_to_config(config, argname='spaces', self._args_to_config(config, argname='spaces',
logstring='Parameter -s/--spaces detected: {}') logstring='Parameter -s/--spaces detected: {}')
self._args_to_config(config, argname='analyze_per_epoch',
logstring='Parameter --analyze-per-epoch detected.')
self._args_to_config(config, argname='print_all', self._args_to_config(config, argname='print_all',
logstring='Parameter --print-all detected ...') logstring='Parameter --print-all detected ...')
@@ -377,7 +381,7 @@ class Configuration:
self._args_to_config(config, argname="hyperopt_ignore_missing_space", self._args_to_config(config, argname="hyperopt_ignore_missing_space",
logstring="Paramter --ignore-missing-space detected: {}") logstring="Paramter --ignore-missing-space detected: {}")
def _process_plot_options(self, config: Dict[str, Any]) -> None: def _process_plot_options(self, config: Config) -> None:
self._args_to_config(config, argname='pairs', self._args_to_config(config, argname='pairs',
logstring='Using pairs {}') logstring='Using pairs {}')
@@ -426,7 +430,10 @@ class Configuration:
self._args_to_config(config, argname='dataformat_trades', self._args_to_config(config, argname='dataformat_trades',
logstring='Using "{}" to store trades data.') logstring='Using "{}" to store trades data.')
def _process_data_options(self, config: Dict[str, Any]) -> None: self._args_to_config(config, argname='show_timerange',
logstring='Detected --show-timerange')
def _process_data_options(self, config: Config) -> None:
self._args_to_config(config, argname='new_pairs_days', self._args_to_config(config, argname='new_pairs_days',
logstring='Detected --new-pairs-days: {}') logstring='Detected --new-pairs-days: {}')
self._args_to_config(config, argname='trading_mode', self._args_to_config(config, argname='trading_mode',
@@ -437,7 +444,7 @@ class Configuration:
self._args_to_config(config, argname='candle_types', self._args_to_config(config, argname='candle_types',
logstring='Detected --candle-types: {}') logstring='Detected --candle-types: {}')
def _process_analyze_options(self, config: Dict[str, Any]) -> None: def _process_analyze_options(self, config: Config) -> None:
self._args_to_config(config, argname='analysis_groups', self._args_to_config(config, argname='analysis_groups',
logstring='Analysis reason groups: {}') logstring='Analysis reason groups: {}')
@@ -450,7 +457,7 @@ class Configuration:
self._args_to_config(config, argname='indicator_list', self._args_to_config(config, argname='indicator_list',
logstring='Analysis indicator list: {}') logstring='Analysis indicator list: {}')
def _process_runmode(self, config: Dict[str, Any]) -> None: def _process_runmode(self, config: Config) -> None:
self._args_to_config(config, argname='dry_run', self._args_to_config(config, argname='dry_run',
logstring='Parameter --dry-run detected, ' logstring='Parameter --dry-run detected, '
@@ -463,7 +470,7 @@ class Configuration:
config.update({'runmode': self.runmode}) config.update({'runmode': self.runmode})
def _process_freqai_options(self, config: Dict[str, Any]) -> None: def _process_freqai_options(self, config: Config) -> None:
self._args_to_config(config, argname='freqaimodel', self._args_to_config(config, argname='freqaimodel',
logstring='Using freqaimodel class name: {}') logstring='Using freqaimodel class name: {}')
@@ -473,7 +480,7 @@ class Configuration:
return return
def _args_to_config(self, config: Dict[str, Any], argname: str, def _args_to_config(self, config: Config, argname: str,
logstring: str, logfun: Optional[Callable] = None, logstring: str, logfun: Optional[Callable] = None,
deprecated_msg: Optional[str] = None) -> None: deprecated_msg: Optional[str] = None) -> None:
""" """
@@ -496,7 +503,7 @@ class Configuration:
if deprecated_msg: if deprecated_msg:
warnings.warn(f"DEPRECATED: {deprecated_msg}", DeprecationWarning) warnings.warn(f"DEPRECATED: {deprecated_msg}", DeprecationWarning)
def _resolve_pairs_list(self, config: Dict[str, Any]) -> None: def _resolve_pairs_list(self, config: Config) -> None:
""" """
Helper for download script. Helper for download script.
Takes first found: Takes first found:

View File

@@ -3,15 +3,16 @@ Functions to handle deprecated settings
""" """
import logging import logging
from typing import Any, Dict, Optional from typing import Optional
from freqtrade.constants import Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def check_conflicting_settings(config: Dict[str, Any], def check_conflicting_settings(config: Config,
section_old: Optional[str], name_old: str, section_old: Optional[str], name_old: str,
section_new: Optional[str], name_new: str) -> None: section_new: Optional[str], name_new: str) -> None:
section_new_config = config.get(section_new, {}) if section_new else config section_new_config = config.get(section_new, {}) if section_new else config
@@ -28,7 +29,7 @@ def check_conflicting_settings(config: Dict[str, Any],
) )
def process_removed_setting(config: Dict[str, Any], def process_removed_setting(config: Config,
section1: str, name1: str, section1: str, name1: str,
section2: Optional[str], name2: str) -> None: section2: Optional[str], name2: str) -> None:
""" """
@@ -47,7 +48,7 @@ def process_removed_setting(config: Dict[str, Any],
) )
def process_deprecated_setting(config: Dict[str, Any], def process_deprecated_setting(config: Config,
section_old: Optional[str], name_old: str, section_old: Optional[str], name_old: str,
section_new: Optional[str], name_new: str section_new: Optional[str], name_new: str
) -> None: ) -> None:
@@ -69,7 +70,7 @@ def process_deprecated_setting(config: Dict[str, Any],
del section_old_config[name_old] del section_old_config[name_old]
def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: def process_temporary_deprecated_settings(config: Config) -> None:
# Kept for future deprecated / moved settings # Kept for future deprecated / moved settings
# check_conflicting_settings(config, 'ask_strategy', 'use_sell_signal', # check_conflicting_settings(config, 'ask_strategy', 'use_sell_signal',

View File

@@ -1,16 +1,16 @@
import logging import logging
import shutil import shutil
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Optional from typing import Optional
from freqtrade.constants import USER_DATA_FILES from freqtrade.constants import USER_DATA_FILES, Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def create_datadir(config: Dict[str, Any], datadir: Optional[str] = None) -> Path: def create_datadir(config: Config, datadir: Optional[str] = None) -> Path:
folder = Path(datadir) if datadir else Path(f"{config['user_data_dir']}/data") folder = Path(datadir) if datadir else Path(f"{config['user_data_dir']}/data")
if not datadir: if not datadir:

View File

@@ -10,7 +10,7 @@ from typing import Any, Dict, List
import rapidjson import rapidjson
from freqtrade.constants import MINIMAL_CONFIG from freqtrade.constants import MINIMAL_CONFIG, Config
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import deep_merge_dicts from freqtrade.misc import deep_merge_dicts
@@ -80,7 +80,7 @@ def load_from_files(files: List[str], base_path: Path = None, level: int = 0) ->
Recursively load configuration files if specified. Recursively load configuration files if specified.
Sub-files are assumed to be relative to the initial config. Sub-files are assumed to be relative to the initial config.
""" """
config: Dict[str, Any] = {} config: Config = {}
if level > 5: if level > 5:
raise OperationalException("Config loop detected.") raise OperationalException("Config loop detected.")

View File

@@ -3,7 +3,7 @@
""" """
bot constants bot constants
""" """
from typing import List, Literal, Tuple from typing import Any, Dict, List, Literal, Tuple
from freqtrade.enums import CandleType from freqtrade.enums import CandleType
@@ -23,7 +23,8 @@ REQUIRED_ORDERTIF = ['entry', 'exit']
REQUIRED_ORDERTYPES = ['entry', 'exit', 'stoploss', 'stoploss_on_exchange'] REQUIRED_ORDERTYPES = ['entry', 'exit', 'stoploss', 'stoploss_on_exchange']
PRICING_SIDES = ['ask', 'bid', 'same', 'other'] PRICING_SIDES = ['ask', 'bid', 'same', 'other']
ORDERTYPE_POSSIBILITIES = ['limit', 'market'] ORDERTYPE_POSSIBILITIES = ['limit', 'market']
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc'] _ORDERTIF_POSSIBILITIES = ['GTC', 'FOK', 'IOC', 'PO']
ORDERTIF_POSSIBILITIES = _ORDERTIF_POSSIBILITIES + [t.lower() for t in _ORDERTIF_POSSIBILITIES]
HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss', HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss',
'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily', 'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily',
'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily', 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily',
@@ -35,7 +36,8 @@ AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList',
'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter', 'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter',
'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter'] 'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter']
AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard'] AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard']
AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] AVAILABLE_DATAHANDLERS_TRADES = ['json', 'jsongz', 'hdf5']
AVAILABLE_DATAHANDLERS = AVAILABLE_DATAHANDLERS_TRADES + ['feather', 'parquet']
BACKTEST_BREAKDOWNS = ['day', 'week', 'month'] BACKTEST_BREAKDOWNS = ['day', 'week', 'month']
BACKTEST_CACHE_AGE = ['none', 'day', 'week', 'month'] BACKTEST_CACHE_AGE = ['none', 'day', 'week', 'month']
BACKTEST_CACHE_DEFAULT = 'day' BACKTEST_CACHE_DEFAULT = 'day'
@@ -242,6 +244,7 @@ CONF_SCHEMA = {
'exchange': {'$ref': '#/definitions/exchange'}, 'exchange': {'$ref': '#/definitions/exchange'},
'edge': {'$ref': '#/definitions/edge'}, 'edge': {'$ref': '#/definitions/edge'},
'freqai': {'$ref': '#/definitions/freqai'}, 'freqai': {'$ref': '#/definitions/freqai'},
'external_message_consumer': {'$ref': '#/definitions/external_message_consumer'},
'experimental': { 'experimental': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -288,11 +291,12 @@ CONF_SCHEMA = {
'warning': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}, 'warning': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
'startup': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}, 'startup': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
'entry': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}, 'entry': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
'entry_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS}, 'entry_fill': {
'entry_fill': {'type': 'string', 'type': 'string',
'enum': TELEGRAM_SETTING_OPTIONS, 'enum': TELEGRAM_SETTING_OPTIONS,
'default': 'off' 'default': 'off'
}, },
'entry_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS, },
'exit': { 'exit': {
'type': ['string', 'object'], 'type': ['string', 'object'],
'additionalProperties': { 'additionalProperties': {
@@ -300,12 +304,12 @@ CONF_SCHEMA = {
'enum': TELEGRAM_SETTING_OPTIONS 'enum': TELEGRAM_SETTING_OPTIONS
} }
}, },
'exit_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
'exit_fill': { 'exit_fill': {
'type': 'string', 'type': 'string',
'enum': TELEGRAM_SETTING_OPTIONS, 'enum': TELEGRAM_SETTING_OPTIONS,
'default': 'on' 'default': 'on'
}, },
'exit_cancel': {'type': 'string', 'enum': TELEGRAM_SETTING_OPTIONS},
'protection_trigger': { 'protection_trigger': {
'type': 'string', 'type': 'string',
'enum': TELEGRAM_SETTING_OPTIONS, 'enum': TELEGRAM_SETTING_OPTIONS,
@@ -314,14 +318,17 @@ CONF_SCHEMA = {
'protection_trigger_global': { 'protection_trigger_global': {
'type': 'string', 'type': 'string',
'enum': TELEGRAM_SETTING_OPTIONS, 'enum': TELEGRAM_SETTING_OPTIONS,
'default': 'on'
}, },
'show_candle': { 'show_candle': {
'type': 'string', 'type': 'string',
'enum': ['off', 'ohlc'], 'enum': ['off', 'ohlc'],
'default': 'off'
}, },
'strategy_msg': { 'strategy_msg': {
'type': 'string', 'type': 'string',
'enum': TELEGRAM_SETTING_OPTIONS, 'enum': TELEGRAM_SETTING_OPTIONS,
'default': 'on'
}, },
} }
}, },
@@ -399,6 +406,7 @@ CONF_SCHEMA = {
}, },
'username': {'type': 'string'}, 'username': {'type': 'string'},
'password': {'type': 'string'}, 'password': {'type': 'string'},
'ws_token': {'type': ['string', 'array'], 'items': {'type': 'string'}},
'jwt_secret_key': {'type': 'string'}, 'jwt_secret_key': {'type': 'string'},
'CORS_origins': {'type': 'array', 'items': {'type': 'string'}}, 'CORS_origins': {'type': 'array', 'items': {'type': 'string'}},
'verbosity': {'type': 'string', 'enum': ['error', 'info']}, 'verbosity': {'type': 'string', 'enum': ['error', 'info']},
@@ -427,7 +435,7 @@ CONF_SCHEMA = {
}, },
'dataformat_trades': { 'dataformat_trades': {
'type': 'string', 'type': 'string',
'enum': AVAILABLE_DATAHANDLERS, 'enum': AVAILABLE_DATAHANDLERS_TRADES,
'default': 'jsongz' 'default': 'jsongz'
}, },
'position_adjustment_enable': {'type': 'boolean'}, 'position_adjustment_enable': {'type': 'boolean'},
@@ -483,6 +491,47 @@ CONF_SCHEMA = {
}, },
'required': ['process_throttle_secs', 'allowed_risk'] 'required': ['process_throttle_secs', 'allowed_risk']
}, },
'external_message_consumer': {
'type': 'object',
'properties': {
'enabled': {'type': 'boolean', 'default': False},
'producers': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'name': {'type': 'string'},
'host': {'type': 'string'},
'port': {
'type': 'integer',
'default': 8080,
'minimum': 0,
'maximum': 65535
},
'ws_token': {'type': 'string'},
},
'required': ['name', 'host', 'ws_token']
}
},
'wait_timeout': {'type': 'integer', 'minimum': 0},
'sleep_time': {'type': 'integer', 'minimum': 0},
'ping_timeout': {'type': 'integer', 'minimum': 0},
'remove_entry_exit_signals': {'type': 'boolean', 'default': False},
'initial_candle_limit': {
'type': 'integer',
'minimum': 0,
'maximum': 1500,
'default': 1500
},
'message_size_limit': { # In megabytes
'type': 'integer',
'minimum': 1,
'maxmium': 20,
'default': 8,
}
},
'required': ['producers']
},
"freqai": { "freqai": {
"type": "object", "type": "object",
"properties": { "properties": {
@@ -503,6 +552,7 @@ CONF_SCHEMA = {
"weight_factor": {"type": "number", "default": 0}, "weight_factor": {"type": "number", "default": 0},
"principal_component_analysis": {"type": "boolean", "default": False}, "principal_component_analysis": {"type": "boolean", "default": False},
"use_SVM_to_remove_outliers": {"type": "boolean", "default": False}, "use_SVM_to_remove_outliers": {"type": "boolean", "default": False},
"plot_feature_importances": {"type": "integer", "default": 0},
"svm_params": {"type": "object", "svm_params": {"type": "object",
"properties": { "properties": {
"shuffle": {"type": "boolean", "default": False}, "shuffle": {"type": "boolean", "default": False},
@@ -602,3 +652,5 @@ LongShort = Literal['long', 'short']
EntryExit = Literal['entry', 'exit'] EntryExit = Literal['entry', 'exit']
BuySell = Literal['buy', 'sell'] BuySell = Literal['buy', 'sell']
MakerTaker = Literal['maker', 'taker'] MakerTaker = Literal['maker', 'taker']
Config = Dict[str, Any]

View File

@@ -284,7 +284,7 @@ def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = Non
df['enter_tag'] = df['buy_tag'] df['enter_tag'] = df['buy_tag']
df = df.drop(['buy_tag'], axis=1) df = df.drop(['buy_tag'], axis=1)
if 'orders' not in df.columns: if 'orders' not in df.columns:
df.loc[:, 'orders'] = None df['orders'] = None
else: else:
# old format - only with lists. # old format - only with lists.
@@ -341,9 +341,9 @@ def trade_list_to_dataframe(trades: List[LocalTrade]) -> pd.DataFrame:
""" """
df = pd.DataFrame.from_records([t.to_json(True) for t in trades], columns=BT_DATA_COLUMNS) df = pd.DataFrame.from_records([t.to_json(True) for t in trades], columns=BT_DATA_COLUMNS)
if len(df) > 0: if len(df) > 0:
df.loc[:, 'close_date'] = pd.to_datetime(df['close_date'], utc=True) df['close_date'] = pd.to_datetime(df['close_date'], utc=True)
df.loc[:, 'open_date'] = pd.to_datetime(df['open_date'], utc=True) df['open_date'] = pd.to_datetime(df['open_date'], utc=True)
df.loc[:, 'close_rate'] = df['close_rate'].astype('float64') df['close_rate'] = df['close_rate'].astype('float64')
return df return df

View File

@@ -5,12 +5,12 @@ import itertools
import logging import logging
from datetime import datetime, timezone from datetime import datetime, timezone
from operator import itemgetter from operator import itemgetter
from typing import Any, Dict, List from typing import Dict, List
import pandas as pd import pandas as pd
from pandas import DataFrame, to_datetime from pandas import DataFrame, to_datetime
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, TradeList from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, Config, TradeList
from freqtrade.enums import CandleType from freqtrade.enums import CandleType
@@ -237,7 +237,7 @@ def trades_to_ohlcv(trades: TradeList, timeframe: str) -> DataFrame:
return df_new.loc[:, DEFAULT_DATAFRAME_COLUMNS] return df_new.loc[:, DEFAULT_DATAFRAME_COLUMNS]
def convert_trades_format(config: Dict[str, Any], convert_from: str, convert_to: str, erase: bool): def convert_trades_format(config: Config, convert_from: str, convert_to: str, erase: bool):
""" """
Convert trades from one format to another format. Convert trades from one format to another format.
:param config: Config dictionary :param config: Config dictionary
@@ -263,7 +263,7 @@ def convert_trades_format(config: Dict[str, Any], convert_from: str, convert_to:
def convert_ohlcv_format( def convert_ohlcv_format(
config: Dict[str, Any], config: Config,
convert_from: str, convert_from: str,
convert_to: str, convert_to: str,
erase: bool, erase: bool,
@@ -292,6 +292,7 @@ def convert_ohlcv_format(
timeframe, timeframe,
candle_type=candle_type candle_type=candle_type
)) ))
config['pairs'] = sorted(set(config['pairs']))
logger.info(f"Converting candle (OHLCV) data for {config['pairs']}") logger.info(f"Converting candle (OHLCV) data for {config['pairs']}")
for timeframe in timeframes: for timeframe in timeframes:
@@ -302,7 +303,7 @@ def convert_ohlcv_format(
drop_incomplete=False, drop_incomplete=False,
startup_candles=0, startup_candles=0,
candle_type=candle_type) candle_type=candle_type)
logger.info(f"Converting {len(data)} {candle_type} candles for {pair}") logger.info(f"Converting {len(data)} {timeframe} {candle_type} candles for {pair}")
if len(data) > 0: if len(data) > 0:
trg.ohlcv_store( trg.ohlcv_store(
pair=pair, pair=pair,

View File

@@ -12,11 +12,12 @@ from typing import Any, Dict, List, Optional, Tuple
from pandas import DataFrame from pandas import DataFrame
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.constants import ListPairsWithTimeframes, PairWithTimeframe from freqtrade.constants import Config, ListPairsWithTimeframes, PairWithTimeframe
from freqtrade.data.history import load_pair_history from freqtrade.data.history import load_pair_history
from freqtrade.enums import CandleType, RunMode from freqtrade.enums import CandleType, RPCMessageType, RunMode
from freqtrade.exceptions import ExchangeError, OperationalException from freqtrade.exceptions import ExchangeError, OperationalException
from freqtrade.exchange import Exchange, timeframe_to_seconds from freqtrade.exchange import Exchange, timeframe_to_seconds
from freqtrade.rpc import RPCManager
from freqtrade.util import PeriodicCache from freqtrade.util import PeriodicCache
@@ -28,17 +29,33 @@ MAX_DATAFRAME_CANDLES = 1000
class DataProvider: class DataProvider:
def __init__(self, config: dict, exchange: Optional[Exchange], pairlists=None) -> None: def __init__(
self,
config: Config,
exchange: Optional[Exchange],
pairlists=None,
rpc: Optional[RPCManager] = None
) -> None:
self._config = config self._config = config
self._exchange = exchange self._exchange = exchange
self._pairlists = pairlists self._pairlists = pairlists
self.__rpc = rpc
self.__cached_pairs: Dict[PairWithTimeframe, Tuple[DataFrame, datetime]] = {} self.__cached_pairs: Dict[PairWithTimeframe, Tuple[DataFrame, datetime]] = {}
self.__slice_index: Optional[int] = None self.__slice_index: Optional[int] = None
self.__cached_pairs_backtesting: Dict[PairWithTimeframe, DataFrame] = {} self.__cached_pairs_backtesting: Dict[PairWithTimeframe, DataFrame] = {}
self.__producer_pairs_df: Dict[str,
Dict[PairWithTimeframe, Tuple[DataFrame, datetime]]] = {}
self.__producer_pairs: Dict[str, List[str]] = {}
self._msg_queue: deque = deque() self._msg_queue: deque = deque()
self._default_candle_type = self._config.get('candle_type_def', CandleType.SPOT)
self._default_timeframe = self._config.get('timeframe', '1h')
self.__msg_cache = PeriodicCache( self.__msg_cache = PeriodicCache(
maxsize=1000, ttl=timeframe_to_seconds(self._config.get('timeframe', '1h'))) maxsize=1000, ttl=timeframe_to_seconds(self._default_timeframe))
self.producers = self._config.get('external_message_consumer', {}).get('producers', [])
self.external_data_enabled = len(self.producers) > 0
def _set_dataframe_max_index(self, limit_index: int): def _set_dataframe_max_index(self, limit_index: int):
""" """
@@ -63,9 +80,110 @@ class DataProvider:
:param dataframe: analyzed dataframe :param dataframe: analyzed dataframe
:param candle_type: Any of the enum CandleType (must match trading mode!) :param candle_type: Any of the enum CandleType (must match trading mode!)
""" """
self.__cached_pairs[(pair, timeframe, candle_type)] = ( pair_key = (pair, timeframe, candle_type)
self.__cached_pairs[pair_key] = (
dataframe, datetime.now(timezone.utc)) dataframe, datetime.now(timezone.utc))
# For multiple producers we will want to merge the pairlists instead of overwriting
def _set_producer_pairs(self, pairlist: List[str], producer_name: str = "default"):
"""
Set the pairs received to later be used.
:param pairlist: List of pairs
"""
self.__producer_pairs[producer_name] = pairlist
def get_producer_pairs(self, producer_name: str = "default") -> List[str]:
"""
Get the pairs cached from the producer
:returns: List of pairs
"""
return self.__producer_pairs.get(producer_name, []).copy()
def _emit_df(
self,
pair_key: PairWithTimeframe,
dataframe: DataFrame
) -> None:
"""
Send this dataframe as an ANALYZED_DF message to RPC
:param pair_key: PairWithTimeframe tuple
:param data: Tuple containing the DataFrame and the datetime it was cached
"""
if self.__rpc:
self.__rpc.send_msg(
{
'type': RPCMessageType.ANALYZED_DF,
'data': {
'key': pair_key,
'df': dataframe,
'la': datetime.now(timezone.utc)
}
}
)
def _add_external_df(
self,
pair: str,
dataframe: DataFrame,
last_analyzed: datetime,
timeframe: str,
candle_type: CandleType,
producer_name: str = "default"
) -> None:
"""
Add the pair data to this class from an external source.
:param pair: pair to get the data for
:param timeframe: Timeframe to get data for
:param candle_type: Any of the enum CandleType (must match trading mode!)
"""
pair_key = (pair, timeframe, candle_type)
if producer_name not in self.__producer_pairs_df:
self.__producer_pairs_df[producer_name] = {}
_last_analyzed = datetime.now(timezone.utc) if not last_analyzed else last_analyzed
self.__producer_pairs_df[producer_name][pair_key] = (dataframe, _last_analyzed)
logger.debug(f"External DataFrame for {pair_key} from {producer_name} added.")
def get_producer_df(
self,
pair: str,
timeframe: Optional[str] = None,
candle_type: Optional[CandleType] = None,
producer_name: str = "default"
) -> Tuple[DataFrame, datetime]:
"""
Get the pair data from producers.
:param pair: pair to get the data for
:param timeframe: Timeframe to get data for
:param candle_type: Any of the enum CandleType (must match trading mode!)
:returns: Tuple of the DataFrame and last analyzed timestamp
"""
_timeframe = self._default_timeframe if not timeframe else timeframe
_candle_type = self._default_candle_type if not candle_type else candle_type
pair_key = (pair, _timeframe, _candle_type)
# If we have no data from this Producer yet
if producer_name not in self.__producer_pairs_df:
# We don't have this data yet, return empty DataFrame and datetime (01-01-1970)
return (DataFrame(), datetime.fromtimestamp(0, tz=timezone.utc))
# If we do have data from that Producer, but no data on this pair_key
if pair_key not in self.__producer_pairs_df[producer_name]:
# We don't have this data yet, return empty DataFrame and datetime (01-01-1970)
return (DataFrame(), datetime.fromtimestamp(0, tz=timezone.utc))
# We have it, return this data
df, la = self.__producer_pairs_df[producer_name][pair_key]
return (df.copy(), la)
def add_pairlisthandler(self, pairlists) -> None: def add_pairlisthandler(self, pairlists) -> None:
""" """
Allow adding pairlisthandler after initialization Allow adding pairlisthandler after initialization
@@ -86,14 +204,16 @@ class DataProvider:
""" """
_candle_type = CandleType.from_string( _candle_type = CandleType.from_string(
candle_type) if candle_type != '' else self._config['candle_type_def'] candle_type) if candle_type != '' else self._config['candle_type_def']
saved_pair = (pair, str(timeframe), _candle_type) saved_pair: PairWithTimeframe = (pair, str(timeframe), _candle_type)
if saved_pair not in self.__cached_pairs_backtesting: if saved_pair not in self.__cached_pairs_backtesting:
timerange = TimeRange.parse_timerange(None if self._config.get( timerange = TimeRange.parse_timerange(None if self._config.get(
'timerange') is None else str(self._config.get('timerange'))) 'timerange') is None else str(self._config.get('timerange')))
# Move informative start time respecting startup_candle_count
timerange.subtract_start( # It is not necessary to add the training candles, as they
timeframe_to_seconds(str(timeframe)) * self._config.get('startup_candle_count', 0) # were already added at the beginning of the backtest.
) startup_candles = self.get_required_startup(str(timeframe), False)
tf_seconds = timeframe_to_seconds(str(timeframe))
timerange.subtract_start(tf_seconds * startup_candles)
self.__cached_pairs_backtesting[saved_pair] = load_pair_history( self.__cached_pairs_backtesting[saved_pair] = load_pair_history(
pair=pair, pair=pair,
timeframe=timeframe or self._config['timeframe'], timeframe=timeframe or self._config['timeframe'],
@@ -105,6 +225,23 @@ class DataProvider:
) )
return self.__cached_pairs_backtesting[saved_pair].copy() return self.__cached_pairs_backtesting[saved_pair].copy()
def get_required_startup(self, timeframe: str, add_train_candles: bool = True) -> int:
freqai_config = self._config.get('freqai', {})
if not freqai_config.get('enabled', False):
return self._config.get('startup_candle_count', 0)
else:
startup_candles = self._config.get('startup_candle_count', 0)
indicator_periods = freqai_config['feature_parameters']['indicator_periods_candles']
# make sure the startupcandles is at least the set maximum indicator periods
self._config['startup_candle_count'] = max(startup_candles, max(indicator_periods))
tf_seconds = timeframe_to_seconds(timeframe)
train_candles = 0
if add_train_candles:
train_candles = freqai_config['train_period_days'] * 86400 / tf_seconds
total_candles = int(self._config['startup_candle_count'] + train_candles)
logger.info(f'Increasing startup_candle_count for freqai to {total_candles}')
return total_candles
def get_pair_dataframe( def get_pair_dataframe(
self, self,
pair: str, pair: str,
@@ -181,7 +318,9 @@ class DataProvider:
Clear pair dataframe cache. Clear pair dataframe cache.
""" """
self.__cached_pairs = {} self.__cached_pairs = {}
self.__cached_pairs_backtesting = {} # Don't reset backtesting pairs -
# otherwise they're reloaded each time during hyperopt due to with analyze_per_epoch
# self.__cached_pairs_backtesting = {}
self.__slice_index = 0 self.__slice_index = 0
# Exchange functions # Exchange functions

View File

@@ -0,0 +1,130 @@
import logging
from typing import Optional
from pandas import DataFrame, read_feather, to_datetime
from freqtrade.configuration import TimeRange
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, TradeList
from freqtrade.enums import CandleType
from .idatahandler import IDataHandler
logger = logging.getLogger(__name__)
class FeatherDataHandler(IDataHandler):
_columns = DEFAULT_DATAFRAME_COLUMNS
def ohlcv_store(
self, pair: str, timeframe: str, data: DataFrame, candle_type: CandleType) -> None:
"""
Store data in json format "values".
format looks as follows:
[[<date>,<open>,<high>,<low>,<close>]]
:param pair: Pair - used to generate filename
:param timeframe: Timeframe - used to generate filename
:param data: Dataframe containing OHLCV data
:param candle_type: Any of the enum CandleType (must match trading mode!)
:return: None
"""
filename = self._pair_data_filename(self._datadir, pair, timeframe, candle_type)
self.create_dir_if_needed(filename)
data.reset_index(drop=True).loc[:, self._columns].to_feather(
filename, compression_level=9, compression='lz4')
def _ohlcv_load(self, pair: str, timeframe: str,
timerange: Optional[TimeRange], candle_type: CandleType
) -> DataFrame:
"""
Internal method used to load data for one pair from disk.
Implements the loading and conversion to a Pandas dataframe.
Timerange trimming and dataframe validation happens outside of this method.
:param pair: Pair to load data
:param timeframe: Timeframe (e.g. "5m")
:param timerange: Limit data to be loaded to this timerange.
Optionally implemented by subclasses to avoid loading
all data where possible.
:param candle_type: Any of the enum CandleType (must match trading mode!)
:return: DataFrame with ohlcv data, or empty DataFrame
"""
filename = self._pair_data_filename(
self._datadir, pair, timeframe, candle_type=candle_type)
if not filename.exists():
# Fallback mode for 1M files
filename = self._pair_data_filename(
self._datadir, pair, timeframe, candle_type=candle_type, no_timeframe_modify=True)
if not filename.exists():
return DataFrame(columns=self._columns)
pairdata = read_feather(filename)
pairdata.columns = self._columns
pairdata = pairdata.astype(dtype={'open': 'float', 'high': 'float',
'low': 'float', 'close': 'float', 'volume': 'float'})
pairdata['date'] = to_datetime(pairdata['date'],
unit='ms',
utc=True,
infer_datetime_format=True)
return pairdata
def ohlcv_append(
self,
pair: str,
timeframe: str,
data: DataFrame,
candle_type: CandleType
) -> None:
"""
Append data to existing data structures
:param pair: Pair
:param timeframe: Timeframe this ohlcv data is for
:param data: Data to append.
:param candle_type: Any of the enum CandleType (must match trading mode!)
"""
raise NotImplementedError()
def trades_store(self, pair: str, data: TradeList) -> None:
"""
Store trades data (list of Dicts) to file
:param pair: Pair - used for filename
:param data: List of Lists containing trade data,
column sequence as in DEFAULT_TRADES_COLUMNS
"""
# filename = self._pair_trades_filename(self._datadir, pair)
raise NotImplementedError()
# array = pa.array(data)
# array
# feather.write_feather(data, filename)
def trades_append(self, pair: str, data: TradeList):
"""
Append data to existing files
:param pair: Pair - used for filename
:param data: List of Lists containing trade data,
column sequence as in DEFAULT_TRADES_COLUMNS
"""
raise NotImplementedError()
def _trades_load(self, pair: str, timerange: Optional[TimeRange] = None) -> TradeList:
"""
Load a pair from file, either .json.gz or .json
# TODO: respect timerange ...
:param pair: Load trades for this pair
:param timerange: Timerange to load trades for - currently not implemented
:return: List of trades
"""
raise NotImplementedError()
# filename = self._pair_trades_filename(self._datadir, pair)
# tradesdata = misc.file_load_json(filename)
# if not tradesdata:
# return []
# return tradesdata
@classmethod
def _get_file_extension(cls):
return "feather"

View File

@@ -1,7 +1,5 @@
import logging import logging
import re from typing import Optional
from pathlib import Path
from typing import List, Optional
import numpy as np import numpy as np
import pandas as pd import pandas as pd
@@ -20,26 +18,6 @@ class HDF5DataHandler(IDataHandler):
_columns = DEFAULT_DATAFRAME_COLUMNS _columns = DEFAULT_DATAFRAME_COLUMNS
@classmethod
def ohlcv_get_pairs(cls, datadir: Path, timeframe: str, candle_type: CandleType) -> List[str]:
"""
Returns a list of all pairs with ohlcv data available in this datadir
for the specified timeframe
:param datadir: Directory to search for ohlcv files
:param timeframe: Timeframe to search pairs for
:param candle_type: Any of the enum CandleType (must match trading mode!)
:return: List of Pairs
"""
candle = ""
if candle_type != CandleType.SPOT:
datadir = datadir.joinpath('futures')
candle = f"-{candle_type}"
_tmp = [re.search(r'^(\S+)(?=\-' + timeframe + candle + '.h5)', p.name)
for p in datadir.glob(f"*{timeframe}{candle}.h5")]
# Check if regex found something and only return these results
return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match]
def ohlcv_store( def ohlcv_store(
self, pair: str, timeframe: str, data: pd.DataFrame, candle_type: CandleType) -> None: self, pair: str, timeframe: str, data: pd.DataFrame, candle_type: CandleType) -> None:
""" """
@@ -103,6 +81,7 @@ class HDF5DataHandler(IDataHandler):
raise ValueError("Wrong dataframe format") raise ValueError("Wrong dataframe format")
pairdata = pairdata.astype(dtype={'open': 'float', 'high': 'float', pairdata = pairdata.astype(dtype={'open': 'float', 'high': 'float',
'low': 'float', 'close': 'float', 'volume': 'float'}) 'low': 'float', 'close': 'float', 'volume': 'float'})
pairdata = pairdata.reset_index(drop=True)
return pairdata return pairdata
def ohlcv_append( def ohlcv_append(
@@ -121,18 +100,6 @@ class HDF5DataHandler(IDataHandler):
""" """
raise NotImplementedError() raise NotImplementedError()
@classmethod
def trades_get_pairs(cls, datadir: Path) -> List[str]:
"""
Returns a list of all pairs for which trade data is available in this
:param datadir: Directory to search for ohlcv files
:return: List of Pairs
"""
_tmp = [re.search(r'^(\S+)(?=\-trades.h5)', p.name)
for p in datadir.glob("*trades.h5")]
# Check if regex found something and only return these results to avoid exceptions.
return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match]
def trades_store(self, pair: str, data: TradeList) -> None: def trades_store(self, pair: str, data: TradeList) -> None:
""" """
Store trades data (list of Dicts) to file Store trades data (list of Dicts) to file

View File

@@ -228,9 +228,9 @@ def _download_pair_history(pair: str, *,
) )
logger.debug("Current Start: %s", logger.debug("Current Start: %s",
f"{data.iloc[0]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None') f"{data.iloc[0]['date']:DATETIME_PRINT_FORMAT}" if not data.empty else 'None')
logger.debug("Current End: %s", logger.debug("Current End: %s",
f"{data.iloc[-1]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None') f"{data.iloc[-1]['date']:DATETIME_PRINT_FORMAT}" if not data.empty else 'None')
# Default since_ms to 30 days if nothing is given # Default since_ms to 30 days if nothing is given
new_data = exchange.get_historic_ohlcv(pair=pair, new_data = exchange.get_historic_ohlcv(pair=pair,
@@ -254,9 +254,9 @@ def _download_pair_history(pair: str, *,
fill_missing=False, drop_incomplete=False) fill_missing=False, drop_incomplete=False)
logger.debug("New Start: %s", logger.debug("New Start: %s",
f"{data.iloc[0]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None') f"{data.iloc[0]['date']:DATETIME_PRINT_FORMAT}" if not data.empty else 'None')
logger.debug("New End: %s", logger.debug("New End: %s",
f"{data.iloc[-1]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None') f"{data.iloc[-1]['date']:DATETIME_PRINT_FORMAT}" if not data.empty else 'None')
data_handler.ohlcv_store(pair, timeframe, data=data, candle_type=candle_type) data_handler.ohlcv_store(pair, timeframe, data=data, candle_type=candle_type)
return True return True
@@ -302,8 +302,8 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes
if trading_mode == 'futures': if trading_mode == 'futures':
# Predefined candletype (and timeframe) depending on exchange # Predefined candletype (and timeframe) depending on exchange
# Downloads what is necessary to backtest based on futures data. # Downloads what is necessary to backtest based on futures data.
tf_mark = exchange._ft_has['mark_ohlcv_timeframe'] tf_mark = exchange.get_option('mark_ohlcv_timeframe')
fr_candle_type = CandleType.from_string(exchange._ft_has['mark_ohlcv_price']) fr_candle_type = CandleType.from_string(exchange.get_option('mark_ohlcv_price'))
# All exchanges need FundingRate for futures trading. # All exchanges need FundingRate for futures trading.
# The timeframe is aligned to the mark-price timeframe. # The timeframe is aligned to the mark-price timeframe.
for funding_candle_type in (CandleType.FUNDING_RATE, fr_candle_type): for funding_candle_type in (CandleType.FUNDING_RATE, fr_candle_type):
@@ -330,13 +330,12 @@ def _download_trades_history(exchange: Exchange,
try: try:
until = None until = None
since = 0
if timerange: if timerange:
if timerange.starttype == 'date': if timerange.starttype == 'date':
since = timerange.startts * 1000 since = timerange.startts * 1000
if timerange.stoptype == 'date': if timerange.stoptype == 'date':
until = timerange.stopts * 1000 until = timerange.stopts * 1000
else:
since = arrow.utcnow().shift(days=-new_pairs_days).int_timestamp * 1000
trades = data_handler.trades_load(pair) trades = data_handler.trades_load(pair)
@@ -349,6 +348,9 @@ def _download_trades_history(exchange: Exchange,
logger.info(f"Start earlier than available data. Redownloading trades for {pair}...") logger.info(f"Start earlier than available data. Redownloading trades for {pair}...")
trades = [] trades = []
if not since:
since = arrow.utcnow().shift(days=-new_pairs_days).int_timestamp * 1000
from_id = trades[-1][1] if trades else None from_id = trades[-1][1] if trades else None
if trades and since < trades[-1][0]: if trades and since < trades[-1][0]:
# Reset since to the last available point # Reset since to the last available point

View File

@@ -9,7 +9,7 @@ from abc import ABC, abstractmethod
from copy import deepcopy from copy import deepcopy
from datetime import datetime, timezone from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
from typing import List, Optional, Type from typing import List, Optional, Tuple, Type
from pandas import DataFrame from pandas import DataFrame
@@ -26,7 +26,7 @@ logger = logging.getLogger(__name__)
class IDataHandler(ABC): class IDataHandler(ABC):
_OHLCV_REGEX = r'^([a-zA-Z_-]+)\-(\d+[a-zA-Z]{1,2})\-?([a-zA-Z_]*)?(?=\.)' _OHLCV_REGEX = r'^([a-zA-Z_\d-]+)\-(\d+[a-zA-Z]{1,2})\-?([a-zA-Z_]*)?(?=\.)'
def __init__(self, datadir: Path) -> None: def __init__(self, datadir: Path) -> None:
self._datadir = datadir self._datadir = datadir
@@ -61,7 +61,6 @@ class IDataHandler(ABC):
) for match in _tmp if match and len(match.groups()) > 1] ) for match in _tmp if match and len(match.groups()) > 1]
@classmethod @classmethod
@abstractmethod
def ohlcv_get_pairs(cls, datadir: Path, timeframe: str, candle_type: CandleType) -> List[str]: def ohlcv_get_pairs(cls, datadir: Path, timeframe: str, candle_type: CandleType) -> List[str]:
""" """
Returns a list of all pairs with ohlcv data available in this datadir Returns a list of all pairs with ohlcv data available in this datadir
@@ -71,6 +70,15 @@ class IDataHandler(ABC):
:param candle_type: Any of the enum CandleType (must match trading mode!) :param candle_type: Any of the enum CandleType (must match trading mode!)
:return: List of Pairs :return: List of Pairs
""" """
candle = ""
if candle_type != CandleType.SPOT:
datadir = datadir.joinpath('futures')
candle = f"-{candle_type}"
ext = cls._get_file_extension()
_tmp = [re.search(r'^(\S+)(?=\-' + timeframe + candle + f'.{ext})', p.name)
for p in datadir.glob(f"*{timeframe}{candle}.{ext}")]
# Check if regex found something and only return these results
return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match]
@abstractmethod @abstractmethod
def ohlcv_store( def ohlcv_store(
@@ -84,6 +92,18 @@ class IDataHandler(ABC):
:return: None :return: None
""" """
def ohlcv_data_min_max(self, pair: str, timeframe: str,
candle_type: CandleType) -> Tuple[datetime, datetime]:
"""
Returns the min and max timestamp for the given pair and timeframe.
:param pair: Pair to get min/max for
:param timeframe: Timeframe to get min/max for
:param candle_type: Any of the enum CandleType (must match trading mode!)
:return: (min, max)
"""
data = self._ohlcv_load(pair, timeframe, None, candle_type)
return data.iloc[0]['date'].to_pydatetime(), data.iloc[-1]['date'].to_pydatetime()
@abstractmethod @abstractmethod
def _ohlcv_load(self, pair: str, timeframe: str, timerange: Optional[TimeRange], def _ohlcv_load(self, pair: str, timeframe: str, timerange: Optional[TimeRange],
candle_type: CandleType candle_type: CandleType
@@ -132,13 +152,17 @@ class IDataHandler(ABC):
""" """
@classmethod @classmethod
@abstractmethod
def trades_get_pairs(cls, datadir: Path) -> List[str]: def trades_get_pairs(cls, datadir: Path) -> List[str]:
""" """
Returns a list of all pairs for which trade data is available in this Returns a list of all pairs for which trade data is available in this
:param datadir: Directory to search for ohlcv files :param datadir: Directory to search for ohlcv files
:return: List of Pairs :return: List of Pairs
""" """
_ext = cls._get_file_extension()
_tmp = [re.search(r'^(\S+)(?=\-trades.' + _ext + ')', p.name)
for p in datadir.glob(f"*trades.{_ext}")]
# Check if regex found something and only return these results to avoid exceptions.
return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match]
@abstractmethod @abstractmethod
def trades_store(self, pair: str, data: TradeList) -> None: def trades_store(self, pair: str, data: TradeList) -> None:
@@ -243,12 +267,12 @@ class IDataHandler(ABC):
Rebuild pair name from filename Rebuild pair name from filename
Assumes a asset name of max. 7 length to also support BTC-PERP and BTC-PERP:USD names. Assumes a asset name of max. 7 length to also support BTC-PERP and BTC-PERP:USD names.
""" """
res = re.sub(r'^(([A-Za-z]{1,10})|^([A-Za-z\-]{1,6}))(_)', r'\g<1>/', pair, 1) res = re.sub(r'^(([A-Za-z\d]{1,10})|^([A-Za-z\-]{1,6}))(_)', r'\g<1>/', pair, 1)
res = re.sub('_', ':', res, 1) res = re.sub('_', ':', res, 1)
return res return res
def ohlcv_load(self, pair, timeframe: str, def ohlcv_load(self, pair, timeframe: str,
candle_type: CandleType, candle_type: CandleType, *,
timerange: Optional[TimeRange] = None, timerange: Optional[TimeRange] = None,
fill_missing: bool = True, fill_missing: bool = True,
drop_incomplete: bool = True, drop_incomplete: bool = True,
@@ -351,6 +375,12 @@ def get_datahandlerclass(datatype: str) -> Type[IDataHandler]:
elif datatype == 'hdf5': elif datatype == 'hdf5':
from .hdf5datahandler import HDF5DataHandler from .hdf5datahandler import HDF5DataHandler
return HDF5DataHandler return HDF5DataHandler
elif datatype == 'feather':
from .featherdatahandler import FeatherDataHandler
return FeatherDataHandler
elif datatype == 'parquet':
from .parquetdatahandler import ParquetDataHandler
return ParquetDataHandler
else: else:
raise ValueError(f"No datahandler for datatype {datatype} available.") raise ValueError(f"No datahandler for datatype {datatype} available.")

View File

@@ -1,7 +1,5 @@
import logging import logging
import re from typing import Optional
from pathlib import Path
from typing import List, Optional
import numpy as np import numpy as np
from pandas import DataFrame, read_json, to_datetime from pandas import DataFrame, read_json, to_datetime
@@ -23,26 +21,6 @@ class JsonDataHandler(IDataHandler):
_use_zip = False _use_zip = False
_columns = DEFAULT_DATAFRAME_COLUMNS _columns = DEFAULT_DATAFRAME_COLUMNS
@classmethod
def ohlcv_get_pairs(cls, datadir: Path, timeframe: str, candle_type: CandleType) -> List[str]:
"""
Returns a list of all pairs with ohlcv data available in this datadir
for the specified timeframe
:param datadir: Directory to search for ohlcv files
:param timeframe: Timeframe to search pairs for
:param candle_type: Any of the enum CandleType (must match trading mode!)
:return: List of Pairs
"""
candle = ""
if candle_type != CandleType.SPOT:
datadir = datadir.joinpath('futures')
candle = f"-{candle_type}"
_tmp = [re.search(r'^(\S+)(?=\-' + timeframe + candle + '.json)', p.name)
for p in datadir.glob(f"*{timeframe}{candle}.{cls._get_file_extension()}")]
# Check if regex found something and only return these results
return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match]
def ohlcv_store( def ohlcv_store(
self, pair: str, timeframe: str, data: DataFrame, candle_type: CandleType) -> None: self, pair: str, timeframe: str, data: DataFrame, candle_type: CandleType) -> None:
""" """
@@ -119,18 +97,6 @@ class JsonDataHandler(IDataHandler):
""" """
raise NotImplementedError() raise NotImplementedError()
@classmethod
def trades_get_pairs(cls, datadir: Path) -> List[str]:
"""
Returns a list of all pairs for which trade data is available in this
:param datadir: Directory to search for ohlcv files
:return: List of Pairs
"""
_tmp = [re.search(r'^(\S+)(?=\-trades.json)', p.name)
for p in datadir.glob(f"*trades.{cls._get_file_extension()}")]
# Check if regex found something and only return these results to avoid exceptions.
return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match]
def trades_store(self, pair: str, data: TradeList) -> None: def trades_store(self, pair: str, data: TradeList) -> None:
""" """
Store trades data (list of Dicts) to file Store trades data (list of Dicts) to file

View File

@@ -0,0 +1,129 @@
import logging
from typing import Optional
from pandas import DataFrame, read_parquet, to_datetime
from freqtrade.configuration import TimeRange
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, TradeList
from freqtrade.enums import CandleType
from .idatahandler import IDataHandler
logger = logging.getLogger(__name__)
class ParquetDataHandler(IDataHandler):
_columns = DEFAULT_DATAFRAME_COLUMNS
def ohlcv_store(
self, pair: str, timeframe: str, data: DataFrame, candle_type: CandleType) -> None:
"""
Store data in json format "values".
format looks as follows:
[[<date>,<open>,<high>,<low>,<close>]]
:param pair: Pair - used to generate filename
:param timeframe: Timeframe - used to generate filename
:param data: Dataframe containing OHLCV data
:param candle_type: Any of the enum CandleType (must match trading mode!)
:return: None
"""
filename = self._pair_data_filename(self._datadir, pair, timeframe, candle_type)
self.create_dir_if_needed(filename)
data.reset_index(drop=True).loc[:, self._columns].to_parquet(filename)
def _ohlcv_load(self, pair: str, timeframe: str,
timerange: Optional[TimeRange], candle_type: CandleType
) -> DataFrame:
"""
Internal method used to load data for one pair from disk.
Implements the loading and conversion to a Pandas dataframe.
Timerange trimming and dataframe validation happens outside of this method.
:param pair: Pair to load data
:param timeframe: Timeframe (e.g. "5m")
:param timerange: Limit data to be loaded to this timerange.
Optionally implemented by subclasses to avoid loading
all data where possible.
:param candle_type: Any of the enum CandleType (must match trading mode!)
:return: DataFrame with ohlcv data, or empty DataFrame
"""
filename = self._pair_data_filename(
self._datadir, pair, timeframe, candle_type=candle_type)
if not filename.exists():
# Fallback mode for 1M files
filename = self._pair_data_filename(
self._datadir, pair, timeframe, candle_type=candle_type, no_timeframe_modify=True)
if not filename.exists():
return DataFrame(columns=self._columns)
pairdata = read_parquet(filename)
pairdata.columns = self._columns
pairdata = pairdata.astype(dtype={'open': 'float', 'high': 'float',
'low': 'float', 'close': 'float', 'volume': 'float'})
pairdata['date'] = to_datetime(pairdata['date'],
unit='ms',
utc=True,
infer_datetime_format=True)
return pairdata
def ohlcv_append(
self,
pair: str,
timeframe: str,
data: DataFrame,
candle_type: CandleType
) -> None:
"""
Append data to existing data structures
:param pair: Pair
:param timeframe: Timeframe this ohlcv data is for
:param data: Data to append.
:param candle_type: Any of the enum CandleType (must match trading mode!)
"""
raise NotImplementedError()
def trades_store(self, pair: str, data: TradeList) -> None:
"""
Store trades data (list of Dicts) to file
:param pair: Pair - used for filename
:param data: List of Lists containing trade data,
column sequence as in DEFAULT_TRADES_COLUMNS
"""
# filename = self._pair_trades_filename(self._datadir, pair)
raise NotImplementedError()
# array = pa.array(data)
# array
# feather.write_feather(data, filename)
def trades_append(self, pair: str, data: TradeList):
"""
Append data to existing files
:param pair: Pair - used for filename
:param data: List of Lists containing trade data,
column sequence as in DEFAULT_TRADES_COLUMNS
"""
raise NotImplementedError()
def _trades_load(self, pair: str, timerange: Optional[TimeRange] = None) -> TradeList:
"""
Load a pair from file, either .json.gz or .json
# TODO: respect timerange ...
:param pair: Load trades for this pair
:param timerange: Timerange to load trades for - currently not implemented
:return: List of trades
"""
raise NotImplementedError()
# filename = self._pair_trades_filename(self._datadir, pair)
# tradesdata = misc.file_load_json(filename)
# if not tradesdata:
# return []
# return tradesdata
@classmethod
def _get_file_extension(cls):
return "parquet"

View File

@@ -11,11 +11,11 @@ import utils_find_1st as utf1st
from pandas import DataFrame from pandas import DataFrame
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.constants import DATETIME_PRINT_FORMAT, UNLIMITED_STAKE_AMOUNT from freqtrade.constants import DATETIME_PRINT_FORMAT, UNLIMITED_STAKE_AMOUNT, Config
from freqtrade.data.history import get_timerange, load_data, refresh_data from freqtrade.data.history import get_timerange, load_data, refresh_data
from freqtrade.enums import CandleType, ExitType, RunMode from freqtrade.enums import CandleType, ExitType, RunMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange.exchange import timeframe_to_seconds from freqtrade.exchange import timeframe_to_seconds
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.interface import IStrategy
@@ -42,10 +42,9 @@ class Edge:
Author: https://github.com/mishaker Author: https://github.com/mishaker
""" """
config: Dict = {}
_cached_pairs: Dict[str, Any] = {} # Keeps a list of pairs _cached_pairs: Dict[str, Any] = {} # Keeps a list of pairs
def __init__(self, config: Dict[str, Any], exchange, strategy) -> None: def __init__(self, config: Config, exchange, strategy) -> None:
self.config = config self.config = config
self.exchange = exchange self.exchange = exchange

View File

@@ -3,9 +3,10 @@ from freqtrade.enums.backteststate import BacktestState
from freqtrade.enums.candletype import CandleType from freqtrade.enums.candletype import CandleType
from freqtrade.enums.exitchecktuple import ExitCheckTuple from freqtrade.enums.exitchecktuple import ExitCheckTuple
from freqtrade.enums.exittype import ExitType from freqtrade.enums.exittype import ExitType
from freqtrade.enums.hyperoptstate import HyperoptState
from freqtrade.enums.marginmode import MarginMode from freqtrade.enums.marginmode import MarginMode
from freqtrade.enums.ordertypevalue import OrderTypeValues from freqtrade.enums.ordertypevalue import OrderTypeValues
from freqtrade.enums.rpcmessagetype import RPCMessageType from freqtrade.enums.rpcmessagetype import RPCMessageType, RPCRequestType
from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode
from freqtrade.enums.signaltype import SignalDirection, SignalTagType, SignalType from freqtrade.enums.signaltype import SignalDirection, SignalTagType, SignalType
from freqtrade.enums.state import State from freqtrade.enums.state import State

View File

@@ -0,0 +1,12 @@
from enum import Enum
class HyperoptState(Enum):
""" Hyperopt states """
STARTUP = 1
DATALOAD = 2
INDICATORS = 3
OPTIMIZE = 4
def __str__(self):
return f"{self.name.lower()}"

View File

@@ -1,7 +1,7 @@
from enum import Enum from enum import Enum
class RPCMessageType(Enum): class RPCMessageType(str, Enum):
STATUS = 'status' STATUS = 'status'
WARNING = 'warning' WARNING = 'warning'
STARTUP = 'startup' STARTUP = 'startup'
@@ -19,8 +19,19 @@ class RPCMessageType(Enum):
STRATEGY_MSG = 'strategy_msg' STRATEGY_MSG = 'strategy_msg'
WHITELIST = 'whitelist'
ANALYZED_DF = 'analyzed_df'
def __repr__(self): def __repr__(self):
return self.value return self.value
def __str__(self): def __str__(self):
return self.value return self.value
# Enum for parsing requests from ws consumers
class RPCRequestType(str, Enum):
SUBSCRIBE = 'subscribe'
WHITELIST = 'whitelist'
ANALYZED_DF = 'analyzed_df'

View File

@@ -9,10 +9,11 @@ from freqtrade.exchange.bitpanda import Bitpanda
from freqtrade.exchange.bittrex import Bittrex from freqtrade.exchange.bittrex import Bittrex
from freqtrade.exchange.bybit import Bybit from freqtrade.exchange.bybit import Bybit
from freqtrade.exchange.coinbasepro import Coinbasepro from freqtrade.exchange.coinbasepro import Coinbasepro
from freqtrade.exchange.exchange import (amount_to_precision, available_exchanges, ccxt_exchanges, from freqtrade.exchange.exchange import (amount_to_contract_precision, amount_to_contracts,
date_minus_candles, is_exchange_known_ccxt, amount_to_precision, available_exchanges, ccxt_exchanges,
is_exchange_officially_supported, market_is_active, contracts_to_amount, date_minus_candles,
price_to_precision, timeframe_to_minutes, is_exchange_known_ccxt, is_exchange_officially_supported,
market_is_active, price_to_precision, timeframe_to_minutes,
timeframe_to_msecs, timeframe_to_next_date, timeframe_to_msecs, timeframe_to_next_date,
timeframe_to_prev_date, timeframe_to_seconds, timeframe_to_prev_date, timeframe_to_seconds,
validate_exchange, validate_exchanges) validate_exchange, validate_exchanges)

View File

@@ -1,5 +1,4 @@
""" Binance exchange subclass """ """ Binance exchange subclass """
import json
import logging import logging
from datetime import datetime from datetime import datetime
from pathlib import Path from pathlib import Path
@@ -12,7 +11,7 @@ from freqtrade.enums import CandleType, MarginMode, TradingMode
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
from freqtrade.exchange import Exchange from freqtrade.exchange import Exchange
from freqtrade.exchange.common import retrier from freqtrade.exchange.common import retrier
from freqtrade.misc import deep_merge_dicts from freqtrade.misc import deep_merge_dicts, json_load
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -23,8 +22,7 @@ class Binance(Exchange):
_ft_has: Dict = { _ft_has: Dict = {
"stoploss_on_exchange": True, "stoploss_on_exchange": True,
"stoploss_order_types": {"limit": "stop_loss_limit"}, "stoploss_order_types": {"limit": "stop_loss_limit"},
"order_time_in_force": ['gtc', 'fok', 'ioc'], "order_time_in_force": ['GTC', 'FOK', 'IOC'],
"time_in_force_parameter": "timeInForce",
"ohlcv_candle_limit": 1000, "ohlcv_candle_limit": 1000,
"trades_pagination": "id", "trades_pagination": "id",
"trades_pagination_arg": "fromId", "trades_pagination_arg": "fromId",
@@ -32,7 +30,7 @@ class Binance(Exchange):
"ccxt_futures_name": "future" "ccxt_futures_name": "future"
} }
_ft_has_futures: Dict = { _ft_has_futures: Dict = {
"stoploss_order_types": {"limit": "stop"}, "stoploss_order_types": {"limit": "limit", "market": "market"},
"tickers_have_price": False, "tickers_have_price": False,
} }
@@ -49,13 +47,12 @@ class Binance(Exchange):
Returns True if adjustment is necessary. Returns True if adjustment is necessary.
:param side: "buy" or "sell" :param side: "buy" or "sell"
""" """
order_types = ('stop_loss_limit', 'stop', 'stop_market')
ordertype = 'stop' if self.trading_mode == TradingMode.FUTURES else 'stop_loss_limit'
return ( return (
order.get('stopPrice', None) is None order.get('stopPrice', None) is None
or ( or (
order['type'] == ordertype order['type'] in order_types
and ( and (
(side == "sell" and stop_loss > float(order['stopPrice'])) or (side == "sell" and stop_loss > float(order['stopPrice'])) or
(side == "buy" and stop_loss < float(order['stopPrice'])) (side == "buy" and stop_loss < float(order['stopPrice']))
@@ -137,23 +134,27 @@ class Binance(Exchange):
pair: str, pair: str,
open_rate: float, # Entry price of position open_rate: float, # Entry price of position
is_short: bool, is_short: bool,
position: float, # Absolute value of position size amount: float,
stake_amount: float,
wallet_balance: float, # Or margin balance wallet_balance: float, # Or margin balance
mm_ex_1: float = 0.0, # (Binance) Cross only mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only upnl_ex_1: float = 0.0, # (Binance) Cross only
) -> Optional[float]: ) -> Optional[float]:
""" """
Important: Must be fetching data from cached values as this is used by backtesting!
MARGIN: https://www.binance.com/en/support/faq/f6b010588e55413aa58b7d63ee0125ed MARGIN: https://www.binance.com/en/support/faq/f6b010588e55413aa58b7d63ee0125ed
PERPETUAL: https://www.binance.com/en/support/faq/b3c689c1f50a44cabb3a84e663b81d93 PERPETUAL: https://www.binance.com/en/support/faq/b3c689c1f50a44cabb3a84e663b81d93
:param exchange_name: :param exchange_name:
:param open_rate: (EP1) Entry price of position :param open_rate: Entry price of position
:param is_short: True if the trade is a short, false otherwise :param is_short: True if the trade is a short, false otherwise
:param position: Absolute value of position size (in base currency) :param amount: Absolute value of position size incl. leverage (in base currency)
:param wallet_balance: (WB) :param stake_amount: Stake amount - Collateral in settle currency.
:param trading_mode: SPOT, MARGIN, FUTURES, etc.
:param margin_mode: Either ISOLATED or CROSS
:param wallet_balance: Amount of margin_mode in the wallet being used to trade
Cross-Margin Mode: crossWalletBalance Cross-Margin Mode: crossWalletBalance
Isolated-Margin Mode: isolatedWalletBalance Isolated-Margin Mode: isolatedWalletBalance
:param maintenance_amt:
# * Only required for Cross # * Only required for Cross
:param mm_ex_1: (TMM) :param mm_ex_1: (TMM)
@@ -165,12 +166,11 @@ class Binance(Exchange):
""" """
side_1 = -1 if is_short else 1 side_1 = -1 if is_short else 1
position = abs(position)
cross_vars = upnl_ex_1 - mm_ex_1 if self.margin_mode == MarginMode.CROSS else 0.0 cross_vars = upnl_ex_1 - mm_ex_1 if self.margin_mode == MarginMode.CROSS else 0.0
# mm_ratio: Binance's formula specifies maintenance margin rate which is mm_ratio * 100% # mm_ratio: Binance's formula specifies maintenance margin rate which is mm_ratio * 100%
# maintenance_amt: (CUM) Maintenance Amount of position # maintenance_amt: (CUM) Maintenance Amount of position
mm_ratio, maintenance_amt = self.get_maintenance_ratio_and_amt(pair, position) mm_ratio, maintenance_amt = self.get_maintenance_ratio_and_amt(pair, stake_amount)
if (maintenance_amt is None): if (maintenance_amt is None):
raise OperationalException( raise OperationalException(
@@ -182,9 +182,9 @@ class Binance(Exchange):
return ( return (
( (
(wallet_balance + cross_vars + maintenance_amt) - (wallet_balance + cross_vars + maintenance_amt) -
(side_1 * position * open_rate) (side_1 * amount * open_rate)
) / ( ) / (
(position * mm_ratio) - (side_1 * position) (amount * mm_ratio) - (side_1 * amount)
) )
) )
else: else:
@@ -199,7 +199,7 @@ class Binance(Exchange):
Path(__file__).parent / 'binance_leverage_tiers.json' Path(__file__).parent / 'binance_leverage_tiers.json'
) )
with open(leverage_tiers_path) as json_file: with open(leverage_tiers_path) as json_file:
return json.load(json_file) return json_load(json_file)
else: else:
try: try:
return self._api.fetch_leverage_tiers() return self._api.fetch_leverage_tiers()

File diff suppressed because it is too large Load Diff

View File

@@ -17,10 +17,12 @@ import ccxt
import ccxt.async_support as ccxt_async import ccxt.async_support as ccxt_async
from cachetools import TTLCache from cachetools import TTLCache
from ccxt import ROUND_DOWN, ROUND_UP, TICK_SIZE, TRUNCATE, decimal_to_precision from ccxt import ROUND_DOWN, ROUND_UP, TICK_SIZE, TRUNCATE, decimal_to_precision
from dateutil import parser
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, BuySell, from freqtrade.constants import (DEFAULT_AMOUNT_RESERVE_PERCENT, NON_OPEN_EXCHANGE_STATES, BuySell,
EntryExit, ListPairsWithTimeframes, MakerTaker, PairWithTimeframe) Config, EntryExit, ListPairsWithTimeframes, MakerTaker,
PairWithTimeframe)
from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list from freqtrade.data.converter import ohlcv_to_dataframe, trades_dict_to_list
from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, TradingMode from freqtrade.enums import OPTIMIZE_MODES, CandleType, MarginMode, TradingMode
from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError, from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
@@ -30,7 +32,8 @@ from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, BAD_EXCHANGE
EXCHANGE_HAS_OPTIONAL, EXCHANGE_HAS_REQUIRED, EXCHANGE_HAS_OPTIONAL, EXCHANGE_HAS_REQUIRED,
SUPPORTED_EXCHANGES, remove_credentials, retrier, SUPPORTED_EXCHANGES, remove_credentials, retrier,
retrier_async) retrier_async)
from freqtrade.misc import chunks, deep_merge_dicts, safe_value_fallback2 from freqtrade.misc import (chunks, deep_merge_dicts, file_dump_json, file_load_json,
safe_value_fallback2)
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.util import FtPrecise from freqtrade.util import FtPrecise
@@ -52,15 +55,15 @@ class Exchange:
# Parameters to add directly to buy/sell calls (like agreeing to trading agreement) # Parameters to add directly to buy/sell calls (like agreeing to trading agreement)
_params: Dict = {} _params: Dict = {}
# Additional headers - added to the ccxt object # Additional parameters - added to the ccxt object
_headers: Dict = {} _ccxt_params: Dict = {}
# Dict to specify which options each exchange implements # Dict to specify which options each exchange implements
# This defines defaults, which can be selectively overridden by subclasses using _ft_has # This defines defaults, which can be selectively overridden by subclasses using _ft_has
# or by specifying them in the configuration. # or by specifying them in the configuration.
_ft_has_default: Dict = { _ft_has_default: Dict = {
"stoploss_on_exchange": False, "stoploss_on_exchange": False,
"order_time_in_force": ["gtc"], "order_time_in_force": ["GTC"],
"time_in_force_parameter": "timeInForce", "time_in_force_parameter": "timeInForce",
"ohlcv_params": {}, "ohlcv_params": {},
"ohlcv_candle_limit": 500, "ohlcv_candle_limit": 500,
@@ -89,7 +92,7 @@ class Exchange:
# TradingMode.SPOT always supported and not required in this list # TradingMode.SPOT always supported and not required in this list
] ]
def __init__(self, config: Dict[str, Any], validate: bool = True, def __init__(self, config: Config, validate: bool = True,
load_leverage_tiers: bool = False) -> None: load_leverage_tiers: bool = False) -> None:
""" """
Initializes this module with the given config, Initializes this module with the given config,
@@ -106,7 +109,7 @@ class Exchange:
self._loop_lock = Lock() self._loop_lock = Lock()
self.loop = asyncio.new_event_loop() self.loop = asyncio.new_event_loop()
asyncio.set_event_loop(self.loop) asyncio.set_event_loop(self.loop)
self._config: Dict = {} self._config: Config = {}
self._config.update(config) self._config.update(config)
@@ -203,7 +206,7 @@ class Exchange:
logger.debug("Exchange object destroyed, closing async loop") logger.debug("Exchange object destroyed, closing async loop")
if (self._api_async and inspect.iscoroutinefunction(self._api_async.close) if (self._api_async and inspect.iscoroutinefunction(self._api_async.close)
and self._api_async.session): and self._api_async.session):
logger.info("Closing async ccxt session.") logger.debug("Closing async ccxt session.")
self.loop.run_until_complete(self._api_async.close()) self.loop.run_until_complete(self._api_async.close())
def validate_config(self, config): def validate_config(self, config):
@@ -240,9 +243,9 @@ class Exchange:
} }
if ccxt_kwargs: if ccxt_kwargs:
logger.info('Applying additional ccxt config: %s', ccxt_kwargs) logger.info('Applying additional ccxt config: %s', ccxt_kwargs)
if self._headers: if self._ccxt_params:
# Inject static headers after the above output to not confuse users. # Inject static options after the above output to not confuse users.
ccxt_kwargs = deep_merge_dicts({'headers': self._headers}, ccxt_kwargs) ccxt_kwargs = deep_merge_dicts(self._ccxt_params, ccxt_kwargs)
if ccxt_kwargs: if ccxt_kwargs:
ex_config.update(ccxt_kwargs) ex_config.update(ccxt_kwargs)
try: try:
@@ -406,7 +409,7 @@ class Exchange:
else: else:
return DataFrame() return DataFrame()
def _get_contract_size(self, pair: str) -> float: def get_contract_size(self, pair: str) -> float:
if self.trading_mode == TradingMode.FUTURES: if self.trading_mode == TradingMode.FUTURES:
market = self.markets[pair] market = self.markets[pair]
contract_size: float = 1.0 contract_size: float = 1.0
@@ -419,7 +422,7 @@ class Exchange:
def _trades_contracts_to_amount(self, trades: List) -> List: def _trades_contracts_to_amount(self, trades: List) -> List:
if len(trades) > 0 and 'symbol' in trades[0]: if len(trades) > 0 and 'symbol' in trades[0]:
contract_size = self._get_contract_size(trades[0]['symbol']) contract_size = self.get_contract_size(trades[0]['symbol'])
if contract_size != 1: if contract_size != 1:
for trade in trades: for trade in trades:
trade['amount'] = trade['amount'] * contract_size trade['amount'] = trade['amount'] * contract_size
@@ -427,7 +430,7 @@ class Exchange:
def _order_contracts_to_amount(self, order: Dict) -> Dict: def _order_contracts_to_amount(self, order: Dict) -> Dict:
if 'symbol' in order and order['symbol'] is not None: if 'symbol' in order and order['symbol'] is not None:
contract_size = self._get_contract_size(order['symbol']) contract_size = self.get_contract_size(order['symbol'])
if contract_size != 1: if contract_size != 1:
for prop in self._ft_has.get('order_props_in_contracts', []): for prop in self._ft_has.get('order_props_in_contracts', []):
if prop in order and order[prop] is not None: if prop in order and order[prop] is not None:
@@ -436,19 +439,22 @@ class Exchange:
def _amount_to_contracts(self, pair: str, amount: float) -> float: def _amount_to_contracts(self, pair: str, amount: float) -> float:
contract_size = self._get_contract_size(pair) contract_size = self.get_contract_size(pair)
if contract_size and contract_size != 1: return amount_to_contracts(amount, contract_size)
return amount / contract_size
else:
return amount
def _contracts_to_amount(self, pair: str, num_contracts: float) -> float: def _contracts_to_amount(self, pair: str, num_contracts: float) -> float:
contract_size = self._get_contract_size(pair) contract_size = self.get_contract_size(pair)
if contract_size and contract_size != 1: return contracts_to_amount(num_contracts, contract_size)
return num_contracts * contract_size
else: def amount_to_contract_precision(self, pair: str, amount: float) -> float:
return num_contracts """
Helper wrapper around amount_to_contract_precision
"""
contract_size = self.get_contract_size(pair)
return amount_to_contract_precision(amount, self.get_precision_amount(pair),
self.precisionMode, contract_size)
def set_sandbox(self, api: ccxt.Exchange, exchange_config: dict, name: str) -> None: def set_sandbox(self, api: ccxt.Exchange, exchange_config: dict, name: str) -> None:
if exchange_config.get('sandbox'): if exchange_config.get('sandbox'):
@@ -615,7 +621,7 @@ class Exchange:
""" """
Checks if order time in force configured in strategy/config are supported Checks if order time in force configured in strategy/config are supported
""" """
if any(v not in self._ft_has["order_time_in_force"] if any(v.upper() not in self._ft_has["order_time_in_force"]
for k, v in order_time_in_force.items()): for k, v in order_time_in_force.items()):
raise OperationalException( raise OperationalException(
f'Time in force policies are not supported for {self.name} yet.') f'Time in force policies are not supported for {self.name} yet.')
@@ -672,6 +678,12 @@ class Exchange:
f"Freqtrade does not support {mm_value} {trading_mode.value} on {self.name}" f"Freqtrade does not support {mm_value} {trading_mode.value} on {self.name}"
) )
def get_option(self, param: str, default: Any = None) -> Any:
"""
Get parameter value from _ft_has
"""
return self._ft_has.get(param, default)
def exchange_has(self, endpoint: str) -> bool: def exchange_has(self, endpoint: str) -> bool:
""" """
Checks if exchange implements a specific API endpoint. Checks if exchange implements a specific API endpoint.
@@ -987,12 +999,12 @@ class Exchange:
ordertype: str, ordertype: str,
leverage: float, leverage: float,
reduceOnly: bool, reduceOnly: bool,
time_in_force: str = 'gtc', time_in_force: str = 'GTC',
) -> Dict: ) -> Dict:
params = self._params.copy() params = self._params.copy()
if time_in_force != 'gtc' and ordertype != 'market': if time_in_force != 'GTC' and ordertype != 'market':
param = self._ft_has.get('time_in_force_parameter', '') param = self._ft_has.get('time_in_force_parameter', '')
params.update({param: time_in_force}) params.update({param: time_in_force.upper()})
if reduceOnly: if reduceOnly:
params.update({'reduceOnly': True}) params.update({'reduceOnly': True})
return params return params
@@ -1007,7 +1019,7 @@ class Exchange:
rate: float, rate: float,
leverage: float, leverage: float,
reduceOnly: bool = False, reduceOnly: bool = False,
time_in_force: str = 'gtc', time_in_force: str = 'GTC',
) -> Dict: ) -> Dict:
if self._config['dry_run']: if self._config['dry_run']:
dry_order = self.create_dry_run_order( dry_order = self.create_dry_run_order(
@@ -2207,6 +2219,7 @@ class Exchange:
@retrier_async @retrier_async
async def get_market_leverage_tiers(self, symbol: str) -> Tuple[str, List[Dict]]: async def get_market_leverage_tiers(self, symbol: str) -> Tuple[str, List[Dict]]:
""" Leverage tiers per symbol """
try: try:
tier = await self._api_async.fetch_market_leverage_tiers(symbol) tier = await self._api_async.fetch_market_leverage_tiers(symbol)
return symbol, tier return symbol, tier
@@ -2238,12 +2251,21 @@ class Exchange:
tiers: Dict[str, List[Dict]] = {} tiers: Dict[str, List[Dict]] = {}
tiers_cached = self.load_cached_leverage_tiers(self._config['stake_currency'])
if tiers_cached:
tiers = tiers_cached
coros = [
self.get_market_leverage_tiers(symbol)
for symbol in sorted(symbols) if symbol not in tiers]
# Be verbose here, as this delays startup by ~1 minute. # Be verbose here, as this delays startup by ~1 minute.
if coros:
logger.info( logger.info(
f"Initializing leverage_tiers for {len(symbols)} markets. " f"Initializing leverage_tiers for {len(symbols)} markets. "
"This will take about a minute.") "This will take about a minute.")
else:
coros = [self.get_market_leverage_tiers(symbol) for symbol in sorted(symbols)] logger.info("Using cached leverage_tiers.")
async def gather_results(): async def gather_results():
return await asyncio.gather(*input_coro, return_exceptions=True) return await asyncio.gather(*input_coro, return_exceptions=True)
@@ -2255,7 +2277,8 @@ class Exchange:
for symbol, res in results: for symbol, res in results:
tiers[symbol] = res tiers[symbol] = res
if len(coros) > 0:
self.cache_leverage_tiers(tiers, self._config['stake_currency'])
logger.info(f"Done initializing {len(symbols)} markets.") logger.info(f"Done initializing {len(symbols)} markets.")
return tiers return tiers
@@ -2264,6 +2287,30 @@ class Exchange:
else: else:
return {} return {}
def cache_leverage_tiers(self, tiers: Dict[str, List[Dict]], stake_currency: str) -> None:
filename = self._config['datadir'] / "futures" / f"leverage_tiers_{stake_currency}.json"
if not filename.parent.is_dir():
filename.parent.mkdir(parents=True)
data = {
"updated": datetime.now(timezone.utc),
"data": tiers,
}
file_dump_json(filename, data)
def load_cached_leverage_tiers(self, stake_currency: str) -> Optional[Dict[str, List[Dict]]]:
filename = self._config['datadir'] / "futures" / f"leverage_tiers_{stake_currency}.json"
if filename.is_file():
tiers = file_load_json(filename)
updated = tiers.get('updated')
if updated:
updated_dt = parser.parse(updated)
if updated_dt < datetime.now(timezone.utc) - timedelta(weeks=4):
logger.info("Cached leverage tiers are outdated. Will update.")
return None
return tiers['data']
return None
def fill_leverage_tiers(self) -> None: def fill_leverage_tiers(self) -> None:
""" """
Assigns property _leverage_tiers to a dictionary of information about the leverage Assigns property _leverage_tiers to a dictionary of information about the leverage
@@ -2279,10 +2326,10 @@ class Exchange:
def parse_leverage_tier(self, tier) -> Dict: def parse_leverage_tier(self, tier) -> Dict:
info = tier.get('info', {}) info = tier.get('info', {})
return { return {
'min': tier['minNotional'], 'minNotional': tier['minNotional'],
'max': tier['maxNotional'], 'maxNotional': tier['maxNotional'],
'mmr': tier['maintenanceMarginRate'], 'maintenanceMarginRate': tier['maintenanceMarginRate'],
'lev': tier['maxLeverage'], 'maxLeverage': tier['maxLeverage'],
'maintAmt': float(info['cum']) if 'cum' in info else None, 'maintAmt': float(info['cum']) if 'cum' in info else None,
} }
@@ -2311,18 +2358,18 @@ class Exchange:
pair_tiers = self._leverage_tiers[pair] pair_tiers = self._leverage_tiers[pair]
if stake_amount == 0: if stake_amount == 0:
return self._leverage_tiers[pair][0]['lev'] # Max lev for lowest amount return self._leverage_tiers[pair][0]['maxLeverage'] # Max lev for lowest amount
for tier_index in range(len(pair_tiers)): for tier_index in range(len(pair_tiers)):
tier = pair_tiers[tier_index] tier = pair_tiers[tier_index]
lev = tier['lev'] lev = tier['maxLeverage']
if tier_index < len(pair_tiers) - 1: if tier_index < len(pair_tiers) - 1:
next_tier = pair_tiers[tier_index + 1] next_tier = pair_tiers[tier_index + 1]
next_floor = next_tier['min'] / next_tier['lev'] next_floor = next_tier['minNotional'] / next_tier['maxLeverage']
if next_floor > stake_amount: # Next tier min too high for stake amount if next_floor > stake_amount: # Next tier min too high for stake amount
return min((tier['max'] / stake_amount), lev) return min((tier['maxNotional'] / stake_amount), lev)
# #
# With the two leverage tiers below, # With the two leverage tiers below,
# - a stake amount of 150 would mean a max leverage of (10000 / 150) = 66.66 # - a stake amount of 150 would mean a max leverage of (10000 / 150) = 66.66
@@ -2343,10 +2390,11 @@ class Exchange:
# #
else: # if on the last tier else: # if on the last tier
if stake_amount > tier['max']: # If stake is > than max tradeable amount if stake_amount > tier['maxNotional']:
# If stake is > than max tradeable amount
raise InvalidOrderException(f'Amount {stake_amount} too high for {pair}') raise InvalidOrderException(f'Amount {stake_amount} too high for {pair}')
else: else:
return tier['lev'] return tier['maxLeverage']
raise OperationalException( raise OperationalException(
'Looped through all tiers without finding a max leverage. Should never be reached' 'Looped through all tiers without finding a max leverage. Should never be reached'
@@ -2394,35 +2442,6 @@ class Exchange:
""" """
return 0.0 return 0.0
def get_liquidation_price(
self,
pair: str,
open_rate: float,
amount: float, # quote currency, includes leverage
leverage: float,
is_short: bool
) -> Optional[float]:
if self.trading_mode in TradingMode.SPOT:
return None
elif (
self.trading_mode == TradingMode.FUTURES
):
wallet_balance = (amount * open_rate) / leverage
isolated_liq = self.get_or_calculate_liquidation_price(
pair=pair,
open_rate=open_rate,
is_short=is_short,
position=amount,
wallet_balance=wallet_balance,
mm_ex_1=0.0,
upnl_ex_1=0.0,
)
return isolated_liq
else:
raise OperationalException(
"Freqtrade currently only supports futures for leverage trading.")
def funding_fee_cutoff(self, open_date: datetime): def funding_fee_cutoff(self, open_date: datetime):
""" """
:param open_date: The open date for a trade :param open_date: The open date for a trade
@@ -2491,8 +2510,13 @@ class Exchange:
cache=False, cache=False,
drop_incomplete=False, drop_incomplete=False,
) )
try:
# we can't assume we always get histories - for example during exchange downtimes
funding_rates = candle_histories[funding_comb] funding_rates = candle_histories[funding_comb]
mark_rates = candle_histories[mark_comb] mark_rates = candle_histories[mark_comb]
except KeyError:
raise ExchangeError("Could not find funding rates.") from None
funding_mark_rates = self.combine_funding_and_mark( funding_mark_rates = self.combine_funding_and_mark(
funding_rates=funding_rates, mark_rates=mark_rates) funding_rates=funding_rates, mark_rates=mark_rates)
@@ -2572,6 +2596,8 @@ class Exchange:
:param is_short: trade direction :param is_short: trade direction
:param amount: Trade amount :param amount: Trade amount
:param open_date: Open date of the trade :param open_date: Open date of the trade
:return: funding fee since open_date
:raies: ExchangeError if something goes wrong.
""" """
if self.trading_mode == TradingMode.FUTURES: if self.trading_mode == TradingMode.FUTURES:
if self._config['dry_run']: if self._config['dry_run']:
@@ -2583,34 +2609,36 @@ class Exchange:
else: else:
return 0.0 return 0.0
def get_or_calculate_liquidation_price( def get_liquidation_price(
self, self,
pair: str, pair: str,
# Dry-run # Dry-run
open_rate: float, # Entry price of position open_rate: float, # Entry price of position
is_short: bool, is_short: bool,
position: float, # Absolute value of position size amount: float, # Absolute value of position size
wallet_balance: float, # Or margin balance stake_amount: float,
wallet_balance: float,
mm_ex_1: float = 0.0, # (Binance) Cross only mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only upnl_ex_1: float = 0.0, # (Binance) Cross only
) -> Optional[float]: ) -> Optional[float]:
""" """
Set's the margin mode on the exchange to cross or isolated for a specific pair Set's the margin mode on the exchange to cross or isolated for a specific pair
:param pair: base/quote currency pair (e.g. "ADA/USDT")
""" """
if self.trading_mode == TradingMode.SPOT: if self.trading_mode == TradingMode.SPOT:
return None return None
elif (self.trading_mode != TradingMode.FUTURES): elif (self.trading_mode != TradingMode.FUTURES):
raise OperationalException( raise OperationalException(
f"{self.name} does not support {self.margin_mode.value} {self.trading_mode.value}") f"{self.name} does not support {self.margin_mode} {self.trading_mode}")
isolated_liq = None
if self._config['dry_run'] or not self.exchange_has("fetchPositions"): if self._config['dry_run'] or not self.exchange_has("fetchPositions"):
isolated_liq = self.dry_run_liquidation_price( isolated_liq = self.dry_run_liquidation_price(
pair=pair, pair=pair,
open_rate=open_rate, open_rate=open_rate,
is_short=is_short, is_short=is_short,
position=position, amount=amount,
stake_amount=stake_amount,
wallet_balance=wallet_balance, wallet_balance=wallet_balance,
mm_ex_1=mm_ex_1, mm_ex_1=mm_ex_1,
upnl_ex_1=upnl_ex_1 upnl_ex_1=upnl_ex_1
@@ -2620,8 +2648,6 @@ class Exchange:
if len(positions) > 0: if len(positions) > 0:
pos = positions[0] pos = positions[0]
isolated_liq = pos['liquidationPrice'] isolated_liq = pos['liquidationPrice']
else:
return None
if isolated_liq: if isolated_liq:
buffer_amount = abs(open_rate - isolated_liq) * self.liquidation_buffer buffer_amount = abs(open_rate - isolated_liq) * self.liquidation_buffer
@@ -2639,22 +2665,24 @@ class Exchange:
pair: str, pair: str,
open_rate: float, # Entry price of position open_rate: float, # Entry price of position
is_short: bool, is_short: bool,
position: float, # Absolute value of position size amount: float,
stake_amount: float,
wallet_balance: float, # Or margin balance wallet_balance: float, # Or margin balance
mm_ex_1: float = 0.0, # (Binance) Cross only mm_ex_1: float = 0.0, # (Binance) Cross only
upnl_ex_1: float = 0.0, # (Binance) Cross only upnl_ex_1: float = 0.0, # (Binance) Cross only
) -> Optional[float]: ) -> Optional[float]:
""" """
Important: Must be fetching data from cached values as this is used by backtesting!
PERPETUAL: PERPETUAL:
gateio: https://www.gate.io/help/futures/perpetual/22160/calculation-of-liquidation-price gateio: https://www.gate.io/help/futures/perpetual/22160/calculation-of-liquidation-price
okex: https://www.okex.com/support/hc/en-us/articles/ okex: https://www.okex.com/support/hc/en-us/articles/
360053909592-VI-Introduction-to-the-isolated-mode-of-Single-Multi-currency-Portfolio-margin 360053909592-VI-Introduction-to-the-isolated-mode-of-Single-Multi-currency-Portfolio-margin
Important: Must be fetching data from cached values as this is used by backtesting!
:param exchange_name: :param exchange_name:
:param open_rate: Entry price of position :param open_rate: Entry price of position
:param is_short: True if the trade is a short, false otherwise :param is_short: True if the trade is a short, false otherwise
:param position: Absolute value of position size incl. leverage (in base currency) :param amount: Absolute value of position size incl. leverage (in base currency)
:param stake_amount: Stake amount - Collateral in settle currency.
:param trading_mode: SPOT, MARGIN, FUTURES, etc. :param trading_mode: SPOT, MARGIN, FUTURES, etc.
:param margin_mode: Either ISOLATED or CROSS :param margin_mode: Either ISOLATED or CROSS
:param wallet_balance: Amount of margin_mode in the wallet being used to trade :param wallet_balance: Amount of margin_mode in the wallet being used to trade
@@ -2668,7 +2696,7 @@ class Exchange:
market = self.markets[pair] market = self.markets[pair]
taker_fee_rate = market['taker'] taker_fee_rate = market['taker']
mm_ratio, _ = self.get_maintenance_ratio_and_amt(pair, position) mm_ratio, _ = self.get_maintenance_ratio_and_amt(pair, stake_amount)
if self.trading_mode == TradingMode.FUTURES and self.margin_mode == MarginMode.ISOLATED: if self.trading_mode == TradingMode.FUTURES and self.margin_mode == MarginMode.ISOLATED:
@@ -2676,7 +2704,7 @@ class Exchange:
raise OperationalException( raise OperationalException(
"Freqtrade does not yet support inverse contracts") "Freqtrade does not yet support inverse contracts")
value = wallet_balance / position value = wallet_balance / amount
mm_ratio_taker = (mm_ratio + taker_fee_rate) mm_ratio_taker = (mm_ratio + taker_fee_rate)
if is_short: if is_short:
@@ -2712,8 +2740,8 @@ class Exchange:
pair_tiers = self._leverage_tiers[pair] pair_tiers = self._leverage_tiers[pair]
for tier in reversed(pair_tiers): for tier in reversed(pair_tiers):
if nominal_value >= tier['min']: if nominal_value >= tier['minNotional']:
return (tier['mmr'], tier['maintAmt']) return (tier['maintenanceMarginRate'], tier['maintAmt'])
raise OperationalException("nominal value can not be lower than 0") raise OperationalException("nominal value can not be lower than 0")
# The lowest notional_floor for any pair in fetch_leverage_tiers is always 0 because it # The lowest notional_floor for any pair in fetch_leverage_tiers is always 0 because it
@@ -2855,6 +2883,33 @@ def market_is_active(market: Dict) -> bool:
return market.get('active', True) is not False return market.get('active', True) is not False
def amount_to_contracts(amount: float, contract_size: Optional[float]) -> float:
"""
Convert amount to contracts.
:param amount: amount to convert
:param contract_size: contract size - taken from exchange.get_contract_size(pair)
:return: num-contracts
"""
if contract_size and contract_size != 1:
return float(FtPrecise(amount) / FtPrecise(contract_size))
else:
return amount
def contracts_to_amount(num_contracts: float, contract_size: Optional[float]) -> float:
"""
Takes num-contracts and converts it to contract size
:param num_contracts: number of contracts
:param contract_size: contract size - taken from exchange.get_contract_size(pair)
:return: Amount
"""
if contract_size and contract_size != 1:
return float(FtPrecise(num_contracts) * FtPrecise(contract_size))
else:
return num_contracts
def amount_to_precision(amount: float, amount_precision: Optional[float], def amount_to_precision(amount: float, amount_precision: Optional[float],
precisionMode: Optional[int]) -> float: precisionMode: Optional[int]) -> float:
""" """
@@ -2879,6 +2934,29 @@ def amount_to_precision(amount: float, amount_precision: Optional[float],
return amount return amount
def amount_to_contract_precision(
amount, amount_precision: Optional[float], precisionMode: Optional[int],
contract_size: Optional[float]) -> float:
"""
Returns the amount to buy or sell to a precision the Exchange accepts
including calculation to and from contracts.
Re-implementation of ccxt internal methods - ensuring we can test the result is correct
based on our definitions.
:param amount: amount to truncate
:param amount_precision: amount precision to use.
should be retrieved from markets[pair]['precision']['amount']
:param precisionMode: precision mode to use. Should be used from precisionMode
one of ccxt's DECIMAL_PLACES, SIGNIFICANT_DIGITS, or TICK_SIZE
:param contract_size: contract size - taken from exchange.get_contract_size(pair)
:return: truncated amount
"""
if amount_precision is not None and precisionMode is not None:
contracts = amount_to_contracts(amount, contract_size)
amount_p = amount_to_precision(contracts, amount_precision, precisionMode)
return contracts_to_amount(amount_p, contract_size)
return amount
def price_to_precision(price: float, price_precision: Optional[float], def price_to_precision(price: float, price_precision: Optional[float],
precisionMode: Optional[int]) -> float: precisionMode: Optional[int]) -> float:
""" """

View File

@@ -19,6 +19,7 @@ logger = logging.getLogger(__name__)
class Ftx(Exchange): class Ftx(Exchange):
_ft_has: Dict = { _ft_has: Dict = {
"order_time_in_force": ['GTC', 'IOC', 'PO'],
"stoploss_on_exchange": True, "stoploss_on_exchange": True,
"ohlcv_candle_limit": 1500, "ohlcv_candle_limit": 1500,
"ohlcv_require_since": True, "ohlcv_require_since": True,

View File

@@ -25,16 +25,13 @@ class Gateio(Exchange):
_ft_has: Dict = { _ft_has: Dict = {
"ohlcv_candle_limit": 1000, "ohlcv_candle_limit": 1000,
"ohlcv_volume_currency": "quote", "order_time_in_force": ['GTC', 'IOC'],
"time_in_force_parameter": "timeInForce",
"order_time_in_force": ['gtc', 'ioc'],
"stoploss_order_types": {"limit": "limit"}, "stoploss_order_types": {"limit": "limit"},
"stoploss_on_exchange": True, "stoploss_on_exchange": True,
} }
_ft_has_futures: Dict = { _ft_has_futures: Dict = {
"needs_trading_fees": True, "needs_trading_fees": True,
"ohlcv_volume_currency": "base",
"fee_cost_in_contracts": False, # Set explicitly to false for clarity "fee_cost_in_contracts": False, # Set explicitly to false for clarity
"order_props_in_contracts": ['amount', 'filled', 'remaining'], "order_props_in_contracts": ['amount', 'filled', 'remaining'],
} }
@@ -59,7 +56,7 @@ class Gateio(Exchange):
ordertype: str, ordertype: str,
leverage: float, leverage: float,
reduceOnly: bool, reduceOnly: bool,
time_in_force: str = 'gtc', time_in_force: str = 'GTC',
) -> Dict: ) -> Dict:
params = super()._get_params( params = super()._get_params(
side=side, side=side,
@@ -71,7 +68,7 @@ class Gateio(Exchange):
if ordertype == 'market' and self.trading_mode == TradingMode.FUTURES: if ordertype == 'market' and self.trading_mode == TradingMode.FUTURES:
params['type'] = 'market' params['type'] = 'market'
param = self._ft_has.get('time_in_force_parameter', '') param = self._ft_has.get('time_in_force_parameter', '')
params.update({param: 'ioc'}) params.update({param: 'IOC'})
return params return params
def get_trades_for_order(self, order_id: str, pair: str, since: datetime, def get_trades_for_order(self, order_id: str, pair: str, since: datetime,

View File

@@ -171,7 +171,7 @@ class Kraken(Exchange):
ordertype: str, ordertype: str,
leverage: float, leverage: float,
reduceOnly: bool, reduceOnly: bool,
time_in_force: str = 'gtc' time_in_force: str = 'GTC'
) -> Dict: ) -> Dict:
params = super()._get_params( params = super()._get_params(
side=side, side=side,

View File

@@ -23,8 +23,7 @@ class Kucoin(Exchange):
"stoploss_order_types": {"limit": "limit", "market": "market"}, "stoploss_order_types": {"limit": "limit", "market": "market"},
"l2_limit_range": [20, 100], "l2_limit_range": [20, 100],
"l2_limit_range_required": False, "l2_limit_range_required": False,
"order_time_in_force": ['gtc', 'fok', 'ioc'], "order_time_in_force": ['GTC', 'FOK', 'IOC'],
"time_in_force_parameter": "timeInForce",
"ohlcv_candle_limit": 1500, "ohlcv_candle_limit": 1500,
} }

View File

@@ -4,8 +4,7 @@ from typing import Dict, List, Optional, Tuple
import ccxt import ccxt
from freqtrade.constants import BuySell from freqtrade.constants import BuySell
from freqtrade.enums import MarginMode, TradingMode from freqtrade.enums import CandleType, MarginMode, TradingMode
from freqtrade.enums.candletype import CandleType
from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError from freqtrade.exceptions import DDosProtection, OperationalException, TemporaryError
from freqtrade.exchange import Exchange, date_minus_candles from freqtrade.exchange import Exchange, date_minus_candles
from freqtrade.exchange.common import retrier from freqtrade.exchange.common import retrier
@@ -39,6 +38,8 @@ class Okx(Exchange):
net_only = True net_only = True
_ccxt_params: Dict = {'options': {'brokerId': 'ffb5405ad327SUDE'}}
def ohlcv_candle_limit( def ohlcv_candle_limit(
self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = None) -> int: self, timeframe: str, candle_type: CandleType, since_ms: Optional[int] = None) -> int:
""" """
@@ -70,6 +71,7 @@ class Okx(Exchange):
try: try:
if self.trading_mode == TradingMode.FUTURES and not self._config['dry_run']: if self.trading_mode == TradingMode.FUTURES and not self._config['dry_run']:
accounts = self._api.fetch_accounts() accounts = self._api.fetch_accounts()
self._log_exchange_response('fetch_accounts', accounts)
if len(accounts) > 0: if len(accounts) > 0:
self.net_only = accounts[0].get('info', {}).get('posMode') == 'net_mode' self.net_only = accounts[0].get('info', {}).get('posMode') == 'net_mode'
except ccxt.DDoSProtection as e: except ccxt.DDoSProtection as e:
@@ -96,7 +98,7 @@ class Okx(Exchange):
ordertype: str, ordertype: str,
leverage: float, leverage: float,
reduceOnly: bool, reduceOnly: bool,
time_in_force: str = 'gtc', time_in_force: str = 'GTC',
) -> Dict: ) -> Dict:
params = super()._get_params( params = super()._get_params(
side=side, side=side,
@@ -144,4 +146,4 @@ class Okx(Exchange):
return float('inf') return float('inf')
pair_tiers = self._leverage_tiers[pair] pair_tiers = self._leverage_tiers[pair]
return pair_tiers[-1]['max'] / leverage return pair_tiers[-1]['maxNotional'] / leverage

View File

@@ -1,4 +1,5 @@
import logging import logging
from time import time
from typing import Any, Tuple from typing import Any, Tuple
import numpy as np import numpy as np
@@ -21,34 +22,36 @@ class BaseClassifierModel(IFreqaiModel):
""" """
def train( def train(
self, unfiltered_dataframe: DataFrame, pair: str, dk: FreqaiDataKitchen self, unfiltered_df: DataFrame, pair: str, dk: FreqaiDataKitchen, **kwargs
) -> Any: ) -> Any:
""" """
Filter the training data and train a model to it. Train makes heavy use of the datakitchen Filter the training data and train a model to it. Train makes heavy use of the datakitchen
for storing, saving, loading, and analyzing the data. for storing, saving, loading, and analyzing the data.
:param unfiltered_dataframe: Full dataframe for the current training period :param unfiltered_df: Full dataframe for the current training period
:param metadata: pair metadata from strategy. :param metadata: pair metadata from strategy.
:return: :return:
:model: Trained model which can be used to inference (self.predict) :model: Trained model which can be used to inference (self.predict)
""" """
logger.info("-------------------- Starting training " f"{pair} --------------------") logger.info(f"-------------------- Starting training {pair} --------------------")
start_time = time()
# filter the features requested by user in the configuration file and elegantly handle NaNs # filter the features requested by user in the configuration file and elegantly handle NaNs
features_filtered, labels_filtered = dk.filter_features( features_filtered, labels_filtered = dk.filter_features(
unfiltered_dataframe, unfiltered_df,
dk.training_features_list, dk.training_features_list,
dk.label_list, dk.label_list,
training_filter=True, training_filter=True,
) )
start_date = unfiltered_dataframe["date"].iloc[0].strftime("%Y-%m-%d") start_date = unfiltered_df["date"].iloc[0].strftime("%Y-%m-%d")
end_date = unfiltered_dataframe["date"].iloc[-1].strftime("%Y-%m-%d") end_date = unfiltered_df["date"].iloc[-1].strftime("%Y-%m-%d")
logger.info(f"-------------------- Training on data from {start_date} to " logger.info(f"-------------------- Training on data from {start_date} to "
f"{end_date} --------------------") f"{end_date} --------------------")
# split data into train/test data. # split data into train/test data.
data_dictionary = dk.make_train_test_datasets(features_filtered, labels_filtered) data_dictionary = dk.make_train_test_datasets(features_filtered, labels_filtered)
if not self.freqai_info.get('fit_live_predictions', 0) or not self.live: if not self.freqai_info.get("fit_live_predictions", 0) or not self.live:
dk.fit_labels() dk.fit_labels()
# normalize all data based on train_dataset only # normalize all data based on train_dataset only
data_dictionary = dk.normalize_data(data_dictionary) data_dictionary = dk.normalize_data(data_dictionary)
@@ -57,36 +60,39 @@ class BaseClassifierModel(IFreqaiModel):
self.data_cleaning_train(dk) self.data_cleaning_train(dk)
logger.info( logger.info(
f'Training model on {len(dk.data_dictionary["train_features"].columns)}' " features" f"Training model on {len(dk.data_dictionary['train_features'].columns)} features"
) )
logger.info(f'Training model on {len(data_dictionary["train_features"])} data points') logger.info(f"Training model on {len(data_dictionary['train_features'])} data points")
model = self.fit(data_dictionary) model = self.fit(data_dictionary, dk)
logger.info(f"--------------------done training {pair}--------------------") end_time = time()
logger.info(f"-------------------- Done training {pair} "
f"({end_time - start_time:.2f} secs) --------------------")
return model return model
def predict( def predict(
self, unfiltered_dataframe: DataFrame, dk: FreqaiDataKitchen, first: bool = False self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs
) -> Tuple[DataFrame, npt.NDArray[np.int_]]: ) -> Tuple[DataFrame, npt.NDArray[np.int_]]:
""" """
Filter the prediction features data and predict with it. Filter the prediction features data and predict with it.
:param: unfiltered_dataframe: Full dataframe for the current backtest period. :param: unfiltered_df: Full dataframe for the current backtest period.
:return: :return:
:pred_df: dataframe containing the predictions :pred_df: dataframe containing the predictions
:do_predict: np.array of 1s and 0s to indicate places where freqai needed to remove :do_predict: np.array of 1s and 0s to indicate places where freqai needed to remove
data (NaNs) or felt uncertain about data (PCA and DI index) data (NaNs) or felt uncertain about data (PCA and DI index)
""" """
dk.find_features(unfiltered_dataframe) dk.find_features(unfiltered_df)
filtered_dataframe, _ = dk.filter_features( filtered_df, _ = dk.filter_features(
unfiltered_dataframe, dk.training_features_list, training_filter=False unfiltered_df, dk.training_features_list, training_filter=False
) )
filtered_dataframe = dk.normalize_data_from_metadata(filtered_dataframe) filtered_df = dk.normalize_data_from_metadata(filtered_df)
dk.data_dictionary["prediction_features"] = filtered_dataframe dk.data_dictionary["prediction_features"] = filtered_df
self.data_cleaning_predict(dk, filtered_dataframe) self.data_cleaning_predict(dk)
predictions = self.model.predict(dk.data_dictionary["prediction_features"]) predictions = self.model.predict(dk.data_dictionary["prediction_features"])
pred_df = DataFrame(predictions, columns=dk.label_list) pred_df = DataFrame(predictions, columns=dk.label_list)

View File

@@ -1,4 +1,5 @@
import logging import logging
from time import time
from typing import Any, Tuple from typing import Any, Tuple
import numpy as np import numpy as np
@@ -20,34 +21,36 @@ class BaseRegressionModel(IFreqaiModel):
""" """
def train( def train(
self, unfiltered_dataframe: DataFrame, pair: str, dk: FreqaiDataKitchen self, unfiltered_df: DataFrame, pair: str, dk: FreqaiDataKitchen, **kwargs
) -> Any: ) -> Any:
""" """
Filter the training data and train a model to it. Train makes heavy use of the datakitchen Filter the training data and train a model to it. Train makes heavy use of the datakitchen
for storing, saving, loading, and analyzing the data. for storing, saving, loading, and analyzing the data.
:param unfiltered_dataframe: Full dataframe for the current training period :param unfiltered_df: Full dataframe for the current training period
:param metadata: pair metadata from strategy. :param metadata: pair metadata from strategy.
:return: :return:
:model: Trained model which can be used to inference (self.predict) :model: Trained model which can be used to inference (self.predict)
""" """
logger.info("-------------------- Starting training " f"{pair} --------------------") logger.info(f"-------------------- Starting training {pair} --------------------")
start_time = time()
# filter the features requested by user in the configuration file and elegantly handle NaNs # filter the features requested by user in the configuration file and elegantly handle NaNs
features_filtered, labels_filtered = dk.filter_features( features_filtered, labels_filtered = dk.filter_features(
unfiltered_dataframe, unfiltered_df,
dk.training_features_list, dk.training_features_list,
dk.label_list, dk.label_list,
training_filter=True, training_filter=True,
) )
start_date = unfiltered_dataframe["date"].iloc[0].strftime("%Y-%m-%d") start_date = unfiltered_df["date"].iloc[0].strftime("%Y-%m-%d")
end_date = unfiltered_dataframe["date"].iloc[-1].strftime("%Y-%m-%d") end_date = unfiltered_df["date"].iloc[-1].strftime("%Y-%m-%d")
logger.info(f"-------------------- Training on data from {start_date} to " logger.info(f"-------------------- Training on data from {start_date} to "
f"{end_date} --------------------") f"{end_date} --------------------")
# split data into train/test data. # split data into train/test data.
data_dictionary = dk.make_train_test_datasets(features_filtered, labels_filtered) data_dictionary = dk.make_train_test_datasets(features_filtered, labels_filtered)
if not self.freqai_info.get('fit_live_predictions', 0) or not self.live: if not self.freqai_info.get("fit_live_predictions", 0) or not self.live:
dk.fit_labels() dk.fit_labels()
# normalize all data based on train_dataset only # normalize all data based on train_dataset only
data_dictionary = dk.normalize_data(data_dictionary) data_dictionary = dk.normalize_data(data_dictionary)
@@ -56,37 +59,40 @@ class BaseRegressionModel(IFreqaiModel):
self.data_cleaning_train(dk) self.data_cleaning_train(dk)
logger.info( logger.info(
f'Training model on {len(dk.data_dictionary["train_features"].columns)}' " features" f"Training model on {len(dk.data_dictionary['train_features'].columns)} features"
) )
logger.info(f'Training model on {len(data_dictionary["train_features"])} data points') logger.info(f"Training model on {len(data_dictionary['train_features'])} data points")
model = self.fit(data_dictionary) model = self.fit(data_dictionary, dk)
logger.info(f"--------------------done training {pair}--------------------") end_time = time()
logger.info(f"-------------------- Done training {pair} "
f"({end_time - start_time:.2f} secs) --------------------")
return model return model
def predict( def predict(
self, unfiltered_dataframe: DataFrame, dk: FreqaiDataKitchen, first: bool = False self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs
) -> Tuple[DataFrame, npt.NDArray[np.int_]]: ) -> Tuple[DataFrame, npt.NDArray[np.int_]]:
""" """
Filter the prediction features data and predict with it. Filter the prediction features data and predict with it.
:param: unfiltered_dataframe: Full dataframe for the current backtest period. :param: unfiltered_df: Full dataframe for the current backtest period.
:return: :return:
:pred_df: dataframe containing the predictions :pred_df: dataframe containing the predictions
:do_predict: np.array of 1s and 0s to indicate places where freqai needed to remove :do_predict: np.array of 1s and 0s to indicate places where freqai needed to remove
data (NaNs) or felt uncertain about data (PCA and DI index) data (NaNs) or felt uncertain about data (PCA and DI index)
""" """
dk.find_features(unfiltered_dataframe) dk.find_features(unfiltered_df)
filtered_dataframe, _ = dk.filter_features( filtered_df, _ = dk.filter_features(
unfiltered_dataframe, dk.training_features_list, training_filter=False unfiltered_df, dk.training_features_list, training_filter=False
) )
filtered_dataframe = dk.normalize_data_from_metadata(filtered_dataframe) filtered_df = dk.normalize_data_from_metadata(filtered_df)
dk.data_dictionary["prediction_features"] = filtered_dataframe dk.data_dictionary["prediction_features"] = filtered_df
# optional additional data cleaning/analysis # optional additional data cleaning/analysis
self.data_cleaning_predict(dk, filtered_dataframe) self.data_cleaning_predict(dk)
predictions = self.model.predict(dk.data_dictionary["prediction_features"]) predictions = self.model.predict(dk.data_dictionary["prediction_features"])
pred_df = DataFrame(predictions, columns=dk.label_list) pred_df = DataFrame(predictions, columns=dk.label_list)

View File

@@ -1,4 +1,5 @@
import logging import logging
from time import time
from typing import Any from typing import Any
from pandas import DataFrame from pandas import DataFrame
@@ -17,34 +18,36 @@ class BaseTensorFlowModel(IFreqaiModel):
""" """
def train( def train(
self, unfiltered_dataframe: DataFrame, pair: str, dk: FreqaiDataKitchen self, unfiltered_df: DataFrame, pair: str, dk: FreqaiDataKitchen, **kwargs
) -> Any: ) -> Any:
""" """
Filter the training data and train a model to it. Train makes heavy use of the datakitchen Filter the training data and train a model to it. Train makes heavy use of the datakitchen
for storing, saving, loading, and analyzing the data. for storing, saving, loading, and analyzing the data.
:param unfiltered_dataframe: Full dataframe for the current training period :param unfiltered_df: Full dataframe for the current training period
:param metadata: pair metadata from strategy. :param metadata: pair metadata from strategy.
:return: :return:
:model: Trained model which can be used to inference (self.predict) :model: Trained model which can be used to inference (self.predict)
""" """
logger.info("-------------------- Starting training " f"{pair} --------------------") logger.info(f"-------------------- Starting training {pair} --------------------")
start_time = time()
# filter the features requested by user in the configuration file and elegantly handle NaNs # filter the features requested by user in the configuration file and elegantly handle NaNs
features_filtered, labels_filtered = dk.filter_features( features_filtered, labels_filtered = dk.filter_features(
unfiltered_dataframe, unfiltered_df,
dk.training_features_list, dk.training_features_list,
dk.label_list, dk.label_list,
training_filter=True, training_filter=True,
) )
start_date = unfiltered_dataframe["date"].iloc[0].strftime("%Y-%m-%d") start_date = unfiltered_df["date"].iloc[0].strftime("%Y-%m-%d")
end_date = unfiltered_dataframe["date"].iloc[-1].strftime("%Y-%m-%d") end_date = unfiltered_df["date"].iloc[-1].strftime("%Y-%m-%d")
logger.info(f"-------------------- Training on data from {start_date} to " logger.info(f"-------------------- Training on data from {start_date} to "
f"{end_date} --------------------") f"{end_date} --------------------")
# split data into train/test data. # split data into train/test data.
data_dictionary = dk.make_train_test_datasets(features_filtered, labels_filtered) data_dictionary = dk.make_train_test_datasets(features_filtered, labels_filtered)
if not self.freqai_info.get('fit_live_predictions', 0) or not self.live: if not self.freqai_info.get("fit_live_predictions", 0) or not self.live:
dk.fit_labels() dk.fit_labels()
# normalize all data based on train_dataset only # normalize all data based on train_dataset only
data_dictionary = dk.normalize_data(data_dictionary) data_dictionary = dk.normalize_data(data_dictionary)
@@ -53,12 +56,15 @@ class BaseTensorFlowModel(IFreqaiModel):
self.data_cleaning_train(dk) self.data_cleaning_train(dk)
logger.info( logger.info(
f'Training model on {len(dk.data_dictionary["train_features"].columns)}' " features" f"Training model on {len(dk.data_dictionary['train_features'].columns)} features"
) )
logger.info(f'Training model on {len(data_dictionary["train_features"])} data points') logger.info(f"Training model on {len(data_dictionary['train_features'])} data points")
model = self.fit(data_dictionary) model = self.fit(data_dictionary, dk)
logger.info(f"--------------------done training {pair}--------------------") end_time = time()
logger.info(f"-------------------- Done training {pair} "
f"({end_time - start_time:.2f} secs) --------------------")
return model return model

View File

@@ -0,0 +1,64 @@
from joblib import Parallel
from sklearn.multioutput import MultiOutputRegressor, _fit_estimator
from sklearn.utils.fixes import delayed
from sklearn.utils.validation import has_fit_parameter
class FreqaiMultiOutputRegressor(MultiOutputRegressor):
def fit(self, X, y, sample_weight=None, fit_params=None):
"""Fit the model to data, separately for each output variable.
Parameters
----------
X : {array-like, sparse matrix} of shape (n_samples, n_features)
The input data.
y : {array-like, sparse matrix} of shape (n_samples, n_outputs)
Multi-output targets. An indicator matrix turns on multilabel
estimation.
sample_weight : array-like of shape (n_samples,), default=None
Sample weights. If `None`, then samples are equally weighted.
Only supported if the underlying regressor supports sample
weights.
fit_params : A list of dicts for the fit_params
Parameters passed to the ``estimator.fit`` method of each step.
Each dict may contain same or different values (e.g. different
eval_sets or init_models)
.. versionadded:: 0.23
Returns
-------
self : object
Returns a fitted instance.
"""
if not hasattr(self.estimator, "fit"):
raise ValueError("The base estimator should implement a fit method")
y = self._validate_data(X="no_validation", y=y, multi_output=True)
if y.ndim == 1:
raise ValueError(
"y must have at least two dimensions for "
"multi-output regression but has only one."
)
if sample_weight is not None and not has_fit_parameter(
self.estimator, "sample_weight"
):
raise ValueError("Underlying estimator does not support sample weights.")
if not fit_params:
fit_params = [None] * y.shape[1]
self.estimators_ = Parallel(n_jobs=self.n_jobs)(
delayed(_fit_estimator)(
self.estimator, X, y[:, i], sample_weight, **fit_params[i]
)
for i in range(y.shape[1])
)
if hasattr(self.estimators_[0], "n_features_in_"):
self.n_features_in_ = self.estimators_[0].n_features_in_
if hasattr(self.estimators_[0], "feature_names_in_"):
self.feature_names_in_ = self.estimators_[0].feature_names_in_
return

View File

@@ -16,6 +16,7 @@ from numpy.typing import NDArray
from pandas import DataFrame from pandas import DataFrame
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.constants import Config
from freqtrade.data.history import load_pair_history from freqtrade.data.history import load_pair_history
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
@@ -27,9 +28,7 @@ logger = logging.getLogger(__name__)
class pair_info(TypedDict): class pair_info(TypedDict):
model_filename: str model_filename: str
first: bool
trained_timestamp: int trained_timestamp: int
priority: int
data_path: str data_path: str
extras: dict extras: dict
@@ -58,7 +57,7 @@ class FreqaiDataDrawer:
Juha Nykänen @suikula, Wagner Costa @wagnercosta, Johan Vlugt @Jooopieeert Juha Nykänen @suikula, Wagner Costa @wagnercosta, Johan Vlugt @Jooopieeert
""" """
def __init__(self, full_path: Path, config: dict, follow_mode: bool = False): def __init__(self, full_path: Path, config: Config, follow_mode: bool = False):
self.config = config self.config = config
self.freqai_info = config.get("freqai", {}) self.freqai_info = config.get("freqai", {})
@@ -76,6 +75,8 @@ class FreqaiDataDrawer:
self.full_path / f"follower_dictionary-{self.follower_name}.json" self.full_path / f"follower_dictionary-{self.follower_name}.json"
) )
self.historic_predictions_path = Path(self.full_path / "historic_predictions.pkl") self.historic_predictions_path = Path(self.full_path / "historic_predictions.pkl")
self.historic_predictions_bkp_path = Path(
self.full_path / "historic_predictions.backup.pkl")
self.pair_dictionary_path = Path(self.full_path / "pair_dictionary.json") self.pair_dictionary_path = Path(self.full_path / "pair_dictionary.json")
self.follow_mode = follow_mode self.follow_mode = follow_mode
if follow_mode: if follow_mode:
@@ -89,7 +90,7 @@ class FreqaiDataDrawer:
self.old_DBSCAN_eps: Dict[str, float] = {} self.old_DBSCAN_eps: Dict[str, float] = {}
self.empty_pair_dict: pair_info = { self.empty_pair_dict: pair_info = {
"model_filename": "", "trained_timestamp": 0, "model_filename": "", "trained_timestamp": 0,
"priority": 1, "first": True, "data_path": "", "extras": {}} "data_path": "", "extras": {}}
def load_drawer_from_disk(self): def load_drawer_from_disk(self):
""" """
@@ -118,6 +119,7 @@ class FreqaiDataDrawer:
""" """
exists = self.historic_predictions_path.is_file() exists = self.historic_predictions_path.is_file()
if exists: if exists:
try:
with open(self.historic_predictions_path, "rb") as fp: with open(self.historic_predictions_path, "rb") as fp:
self.historic_predictions = cloudpickle.load(fp) self.historic_predictions = cloudpickle.load(fp)
logger.info( logger.info(
@@ -125,6 +127,13 @@ class FreqaiDataDrawer:
"that statistics may be inaccurate if the bot has been offline for " "that statistics may be inaccurate if the bot has been offline for "
"an extended period of time." "an extended period of time."
) )
except EOFError:
logger.warning(
'Historical prediction file was corrupted. Trying to load backup file.')
with open(self.historic_predictions_bkp_path, "rb") as fp:
self.historic_predictions = cloudpickle.load(fp)
logger.warning('FreqAI successfully loaded the backup historical predictions file.')
elif not self.follow_mode: elif not self.follow_mode:
logger.info("Could not find existing historic_predictions, starting from scratch") logger.info("Could not find existing historic_predictions, starting from scratch")
else: else:
@@ -142,6 +151,9 @@ class FreqaiDataDrawer:
with open(self.historic_predictions_path, "wb") as fp: with open(self.historic_predictions_path, "wb") as fp:
cloudpickle.dump(self.historic_predictions, fp, protocol=cloudpickle.DEFAULT_PROTOCOL) cloudpickle.dump(self.historic_predictions, fp, protocol=cloudpickle.DEFAULT_PROTOCOL)
# create a backup
shutil.copy(self.historic_predictions_path, self.historic_predictions_bkp_path)
def save_drawer_to_disk(self): def save_drawer_to_disk(self):
""" """
Save data drawer full of all pair model metadata in present model folder. Save data drawer full of all pair model metadata in present model folder.
@@ -203,7 +215,6 @@ class FreqaiDataDrawer:
self.pair_dict[pair] = self.empty_pair_dict.copy() self.pair_dict[pair] = self.empty_pair_dict.copy()
model_filename = "" model_filename = ""
trained_timestamp = 0 trained_timestamp = 0
self.pair_dict[pair]["priority"] = len(self.pair_dict)
if not data_path_set and self.follow_mode: if not data_path_set and self.follow_mode:
logger.warning( logger.warning(
@@ -223,18 +234,9 @@ class FreqaiDataDrawer:
return return
else: else:
self.pair_dict[metadata["pair"]] = self.empty_pair_dict.copy() self.pair_dict[metadata["pair"]] = self.empty_pair_dict.copy()
self.pair_dict[metadata["pair"]]["priority"] = len(self.pair_dict)
return return
def pair_to_end_of_training_queue(self, pair: str) -> None:
# march all pairs up in the queue
with self.pair_dict_lock:
for p in self.pair_dict:
self.pair_dict[p]["priority"] -= 1
# send pair to end of queue
self.pair_dict[pair]["priority"] = len(self.pair_dict)
def set_initial_return_values(self, pair: str, pred_df: DataFrame) -> None: def set_initial_return_values(self, pair: str, pred_df: DataFrame) -> None:
""" """
Set the initial return values to the historical predictions dataframe. This avoids needing Set the initial return values to the historical predictions dataframe. This avoids needing
@@ -311,6 +313,7 @@ class FreqaiDataDrawer:
""" """
dk.find_features(dataframe) dk.find_features(dataframe)
dk.find_labels(dataframe)
full_labels = dk.label_list + dk.unique_class_list full_labels = dk.label_list + dk.unique_class_list
@@ -342,7 +345,7 @@ class FreqaiDataDrawer:
for dir in model_folders: for dir in model_folders:
result = pattern.match(str(dir.name)) result = pattern.match(str(dir.name))
if result is None: if result is None:
break continue
coin = result.group(1) coin = result.group(1)
timestamp = result.group(2) timestamp = result.group(2)
@@ -374,7 +377,27 @@ class FreqaiDataDrawer:
if self.config.get("freqai", {}).get("purge_old_models", False): if self.config.get("freqai", {}).get("purge_old_models", False):
self.purge_old_models() self.purge_old_models()
# Functions pulled back from FreqaiDataKitchen because they relied on DataDrawer def save_metadata(self, dk: FreqaiDataKitchen) -> None:
"""
Saves only metadata for backtesting studies if user prefers
not to save model data. This saves tremendous amounts of space
for users generating huge studies.
This is only active when `save_backtest_models`: false (not default)
"""
if not dk.data_path.is_dir():
dk.data_path.mkdir(parents=True, exist_ok=True)
save_path = Path(dk.data_path)
dk.data["data_path"] = str(dk.data_path)
dk.data["model_filename"] = str(dk.model_filename)
dk.data["training_features_list"] = list(dk.data_dictionary["train_features"].columns)
dk.data["label_list"] = dk.label_list
with open(save_path / f"{dk.model_filename}_metadata.json", "w") as fp:
rapidjson.dump(dk.data, fp, default=self.np_encoder, number_mode=rapidjson.NM_NATIVE)
return
def save_data(self, model: Any, coin: str, dk: FreqaiDataKitchen) -> None: def save_data(self, model: Any, coin: str, dk: FreqaiDataKitchen) -> None:
""" """
@@ -400,7 +423,7 @@ class FreqaiDataDrawer:
dk.data["data_path"] = str(dk.data_path) dk.data["data_path"] = str(dk.data_path)
dk.data["model_filename"] = str(dk.model_filename) dk.data["model_filename"] = str(dk.model_filename)
dk.data["training_features_list"] = list(dk.data_dictionary["train_features"].columns) dk.data["training_features_list"] = dk.training_features_list
dk.data["label_list"] = dk.label_list dk.data["label_list"] = dk.label_list
# store the metadata # store the metadata
with open(save_path / f"{dk.model_filename}_metadata.json", "w") as fp: with open(save_path / f"{dk.model_filename}_metadata.json", "w") as fp:
@@ -421,13 +444,23 @@ class FreqaiDataDrawer:
) )
# if self.live: # if self.live:
self.model_dictionary[dk.model_filename] = model self.model_dictionary[coin] = model
self.pair_dict[coin]["model_filename"] = dk.model_filename self.pair_dict[coin]["model_filename"] = dk.model_filename
self.pair_dict[coin]["data_path"] = str(dk.data_path) self.pair_dict[coin]["data_path"] = str(dk.data_path)
self.save_drawer_to_disk() self.save_drawer_to_disk()
return return
def load_metadata(self, dk: FreqaiDataKitchen) -> None:
"""
Load only metadata into datakitchen to increase performance during
presaved backtesting (prediction file loading).
"""
with open(dk.data_path / f"{dk.model_filename}_metadata.json", "r") as fp:
dk.data = json.load(fp)
dk.training_features_list = dk.data["training_features_list"]
dk.label_list = dk.data["label_list"]
def load_data(self, coin: str, dk: FreqaiDataKitchen) -> Any: def load_data(self, coin: str, dk: FreqaiDataKitchen) -> Any:
""" """
loads all data required to make a prediction on a sub-train time range loads all data required to make a prediction on a sub-train time range
@@ -460,8 +493,8 @@ class FreqaiDataDrawer:
) )
# try to access model in memory instead of loading object from disk to save time # try to access model in memory instead of loading object from disk to save time
if dk.live and dk.model_filename in self.model_dictionary: if dk.live and coin in self.model_dictionary:
model = self.model_dictionary[dk.model_filename] model = self.model_dictionary[coin]
elif not dk.keras: elif not dk.keras:
model = load(dk.data_path / f"{dk.model_filename}_model.joblib") model = load(dk.data_path / f"{dk.model_filename}_model.joblib")
else: else:
@@ -566,7 +599,6 @@ class FreqaiDataDrawer:
for training according to user defined train_period_days for training according to user defined train_period_days
metadata: dict = strategy furnished pair metadata metadata: dict = strategy furnished pair metadata
""" """
with self.history_lock: with self.history_lock:
corr_dataframes: Dict[Any, Any] = {} corr_dataframes: Dict[Any, Any] = {}
base_dataframes: Dict[Any, Any] = {} base_dataframes: Dict[Any, Any] = {}

View File

@@ -1,7 +1,8 @@
import copy import copy
import datetime
import logging import logging
import shutil import shutil
from datetime import datetime, timezone
from math import cos, sin
from pathlib import Path from pathlib import Path
from typing import Any, Dict, List, Tuple from typing import Any, Dict, List, Tuple
@@ -9,6 +10,7 @@ import numpy as np
import numpy.typing as npt import numpy.typing as npt
import pandas as pd import pandas as pd
from pandas import DataFrame from pandas import DataFrame
from scipy import stats
from sklearn import linear_model from sklearn import linear_model
from sklearn.cluster import DBSCAN from sklearn.cluster import DBSCAN
from sklearn.metrics.pairwise import pairwise_distances from sklearn.metrics.pairwise import pairwise_distances
@@ -16,8 +18,7 @@ from sklearn.model_selection import train_test_split
from sklearn.neighbors import NearestNeighbors from sklearn.neighbors import NearestNeighbors
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.data.dataprovider import DataProvider from freqtrade.constants import Config
from freqtrade.data.history.history_utils import refresh_backtest_ohlcv_data
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_seconds from freqtrade.exchange import timeframe_to_seconds
from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.interface import IStrategy
@@ -57,7 +58,7 @@ class FreqaiDataKitchen:
def __init__( def __init__(
self, self,
config: Dict[str, Any], config: Config,
live: bool = False, live: bool = False,
pair: str = "", pair: str = "",
): ):
@@ -71,6 +72,8 @@ class FreqaiDataKitchen:
self.label_list: List = [] self.label_list: List = []
self.training_features_list: List = [] self.training_features_list: List = []
self.model_filename: str = "" self.model_filename: str = ""
self.backtesting_results_path = Path()
self.backtest_predictions_folder: str = "backtesting_predictions"
self.live = live self.live = live
self.pair = pair self.pair = pair
@@ -168,13 +171,21 @@ class FreqaiDataKitchen:
train_labels = labels train_labels = labels
train_weights = weights train_weights = weights
# Simplest way to reverse the order of training and test data:
if self.freqai_config['feature_parameters'].get('reverse_train_test_order', False):
return self.build_data_dictionary( return self.build_data_dictionary(
train_features, test_features, train_labels, test_labels, train_weights, test_weights test_features, train_features, test_labels,
train_labels, test_weights, train_weights
)
else:
return self.build_data_dictionary(
train_features, test_features, train_labels,
test_labels, train_weights, test_weights
) )
def filter_features( def filter_features(
self, self,
unfiltered_dataframe: DataFrame, unfiltered_df: DataFrame,
training_feature_list: List, training_feature_list: List,
label_list: List = list(), label_list: List = list(),
training_filter: bool = True, training_filter: bool = True,
@@ -185,31 +196,35 @@ class FreqaiDataKitchen:
0s in the prediction dataset. However, prediction dataset do_predict will reflect any 0s in the prediction dataset. However, prediction dataset do_predict will reflect any
row that had a NaN and will shield user from that prediction. row that had a NaN and will shield user from that prediction.
:params: :params:
:unfiltered_dataframe: the full dataframe for the present training period :unfiltered_df: the full dataframe for the present training period
:training_feature_list: list, the training feature list constructed by :training_feature_list: list, the training feature list constructed by
self.build_feature_list() according to user specified parameters in the configuration file. self.build_feature_list() according to user specified parameters in the configuration file.
:labels: the labels for the dataset :labels: the labels for the dataset
:training_filter: boolean which lets the function know if it is training data or :training_filter: boolean which lets the function know if it is training data or
prediction data to be filtered. prediction data to be filtered.
:returns: :returns:
:filtered_dataframe: dataframe cleaned of NaNs and only containing the user :filtered_df: dataframe cleaned of NaNs and only containing the user
requested feature set. requested feature set.
:labels: labels cleaned of NaNs. :labels: labels cleaned of NaNs.
""" """
filtered_dataframe = unfiltered_dataframe.filter(training_feature_list, axis=1) filtered_df = unfiltered_df.filter(training_feature_list, axis=1)
filtered_dataframe = filtered_dataframe.replace([np.inf, -np.inf], np.nan) filtered_df = filtered_df.replace([np.inf, -np.inf], np.nan)
drop_index = pd.isnull(filtered_dataframe).any(1) # get the rows that have NaNs, drop_index = pd.isnull(filtered_df).any(axis=1) # get the rows that have NaNs,
drop_index = drop_index.replace(True, 1).replace(False, 0) # pep8 requirement. drop_index = drop_index.replace(True, 1).replace(False, 0) # pep8 requirement.
if (training_filter): if (training_filter):
const_cols = list((filtered_df.nunique() == 1).loc[lambda x: x].index)
if const_cols:
filtered_df = filtered_df.filter(filtered_df.columns.difference(const_cols))
logger.warning(f"Removed features {const_cols} with constant values.")
# we don't care about total row number (total no. datapoints) in training, we only care # we don't care about total row number (total no. datapoints) in training, we only care
# about removing any row with NaNs # about removing any row with NaNs
# if labels has multiple columns (user wants to train multiple modelEs), we detect here # if labels has multiple columns (user wants to train multiple modelEs), we detect here
labels = unfiltered_dataframe.filter(label_list, axis=1) labels = unfiltered_df.filter(label_list, axis=1)
drop_index_labels = pd.isnull(labels).any(1) drop_index_labels = pd.isnull(labels).any(axis=1)
drop_index_labels = drop_index_labels.replace(True, 1).replace(False, 0) drop_index_labels = drop_index_labels.replace(True, 1).replace(False, 0)
dates = unfiltered_dataframe['date'] dates = unfiltered_df['date']
filtered_dataframe = filtered_dataframe[ filtered_df = filtered_df[
(drop_index == 0) & (drop_index_labels == 0) (drop_index == 0) & (drop_index_labels == 0)
] # dropping values ] # dropping values
labels = labels[ labels = labels[
@@ -219,13 +234,13 @@ class FreqaiDataKitchen:
(drop_index == 0) & (drop_index_labels == 0) (drop_index == 0) & (drop_index_labels == 0)
] ]
logger.info( logger.info(
f"dropped {len(unfiltered_dataframe) - len(filtered_dataframe)} training points" f"dropped {len(unfiltered_df) - len(filtered_df)} training points"
f" due to NaNs in populated dataset {len(unfiltered_dataframe)}." f" due to NaNs in populated dataset {len(unfiltered_df)}."
) )
if (1 - len(filtered_dataframe) / len(unfiltered_dataframe)) > 0.1 and self.live: if (1 - len(filtered_df) / len(unfiltered_df)) > 0.1 and self.live:
worst_indicator = str(unfiltered_dataframe.count().idxmin()) worst_indicator = str(unfiltered_df.count().idxmin())
logger.warning( logger.warning(
f" {(1 - len(filtered_dataframe)/len(unfiltered_dataframe)) * 100:.0f} percent " f" {(1 - len(filtered_df)/len(unfiltered_df)) * 100:.0f} percent "
" of training data dropped due to NaNs, model may perform inconsistent " " of training data dropped due to NaNs, model may perform inconsistent "
f"with expectations. Verify {worst_indicator}" f"with expectations. Verify {worst_indicator}"
) )
@@ -234,9 +249,9 @@ class FreqaiDataKitchen:
else: else:
# we are backtesting so we need to preserve row number to send back to strategy, # we are backtesting so we need to preserve row number to send back to strategy,
# so now we use do_predict to avoid any prediction based on a NaN # so now we use do_predict to avoid any prediction based on a NaN
drop_index = pd.isnull(filtered_dataframe).any(1) drop_index = pd.isnull(filtered_df).any(axis=1)
self.data["filter_drop_index_prediction"] = drop_index self.data["filter_drop_index_prediction"] = drop_index
filtered_dataframe.fillna(0, inplace=True) filtered_df.fillna(0, inplace=True)
# replacing all NaNs with zeros to avoid issues in 'prediction', but any prediction # replacing all NaNs with zeros to avoid issues in 'prediction', but any prediction
# that was based on a single NaN is ultimately protected from buys with do_predict # that was based on a single NaN is ultimately protected from buys with do_predict
drop_index = ~drop_index drop_index = ~drop_index
@@ -245,11 +260,11 @@ class FreqaiDataKitchen:
logger.info( logger.info(
"dropped %s of %s prediction data points due to NaNs.", "dropped %s of %s prediction data points due to NaNs.",
len(self.do_predict) - self.do_predict.sum(), len(self.do_predict) - self.do_predict.sum(),
len(filtered_dataframe), len(filtered_df),
) )
labels = [] labels = []
return filtered_dataframe, labels return filtered_df, labels
def build_data_dictionary( def build_data_dictionary(
self, self,
@@ -281,6 +296,7 @@ class FreqaiDataKitchen:
:returns: :returns:
:data_dictionary: updated dictionary with standardized values. :data_dictionary: updated dictionary with standardized values.
""" """
# standardize the data by training stats # standardize the data by training stats
train_max = data_dictionary["train_features"].max() train_max = data_dictionary["train_features"].max()
train_min = data_dictionary["train_features"].min() train_min = data_dictionary["train_features"].min()
@@ -314,10 +330,24 @@ class FreqaiDataKitchen:
- 1 - 1
) )
self.data[f"{item}_max"] = train_labels_max # .to_dict() self.data[f"{item}_max"] = train_labels_max
self.data[f"{item}_min"] = train_labels_min # .to_dict() self.data[f"{item}_min"] = train_labels_min
return data_dictionary return data_dictionary
def normalize_single_dataframe(self, df: DataFrame) -> DataFrame:
train_max = df.max()
train_min = df.min()
df = (
2 * (df - train_min) / (train_max - train_min) - 1
)
for item in train_max.keys():
self.data[item + "_max"] = train_max[item]
self.data[item + "_min"] = train_min[item]
return df
def normalize_data_from_metadata(self, df: DataFrame) -> DataFrame: def normalize_data_from_metadata(self, df: DataFrame) -> DataFrame:
""" """
Normalize a set of data using the mean and standard deviation from Normalize a set of data using the mean and standard deviation from
@@ -337,7 +367,7 @@ class FreqaiDataKitchen:
def denormalize_labels_from_metadata(self, df: DataFrame) -> DataFrame: def denormalize_labels_from_metadata(self, df: DataFrame) -> DataFrame:
""" """
Normalize a set of data using the mean and standard deviation from Denormalize a set of data using the mean and standard deviation from
the associated training data. the associated training data.
:param df: Dataframe of predictions to be denormalized :param df: Dataframe of predictions to be denormalized
""" """
@@ -376,7 +406,7 @@ class FreqaiDataKitchen:
config_timerange = TimeRange.parse_timerange(self.config["timerange"]) config_timerange = TimeRange.parse_timerange(self.config["timerange"])
if config_timerange.stopts == 0: if config_timerange.stopts == 0:
config_timerange.stopts = int( config_timerange.stopts = int(
datetime.datetime.now(tz=datetime.timezone.utc).timestamp() datetime.now(tz=timezone.utc).timestamp()
) )
timerange_train = copy.deepcopy(full_timerange) timerange_train = copy.deepcopy(full_timerange)
timerange_backtest = copy.deepcopy(full_timerange) timerange_backtest = copy.deepcopy(full_timerange)
@@ -393,8 +423,8 @@ class FreqaiDataKitchen:
timerange_train.stopts = timerange_train.startts + train_period_days timerange_train.stopts = timerange_train.startts + train_period_days
first = False first = False
start = datetime.datetime.utcfromtimestamp(timerange_train.startts) start = datetime.fromtimestamp(timerange_train.startts, tz=timezone.utc)
stop = datetime.datetime.utcfromtimestamp(timerange_train.stopts) stop = datetime.fromtimestamp(timerange_train.stopts, tz=timezone.utc)
tr_training_list.append(start.strftime("%Y%m%d") + "-" + stop.strftime("%Y%m%d")) tr_training_list.append(start.strftime("%Y%m%d") + "-" + stop.strftime("%Y%m%d"))
tr_training_list_timerange.append(copy.deepcopy(timerange_train)) tr_training_list_timerange.append(copy.deepcopy(timerange_train))
@@ -407,8 +437,8 @@ class FreqaiDataKitchen:
if timerange_backtest.stopts > config_timerange.stopts: if timerange_backtest.stopts > config_timerange.stopts:
timerange_backtest.stopts = config_timerange.stopts timerange_backtest.stopts = config_timerange.stopts
start = datetime.datetime.utcfromtimestamp(timerange_backtest.startts) start = datetime.fromtimestamp(timerange_backtest.startts, tz=timezone.utc)
stop = datetime.datetime.utcfromtimestamp(timerange_backtest.stopts) stop = datetime.fromtimestamp(timerange_backtest.stopts, tz=timezone.utc)
tr_backtesting_list.append(start.strftime("%Y%m%d") + "-" + stop.strftime("%Y%m%d")) tr_backtesting_list.append(start.strftime("%Y%m%d") + "-" + stop.strftime("%Y%m%d"))
tr_backtesting_list_timerange.append(copy.deepcopy(timerange_backtest)) tr_backtesting_list_timerange.append(copy.deepcopy(timerange_backtest))
@@ -428,10 +458,11 @@ class FreqaiDataKitchen:
it is sliced down to just the present training period. it is sliced down to just the present training period.
""" """
start = datetime.datetime.fromtimestamp(timerange.startts, tz=datetime.timezone.utc) start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc)
stop = datetime.datetime.fromtimestamp(timerange.stopts, tz=datetime.timezone.utc) stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc)
df = df.loc[df["date"] >= start, :] df = df.loc[df["date"] >= start, :]
df = df.loc[df["date"] <= stop, :] if not self.live:
df = df.loc[df["date"] < stop, :]
return df return df
@@ -444,23 +475,23 @@ class FreqaiDataKitchen:
from sklearn.decomposition import PCA # avoid importing if we dont need it from sklearn.decomposition import PCA # avoid importing if we dont need it
n_components = self.data_dictionary["train_features"].shape[1] pca = PCA(0.999)
pca = PCA(n_components=n_components)
pca = pca.fit(self.data_dictionary["train_features"]) pca = pca.fit(self.data_dictionary["train_features"])
n_keep_components = np.argmin(pca.explained_variance_ratio_.cumsum() < 0.999) n_keep_components = pca.n_components_
pca2 = PCA(n_components=n_keep_components)
self.data["n_kept_components"] = n_keep_components self.data["n_kept_components"] = n_keep_components
pca2 = pca2.fit(self.data_dictionary["train_features"]) n_components = self.data_dictionary["train_features"].shape[1]
logger.info("reduced feature dimension by %s", n_components - n_keep_components) logger.info("reduced feature dimension by %s", n_components - n_keep_components)
logger.info("explained variance %f", np.sum(pca2.explained_variance_ratio_)) logger.info("explained variance %f", np.sum(pca.explained_variance_ratio_))
train_components = pca2.transform(self.data_dictionary["train_features"])
test_components = pca2.transform(self.data_dictionary["test_features"])
train_components = pca.transform(self.data_dictionary["train_features"])
self.data_dictionary["train_features"] = pd.DataFrame( self.data_dictionary["train_features"] = pd.DataFrame(
data=train_components, data=train_components,
columns=["PC" + str(i) for i in range(0, n_keep_components)], columns=["PC" + str(i) for i in range(0, n_keep_components)],
index=self.data_dictionary["train_features"].index, index=self.data_dictionary["train_features"].index,
) )
# normalsing transformed training features
self.data_dictionary["train_features"] = self.normalize_single_dataframe(
self.data_dictionary["train_features"])
# keeping a copy of the non-transformed features so we can check for errors during # keeping a copy of the non-transformed features so we can check for errors during
# model load from disk # model load from disk
@@ -468,14 +499,18 @@ class FreqaiDataKitchen:
self.training_features_list = self.data_dictionary["train_features"].columns self.training_features_list = self.data_dictionary["train_features"].columns
if self.freqai_config.get('data_split_parameters', {}).get('test_size', 0.1) != 0: if self.freqai_config.get('data_split_parameters', {}).get('test_size', 0.1) != 0:
test_components = pca.transform(self.data_dictionary["test_features"])
self.data_dictionary["test_features"] = pd.DataFrame( self.data_dictionary["test_features"] = pd.DataFrame(
data=test_components, data=test_components,
columns=["PC" + str(i) for i in range(0, n_keep_components)], columns=["PC" + str(i) for i in range(0, n_keep_components)],
index=self.data_dictionary["test_features"].index, index=self.data_dictionary["test_features"].index,
) )
# normalise transformed test feature to transformed training features
self.data_dictionary["test_features"] = self.normalize_data_from_metadata(
self.data_dictionary["test_features"])
self.data["n_kept_components"] = n_keep_components self.data["n_kept_components"] = n_keep_components
self.pca = pca2 self.pca = pca
logger.info(f"PCA reduced total features from {n_components} to {n_keep_components}") logger.info(f"PCA reduced total features from {n_components} to {n_keep_components}")
@@ -496,6 +531,9 @@ class FreqaiDataKitchen:
columns=["PC" + str(i) for i in range(0, self.data["n_kept_components"])], columns=["PC" + str(i) for i in range(0, self.data["n_kept_components"])],
index=filtered_dataframe.index, index=filtered_dataframe.index,
) )
# normalise transformed predictions to transformed training features
self.data_dictionary["prediction_features"] = self.normalize_data_from_metadata(
self.data_dictionary["prediction_features"])
def compute_distances(self) -> float: def compute_distances(self) -> float:
""" """
@@ -506,10 +544,25 @@ class FreqaiDataKitchen:
# logger.info("computing average mean distance for all training points") # logger.info("computing average mean distance for all training points")
pairwise = pairwise_distances( pairwise = pairwise_distances(
self.data_dictionary["train_features"], n_jobs=self.thread_count) self.data_dictionary["train_features"], n_jobs=self.thread_count)
avg_mean_dist = pairwise.mean(axis=1).mean() # remove the diagonal distances which are itself distances ~0
np.fill_diagonal(pairwise, np.NaN)
pairwise = pairwise.reshape(-1, 1)
avg_mean_dist = pairwise[~np.isnan(pairwise)].mean()
return avg_mean_dist return avg_mean_dist
def get_outlier_percentage(self, dropped_pts: npt.NDArray) -> float:
"""
Check if more than X% of points werer dropped during outlier detection.
"""
outlier_protection_pct = self.freqai_config["feature_parameters"].get(
"outlier_protection_percentage", 30)
outlier_pct = (dropped_pts.sum() / len(dropped_pts)) * 100
if outlier_pct >= outlier_protection_pct:
return outlier_pct
else:
return 0.0
def use_SVM_to_remove_outliers(self, predict: bool) -> None: def use_SVM_to_remove_outliers(self, predict: bool) -> None:
""" """
Build/inference a Support Vector Machine to detect outliers Build/inference a Support Vector Machine to detect outliers
@@ -547,8 +600,17 @@ class FreqaiDataKitchen:
self.data_dictionary["train_features"] self.data_dictionary["train_features"]
) )
y_pred = self.svm_model.predict(self.data_dictionary["train_features"]) y_pred = self.svm_model.predict(self.data_dictionary["train_features"])
dropped_points = np.where(y_pred == -1, 0, y_pred) kept_points = np.where(y_pred == -1, 0, y_pred)
# keep_index = np.where(y_pred == 1) # keep_index = np.where(y_pred == 1)
outlier_pct = self.get_outlier_percentage(1 - kept_points)
if outlier_pct:
logger.warning(
f"SVM detected {outlier_pct:.2f}% of the points as outliers. "
f"Keeping original dataset."
)
self.svm_model = None
return
self.data_dictionary["train_features"] = self.data_dictionary["train_features"][ self.data_dictionary["train_features"] = self.data_dictionary["train_features"][
(y_pred == 1) (y_pred == 1)
] ]
@@ -560,7 +622,7 @@ class FreqaiDataKitchen:
] ]
logger.info( logger.info(
f"SVM tossed {len(y_pred) - dropped_points.sum()}" f"SVM tossed {len(y_pred) - kept_points.sum()}"
f" train points from {len(y_pred)} total points." f" train points from {len(y_pred)} total points."
) )
@@ -569,7 +631,7 @@ class FreqaiDataKitchen:
# to reduce code duplication # to reduce code duplication
if self.freqai_config['data_split_parameters'].get('test_size', 0.1) != 0: if self.freqai_config['data_split_parameters'].get('test_size', 0.1) != 0:
y_pred = self.svm_model.predict(self.data_dictionary["test_features"]) y_pred = self.svm_model.predict(self.data_dictionary["test_features"])
dropped_points = np.where(y_pred == -1, 0, y_pred) kept_points = np.where(y_pred == -1, 0, y_pred)
self.data_dictionary["test_features"] = self.data_dictionary["test_features"][ self.data_dictionary["test_features"] = self.data_dictionary["test_features"][
(y_pred == 1) (y_pred == 1)
] ]
@@ -580,7 +642,7 @@ class FreqaiDataKitchen:
] ]
logger.info( logger.info(
f"SVM tossed {len(y_pred) - dropped_points.sum()}" f"SVM tossed {len(y_pred) - kept_points.sum()}"
f" test points from {len(y_pred)} total points." f" test points from {len(y_pred)} total points."
) )
@@ -599,6 +661,8 @@ class FreqaiDataKitchen:
""" """
if predict: if predict:
if not self.data['DBSCAN_eps']:
return
train_ft_df = self.data_dictionary['train_features'] train_ft_df = self.data_dictionary['train_features']
pred_ft_df = self.data_dictionary['prediction_features'] pred_ft_df = self.data_dictionary['prediction_features']
num_preds = len(pred_ft_df) num_preds = len(pred_ft_df)
@@ -616,28 +680,61 @@ class FreqaiDataKitchen:
else: else:
MinPts = len(self.data_dictionary['train_features'].columns) * 2 def normalise_distances(distances):
# measure pairwise distances to train_features.shape[1]*2 nearest neighbours normalised_distances = (distances - distances.min()) / \
(distances.max() - distances.min())
return normalised_distances
def rotate_point(origin, point, angle):
# rotate a point counterclockwise by a given angle (in radians)
# around a given origin
x = origin[0] + cos(angle) * (point[0] - origin[0]) - \
sin(angle) * (point[1] - origin[1])
y = origin[1] + sin(angle) * (point[0] - origin[0]) + \
cos(angle) * (point[1] - origin[1])
return (x, y)
MinPts = int(len(self.data_dictionary['train_features'].index) * 0.25)
# measure pairwise distances to nearest neighbours
neighbors = NearestNeighbors( neighbors = NearestNeighbors(
n_neighbors=MinPts, n_jobs=self.thread_count) n_neighbors=MinPts, n_jobs=self.thread_count)
neighbors_fit = neighbors.fit(self.data_dictionary['train_features']) neighbors_fit = neighbors.fit(self.data_dictionary['train_features'])
distances, _ = neighbors_fit.kneighbors(self.data_dictionary['train_features']) distances, _ = neighbors_fit.kneighbors(self.data_dictionary['train_features'])
distances = np.sort(distances, axis=0) distances = np.sort(distances, axis=0).mean(axis=1)
index_ten_pct = int(len(distances[:, 1]) * 0.1)
distances = distances[index_ten_pct:, 1] normalised_distances = normalise_distances(distances)
epsilon = distances[-1] x_range = np.linspace(0, 1, len(distances))
line = np.linspace(normalised_distances[0],
normalised_distances[-1], len(normalised_distances))
deflection = np.abs(normalised_distances - line)
max_deflection_loc = np.where(deflection == deflection.max())[0][0]
origin = x_range[max_deflection_loc], line[max_deflection_loc]
point = x_range[max_deflection_loc], normalised_distances[max_deflection_loc]
rot_angle = np.pi / 4
elbow_loc = rotate_point(origin, point, rot_angle)
epsilon = elbow_loc[1] * (distances[-1] - distances[0]) + distances[0]
clustering = DBSCAN(eps=epsilon, min_samples=MinPts, clustering = DBSCAN(eps=epsilon, min_samples=MinPts,
n_jobs=int(self.thread_count)).fit( n_jobs=int(self.thread_count)).fit(
self.data_dictionary['train_features'] self.data_dictionary['train_features']
) )
logger.info(f'DBSCAN found eps of {epsilon}.') logger.info(f'DBSCAN found eps of {epsilon:.2f}.')
self.data['DBSCAN_eps'] = epsilon self.data['DBSCAN_eps'] = epsilon
self.data['DBSCAN_min_samples'] = MinPts self.data['DBSCAN_min_samples'] = MinPts
dropped_points = np.where(clustering.labels_ == -1, 1, 0) dropped_points = np.where(clustering.labels_ == -1, 1, 0)
outlier_pct = self.get_outlier_percentage(dropped_points)
if outlier_pct:
logger.warning(
f"DBSCAN detected {outlier_pct:.2f}% of the points as outliers. "
f"Keeping original dataset."
)
self.data['DBSCAN_eps'] = 0
return
self.data_dictionary['train_features'] = self.data_dictionary['train_features'][ self.data_dictionary['train_features'] = self.data_dictionary['train_features'][
(clustering.labels_ != -1) (clustering.labels_ != -1)
] ]
@@ -657,16 +754,23 @@ class FreqaiDataKitchen:
def compute_inlier_metric(self, set_='train') -> None: def compute_inlier_metric(self, set_='train') -> None:
""" """
Compute inlier metric from backwards distance distributions. Compute inlier metric from backwards distance distributions.
This metric defines how well features from a timepoint fit This metric defines how well features from a timepoint fit
into previous timepoints. into previous timepoints.
""" """
import scipy.stats as ss def normalise(dataframe: DataFrame, key: str) -> DataFrame:
if set_ == 'train':
min_value = dataframe.min()
max_value = dataframe.max()
self.data[f'{key}_min'] = min_value
self.data[f'{key}_max'] = max_value
else:
min_value = self.data[f'{key}_min']
max_value = self.data[f'{key}_max']
return (dataframe - min_value) / (max_value - min_value)
no_prev_pts = self.freqai_config["feature_parameters"]["inlier_metric_window"] no_prev_pts = self.freqai_config["feature_parameters"]["inlier_metric_window"]
weib_pct = self.freqai_config["feature_parameters"]["inlier_metric_weibull_cutoff"]
if set_ == 'train': if set_ == 'train':
compute_df = copy.deepcopy(self.data_dictionary['train_features']) compute_df = copy.deepcopy(self.data_dictionary['train_features'])
@@ -704,19 +808,22 @@ class FreqaiDataKitchen:
:, :no_prev_pts :, :no_prev_pts
] ]
distances = distances.replace([np.inf, -np.inf], np.nan) distances = distances.replace([np.inf, -np.inf], np.nan)
drop_index = pd.isnull(distances).any(1) drop_index = pd.isnull(distances).any(axis=1)
distances = distances[drop_index == 0] distances = distances[drop_index == 0]
inliers = pd.DataFrame(index=distances.index) inliers = pd.DataFrame(index=distances.index)
for key in distances.keys(): for key in distances.keys():
current_distances = distances[key].dropna() current_distances = distances[key].dropna()
fit_params = ss.weibull_min.fit(current_distances) current_distances = normalise(current_distances, key)
cutoff = ss.weibull_min.ppf(weib_pct, *fit_params) if set_ == 'train':
is_inlier = np.where( fit_params = stats.weibull_min.fit(current_distances)
current_distances <= cutoff, 1, 0 self.data[f'{key}_fit_params'] = fit_params
) else:
fit_params = self.data[f'{key}_fit_params']
quantiles = stats.weibull_min.cdf(current_distances, *fit_params)
df_inlier = pd.DataFrame( df_inlier = pd.DataFrame(
{key + '_IsInlier': is_inlier}, index=distances.index {key: quantiles}, index=distances.index
) )
inliers = pd.concat( inliers = pd.concat(
[inliers, df_inlier], axis=1 [inliers, df_inlier], axis=1
@@ -724,12 +831,12 @@ class FreqaiDataKitchen:
inlier_metric = pd.DataFrame( inlier_metric = pd.DataFrame(
data=inliers.sum(axis=1) / no_prev_pts, data=inliers.sum(axis=1) / no_prev_pts,
columns=['inlier_metric'], columns=['%-inlier_metric'],
index=compute_df.index index=compute_df.index
) )
inlier_metric = 2 * (inlier_metric - inlier_metric.min()) / \ inlier_metric = (2 * (inlier_metric - inlier_metric.min()) /
(inlier_metric.max() - inlier_metric.min()) - 1 (inlier_metric.max() - inlier_metric.min()) - 1)
if set_ in ('train', 'test'): if set_ in ('train', 'test'):
inlier_metric = inlier_metric.iloc[no_prev_pts:] inlier_metric = inlier_metric.iloc[no_prev_pts:]
@@ -742,6 +849,8 @@ class FreqaiDataKitchen:
[compute_df, inlier_metric], axis=1) [compute_df, inlier_metric], axis=1)
self.data_dictionary['prediction_features'].fillna(0, inplace=True) self.data_dictionary['prediction_features'].fillna(0, inplace=True)
logger.info('Inlier metric computed and added to features.')
return None return None
def remove_beginning_points_from_data_dict(self, set_='train', no_prev_pts: int = 10): def remove_beginning_points_from_data_dict(self, set_='train', no_prev_pts: int = 10):
@@ -772,11 +881,15 @@ class FreqaiDataKitchen:
""" """
column_names = dataframe.columns column_names = dataframe.columns
features = [c for c in column_names if "%" in c] features = [c for c in column_names if "%" in c]
labels = [c for c in column_names if "&" in c]
if not features: if not features:
raise OperationalException("Could not find any features!") raise OperationalException("Could not find any features!")
self.training_features_list = features self.training_features_list = features
def find_labels(self, dataframe: DataFrame) -> None:
column_names = dataframe.columns
labels = [c for c in column_names if "&" in c]
self.label_list = labels self.label_list = labels
def check_if_pred_in_training_spaces(self) -> None: def check_if_pred_in_training_spaces(self) -> None:
@@ -803,8 +916,8 @@ class FreqaiDataKitchen:
if (len(do_predict) - do_predict.sum()) > 0: if (len(do_predict) - do_predict.sum()) > 0:
logger.info( logger.info(
f"DI tossed {len(do_predict) - do_predict.sum():.2f} predictions for " f"DI tossed {len(do_predict) - do_predict.sum()} predictions for "
"being too far from training data" "being too far from training data."
) )
self.do_predict += do_predict self.do_predict += do_predict
@@ -819,9 +932,10 @@ class FreqaiDataKitchen:
weights = np.exp(-np.arange(num_weights) / (wfactor * num_weights))[::-1] weights = np.exp(-np.arange(num_weights) / (wfactor * num_weights))[::-1]
return weights return weights
def append_predictions(self, predictions: DataFrame, do_predict: npt.ArrayLike) -> None: def get_predictions_to_append(self, predictions: DataFrame,
do_predict: npt.ArrayLike) -> DataFrame:
""" """
Append backtest prediction from current backtest period to all previous periods Get backtest prediction from current backtest period
""" """
append_df = DataFrame() append_df = DataFrame()
@@ -836,13 +950,18 @@ class FreqaiDataKitchen:
if self.freqai_config["feature_parameters"].get("DI_threshold", 0) > 0: if self.freqai_config["feature_parameters"].get("DI_threshold", 0) > 0:
append_df["DI_values"] = self.DI_values append_df["DI_values"] = self.DI_values
return append_df
def append_predictions(self, append_df: DataFrame) -> None:
"""
Append backtest prediction from current backtest period to all previous periods
"""
if self.full_df.empty: if self.full_df.empty:
self.full_df = append_df self.full_df = append_df
else: else:
self.full_df = pd.concat([self.full_df, append_df], axis=0) self.full_df = pd.concat([self.full_df, append_df], axis=0)
return
def fill_predictions(self, dataframe): def fill_predictions(self, dataframe):
""" """
Back fill values to before the backtesting range so that the dataframe matches size Back fill values to before the backtesting range so that the dataframe matches size
@@ -858,7 +977,6 @@ class FreqaiDataKitchen:
to_keep = [col for col in dataframe.columns if not col.startswith("&")] to_keep = [col for col in dataframe.columns if not col.startswith("&")]
self.return_dataframe = pd.concat([dataframe[to_keep], self.full_df], axis=1) self.return_dataframe = pd.concat([dataframe[to_keep], self.full_df], axis=1)
self.full_df = DataFrame() self.full_df = DataFrame()
return return
@@ -882,14 +1000,14 @@ class FreqaiDataKitchen:
"Please indicate the end date of your desired backtesting. " "Please indicate the end date of your desired backtesting. "
"timerange.") "timerange.")
# backtest_timerange.stopts = int( # backtest_timerange.stopts = int(
# datetime.datetime.now(tz=datetime.timezone.utc).timestamp() # datetime.now(tz=timezone.utc).timestamp()
# ) # )
backtest_timerange.startts = ( backtest_timerange.startts = (
backtest_timerange.startts - backtest_period_days * SECONDS_IN_DAY backtest_timerange.startts - backtest_period_days * SECONDS_IN_DAY
) )
start = datetime.datetime.utcfromtimestamp(backtest_timerange.startts) start = datetime.fromtimestamp(backtest_timerange.startts, tz=timezone.utc)
stop = datetime.datetime.utcfromtimestamp(backtest_timerange.stopts) stop = datetime.fromtimestamp(backtest_timerange.stopts, tz=timezone.utc)
full_timerange = start.strftime("%Y%m%d") + "-" + stop.strftime("%Y%m%d") full_timerange = start.strftime("%Y%m%d") + "-" + stop.strftime("%Y%m%d")
self.full_path = Path( self.full_path = Path(
@@ -915,7 +1033,7 @@ class FreqaiDataKitchen:
:return: :return:
bool = If the model is expired or not. bool = If the model is expired or not.
""" """
time = datetime.datetime.now(tz=datetime.timezone.utc).timestamp() time = datetime.now(tz=timezone.utc).timestamp()
elapsed_time = (time - trained_timestamp) / 3600 # hours elapsed_time = (time - trained_timestamp) / 3600 # hours
max_time = self.freqai_config.get("expiration_hours", 0) max_time = self.freqai_config.get("expiration_hours", 0)
if max_time > 0: if max_time > 0:
@@ -927,7 +1045,7 @@ class FreqaiDataKitchen:
self, trained_timestamp: int self, trained_timestamp: int
) -> Tuple[bool, TimeRange, TimeRange]: ) -> Tuple[bool, TimeRange, TimeRange]:
time = datetime.datetime.now(tz=datetime.timezone.utc).timestamp() time = datetime.now(tz=timezone.utc).timestamp()
trained_timerange = TimeRange() trained_timerange = TimeRange()
data_load_timerange = TimeRange() data_load_timerange = TimeRange()
@@ -942,9 +1060,7 @@ class FreqaiDataKitchen:
# We notice that users like to use exotic indicators where # We notice that users like to use exotic indicators where
# they do not know the required timeperiod. Here we include a factor # they do not know the required timeperiod. Here we include a factor
# of safety by multiplying the user considered "max" by 2. # of safety by multiplying the user considered "max" by 2.
max_period = self.freqai_config["feature_parameters"].get( max_period = self.config.get('startup_candle_count', 20) * 2
"indicator_max_period_candles", 20
) * 2
additional_seconds = max_period * max_tf_seconds additional_seconds = max_period * max_tf_seconds
if trained_timestamp != 0: if trained_timestamp != 0:
@@ -978,13 +1094,6 @@ class FreqaiDataKitchen:
data_load_timerange.stopts = int(time) data_load_timerange.stopts = int(time)
retrain = True retrain = True
# logger.info(
# f"downloading data for "
# f"{(data_load_timerange.stopts-data_load_timerange.startts)/SECONDS_IN_DAY:.2f} "
# " days. "
# f"Extension of {additional_seconds/SECONDS_IN_DAY:.2f} days"
# )
return retrain, trained_timerange, data_load_timerange return retrain, trained_timerange, data_load_timerange
def set_new_model_names(self, pair: str, trained_timerange: TimeRange): def set_new_model_names(self, pair: str, trained_timerange: TimeRange):
@@ -997,31 +1106,6 @@ class FreqaiDataKitchen:
self.model_filename = f"cb_{coin.lower()}_{int(trained_timerange.stopts)}" self.model_filename = f"cb_{coin.lower()}_{int(trained_timerange.stopts)}"
def download_all_data_for_training(self, timerange: TimeRange, dp: DataProvider) -> None:
"""
Called only once upon start of bot to download the necessary data for
populating indicators and training the model.
:param timerange: TimeRange = The full data timerange for populating the indicators
and training the model.
:param dp: DataProvider instance attached to the strategy
"""
new_pairs_days = int((timerange.stopts - timerange.startts) / SECONDS_IN_DAY)
if not dp._exchange:
# Not realistic - this is only called in live mode.
raise OperationalException("Dataprovider did not have an exchange attached.")
refresh_backtest_ohlcv_data(
dp._exchange,
pairs=self.all_pairs,
timeframes=self.freqai_config["feature_parameters"].get("include_timeframes"),
datadir=self.config["datadir"],
timerange=timerange,
new_pairs_days=new_pairs_days,
erase=False,
data_format=self.config.get("dataformat_ohlcv", "json"),
trading_mode=self.config.get("trading_mode", "spot"),
prepend=self.config.get("prepend_data", False),
)
def set_all_pairs(self) -> None: def set_all_pairs(self) -> None:
self.all_pairs = copy.deepcopy( self.all_pairs = copy.deepcopy(
@@ -1126,7 +1210,8 @@ class FreqaiDataKitchen:
def get_unique_classes_from_labels(self, dataframe: DataFrame) -> None: def get_unique_classes_from_labels(self, dataframe: DataFrame) -> None:
self.find_features(dataframe) # self.find_features(dataframe)
self.find_labels(dataframe)
for key in self.label_list: for key in self.label_list:
if dataframe[key].dtype == object: if dataframe[key].dtype == object:
@@ -1135,3 +1220,48 @@ class FreqaiDataKitchen:
if self.unique_classes: if self.unique_classes:
for label in self.unique_classes: for label in self.unique_classes:
self.unique_class_list += list(self.unique_classes[label]) self.unique_class_list += list(self.unique_classes[label])
def save_backtesting_prediction(
self, append_df: DataFrame
) -> None:
"""
Save prediction dataframe from backtesting to h5 file format
:param append_df: dataframe for backtesting period
"""
full_predictions_folder = Path(self.full_path / self.backtest_predictions_folder)
if not full_predictions_folder.is_dir():
full_predictions_folder.mkdir(parents=True, exist_ok=True)
append_df.to_hdf(self.backtesting_results_path, key='append_df', mode='w')
def get_backtesting_prediction(
self
) -> DataFrame:
"""
Get prediction dataframe from h5 file format
"""
append_df = pd.read_hdf(self.backtesting_results_path)
return append_df
def check_if_backtest_prediction_exists(
self
) -> bool:
"""
Check if a backtesting prediction already exists
:param dk: FreqaiDataKitchen
:return:
:boolean: whether the prediction file exists or not.
"""
path_to_predictionfile = Path(self.full_path /
self.backtest_predictions_folder /
f"{self.model_filename}_prediction.h5")
self.backtesting_results_path = path_to_predictionfile
file_exists = path_to_predictionfile.is_file()
if file_exists:
logger.info(f"Found backtesting prediction file at {path_to_predictionfile}")
else:
logger.info(
f"Could not find backtesting prediction file at {path_to_predictionfile}"
)
return file_exists

View File

@@ -1,13 +1,13 @@
# import contextlib
import datetime
import logging import logging
import shutil import shutil
import threading import threading
import time import time
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from collections import deque
from datetime import datetime, timezone
from pathlib import Path from pathlib import Path
from threading import Lock from threading import Lock
from typing import Any, Dict, Tuple from typing import Any, Dict, List, Tuple
import numpy as np import numpy as np
import pandas as pd import pandas as pd
@@ -15,11 +15,13 @@ from numpy.typing import NDArray
from pandas import DataFrame from pandas import DataFrame
from freqtrade.configuration import TimeRange from freqtrade.configuration import TimeRange
from freqtrade.constants import DATETIME_PRINT_FORMAT, Config
from freqtrade.enums import RunMode from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_seconds from freqtrade.exchange import timeframe_to_seconds
from freqtrade.freqai.data_drawer import FreqaiDataDrawer from freqtrade.freqai.data_drawer import FreqaiDataDrawer
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
from freqtrade.freqai.utils import plot_feature_importance
from freqtrade.strategy.interface import IStrategy from freqtrade.strategy.interface import IStrategy
@@ -27,13 +29,6 @@ pd.options.mode.chained_assignment = None
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs).start()
return wrapper
class IFreqaiModel(ABC): class IFreqaiModel(ABC):
""" """
Class containing all tools for training and prediction in the strategy. Class containing all tools for training and prediction in the strategy.
@@ -57,7 +52,7 @@ class IFreqaiModel(ABC):
Juha Nykänen @suikula, Wagner Costa @wagnercosta, Johan Vlugt @Jooopieeert Juha Nykänen @suikula, Wagner Costa @wagnercosta, Johan Vlugt @Jooopieeert
""" """
def __init__(self, config: Dict[str, Any]) -> None: def __init__(self, config: Config) -> None:
self.config = config self.config = config
self.assert_config(self.config) self.assert_config(self.config)
@@ -70,6 +65,9 @@ class IFreqaiModel(ABC):
self.first = True self.first = True
self.set_full_path() self.set_full_path()
self.follow_mode: bool = self.freqai_info.get("follow_mode", False) self.follow_mode: bool = self.freqai_info.get("follow_mode", False)
self.save_backtest_models: bool = self.freqai_info.get("save_backtest_models", True)
if self.save_backtest_models:
logger.info('Backtesting module configured to save all models.')
self.dd = FreqaiDataDrawer(Path(self.full_path), self.config, self.follow_mode) self.dd = FreqaiDataDrawer(Path(self.full_path), self.config, self.follow_mode)
self.identifier: str = self.freqai_info.get("identifier", "no_id_provided") self.identifier: str = self.freqai_info.get("identifier", "no_id_provided")
self.scanning = False self.scanning = False
@@ -82,15 +80,30 @@ class IFreqaiModel(ABC):
if self.ft_params.get("inlier_metric_window", 0): if self.ft_params.get("inlier_metric_window", 0):
self.CONV_WIDTH = self.ft_params.get("inlier_metric_window", 0) * 2 self.CONV_WIDTH = self.ft_params.get("inlier_metric_window", 0) * 2
self.pair_it = 0 self.pair_it = 0
self.pair_it_train = 0
self.total_pairs = len(self.config.get("exchange", {}).get("pair_whitelist")) self.total_pairs = len(self.config.get("exchange", {}).get("pair_whitelist"))
self.train_queue = self._set_train_queue()
self.last_trade_database_summary: DataFrame = {} self.last_trade_database_summary: DataFrame = {}
self.current_trade_database_summary: DataFrame = {} self.current_trade_database_summary: DataFrame = {}
self.analysis_lock = Lock() self.analysis_lock = Lock()
self.inference_time: float = 0 self.inference_time: float = 0
self.train_time: float = 0
self.begin_time: float = 0 self.begin_time: float = 0
self.begin_time_train: float = 0
self.base_tf_seconds = timeframe_to_seconds(self.config['timeframe']) self.base_tf_seconds = timeframe_to_seconds(self.config['timeframe'])
self.continual_learning = self.freqai_info.get('continual_learning', False)
self.plot_features = self.ft_params.get("plot_feature_importances", 0)
def assert_config(self, config: Dict[str, Any]) -> None: self._threads: List[threading.Thread] = []
self._stop_event = threading.Event()
def __getstate__(self):
"""
Return an empty state to be pickled in hyperopt
"""
return ({})
def assert_config(self, config: Config) -> None:
if not config.get("freqai", {}): if not config.get("freqai", {}):
raise OperationalException("No freqai parameters found in configuration file.") raise OperationalException("No freqai parameters found in configuration file.")
@@ -123,34 +136,65 @@ class IFreqaiModel(ABC):
elif not self.follow_mode: elif not self.follow_mode:
self.dk = FreqaiDataKitchen(self.config, self.live, metadata["pair"]) self.dk = FreqaiDataKitchen(self.config, self.live, metadata["pair"])
logger.info(f"Training {len(self.dk.training_timeranges)} timeranges") logger.info(f"Training {len(self.dk.training_timeranges)} timeranges")
with self.analysis_lock:
dataframe = self.dk.use_strategy_to_populate_indicators( dataframe = self.dk.use_strategy_to_populate_indicators(
strategy, prediction_dataframe=dataframe, pair=metadata["pair"] strategy, prediction_dataframe=dataframe, pair=metadata["pair"]
) )
dk = self.start_backtesting(dataframe, metadata, self.dk) dk = self.start_backtesting(dataframe, metadata, self.dk)
dataframe = dk.remove_features_from_df(dk.return_dataframe) dataframe = dk.remove_features_from_df(dk.return_dataframe)
del dk self.clean_up()
if self.live: if self.live:
self.inference_timer('stop') self.inference_timer('stop')
return dataframe return dataframe
@threaded def clean_up(self):
def start_scanning(self, strategy: IStrategy) -> None: """
Objects that should be handled by GC already between coins, but
are explicitly shown here to help demonstrate the non-persistence of these
objects.
"""
self.model = None
self.dk = None
def shutdown(self):
"""
Cleans up threads on Shutdown, set stop event. Join threads to wait
for current training iteration.
"""
logger.info("Stopping FreqAI")
self._stop_event.set()
logger.info("Waiting on Training iteration")
for _thread in self._threads:
_thread.join()
def start_scanning(self, *args, **kwargs) -> None:
"""
Start `self._start_scanning` in a separate thread
"""
_thread = threading.Thread(target=self._start_scanning, args=args, kwargs=kwargs)
self._threads.append(_thread)
_thread.start()
def _start_scanning(self, strategy: IStrategy) -> None:
""" """
Function designed to constantly scan pairs for retraining on a separate thread (intracandle) Function designed to constantly scan pairs for retraining on a separate thread (intracandle)
to improve model youth. This function is agnostic to data preparation/collection/storage, to improve model youth. This function is agnostic to data preparation/collection/storage,
it simply trains on what ever data is available in the self.dd. it simply trains on what ever data is available in the self.dd.
:param strategy: IStrategy = The user defined strategy class :param strategy: IStrategy = The user defined strategy class
""" """
while 1: while not self._stop_event.is_set():
time.sleep(1) time.sleep(1)
for pair in self.config.get("exchange", {}).get("pair_whitelist"): pair = self.train_queue[0]
# ensure pair is avaialble in dp
if pair not in strategy.dp.current_whitelist():
self.train_queue.popleft()
logger.warning(f'{pair} not in current whitelist, removing from train queue.')
continue
(_, trained_timestamp, _) = self.dd.get_pair_dict_info(pair) (_, trained_timestamp, _) = self.dd.get_pair_dict_info(pair)
if self.dd.pair_dict[pair]["priority"] != 1:
continue
dk = FreqaiDataKitchen(self.config, self.live, pair) dk = FreqaiDataKitchen(self.config, self.live, pair)
dk.set_paths(pair, trained_timestamp) dk.set_paths(pair, trained_timestamp)
( (
@@ -161,9 +205,18 @@ class IFreqaiModel(ABC):
dk.set_paths(pair, new_trained_timerange.stopts) dk.set_paths(pair, new_trained_timerange.stopts)
if retrain: if retrain:
self.train_model_in_series( self.train_timer('start')
try:
self.extract_data_and_train_model(
new_trained_timerange, pair, strategy, dk, data_load_timerange new_trained_timerange, pair, strategy, dk, data_load_timerange
) )
except Exception as msg:
logger.warning(f'Training {pair} raised exception {msg}, skipping.')
self.train_timer('stop')
# only rotate the queue after the first has been trained.
self.train_queue.rotate(-1)
self.dd.save_historic_predictions_to_disk() self.dd.save_historic_predictions_to_disk()
@@ -192,7 +245,8 @@ class IFreqaiModel(ABC):
# following tr_train. Both of these windows slide through the # following tr_train. Both of these windows slide through the
# entire backtest # entire backtest
for tr_train, tr_backtest in zip(dk.training_timeranges, dk.backtesting_timeranges): for tr_train, tr_backtest in zip(dk.training_timeranges, dk.backtesting_timeranges):
(_, _, _) = self.dd.get_pair_dict_info(metadata["pair"]) pair = metadata["pair"]
(_, _, _) = self.dd.get_pair_dict_info(pair)
train_it += 1 train_it += 1
total_trains = len(dk.backtesting_timeranges) total_trains = len(dk.backtesting_timeranges)
self.training_timerange = tr_train self.training_timerange = tr_train
@@ -200,40 +254,53 @@ class IFreqaiModel(ABC):
dataframe_backtest = dk.slice_dataframe(tr_backtest, dataframe) dataframe_backtest = dk.slice_dataframe(tr_backtest, dataframe)
trained_timestamp = tr_train trained_timestamp = tr_train
tr_train_startts_str = datetime.datetime.utcfromtimestamp(tr_train.startts).strftime( tr_train_startts_str = datetime.fromtimestamp(
"%Y-%m-%d %H:%M:%S" tr_train.startts,
) tz=timezone.utc).strftime(DATETIME_PRINT_FORMAT)
tr_train_stopts_str = datetime.datetime.utcfromtimestamp(tr_train.stopts).strftime( tr_train_stopts_str = datetime.fromtimestamp(
"%Y-%m-%d %H:%M:%S" tr_train.stopts,
) tz=timezone.utc).strftime(DATETIME_PRINT_FORMAT)
logger.info( logger.info(
f"Training {metadata['pair']}, {self.pair_it}/{self.total_pairs} pairs" f"Training {pair}, {self.pair_it}/{self.total_pairs} pairs"
f" from {tr_train_startts_str} to {tr_train_stopts_str}, {train_it}/{total_trains} " f" from {tr_train_startts_str} to {tr_train_stopts_str}, {train_it}/{total_trains} "
"trains" "trains"
) )
trained_timestamp_int = int(trained_timestamp.stopts)
dk.data_path = Path( dk.data_path = Path(
dk.full_path dk.full_path / f"sub-train-{pair.split('/')[0]}_{trained_timestamp_int}"
/
f"sub-train-{metadata['pair'].split('/')[0]}_{int(trained_timestamp.stopts)}"
) )
if not self.model_exists(
metadata["pair"], dk, trained_timestamp=int(trained_timestamp.stopts)
):
dk.find_features(dataframe_train)
self.model = self.train(dataframe_train, metadata["pair"], dk)
self.dd.pair_dict[metadata["pair"]]["trained_timestamp"] = int(
trained_timestamp.stopts)
dk.set_new_model_names(metadata["pair"], trained_timestamp)
self.dd.save_data(self.model, metadata["pair"], dk)
else:
self.model = self.dd.load_data(metadata["pair"], dk)
self.check_if_feature_list_matches_strategy(dataframe_train, dk) dk.set_new_model_names(pair, trained_timestamp)
if dk.check_if_backtest_prediction_exists():
self.dd.load_metadata(dk)
dk.find_features(dataframe_train)
self.check_if_feature_list_matches_strategy(dk)
append_df = dk.get_backtesting_prediction()
dk.append_predictions(append_df)
else:
if not self.model_exists(dk):
dk.find_features(dataframe_train)
dk.find_labels(dataframe_train)
self.model = self.train(dataframe_train, pair, dk)
self.dd.pair_dict[pair]["trained_timestamp"] = int(
trained_timestamp.stopts)
if self.plot_features:
plot_feature_importance(self.model, pair, dk, self.plot_features)
if self.save_backtest_models:
logger.info('Saving backtest model to disk.')
self.dd.save_data(self.model, pair, dk)
else:
logger.info('Saving metadata to disk.')
self.dd.save_metadata(dk)
else:
self.model = self.dd.load_data(pair, dk)
pred_df, do_preds = self.predict(dataframe_backtest, dk) pred_df, do_preds = self.predict(dataframe_backtest, dk)
append_df = dk.get_predictions_to_append(pred_df, do_preds)
dk.append_predictions(pred_df, do_preds) dk.append_predictions(append_df)
dk.save_backtesting_prediction(append_df)
dk.fill_predictions(dataframe) dk.fill_predictions(dataframe)
@@ -278,14 +345,8 @@ class IFreqaiModel(ABC):
) )
dk.set_paths(metadata["pair"], new_trained_timerange.stopts) dk.set_paths(metadata["pair"], new_trained_timerange.stopts)
# download candle history if it is not already in memory # load candle history into memory if it is not yet.
if not self.dd.historic_data: if not self.dd.historic_data:
logger.info(
"Downloading all training data for all pairs in whitelist and "
"corr_pairlist, this may take a while if you do not have the "
"data saved"
)
dk.download_all_data_for_training(data_load_timerange, strategy.dp)
self.dd.load_all_pair_histories(data_load_timerange, dk) self.dd.load_all_pair_histories(data_load_timerange, dk)
if not self.scanning: if not self.scanning:
@@ -314,8 +375,7 @@ class IFreqaiModel(ABC):
self.dd.return_null_values_to_strategy(dataframe, dk) self.dd.return_null_values_to_strategy(dataframe, dk)
return dk return dk
# ensure user is feeding the correct indicators to the model dk.find_labels(dataframe)
self.check_if_feature_list_matches_strategy(dataframe, dk)
self.build_strategy_return_arrays(dataframe, dk, metadata["pair"], trained_timestamp) self.build_strategy_return_arrays(dataframe, dk, metadata["pair"], trained_timestamp)
@@ -360,7 +420,7 @@ class IFreqaiModel(ABC):
return return
def check_if_feature_list_matches_strategy( def check_if_feature_list_matches_strategy(
self, dataframe: DataFrame, dk: FreqaiDataKitchen self, dk: FreqaiDataKitchen
) -> None: ) -> None:
""" """
Ensure user is passing the proper feature set if they are reusing an `identifier` pointing Ensure user is passing the proper feature set if they are reusing an `identifier` pointing
@@ -369,18 +429,21 @@ class IFreqaiModel(ABC):
:param dk: FreqaiDataKitchen = non-persistent data container/analyzer for :param dk: FreqaiDataKitchen = non-persistent data container/analyzer for
current coin/bot loop current coin/bot loop
""" """
dk.find_features(dataframe)
if "training_features_list_raw" in dk.data: if "training_features_list_raw" in dk.data:
feature_list = dk.data["training_features_list_raw"] feature_list = dk.data["training_features_list_raw"]
else: else:
feature_list = dk.training_features_list feature_list = dk.data['training_features_list']
if dk.training_features_list != feature_list: if dk.training_features_list != feature_list:
raise OperationalException( raise OperationalException(
"Trying to access pretrained model with `identifier` " "Trying to access pretrained model with `identifier` "
"but found different features furnished by current strategy." "but found different features furnished by current strategy."
"Change `identifier` to train from scratch, or ensure the" "Change `identifier` to train from scratch, or ensure the"
"strategy is furnishing the same features as the pretrained" "strategy is furnishing the same features as the pretrained"
"model" "model. In case of --strategy-list, please be aware that FreqAI "
"requires all strategies to maintain identical "
"populate_any_indicator() functions"
) )
def data_cleaning_train(self, dk: FreqaiDataKitchen) -> None: def data_cleaning_train(self, dk: FreqaiDataKitchen) -> None:
@@ -392,6 +455,11 @@ class IFreqaiModel(ABC):
ft_params = self.freqai_info["feature_parameters"] ft_params = self.freqai_info["feature_parameters"]
if ft_params.get('inlier_metric_window', 0):
dk.compute_inlier_metric(set_='train')
if self.freqai_info["data_split_parameters"]["test_size"] > 0:
dk.compute_inlier_metric(set_='test')
if ft_params.get( if ft_params.get(
"principal_component_analysis", False "principal_component_analysis", False
): ):
@@ -411,28 +479,26 @@ class IFreqaiModel(ABC):
dk.use_DBSCAN_to_remove_outliers(predict=False, eps=eps) dk.use_DBSCAN_to_remove_outliers(predict=False, eps=eps)
self.dd.old_DBSCAN_eps[dk.pair] = dk.data['DBSCAN_eps'] self.dd.old_DBSCAN_eps[dk.pair] = dk.data['DBSCAN_eps']
if ft_params.get('inlier_metric_window', 0):
dk.compute_inlier_metric(set_='train')
if self.freqai_info["data_split_parameters"]["test_size"] > 0:
dk.compute_inlier_metric(set_='test')
if self.freqai_info["feature_parameters"].get('noise_standard_deviation', 0): if self.freqai_info["feature_parameters"].get('noise_standard_deviation', 0):
dk.add_noise_to_training_features() dk.add_noise_to_training_features()
def data_cleaning_predict(self, dk: FreqaiDataKitchen, dataframe: DataFrame) -> None: def data_cleaning_predict(self, dk: FreqaiDataKitchen) -> None:
""" """
Base data cleaning method for predict. Base data cleaning method for predict.
Functions here are complementary to the functions of data_cleaning_train. Functions here are complementary to the functions of data_cleaning_train.
""" """
ft_params = self.freqai_info["feature_parameters"] ft_params = self.freqai_info["feature_parameters"]
# ensure user is feeding the correct indicators to the model
self.check_if_feature_list_matches_strategy(dk)
if ft_params.get('inlier_metric_window', 0): if ft_params.get('inlier_metric_window', 0):
dk.compute_inlier_metric(set_='predict') dk.compute_inlier_metric(set_='predict')
if ft_params.get( if ft_params.get(
"principal_component_analysis", False "principal_component_analysis", False
): ):
dk.pca_transform(dataframe) dk.pca_transform(dk.data_dictionary['prediction_features'])
if ft_params.get("use_SVM_to_remove_outliers", False): if ft_params.get("use_SVM_to_remove_outliers", False):
dk.use_SVM_to_remove_outliers(predict=True) dk.use_SVM_to_remove_outliers(predict=True)
@@ -443,14 +509,7 @@ class IFreqaiModel(ABC):
if ft_params.get("use_DBSCAN_to_remove_outliers", False): if ft_params.get("use_DBSCAN_to_remove_outliers", False):
dk.use_DBSCAN_to_remove_outliers(predict=True) dk.use_DBSCAN_to_remove_outliers(predict=True)
def model_exists( def model_exists(self, dk: FreqaiDataKitchen) -> bool:
self,
pair: str,
dk: FreqaiDataKitchen,
trained_timestamp: int = None,
model_filename: str = "",
scanning: bool = False,
) -> bool:
""" """
Given a pair and path, check if a model already exists Given a pair and path, check if a model already exists
:param pair: pair e.g. BTC/USD :param pair: pair e.g. BTC/USD
@@ -458,16 +517,11 @@ class IFreqaiModel(ABC):
:return: :return:
:boolean: whether the model file exists or not. :boolean: whether the model file exists or not.
""" """
coin, _ = pair.split("/") path_to_modelfile = Path(dk.data_path / f"{dk.model_filename}_model.joblib")
if not self.live:
dk.model_filename = model_filename = f"cb_{coin.lower()}_{trained_timestamp}"
path_to_modelfile = Path(dk.data_path / f"{model_filename}_model.joblib")
file_exists = path_to_modelfile.is_file() file_exists = path_to_modelfile.is_file()
if file_exists and not scanning: if file_exists:
logger.info("Found model at %s", dk.data_path / dk.model_filename) logger.info("Found model at %s", dk.data_path / dk.model_filename)
elif not scanning: else:
logger.info("Could not find model at %s", dk.data_path / dk.model_filename) logger.info("Could not find model at %s", dk.data_path / dk.model_filename)
return file_exists return file_exists
@@ -481,7 +535,7 @@ class IFreqaiModel(ABC):
Path(self.full_path, Path(self.config["config_files"][0]).name), Path(self.full_path, Path(self.config["config_files"][0]).name),
) )
def train_model_in_series( def extract_data_and_train_model(
self, self,
new_trained_timerange: TimeRange, new_trained_timerange: TimeRange,
pair: str, pair: str,
@@ -490,8 +544,7 @@ class IFreqaiModel(ABC):
data_load_timerange: TimeRange, data_load_timerange: TimeRange,
): ):
""" """
Retrieve data and train model in single threaded mode (only used if model directory is empty Retrieve data and train model.
upon startup for dry/live )
:param new_trained_timerange: TimeRange = the timerange to train the model on :param new_trained_timerange: TimeRange = the timerange to train the model on
:param metadata: dict = strategy provided metadata :param metadata: dict = strategy provided metadata
:param strategy: IStrategy = user defined strategy object :param strategy: IStrategy = user defined strategy object
@@ -515,16 +568,17 @@ class IFreqaiModel(ABC):
# find the features indicated by strategy and store in datakitchen # find the features indicated by strategy and store in datakitchen
dk.find_features(unfiltered_dataframe) dk.find_features(unfiltered_dataframe)
dk.find_labels(unfiltered_dataframe)
model = self.train(unfiltered_dataframe, pair, dk) model = self.train(unfiltered_dataframe, pair, dk)
self.dd.pair_dict[pair]["trained_timestamp"] = new_trained_timerange.stopts self.dd.pair_dict[pair]["trained_timestamp"] = new_trained_timerange.stopts
dk.set_new_model_names(pair, new_trained_timerange) dk.set_new_model_names(pair, new_trained_timerange)
self.dd.pair_dict[pair]["first"] = False
if self.dd.pair_dict[pair]["priority"] == 1 and self.scanning:
self.dd.pair_to_end_of_training_queue(pair)
self.dd.save_data(model, pair, dk) self.dd.save_data(model, pair, dk)
if self.plot_features:
plot_feature_importance(model, pair, dk, self.plot_features)
if self.freqai_info.get("purge_old_models", False): if self.freqai_info.get("purge_old_models", False):
self.dd.purge_old_models() self.dd.purge_old_models()
@@ -574,7 +628,7 @@ class IFreqaiModel(ABC):
# # for keras type models, the conv_window needs to be prepended so # # for keras type models, the conv_window needs to be prepended so
# # viewing is correct in frequi # # viewing is correct in frequi
if self.freqai_info.get('keras', False): if self.freqai_info.get('keras', False) or self.ft_params.get('inlier_metric_window', 0):
n_lost_points = self.freqai_info.get('conv_width', 2) n_lost_points = self.freqai_info.get('conv_width', 2)
zeros_df = DataFrame(np.zeros((n_lost_points, len(hist_preds_df.columns))), zeros_df = DataFrame(np.zeros((n_lost_points, len(hist_preds_df.columns))),
columns=hist_preds_df.columns) columns=hist_preds_df.columns)
@@ -616,27 +670,80 @@ class IFreqaiModel(ABC):
logger.info( logger.info(
f'Total time spent inferencing pairlist {self.inference_time:.2f} seconds') f'Total time spent inferencing pairlist {self.inference_time:.2f} seconds')
if self.inference_time > 0.25 * self.base_tf_seconds: if self.inference_time > 0.25 * self.base_tf_seconds:
logger.warning('Inference took over 25/% of the candle time. Reduce pairlist to' logger.warning("Inference took over 25% of the candle time. Reduce pairlist to"
' avoid blinding open trades and degrading performance.') " avoid blinding open trades and degrading performance.")
self.pair_it = 0 self.pair_it = 0
self.inference_time = 0 self.inference_time = 0
return return
def train_timer(self, do='start'):
"""
Timer designed to track the cumulative time spent training the full pairlist in
FreqAI.
"""
if do == 'start':
self.pair_it_train += 1
self.begin_time_train = time.time()
elif do == 'stop':
end = time.time()
self.train_time += (end - self.begin_time_train)
if self.pair_it_train == self.total_pairs:
logger.info(
f'Total time spent training pairlist {self.train_time:.2f} seconds')
self.pair_it_train = 0
self.train_time = 0
return
def get_init_model(self, pair: str) -> Any:
if pair not in self.dd.model_dictionary or not self.continual_learning:
init_model = None
else:
init_model = self.dd.model_dictionary[pair]
return init_model
def _set_train_queue(self):
"""
Sets train queue from existing train timestamps if they exist
otherwise it sets the train queue based on the provided whitelist.
"""
current_pairlist = self.config.get("exchange", {}).get("pair_whitelist")
if not self.dd.pair_dict:
logger.info('Set fresh train queue from whitelist. '
f'Queue: {current_pairlist}')
return deque(current_pairlist)
best_queue = deque()
pair_dict_sorted = sorted(self.dd.pair_dict.items(),
key=lambda k: k[1]['trained_timestamp'])
for pair in pair_dict_sorted:
if pair[0] in current_pairlist:
best_queue.append(pair[0])
for pair in current_pairlist:
if pair not in best_queue:
best_queue.appendleft(pair)
logger.info('Set existing queue from trained timestamps. '
f'Best approximation queue: {best_queue}')
return best_queue
# Following methods which are overridden by user made prediction models. # Following methods which are overridden by user made prediction models.
# See freqai/prediction_models/CatboostPredictionModel.py for an example. # See freqai/prediction_models/CatboostPredictionModel.py for an example.
@abstractmethod @abstractmethod
def train(self, unfiltered_dataframe: DataFrame, pair: str, dk: FreqaiDataKitchen) -> Any: def train(self, unfiltered_df: DataFrame, pair: str,
dk: FreqaiDataKitchen, **kwargs) -> Any:
""" """
Filter the training data and train a model to it. Train makes heavy use of the datahandler Filter the training data and train a model to it. Train makes heavy use of the datahandler
for storing, saving, loading, and analyzing the data. for storing, saving, loading, and analyzing the data.
:param unfiltered_dataframe: Full dataframe for the current training period :param unfiltered_df: Full dataframe for the current training period
:param metadata: pair metadata from strategy. :param metadata: pair metadata from strategy.
:return: Trained model which can be used to inference (self.predict) :return: Trained model which can be used to inference (self.predict)
""" """
@abstractmethod @abstractmethod
def fit(self, data_dictionary: Dict[str, Any]) -> Any: def fit(self, data_dictionary: Dict[str, Any], dk: FreqaiDataKitchen, **kwargs) -> Any:
""" """
Most regressors use the same function names and arguments e.g. user Most regressors use the same function names and arguments e.g. user
can drop in LGBMRegressor in place of CatBoostRegressor and all data can drop in LGBMRegressor in place of CatBoostRegressor and all data
@@ -649,11 +756,11 @@ class IFreqaiModel(ABC):
@abstractmethod @abstractmethod
def predict( def predict(
self, dataframe: DataFrame, dk: FreqaiDataKitchen, first: bool = True self, unfiltered_df: DataFrame, dk: FreqaiDataKitchen, **kwargs
) -> Tuple[DataFrame, NDArray[np.int_]]: ) -> Tuple[DataFrame, NDArray[np.int_]]:
""" """
Filter the prediction features data and predict with it. Filter the prediction features data and predict with it.
:param unfiltered_dataframe: Full dataframe for the current backtest period. :param unfiltered_df: Full dataframe for the current backtest period.
:param dk: FreqaiDataKitchen = Data management/analysis tool associated to present pair only :param dk: FreqaiDataKitchen = Data management/analysis tool associated to present pair only
:param first: boolean = whether this is the first prediction or not. :param first: boolean = whether this is the first prediction or not.
:return: :return:

View File

@@ -3,7 +3,8 @@ from typing import Any, Dict
from catboost import CatBoostClassifier, Pool from catboost import CatBoostClassifier, Pool
from freqtrade.freqai.prediction_models.BaseClassifierModel import BaseClassifierModel from freqtrade.freqai.base_models.BaseClassifierModel import BaseClassifierModel
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -16,7 +17,7 @@ class CatboostClassifier(BaseClassifierModel):
has its own DataHandler where data is held, saved, loaded, and managed. has its own DataHandler where data is held, saved, loaded, and managed.
""" """
def fit(self, data_dictionary: Dict) -> Any: def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
""" """
User sets up the training and test data to fit their desired model here User sets up the training and test data to fit their desired model here
:params: :params:
@@ -36,6 +37,8 @@ class CatboostClassifier(BaseClassifierModel):
**self.model_training_parameters, **self.model_training_parameters,
) )
cbr.fit(train_data) init_model = self.get_init_model(dk.pair)
cbr.fit(train_data, init_model=init_model)
return cbr return cbr

View File

@@ -1,10 +1,10 @@
import gc
import logging import logging
from typing import Any, Dict from typing import Any, Dict
from catboost import CatBoostRegressor, Pool from catboost import CatBoostRegressor, Pool
from freqtrade.freqai.prediction_models.BaseRegressionModel import BaseRegressionModel from freqtrade.freqai.base_models.BaseRegressionModel import BaseRegressionModel
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -17,7 +17,7 @@ class CatboostRegressor(BaseRegressionModel):
has its own DataHandler where data is held, saved, loaded, and managed. has its own DataHandler where data is held, saved, loaded, and managed.
""" """
def fit(self, data_dictionary: Dict) -> Any: def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
""" """
User sets up the training and test data to fit their desired model here User sets up the training and test data to fit their desired model here
:param data_dictionary: the dictionary constructed by DataHandler to hold :param data_dictionary: the dictionary constructed by DataHandler to hold
@@ -38,16 +38,13 @@ class CatboostRegressor(BaseRegressionModel):
weight=data_dictionary["test_weights"], weight=data_dictionary["test_weights"],
) )
init_model = self.get_init_model(dk.pair)
model = CatBoostRegressor( model = CatBoostRegressor(
allow_writing_files=False, allow_writing_files=False,
**self.model_training_parameters, **self.model_training_parameters,
) )
model.fit(X=train_data, eval_set=test_data) model.fit(X=train_data, eval_set=test_data, init_model=init_model)
# some evidence that catboost pools have memory leaks:
# https://github.com/catboost/catboost/issues/1835
del train_data, test_data
gc.collect()
return model return model

View File

@@ -1,10 +1,11 @@
import logging import logging
from typing import Any, Dict from typing import Any, Dict
from catboost import CatBoostRegressor # , Pool from catboost import CatBoostRegressor, Pool
from sklearn.multioutput import MultiOutputRegressor
from freqtrade.freqai.prediction_models.BaseRegressionModel import BaseRegressionModel from freqtrade.freqai.base_models.BaseRegressionModel import BaseRegressionModel
from freqtrade.freqai.base_models.FreqaiMultiOutputRegressor import FreqaiMultiOutputRegressor
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -17,7 +18,7 @@ class CatboostRegressorMultiTarget(BaseRegressionModel):
has its own DataHandler where data is held, saved, loaded, and managed. has its own DataHandler where data is held, saved, loaded, and managed.
""" """
def fit(self, data_dictionary: Dict) -> Any: def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
""" """
User sets up the training and test data to fit their desired model here User sets up the training and test data to fit their desired model here
:param data_dictionary: the dictionary constructed by DataHandler to hold :param data_dictionary: the dictionary constructed by DataHandler to hold
@@ -31,14 +32,37 @@ class CatboostRegressorMultiTarget(BaseRegressionModel):
X = data_dictionary["train_features"] X = data_dictionary["train_features"]
y = data_dictionary["train_labels"] y = data_dictionary["train_labels"]
eval_set = (data_dictionary["test_features"], data_dictionary["test_labels"])
sample_weight = data_dictionary["train_weights"] sample_weight = data_dictionary["train_weights"]
model = MultiOutputRegressor(estimator=cbr) eval_sets = [None] * y.shape[1]
model.fit(X=X, y=y, sample_weight=sample_weight) # , eval_set=eval_set)
if self.freqai_info.get('data_split_parameters', {}).get('test_size', 0.1) != 0: if self.freqai_info.get('data_split_parameters', {}).get('test_size', 0.1) != 0:
train_score = model.score(X, y) eval_sets = [None] * data_dictionary['test_labels'].shape[1]
test_score = model.score(*eval_set)
logger.info(f"Train score {train_score}, Test score {test_score}") for i in range(data_dictionary['test_labels'].shape[1]):
eval_sets[i] = Pool(
data=data_dictionary["test_features"],
label=data_dictionary["test_labels"].iloc[:, i],
weight=data_dictionary["test_weights"],
)
init_model = self.get_init_model(dk.pair)
if init_model:
init_models = init_model.estimators_
else:
init_models = [None] * y.shape[1]
fit_params = []
for i in range(len(eval_sets)):
fit_params.append(
{'eval_set': eval_sets[i], 'init_model': init_models[i]})
model = FreqaiMultiOutputRegressor(estimator=cbr)
thread_training = self.freqai_info.get('multitarget_parallel_training', False)
if thread_training:
model.n_jobs = y.shape[1]
model.fit(X=X, y=y, sample_weight=sample_weight, fit_params=fit_params)
return model return model

View File

@@ -3,7 +3,8 @@ from typing import Any, Dict
from lightgbm import LGBMClassifier from lightgbm import LGBMClassifier
from freqtrade.freqai.prediction_models.BaseClassifierModel import BaseClassifierModel from freqtrade.freqai.base_models.BaseClassifierModel import BaseClassifierModel
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -16,7 +17,7 @@ class LightGBMClassifier(BaseClassifierModel):
has its own DataHandler where data is held, saved, loaded, and managed. has its own DataHandler where data is held, saved, loaded, and managed.
""" """
def fit(self, data_dictionary: Dict) -> Any: def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
""" """
User sets up the training and test data to fit their desired model here User sets up the training and test data to fit their desired model here
:params: :params:
@@ -35,9 +36,11 @@ class LightGBMClassifier(BaseClassifierModel):
y = data_dictionary["train_labels"].to_numpy()[:, 0] y = data_dictionary["train_labels"].to_numpy()[:, 0]
train_weights = data_dictionary["train_weights"] train_weights = data_dictionary["train_weights"]
init_model = self.get_init_model(dk.pair)
model = LGBMClassifier(**self.model_training_parameters) model = LGBMClassifier(**self.model_training_parameters)
model.fit(X=X, y=y, eval_set=eval_set, sample_weight=train_weights, model.fit(X=X, y=y, eval_set=eval_set, sample_weight=train_weights,
eval_sample_weight=[test_weights]) eval_sample_weight=[test_weights], init_model=init_model)
return model return model

View File

@@ -3,7 +3,8 @@ from typing import Any, Dict
from lightgbm import LGBMRegressor from lightgbm import LGBMRegressor
from freqtrade.freqai.prediction_models.BaseRegressionModel import BaseRegressionModel from freqtrade.freqai.base_models.BaseRegressionModel import BaseRegressionModel
from freqtrade.freqai.data_kitchen import FreqaiDataKitchen
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -16,7 +17,7 @@ class LightGBMRegressor(BaseRegressionModel):
has its own DataHandler where data is held, saved, loaded, and managed. has its own DataHandler where data is held, saved, loaded, and managed.
""" """
def fit(self, data_dictionary: Dict) -> Any: def fit(self, data_dictionary: Dict, dk: FreqaiDataKitchen, **kwargs) -> Any:
""" """
Most regressors use the same function names and arguments e.g. user Most regressors use the same function names and arguments e.g. user
can drop in LGBMRegressor in place of CatBoostRegressor and all data can drop in LGBMRegressor in place of CatBoostRegressor and all data
@@ -35,9 +36,11 @@ class LightGBMRegressor(BaseRegressionModel):
y = data_dictionary["train_labels"] y = data_dictionary["train_labels"]
train_weights = data_dictionary["train_weights"] train_weights = data_dictionary["train_weights"]
init_model = self.get_init_model(dk.pair)
model = LGBMRegressor(**self.model_training_parameters) model = LGBMRegressor(**self.model_training_parameters)
model.fit(X=X, y=y, eval_set=eval_set, sample_weight=train_weights, model.fit(X=X, y=y, eval_set=eval_set, sample_weight=train_weights,
eval_sample_weight=[eval_weights]) eval_sample_weight=[eval_weights], init_model=init_model)
return model return model

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