Compare commits

..

800 Commits

Author SHA1 Message Date
Matthias
38b96f071f Merge pull request #4434 from freqtrade/new_release
New release 2020.2
2021-02-24 19:27:43 +01:00
Matthias
834f00f580 Refresh slack link 2021-02-24 06:46:07 +01:00
Matthias
aea8f05d10 Version bump 2021.2 2021-02-24 06:39:59 +01:00
Matthias
cae67b02df Merge branch 'stable' into new_release 2021-02-24 06:39:51 +01:00
Matthias
d6d8678fd6 Fix missleading FAQ information 2021-02-24 06:34:10 +01:00
Matthias
133562ba06 Merge pull request #4428 from freqtrade/dependabot/pip/develop/ccxt-1.42.19
Bump ccxt from 1.41.90 to 1.42.19
2021-02-22 22:01:38 +01:00
Matthias
e8794e8b8c Merge pull request #4429 from freqtrade/dependabot/pip/develop/tabulate-0.8.9
Bump tabulate from 0.8.8 to 0.8.9
2021-02-22 21:59:44 +01:00
dependabot[bot]
3612c786b5 Bump tabulate from 0.8.8 to 0.8.9
Bumps [tabulate](https://github.com/astanin/python-tabulate) from 0.8.8 to 0.8.9.
- [Release notes](https://github.com/astanin/python-tabulate/releases)
- [Changelog](https://github.com/astanin/python-tabulate/blob/master/CHANGELOG)
- [Commits](https://github.com/astanin/python-tabulate/compare/v0.8.8...v0.8.9)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-22 19:06:39 +00:00
dependabot[bot]
a0fa1e84fc Bump ccxt from 1.41.90 to 1.42.19
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.41.90 to 1.42.19.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.41.90...1.42.19)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-22 19:06:36 +00:00
Matthias
c71ecd3680 Fix wrong pair-content in strategy-analysis notebook and documentation
closes #4399
2021-02-22 20:04:36 +01:00
Matthias
2b5f1ff256 Merge pull request #4426 from freqtrade/fix/4405
Don't fail API calls when live price is not available
2021-02-22 14:09:20 +01:00
Matthias
228e51b60b Fix #4405 2021-02-22 13:34:52 +01:00
Matthias
5e4730b73b Add test confirming #4405 2021-02-22 11:44:39 +01:00
Matthias
62885166a9 Merge pull request #4424 from freqtrade/dependabot/docker/python-3.9.2-slim-buster
Bump python from 3.9.1-slim-buster to 3.9.2-slim-buster
2021-02-22 08:43:54 +01:00
Matthias
21933a55f7 Merge pull request #4423 from freqtrade/dependabot/pip/develop/cryptography-3.4.6
Bump cryptography from 3.4.5 to 3.4.6
2021-02-22 08:23:19 +01:00
Matthias
8a62bfa0e5 armhf image should not be updated to python3.9 2021-02-22 08:20:45 +01:00
Matthias
8ffeafd2c3 Merge pull request #4422 from freqtrade/dependabot/pip/develop/scipy-1.6.1
Bump scipy from 1.6.0 to 1.6.1
2021-02-22 08:19:12 +01:00
Matthias
e34d8cba0e Merge pull request #4421 from freqtrade/dependabot/pip/develop/uvicorn-0.13.4
Bump uvicorn from 0.13.3 to 0.13.4
2021-02-22 08:02:16 +01:00
dependabot[bot]
d8c7e5ce8d Bump python from 3.9.1-slim-buster to 3.9.2-slim-buster
Bumps python from 3.9.1-slim-buster to 3.9.2-slim-buster.

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-22 05:56:25 +00:00
Matthias
6feabd51a2 Merge pull request #4418 from freqtrade/dependabot/pip/develop/python-telegram-bot-13.3
Bump python-telegram-bot from 13.2 to 13.3
2021-02-22 06:48:25 +01:00
Matthias
d7cc86735b Merge pull request #4417 from freqtrade/dependabot/pip/develop/tabulate-0.8.8
Bump tabulate from 0.8.7 to 0.8.8
2021-02-22 06:47:57 +01:00
dependabot[bot]
85f12f8c28 Bump cryptography from 3.4.5 to 3.4.6
Bumps [cryptography](https://github.com/pyca/cryptography) from 3.4.5 to 3.4.6.
- [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/3.4.5...3.4.6)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-22 05:27:57 +00:00
dependabot[bot]
dea04c6452 Bump scipy from 1.6.0 to 1.6.1
Bumps [scipy](https://github.com/scipy/scipy) from 1.6.0 to 1.6.1.
- [Release notes](https://github.com/scipy/scipy/releases)
- [Commits](https://github.com/scipy/scipy/compare/v1.6.0...v1.6.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-22 05:27:54 +00:00
dependabot[bot]
932aabd012 Bump uvicorn from 0.13.3 to 0.13.4
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.13.3 to 0.13.4.
- [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.13.3...0.13.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-22 05:27:51 +00:00
dependabot[bot]
8c398acc09 Bump python-telegram-bot from 13.2 to 13.3
Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 13.2 to 13.3.
- [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases)
- [Changelog](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/CHANGES.rst)
- [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v13.2...v13.3)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-22 05:27:42 +00:00
dependabot[bot]
ab74c6e771 Bump tabulate from 0.8.7 to 0.8.8
Bumps [tabulate](https://github.com/astanin/python-tabulate) from 0.8.7 to 0.8.8.
- [Release notes](https://github.com/astanin/python-tabulate/releases)
- [Changelog](https://github.com/astanin/python-tabulate/blob/master/CHANGELOG)
- [Commits](https://github.com/astanin/python-tabulate/compare/v0.8.7...v0.8.8)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-22 05:27:41 +00:00
Matthias
95fcb1eb27 Merge pull request #4415 from The-smooth-operator/develop
Fix example in storing-information docs
2021-02-21 19:34:11 +01:00
Alberto del Barrio
188d7aaf8c Fix example in storing-information docs 2021-02-21 18:50:11 +01:00
Matthias
3629892fc3 Stoploss-guard should use the trade_limit or more
fix #4404
2021-02-20 19:38:44 +01:00
Matthias
245e39e523 dry-run should be a bool, not a string 2021-02-20 19:17:26 +01:00
Matthias
4e5f8478b1 Merge pull request #4394 from JoeSchr/develop
fix(doc/plotting): misplaced comma in example code
2021-02-18 17:43:53 +01:00
JoeSchr
c9688f1c89 fix(doc/plotting): misplaced comma in example code 2021-02-18 17:30:29 +01:00
Matthias
2b0d2070d0 Avoid crash with /delete
When a trade is deleted between querying the database and actually
handling the trade.

closes #4326
2021-02-18 12:49:14 +01:00
Matthias
327c23618f Improve documentation for get_analyzed_dataframe 2021-02-18 09:30:35 +01:00
Matthias
b5a9ce2894 Download data in the right format as well ...
closes #4393
2021-02-18 09:26:35 +01:00
Matthias
87dc1d3955 Explicitly push tag and tag_plot images 2021-02-17 20:52:25 +01:00
Matthias
fedbb5c0c4 Remove last flask occurance from setup.py
fixes #4390
2021-02-17 20:47:11 +01:00
Matthias
11b20d6932 Add config to hyperopt_loss_function documentation 2021-02-17 07:04:29 +01:00
Matthias
eff0d46ea1 Merge pull request #4375 from flomerz/pass_processed_data
pass data and config to loss function
2021-02-16 20:06:50 +01:00
Matthias
009a447d8a Adjust documentation for new parameter in loss functions 2021-02-16 19:51:09 +01:00
Florian Merz
3e06cd8b3a pass data and config to loss function 2021-02-16 10:11:33 +01:00
Matthias
a97a5a7ca8 Merge pull request #4372 from freqtrade/dependabot/pip/develop/cryptography-3.4.5
Bump cryptography from 3.3.2 to 3.4.5
2021-02-15 14:24:37 +01:00
dependabot[bot]
bc188907b8 Bump cryptography from 3.3.2 to 3.4.5
Bumps [cryptography](https://github.com/pyca/cryptography) from 3.3.2 to 3.4.5.
- [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/3.3.2...3.4.5)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-15 08:46:46 +00:00
Matthias
eab1d298bc Merge pull request #4374 from freqtrade/dependabot/pip/develop/ccxt-1.41.90
Bump ccxt from 1.41.70 to 1.41.90
2021-02-15 09:45:44 +01:00
dependabot[bot]
5f25139348 Bump ccxt from 1.41.70 to 1.41.90
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.41.70 to 1.41.90.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.41.70...1.41.90)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-15 08:18:04 +00:00
Matthias
1a3e7191ed Merge pull request #4116 from squat0001/develop-squat
Develop squat
2021-02-15 07:15:11 +01:00
Matthias
833e2768e6 Merge pull request #4371 from freqtrade/dependabot/pip/develop/prompt-toolkit-3.0.16
Bump prompt-toolkit from 3.0.14 to 3.0.16
2021-02-15 06:59:37 +01:00
Matthias
86fdc3016c Merge pull request #4369 from freqtrade/dependabot/pip/develop/joblib-1.0.1
Bump joblib from 1.0.0 to 1.0.1
2021-02-15 06:46:49 +01:00
Matthias
4503fd0790 Merge pull request #4370 from freqtrade/dependabot/pip/develop/pandas-1.2.2
Bump pandas from 1.2.1 to 1.2.2
2021-02-15 06:45:29 +01:00
dependabot[bot]
dbef5425c5 Bump prompt-toolkit from 3.0.14 to 3.0.16
Bumps [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) from 3.0.14 to 3.0.16.
- [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.14...3.0.16)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-15 05:27:12 +00:00
dependabot[bot]
d08572ea0d Bump pandas from 1.2.1 to 1.2.2
Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.2.1 to 1.2.2.
- [Release notes](https://github.com/pandas-dev/pandas/releases)
- [Changelog](https://github.com/pandas-dev/pandas/blob/master/RELEASE.md)
- [Commits](https://github.com/pandas-dev/pandas/compare/v1.2.1...v1.2.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-15 05:27:05 +00:00
dependabot[bot]
44cb206688 Bump joblib from 1.0.0 to 1.0.1
Bumps [joblib](https://github.com/joblib/joblib) from 1.0.0 to 1.0.1.
- [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.0.0...1.0.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-15 05:27:01 +00:00
Matthias
1a166f639d Add test for calcuate_csum 2021-02-14 19:44:13 +01:00
Florian Reitmeir
5c263c7ffd add backtesting results abs profit min/abs profit max, to get a better view if a strategy has a enough money to succeed 2021-02-14 19:41:12 +01:00
Matthias
f82dd55153 Merge pull request #4367 from freqtrade/fix/4181
ohlcv_candle_limit per timeframe
2021-02-14 19:32:05 +01:00
Matthias
ee74bc1f52 timeframe is mandatory, no need to use .get() 2021-02-14 11:01:12 +01:00
Matthias
ffca09bbcb Test ohlcv_candle_limit explicitly 2021-02-14 10:38:49 +01:00
Matthias
da89838b5c Set bittrex limits as returned by the exchange
closes #4181
2021-02-14 10:32:55 +01:00
Matthias
5622bb3247 Make candle_limit optionally timeframe dependent 2021-02-14 10:29:45 +01:00
Matthias
7ecf8f8b80 Cleanup candle_limit usage 2021-02-14 10:08:05 +01:00
Matthias
10a11bda34 Document bitvavo as community tested
closes #4360
2021-02-14 09:42:25 +01:00
Matthias
6f77ec063e Fix cookieError on python<3.8
Only occurs in combination with api-server enabled,
due to some hot-fixing starlette does.
Since we load starlette at a later point, we need to replicate
starlette's behaviour for now, so sameSite cookies don't create a
problem.

closes #4356
2021-02-14 07:22:08 +01:00
Matthias
73d91275c4 Reset sell_order_status if a new sell-order is placed
closes #4365
2021-02-14 07:11:07 +01:00
Matthias
4b5f4aa1c1 Merge pull request #4361 from freqtrade/format_currencies
Format currencies
2021-02-13 19:23:23 +01:00
Matthias
d4c8be915c Use fstring where possible 2021-02-13 16:11:49 +01:00
Matthias
e7acee7904 Improve coin value output by rounding coin specific 2021-02-13 16:05:56 +01:00
Matthias
072abde9b7 Introduce round_coin_value to simplify coin rounding 2021-02-13 16:05:35 +01:00
Matthias
dd23f6bcbc Fix type for getting pairs 2021-02-11 20:29:31 +01:00
Matthias
843fb204e9 Fix problem with inf values returned from dataframe for api methods 2021-02-11 20:21:31 +01:00
Matthias
aa79574c0c Position-size should NEVER be over available_capital
Part of #4353
2021-02-11 17:09:31 +01:00
Matthias
3110d2dbb1 Add small test cases 2021-02-09 20:09:10 +01:00
Matthias
86fa75b286 Pin version of cryptography 2021-02-09 06:55:36 +01:00
Matthias
7ee149da5d Improve plotting errorhandling
closes #4327
2021-02-08 20:08:32 +01:00
Matthias
427d762746 Improve tests for cancel_order to be more realistic 2021-02-08 19:37:24 +01:00
Matthias
c5ab3a80a5 Check if order is a dict before parsing
closes #4331
2021-02-08 19:35:22 +01:00
Matthias
de727645ab FIx random test failure if certain files exist 2021-02-08 19:21:33 +01:00
Matthias
afaac92685 Merge pull request #4337 from freqtrade/dependabot/pip/develop/python-telegram-bot-13.2
Bump python-telegram-bot from 13.1 to 13.2
2021-02-08 09:46:10 +01:00
Matthias
48e203f6a4 Merge pull request #4338 from freqtrade/dependabot/pip/develop/ccxt-1.41.70
Bump ccxt from 1.41.62 to 1.41.70
2021-02-08 09:45:49 +01:00
Matthias
f999366bf5 Merge pull request #4333 from freqtrade/dependabot/pip/develop/py-find-1st-1.1.5
Bump py-find-1st from 1.1.4 to 1.1.5
2021-02-08 09:45:20 +01:00
dependabot[bot]
c412f8df62 Bump python-telegram-bot from 13.1 to 13.2
Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 13.1 to 13.2.
- [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases)
- [Changelog](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/CHANGES.rst)
- [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v13.1...v13.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-08 08:28:11 +00:00
dependabot[bot]
12168cbf01 Bump ccxt from 1.41.62 to 1.41.70
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.41.62 to 1.41.70.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.41.62...1.41.70)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-08 08:26:18 +00:00
Matthias
0850145b3d Merge pull request #4334 from freqtrade/dependabot/pip/develop/numpy-1.20.1
Bump numpy from 1.20.0 to 1.20.1
2021-02-08 08:22:30 +01:00
Matthias
9821d3a554 Merge pull request #4335 from freqtrade/dependabot/pip/develop/sqlalchemy-1.3.23
Bump sqlalchemy from 1.3.22 to 1.3.23
2021-02-08 08:21:46 +01:00
Matthias
d681565756 Merge pull request #4336 from freqtrade/dependabot/pip/develop/mkdocs-material-6.2.8
Bump mkdocs-material from 6.2.7 to 6.2.8
2021-02-08 08:21:22 +01:00
dependabot[bot]
22d447b3f5 Bump mkdocs-material from 6.2.7 to 6.2.8
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 6.2.7 to 6.2.8.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/6.2.7...6.2.8)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-08 05:27:46 +00:00
dependabot[bot]
676cd7bb55 Bump sqlalchemy from 1.3.22 to 1.3.23
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.3.22 to 1.3.23.
- [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases)
- [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/master/CHANGES)
- [Commits](https://github.com/sqlalchemy/sqlalchemy/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-08 05:27:45 +00:00
dependabot[bot]
dd7f9181c5 Bump numpy from 1.20.0 to 1.20.1
Bumps [numpy](https://github.com/numpy/numpy) from 1.20.0 to 1.20.1.
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/master/doc/HOWTO_RELEASE.rst.txt)
- [Commits](https://github.com/numpy/numpy/compare/v1.20.0...v1.20.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-08 05:27:44 +00:00
dependabot[bot]
d1bb46bed0 Bump py-find-1st from 1.1.4 to 1.1.5
Bumps [py-find-1st](https://github.com/roebel/py_find_1st) from 1.1.4 to 1.1.5.
- [Release notes](https://github.com/roebel/py_find_1st/releases)
- [Commits](https://github.com/roebel/py_find_1st/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-08 05:27:42 +00:00
Matthias
f6cdc6d9a2 Merge pull request #4325 from freqtrade/refresh_order_skip
Refresh order skip
2021-02-07 08:23:22 +01:00
Matthias
4cb67f140a Merge pull request #4323 from eselskas/patch-2
Fix sample strategy documentation link
2021-02-06 20:17:20 +01:00
Edvinas Selskas
694f55c0a5 Use suggested link 2021-02-06 14:43:50 +00:00
Matthias
d5cf837c0f Parse regular cancel_order call to update orders table 2021-02-06 09:23:10 +01:00
Matthias
729e773353 Merge pull request #4319 from JoeSchr/patch-2
Update data-download.md
2021-02-06 09:11:49 +01:00
Edvinas Selskas
bc8fda8d63 Update sample_strategy.py
Fix test
2021-02-06 03:13:53 +00:00
Edvinas Selskas
0a43988f3f Fix sample strategy documentation link
Noticed that the current link is dead. I think this would be the most appropriate link in this case.
2021-02-06 03:02:37 +00:00
Matthias
aec22c5c3d introduce skip_open_order_update parameter
skips startup-open-order-update
closes #4128
2021-02-05 20:17:53 +01:00
Matthias
86a97988c0 Improve wording 2021-02-05 20:09:13 +01:00
Matthias
0806202d47 ccxt version bump to 1.41.62 2021-02-05 20:02:55 +01:00
Matthias
2c71b3b118 Merge pull request #4309 from freqtrade/extract_stake_amount
Move get_trade_stake_amount to wallets
2021-02-05 19:47:30 +01:00
Matthias
545a94f360 Merge pull request #4321 from JoeSchr/fix/lint-binance-example-config
chore(lint): lint binance example config
2021-02-05 14:58:24 +01:00
Joe Schr
a816fb1245 chore(lint): lint binance example config 2021-02-05 12:43:19 +01:00
Matthias
1310a7b547 Fix bug with wrong conversion for BTCST/BTC
This can happen if a pair starts with the stake-currency

closes #4307
2021-02-04 19:58:44 +01:00
Matthias
17e1cfbd43 Merge pull request #4313 from raoulbuzziol/develop
setting resize_keyboard=True for slightly smaller Telegram buttons
2021-02-04 19:42:45 +01:00
JoeSchr
428d2af312 add sudo to chown
that was the whole point d'oh
2021-02-04 19:39:25 +01:00
JoeSchr
5165357f40 Update data-download.md
Fix wrong path
Add section about fixing wrong docker permission, if user_data is created by docker, it's permission are set to `root`
2021-02-04 19:36:04 +01:00
Matthias
19e43e2e9d Merge pull request #4314 from JoeSchr/patch-1
Update README.md
2021-02-04 17:34:23 +01:00
JoeSchr
5cd8745997 Update README.md
Typo playing -> paying
2021-02-04 16:26:03 +01:00
raoulus
99b2214d1f setting resize_keyboard=True for slightly smaller Telegram buttons 2021-02-04 15:27:18 +01:00
Matthias
e8e5acc2e2 Fix import in strategy template 2021-02-03 20:15:08 +01:00
Matthias
024849d844 Merge pull request #4285 from freqtrade/ui_deploy
Deploy FreqUI into webserver
2021-02-03 20:09:31 +01:00
Matthias
b8cb39462c Move get_trade_stake_amount to wallets
this way it can be easier used by other functions
2021-02-03 20:00:33 +01:00
Matthias
6c87c49871 Merge pull request #4306 from The-smooth-operator/docs
Fix documentation links pointing to pairlists
2021-02-03 19:30:20 +01:00
Alberto del Barrio
f36c61e32f Fix documentation links pointing to pairlists 2021-02-03 18:12:48 +01:00
Matthias
caa3e1a7fa Merge pull request #4301 from freqtrade/doc_reorg
Doc reorg
2021-02-03 12:02:05 +01:00
Matthias
de72734076 Merge pull request #4302 from mobrine1/patch-1
#4289 printing json output
2021-02-03 10:13:44 +01:00
mobrine1
06b56544a8 printing json by default now 2021-02-03 03:27:54 -05:00
mobrine1
56569690d9 Update rest_client.py 2021-02-02 15:59:48 -05:00
mobrine1
12bcbf4374 #4289 printing json output
Adding --json flag to print json output
2021-02-02 15:40:33 -05:00
Matthias
cd41d11b85 Merge pull request #4300 from freqtrade/extract_get_min_stake_amount
Extract min stake amount from bot to exchange class
2021-02-02 20:28:35 +01:00
Matthias
43986d3f73 Move Pricing to subpage 2021-02-02 20:26:01 +01:00
Matthias
b41078cc46 Don't include plugin documentation in Configuration page 2021-02-02 20:23:30 +01:00
Matthias
dabe456d65 Improve wording of configuration doc
remove unneeded sections
2021-02-02 20:20:34 +01:00
Matthias
3e3c9e99c7 Move command references to their respective subpages 2021-02-02 20:03:28 +01:00
Matthias
f0532f28cf Small doc-reorg adding "advanced topics" as main header 2021-02-02 20:03:01 +01:00
Matthias
cfd0bb8964 Extract min stake amount from bot to exchange class 2021-02-02 19:47:21 +01:00
Matthias
fa8156b321 Merge pull request #4282 from pan-long/patch-1
Fix a bug when compare sell_profit_offset
2021-02-02 08:22:38 +01:00
Matthias
3d9b4034e6 Use already calculated current_profit for sell_profit_offset comparison 2021-02-02 08:06:19 +01:00
Matthias
a69fde39e5 Merge pull request #4296 from freqtrade/fix/pairlistbug
Fix disappearing pairs pairlist bug
2021-02-01 20:02:50 +01:00
Matthias
a9f1c871dd Add path loading snippet to derived strategies
closes #4279
2021-02-01 19:48:29 +01:00
Matthias
52acf9aaf6 Fix "disappearing pairs" bug
closes #4277
2021-02-01 19:40:31 +01:00
Matthias
130a9b4db3 Add test to call verify_pairlist multiple times 2021-02-01 19:39:55 +01:00
Matthias
55c9489eb2 Downgrade RPI docker-image to 3.7
otherwise piwheels.org does not work at the moment
2021-02-01 15:11:04 +01:00
Matthias
cd5c58fd37 Properly patch exchange for plot_profit test 2021-02-01 12:58:18 +01:00
Matthias
b33534b8f5 Merge pull request #4290 from freqtrade/dependabot/pip/develop/urllib3-1.26.3
Bump urllib3 from 1.26.2 to 1.26.3
2021-02-01 11:01:45 +01:00
Matthias
4facf662de Fix random test-failure caused by un-clean hyperopt shutdown
pytest --random-order-seed=415781
2021-02-01 11:00:55 +01:00
Matthias
fd5468f9cc Merge pull request #4292 from freqtrade/dependabot/pip/develop/pymdown-extensions-8.1.1
Bump pymdown-extensions from 8.1 to 8.1.1
2021-02-01 10:52:47 +01:00
dependabot[bot]
ccdac3d4c3 Bump urllib3 from 1.26.2 to 1.26.3
Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.2 to 1.26.3.
- [Release notes](https://github.com/urllib3/urllib3/releases)
- [Changelog](https://github.com/urllib3/urllib3/blob/1.26.3/CHANGES.rst)
- [Commits](https://github.com/urllib3/urllib3/compare/1.26.2...1.26.3)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-01 08:50:24 +00:00
dependabot[bot]
7fcf0d5231 Bump pymdown-extensions from 8.1 to 8.1.1
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 8.1 to 8.1.1.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/8.1...8.1.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-01 08:45:31 +00:00
Matthias
dc55c79e41 Merge pull request #4286 from freqtrade/dataload_valueerror
Fix valueerror in case of empty array files
2021-02-01 07:52:05 +01:00
Matthias
ed2ae65ffb Merge pull request #4293 from freqtrade/dependabot/pip/develop/jinja2-2.11.3
Bump jinja2 from 2.11.2 to 2.11.3
2021-02-01 07:48:22 +01:00
Matthias
8e1f3a5196 Merge pull request #4291 from freqtrade/dependabot/pip/develop/pytest-6.2.2
Bump pytest from 6.2.1 to 6.2.2
2021-02-01 07:47:40 +01:00
Matthias
382cd9eaaf Merge pull request #4295 from freqtrade/dependabot/pip/develop/mkdocs-material-6.2.7
Bump mkdocs-material from 6.2.5 to 6.2.7
2021-02-01 07:47:23 +01:00
Matthias
8cf8ef98c4 Merge pull request #4294 from freqtrade/dependabot/pip/develop/ccxt-1.41.35
Bump ccxt from 1.40.99 to 1.41.35
2021-02-01 07:47:06 +01:00
dependabot[bot]
2a5e0920ec Bump mkdocs-material from 6.2.5 to 6.2.7
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 6.2.5 to 6.2.7.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/6.2.5...6.2.7)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-01 05:42:08 +00:00
dependabot[bot]
aa7120f27c Bump ccxt from 1.40.99 to 1.41.35
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.40.99 to 1.41.35.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.40.99...1.41.35)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-01 05:42:06 +00:00
dependabot[bot]
ed1d4f0568 Bump jinja2 from 2.11.2 to 2.11.3
Bumps [jinja2](https://github.com/pallets/jinja) from 2.11.2 to 2.11.3.
- [Release notes](https://github.com/pallets/jinja/releases)
- [Changelog](https://github.com/pallets/jinja/blob/master/CHANGES.rst)
- [Commits](https://github.com/pallets/jinja/compare/2.11.2...2.11.3)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-01 05:42:05 +00:00
dependabot[bot]
76312d722a Bump pytest from 6.2.1 to 6.2.2
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.1 to 6.2.2.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/6.2.1...6.2.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-02-01 05:41:53 +00:00
Matthias
2c80388b40 Fix valueerror in case of empty array files 2021-02-01 06:28:49 +01:00
Matthias
06e2bc94c3 Deploy to subdirectory 2021-01-31 16:01:00 +01:00
Matthias
e4a085027b Add test for UI methods 2021-01-31 15:27:00 +01:00
Matthias
28be71806f Install html file as well 2021-01-31 15:13:51 +01:00
Matthias
a87a885ccd Don't use Path object to return fileresponses 2021-01-31 14:54:58 +01:00
Matthias
7b3d99819f Fix bug with not cleaning UI folder 2021-01-31 14:50:54 +01:00
Matthias
944d674eeb Store freqUI version and read it again 2021-01-31 14:50:54 +01:00
Matthias
2af1d2d639 Extract last FreqUI version from api response 2021-01-31 14:50:54 +01:00
Matthias
1df0aa8751 Add ui installation to docker container builds 2021-01-31 14:50:54 +01:00
Matthias
35c2e2556e Document FreqUI usage 2021-01-31 14:50:54 +01:00
Matthias
e928d2991d Add fallback file 2021-01-31 14:50:54 +01:00
Matthias
f05f2c45e8 Allow deleting of UI only 2021-01-31 14:50:54 +01:00
Matthias
ddc99553bd Add test case for get_ui_download_url 2021-01-31 14:50:54 +01:00
Matthias
a1a35115ad Extract get_ui_download_url 2021-01-31 14:50:54 +01:00
Matthias
87ed2d7502 Write some tests for UI Downloading 2021-01-31 14:50:54 +01:00
Matthias
a47616eed4 Add UI installation subcommand 2021-01-31 14:50:54 +01:00
Matthias
27970b424d Add webUI serving to api-server 2021-01-31 14:50:54 +01:00
Matthias
79087ba166 Fix intermitted test failure 2021-01-31 14:50:39 +01:00
Matthias
f288ed1f36 Merge pull request #4284 from freqtrade/windows_ci_error
Version bump numpy 1.20.0
2021-01-31 13:59:36 +01:00
Matthias
5724371a4f Fix types for numpy 1.20.0 upgrade 2021-01-31 11:21:23 +01:00
Matthias
92721db583 Version bump numpy to 1.20.0 2021-01-31 10:51:21 +01:00
Matthias
bc586fe73b Try fix CI 2021-01-31 10:29:43 +01:00
Pan Long
4cc93151c5 Fix a bug when compare sell_profit_offset
It should be comparing the ratio instead of absolut profit.

Also updated the comment.
2021-01-31 12:14:09 +08:00
Matthias
16dad8b6d4 Allow custom_stoploss to cooperate with stoploss on exchange 2021-01-30 20:11:18 +01:00
Matthias
30e5c01cb1 Improve formatting of custom_stoploss docs 2021-01-30 19:59:14 +01:00
Matthias
afdb39d78f Merge pull request #4280 from andre-ac/develop
Fixed virtualenv link
2021-01-30 19:20:13 +01:00
andre-ac
6b63129eb0 Fixed virtualenv link 2021-01-30 15:36:59 +00:00
Matthias
406682c3bb Fix random api failure in slow cases 2021-01-30 10:20:40 +01:00
Matthias
b68ed458b8 Merge pull request #4067 from freqtrade/dependabot/docker/python-3.9.1-slim-buster
Bump python from 3.8.6-slim-buster to 3.9.1-slim-buster
2021-01-30 10:13:32 +01:00
Matthias
5d18289821 Fix name in issue template 2021-01-30 07:17:25 +01:00
Matthias
375f551e5d Merge pull request #4218 from sobeit2020/develop
Conda - installation process : adding and explaining
2021-01-29 20:01:07 +01:00
Matthias
1e6194fa30 Improve wording, fix hirerchial hierarchy 2021-01-29 19:46:45 +01:00
Matthias
d8353bc90e Merge branch 'develop' into pr/sobeit2020/4218 2021-01-29 19:11:19 +01:00
Matthias
ea0ffbae73 use profit_ratio in calculate_cum_profit 2021-01-29 19:06:57 +01:00
Matthias
ad9efd3ac5 Merge pull request #4275 from freqtrade/markets_ref
Cache markets in the exchange object
2021-01-29 16:57:57 +01:00
sobeit2020
b12d0b110e Update installation.md 2021-01-28 23:09:39 +00:00
Matthias
5cdd9dd445 Cache markets in the exchange object 2021-01-28 19:47:32 +01:00
dependabot[bot]
4bb2a00f03 Bump python from 3.8.6-slim-buster to 3.9.1-slim-buster
Bumps python from 3.8.6-slim-buster to 3.9.1-slim-buster.

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-27 18:12:56 +00:00
Matthias
65459086a3 Merge pull request #4268 from freqtrade/backtest_trade_object
Backtest trade object
2021-01-27 19:10:21 +01:00
Matthias
766c786d90 Merge pull request #4273 from freqtrade/new_release
New release 2021.1
2021-01-27 19:02:52 +01:00
sobeit2020
d1d77f56df Update installation.md 2021-01-27 13:38:59 +00:00
sobeit2020
a414d5d75a Update installation.md 2021-01-27 13:25:16 +00:00
sobeit2020
ec2cf7f979 Update installation.md 2021-01-27 13:23:53 +00:00
sobeit2020
5da8a3078b Update installation.md 2021-01-27 13:04:04 +00:00
Matthias
eac98dbbd6 Version bump to 2021.1 2021-01-27 07:29:40 +01:00
Matthias
a9b4d6de33 Check for existance of ask key in ticker
closes #4267
2021-01-26 17:18:55 +01:00
Matthias
4d7f3e570b Add test for spreadfilter division exception 2021-01-26 17:18:51 +01:00
Matthias
5ab8cc56a4 Update docs to also work for postgres 2021-01-26 08:13:43 +01:00
Matthias
9005cd25b8 Merge pull request #4256 from thopd88/patch-2
Fix operator does not exist: boolean = integer
2021-01-26 07:21:39 +01:00
Tho Pham (Alex)
8f529f48da Update freqtrade/freqtradebot.py use is_open.is_(True)
Co-authored-by: Matthias <xmatthias@outlook.com>
2021-01-26 07:38:25 +07:00
sobeit2020
188010329c Update installation.md 2021-01-25 21:22:43 +00:00
sobeit2020
bcc7adb186 Update installation.md 2021-01-25 21:18:38 +00:00
sobeit2020
d848242379 Update installation.md 2021-01-25 21:12:48 +00:00
sobeit2020
39cef2dbe0 Update environment.yml 2021-01-25 20:45:35 +00:00
sobeit2020
4a28fab8a1 Update installation.md 2021-01-25 20:41:55 +00:00
Matthias
91b6c02947 Update download-data --dl-trades sample command 2021-01-25 20:57:05 +01:00
sobeit2020
65e0ba60dc Update installation.md 2021-01-25 19:51:01 +00:00
Matthias
13ad6dd461 Fix documentation 2021-01-25 19:56:40 +01:00
Matthias
c659150d9f Also print trade_duration in seconds to json 2021-01-25 19:42:34 +01:00
Matthias
3a83492999 Merge pull request #4265 from freqtrade/dependabot/pip/develop/ccxt-1.40.99
Bump ccxt from 1.40.74 to 1.40.99
2021-01-25 10:11:56 +01:00
dependabot[bot]
f98bd40955 Bump ccxt from 1.40.74 to 1.40.99
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.40.74 to 1.40.99.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.40.74...1.40.99)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-25 08:24:17 +00:00
Matthias
6fc165c133 Merge pull request #4264 from freqtrade/dependabot/pip/develop/scikit-learn-0.24.1
Bump scikit-learn from 0.24.0 to 0.24.1
2021-01-25 08:23:57 +01:00
Matthias
d10e00b61b Merge pull request #4262 from freqtrade/dependabot/pip/develop/prompt-toolkit-3.0.14
Bump prompt-toolkit from 3.0.10 to 3.0.14
2021-01-25 08:23:30 +01:00
Matthias
c0df15af29 Merge pull request #4257 from freqtrade/dependabot/pip/develop/cachetools-4.2.1
Bump cachetools from 4.2.0 to 4.2.1
2021-01-25 08:22:46 +01:00
Matthias
9b8148356e Merge pull request #4261 from freqtrade/dependabot/pip/develop/pandas-1.2.1
Bump pandas from 1.2.0 to 1.2.1
2021-01-25 08:22:12 +01:00
Matthias
04058b1a33 Merge pull request #4258 from freqtrade/dependabot/pip/develop/blosc-1.10.2
Bump blosc from 1.10.1 to 1.10.2
2021-01-25 08:04:27 +01:00
Matthias
1314e75bc6 Merge pull request #4259 from freqtrade/dependabot/pip/develop/pytest-cov-2.11.1
Bump pytest-cov from 2.10.1 to 2.11.1
2021-01-25 08:03:24 +01:00
dependabot[bot]
cb749b578d Bump scikit-learn from 0.24.0 to 0.24.1
Bumps [scikit-learn](https://github.com/scikit-learn/scikit-learn) from 0.24.0 to 0.24.1.
- [Release notes](https://github.com/scikit-learn/scikit-learn/releases)
- [Commits](https://github.com/scikit-learn/scikit-learn/compare/0.24.0...0.24.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-25 05:37:51 +00:00
dependabot[bot]
fb99cf1459 Bump prompt-toolkit from 3.0.10 to 3.0.14
Bumps [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) from 3.0.10 to 3.0.14.
- [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.10...3.0.14)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-25 05:37:50 +00:00
dependabot[bot]
d4e9037e6e Bump pandas from 1.2.0 to 1.2.1
Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.2.0 to 1.2.1.
- [Release notes](https://github.com/pandas-dev/pandas/releases)
- [Changelog](https://github.com/pandas-dev/pandas/blob/master/RELEASE.md)
- [Commits](https://github.com/pandas-dev/pandas/compare/v1.2.0...v1.2.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-25 05:37:49 +00:00
dependabot[bot]
afdcd2c0af Bump pytest-cov from 2.10.1 to 2.11.1
Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 2.10.1 to 2.11.1.
- [Release notes](https://github.com/pytest-dev/pytest-cov/releases)
- [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-cov/compare/v2.10.1...v2.11.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-25 05:37:42 +00:00
dependabot[bot]
9422062cbd Bump blosc from 1.10.1 to 1.10.2
Bumps [blosc](https://github.com/blosc/python-blosc) from 1.10.1 to 1.10.2.
- [Release notes](https://github.com/blosc/python-blosc/releases)
- [Changelog](https://github.com/Blosc/python-blosc/blob/master/RELEASE_NOTES.rst)
- [Commits](https://github.com/blosc/python-blosc/compare/v1.10.1...v1.10.2)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-25 05:37:25 +00:00
dependabot[bot]
b976baae3f Bump cachetools from 4.2.0 to 4.2.1
Bumps [cachetools](https://github.com/tkem/cachetools) from 4.2.0 to 4.2.1.
- [Release notes](https://github.com/tkem/cachetools/releases)
- [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/tkem/cachetools/compare/v4.2.0...v4.2.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-25 05:37:16 +00:00
Tho Pham (Alex)
c22cccb55b Fix operator does not exist: boolean = integer 2021-01-25 12:24:47 +07:00
sobeit2020
2226f6781f Update installation.md 2021-01-24 21:31:36 +00:00
Matthias
62e43539c9 Limit max_open_trades to maximum available pairs
closes #4008
2021-01-24 19:59:54 +01:00
Matthias
789a980a30 Fix tests for new export format 2021-01-24 19:42:32 +01:00
sobeit2020
2c2a33b2e8 updated environemnt.ylm 2021-01-24 17:06:40 +00:00
sobeit2020
9af89786ba update installation.md
ma
2021-01-24 17:03:56 +00:00
Matthias
deb8432d33 Streamline trade to dataframe conversion 2021-01-24 08:58:41 +01:00
Matthias
8ee264bc59 Don't use profit_percent for backtesting results anymore 2021-01-24 08:58:41 +01:00
Matthias
48977493bb Backtesting does not need to convert to BacktestResult object 2021-01-24 08:58:41 +01:00
Matthias
3b51545d23 Add trade_duration to to_json 2021-01-24 08:58:41 +01:00
Matthias
9a3c425cf4 Update slack link 2021-01-24 08:53:05 +01:00
Matthias
16f9675356 Fix whitelist expansion problem 2021-01-23 20:40:27 +01:00
Matthias
37acaa685b Merge pull request #4249 from freqtrade/config_rename
Config rename
2021-01-23 09:20:42 +01:00
Matthias
31e0b09643 Rename config.json.example
it's really the config dedicated to bittrex,
so the name should reflect this in beeing config_bittrex.json.example
2021-01-22 19:18:34 +01:00
Matthias
371b374ea6 Remove unused config setup from setup.sh 2021-01-22 19:12:34 +01:00
Matthias
bec9b580b0 sell_profit_offset should be documented in the strategy override section 2021-01-22 17:38:55 +01:00
Matthias
e94e2dd383 Remove docker config without compose 2021-01-22 17:32:57 +01:00
Matthias
c42241986e further investigate random test failure 2021-01-21 19:20:38 +01:00
Matthias
c998577d4a Merge pull request #4244 from dannoso/patch-1
Fixed quickstart link in docs
2021-01-21 19:12:47 +01:00
Davide
fd379d36ac Fixed quickstart link in docs 2021-01-21 12:27:22 +01:00
Matthias
5c0f98b518 Blacklist Poloniex - as ccxt does not provide a fetch_order endpoint 2021-01-20 19:31:17 +01:00
Matthias
06cae1b60c Merge pull request #4241 from tijmenvandenbrink/develop
Small improvement to MaxDrawDown protection
2021-01-20 14:16:08 +01:00
Matthias
5f5f75e147 Improve wording in protections documentation 2021-01-20 13:57:53 +01:00
Tijmen van den Brink
992d6b8018 Small improvement to MaxDrawDown protection 2021-01-20 09:24:30 +01:00
Matthias
7c80eeea95 Add use_custom_stoploss to optimize_report 2021-01-19 22:51:12 +01:00
Matthias
86b3306a3b Small doc refactoring 2021-01-19 22:07:10 +01:00
Matthias
7c99e6f0e6 Avoid random test failure 2021-01-19 20:49:28 +01:00
Matthias
20591b539a Merge pull request #4228 from baartch/develop
Extending the Telegram Bot command /status with the possibility to query specific trade_ids
2021-01-19 20:08:49 +01:00
Matthias
cd8d4da466 Add test for /status <tradeids> functionality 2021-01-19 19:45:13 +01:00
Andreas Brunner
a68a546dd9 _rpc_trade_status argument datatype optimizations 2021-01-18 15:26:53 +01:00
Matthias
c785bce7e6 Merge pull request #4234 from freqtrade/dependabot/pip/develop/pyjwt-2.0.1
Bump pyjwt from 2.0.0 to 2.0.1
2021-01-18 09:20:16 +01:00
Matthias
d05cbe239a Merge pull request #4231 from freqtrade/dependabot/pip/develop/coveralls-3.0.0
Bump coveralls from 2.2.0 to 3.0.0
2021-01-18 08:59:05 +01:00
Matthias
792c8503f9 Merge pull request #4233 from freqtrade/dependabot/pip/develop/mkdocs-material-6.2.5
Bump mkdocs-material from 6.2.4 to 6.2.5
2021-01-18 08:48:44 +01:00
Matthias
10104927c9 Fix devcontainer
closes #4230
2021-01-18 07:46:19 +00:00
Matthias
611dbdc522 Merge pull request #4235 from freqtrade/dependabot/pip/develop/ccxt-1.40.74
Bump ccxt from 1.40.30 to 1.40.74
2021-01-18 07:31:34 +01:00
Matthias
3386ca9999 Merge pull request #4232 from freqtrade/dependabot/pip/develop/plotly-4.14.3
Bump plotly from 4.14.1 to 4.14.3
2021-01-18 07:29:41 +01:00
dependabot[bot]
994b4013ad Bump ccxt from 1.40.30 to 1.40.74
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.40.30 to 1.40.74.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.40.30...1.40.74)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-18 05:38:06 +00:00
dependabot[bot]
8b5f8937cc Bump pyjwt from 2.0.0 to 2.0.1
Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 2.0.0 to 2.0.1.
- [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.0.0...2.0.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-18 05:37:48 +00:00
dependabot[bot]
7f8dbce367 Bump mkdocs-material from 6.2.4 to 6.2.5
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 6.2.4 to 6.2.5.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/6.2.4...6.2.5)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-18 05:37:45 +00:00
dependabot[bot]
6a8e495102 Bump plotly from 4.14.1 to 4.14.3
Bumps [plotly](https://github.com/plotly/plotly.py) from 4.14.1 to 4.14.3.
- [Release notes](https://github.com/plotly/plotly.py/releases)
- [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md)
- [Commits](https://github.com/plotly/plotly.py/compare/v4.14.1...v4.14.3)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-18 05:37:37 +00:00
dependabot[bot]
296a6bd43c Bump coveralls from 2.2.0 to 3.0.0
Bumps [coveralls](https://github.com/coveralls-clients/coveralls-python) from 2.2.0 to 3.0.0.
- [Release notes](https://github.com/coveralls-clients/coveralls-python/releases)
- [Changelog](https://github.com/TheKevJames/coveralls-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/coveralls-clients/coveralls-python/compare/2.2.0...3.0.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-18 05:37:29 +00:00
Andreas Brunner
eb95d970e9 flake8 beautify 2021-01-17 21:26:55 +01:00
Andreas Brunner
d21eff0d52 fix, if an non existing trade_id is provided 2021-01-17 21:21:31 +01:00
Andreas Brunner
3ea33d1737 updating doc and help with new /status argument 2021-01-17 21:15:17 +01:00
Andreas Brunner
6d40814dbf extend status bot command to query specific trades 2021-01-17 20:39:35 +01:00
Matthias
a8bae3a381 Don't update trade fees for dry-run orders 2021-01-17 20:31:27 +01:00
Matthias
389db2fe7d Enhance wording of docker quickstart 2021-01-17 19:11:00 +01:00
Matthias
3a2bac4ae3 Merge pull request #4223 from freqtrade/bot_name
Add bot_name setting
2021-01-17 15:23:41 +01:00
sobeit2020
172a629c58 Update installation.md 2021-01-16 22:41:37 +00:00
sobeit2020
f9dd74585e Update installation.md 2021-01-16 22:39:11 +00:00
sobeit2020
73206a9194 Update installation.md 2021-01-16 22:35:42 +00:00
sobeit2020
63be27f671 Update installation.md 2021-01-16 22:28:23 +00:00
sobeit2020
9ad0817105 Update installation.md 2021-01-16 22:27:25 +00:00
sobeit2020
a271c9e98e Update installation.md 2021-01-16 22:24:22 +00:00
Matthias
53c208197d Add bot_name setting
allows naming the bot to simply differentiate when running different
bots.
2021-01-16 16:19:49 +01:00
Matthias
572f5f9186 Fix fstring syntax error 2021-01-16 10:05:47 +01:00
Matthias
9f338ba6ed Debug random test failure in CI 2021-01-16 10:01:31 +01:00
Matthias
3fefb6f1c8 Merge pull request #4215 from freqtrade/refactor/backtest
Small backtest refactor, introduce calling `bot_loop_start` in backtesting
2021-01-16 09:32:19 +01:00
Matthias
d74376726a api-server should fully support max_open_trades=-1 2021-01-15 20:47:12 +01:00
Matthias
baef8b4f79 Merge pull request #4197 from nas-/develop
Added support for regex in whitelist
2021-01-15 07:34:49 +01:00
Matthias
bf5868c96d Add testcase for nonexisting pairs on whitelist 2021-01-15 07:10:17 +01:00
nas-
f72d53351c Added ability to keep invalid pairs while expanding expand_pairlist 2021-01-15 06:37:57 +01:00
sobeit2020
ce5ba1bb6e Adding Conda installation process 2021-01-15 00:14:11 +00:00
Matthias
0b65fe6afe Capture backtest start / end time 2021-01-14 19:09:25 +01:00
Matthias
9147106259 call bot_loop_start() in backtesting to allow setup-code to run 2021-01-14 19:09:25 +01:00
Matthias
baa1142afa Use preprocessed to get min/max date in hyperopt 2021-01-14 19:09:21 +01:00
Matthias
9d4cdcad10 Extract backtesting of one strategy 2021-01-14 19:04:42 +01:00
Matthias
6d1fba1409 Remove unnecessary log output tests 2021-01-14 19:04:42 +01:00
Matthias
f3de0dd3eb Fix support for protections in hyperopt
closes #4208
2021-01-14 06:53:40 +01:00
Matthias
d289fe44cb Merge pull request #4205 from tejeshreddy/docs-edge
fix: edge doc typos [done]
2021-01-13 16:43:43 +01:00
tejeshreddy
950c5c0113 fix: edge doc typos 2021-01-13 16:50:38 +05:30
Matthias
adb3fb123e Fix typo 2021-01-12 19:35:02 +01:00
Matthias
47a06c6213 Fix enable/reenable of swagger UI endpoint 2021-01-12 19:28:22 +01:00
Matthias
ac43591c44 Fix failing api when max_open_trades is unlimited 2021-01-12 19:24:37 +01:00
Matthias
60ea32e398 Improve wording 2021-01-12 19:05:25 +01:00
Matthias
6007d5182a Merge pull request #4147 from hoeckxer/ignore_expired_candle
Ignoring candles that have expired within timeframe
2021-01-12 19:04:16 +01:00
hoeckxer
1f6a71fdd9 Reformat code on new version 2021-01-12 08:24:11 +01:00
Matthias
951c6ac1d4 Merge pull request #4193 from freqtrade/sell_profit_offset
Sell profit offset
2021-01-12 07:58:07 +01:00
hoeckxer
71f45021b9 Removed redundant statement 2021-01-12 07:35:30 +01:00
hoeckxer
e328182bd7 Changed workings so it only needs to timing-parameter, instead of also requiring a boolean value 2021-01-12 07:30:39 +01:00
nas-
4d7ffa8c81 Added suppoort for regex in whitelist 2021-01-12 01:13:58 +01:00
Matthias
b062b836cc Add test for sell_profit_offset 2021-01-11 19:42:44 +01:00
Matthias
63a579dbab Add sell_profit_offset parameter
Allows defining positive offsets before enabling the sell signal
2021-01-11 19:30:25 +01:00
Matthias
dbc25f00ac Switch full config from bittrex to binance
bittrex no longer supports volumepairlist.

closes #4192
2021-01-11 19:12:03 +01:00
Matthias
0c6c5162e8 Merge pull request #4189 from freqtrade/fix/4183
Include stoploss_on_exchange in stoploss_guard
2021-01-11 08:14:59 +01:00
Matthias
689c19620c Merge pull request #4188 from freqtrade/dependabot/pip/develop/pytest-mock-3.5.1
Bump pytest-mock from 3.5.0 to 3.5.1
2021-01-11 07:57:19 +01:00
Matthias
f159c46438 Include stoploss_on_exchange in stoploss_guard
fix #4183
2021-01-11 07:55:01 +01:00
Matthias
bc0550358f Merge pull request #4186 from freqtrade/dependabot/pip/develop/prompt-toolkit-3.0.10
Bump prompt-toolkit from 3.0.9 to 3.0.10
2021-01-11 07:36:29 +01:00
Matthias
d78fd3fa8f Merge pull request #4185 from freqtrade/dependabot/pip/develop/mkdocs-material-6.2.4
Bump mkdocs-material from 6.2.3 to 6.2.4
2021-01-11 07:34:48 +01:00
Matthias
6c29964bcc Merge pull request #4187 from freqtrade/dependabot/pip/develop/ccxt-1.40.30
Bump ccxt from 1.40.25 to 1.40.30
2021-01-11 07:33:55 +01:00
dependabot[bot]
59efc5f083 Bump pytest-mock from 3.5.0 to 3.5.1
Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 3.5.0 to 3.5.1.
- [Release notes](https://github.com/pytest-dev/pytest-mock/releases)
- [Changelog](https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.5.0...v3.5.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-11 05:38:47 +00:00
dependabot[bot]
f1809286cf Bump ccxt from 1.40.25 to 1.40.30
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.40.25 to 1.40.30.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.40.25...1.40.30)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-11 05:38:47 +00:00
dependabot[bot]
a34753fcb1 Bump prompt-toolkit from 3.0.9 to 3.0.10
Bumps [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) from 3.0.9 to 3.0.10.
- [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.9...3.0.10)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-11 05:38:45 +00:00
dependabot[bot]
ddecf3ef98 Bump mkdocs-material from 6.2.3 to 6.2.4
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 6.2.3 to 6.2.4.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/6.2.3...6.2.4)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-11 05:38:34 +00:00
Matthias
5102dfd6df Merge pull request #4144 from freqtrade/improve_informativepair
Improve merge_informative_pairs to properly merge correct timeframes
2021-01-09 10:15:59 +01:00
Matthias
61d225a575 Merge pull request #4170 from freqtrade/dependabot/pip/develop/pyjwt-2.0.0
Bump pyjwt from 1.7.1 to 2.0.0
2021-01-08 19:48:04 +01:00
Matthias
8631a54514 Fix test due to pyjwt2.0 2021-01-08 19:34:01 +01:00
Matthias
5f17dd06a5 Merge pull request #4173 from freqtrade/fix/4161
Fix #4161 - by not using the problematic method for windows
2021-01-08 19:22:48 +01:00
Matthias
47f391e43e Merge pull request #4164 from freqtrade/ci_macos_39
Run CI for mac on 3.9
2021-01-08 19:22:15 +01:00
Matthias
378a252ad1 Fix #4161 - by not using the problematic method for windows 2021-01-08 13:46:43 +01:00
Matthias
cc428d7e36 Merge pull request #4171 from freqtrade/dependabot/pip/develop/numpy-1.19.5
Bump numpy from 1.19.4 to 1.19.5
2021-01-08 13:44:09 +01:00
dependabot[bot]
c8df3c4730 Bump pyjwt from 1.7.1 to 2.0.0
Bumps [pyjwt](https://github.com/jpadilla/pyjwt) from 1.7.1 to 2.0.0.
- [Release notes](https://github.com/jpadilla/pyjwt/releases)
- [Changelog](https://github.com/jpadilla/pyjwt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/jpadilla/pyjwt/compare/1.7.1...2.0.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-08 07:48:31 +00:00
Matthias
de26867ad2 Merge pull request #4166 from freqtrade/dependabot/pip/develop/pytest-mock-3.5.0
Bump pytest-mock from 3.4.0 to 3.5.0
2021-01-08 08:48:14 +01:00
dependabot[bot]
4d2c59b7ec Bump numpy from 1.19.4 to 1.19.5
Bumps [numpy](https://github.com/numpy/numpy) from 1.19.4 to 1.19.5.
- [Release notes](https://github.com/numpy/numpy/releases)
- [Changelog](https://github.com/numpy/numpy/blob/master/doc/HOWTO_RELEASE.rst.txt)
- [Commits](https://github.com/numpy/numpy/compare/v1.19.4...v1.19.5)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-08 07:47:47 +00:00
Matthias
eb9d137d5b Merge pull request #4167 from freqtrade/dependabot/pip/develop/prompt-toolkit-3.0.9
Bump prompt-toolkit from 3.0.8 to 3.0.9
2021-01-08 08:47:32 +01:00
Matthias
ee6d340aa7 Merge pull request #4169 from freqtrade/dependabot/pip/develop/ccxt-1.40.25
Bump ccxt from 1.40.14 to 1.40.25
2021-01-08 08:47:12 +01:00
Matthias
3bc59d427c Merge pull request #4168 from freqtrade/dependabot/pip/develop/uvicorn-0.13.3
Bump uvicorn from 0.13.2 to 0.13.3
2021-01-08 08:46:42 +01:00
dependabot[bot]
3cf506fa5d Bump ccxt from 1.40.14 to 1.40.25
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.40.14 to 1.40.25.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.40.14...1.40.25)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-08 07:16:49 +00:00
dependabot[bot]
784630e2f2 Bump uvicorn from 0.13.2 to 0.13.3
Bumps [uvicorn](https://github.com/encode/uvicorn) from 0.13.2 to 0.13.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.13.2...0.13.3)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-08 07:16:49 +00:00
dependabot[bot]
f3319e1382 Bump prompt-toolkit from 3.0.8 to 3.0.9
Bumps [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) from 3.0.8 to 3.0.9.
- [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/commits/3.0.9)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-08 07:16:48 +00:00
dependabot[bot]
bd5f46e4c2 Bump pytest-mock from 3.4.0 to 3.5.0
Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 3.4.0 to 3.5.0.
- [Release notes](https://github.com/pytest-dev/pytest-mock/releases)
- [Changelog](https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.4.0...v3.5.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-08 07:16:36 +00:00
Matthias
2e7faa782c Add documentation section for macOS installation error on 3.999999999 2021-01-08 06:51:37 +01:00
Matthias
3a9583403b Merge pull request #4165 from freqtrade/trades_dl
Trades dl fix faulty behaviour
2021-01-08 06:22:13 +01:00
Matthias
4f126bea35 Change trades-test2 to better test correct behaviour 2021-01-07 20:06:26 +01:00
Matthias
bf182dc01e Fix wrong key usage in trade_history_timebased 2021-01-07 20:03:34 +01:00
Matthias
124cb5c5bf Add cblosc brew dependency 2021-01-07 19:36:50 +01:00
Matthias
54ab61d18a Install hdf5 via brew 2021-01-07 19:27:35 +01:00
Matthias
9e66417e85 Run CI for mac on 3.9 2021-01-07 19:21:42 +01:00
Matthias
b43ef474ad Fix expired candle implementation
Improve and simplify test by passing the current time to the function
2021-01-07 07:51:49 +01:00
Matthias
7a628432a8 Merge pull request #4159 from freqtrade/protections/strategy
Allow protections to be set in the strategy
2021-01-07 06:39:34 +01:00
Matthias
c9e477214f Allow protections to be set in the strategy 2021-01-06 16:37:09 +01:00
Matthias
6ca2b2d52d Merge pull request #4158 from freqtrade/fix/rpc_history_bug
Fix bug in RPC history mode when no data is found
2021-01-06 16:03:03 +01:00
Matthias
e69dac2704 Fix bug in RPC history mode when no data is found 2021-01-06 15:38:46 +01:00
Matthias
a9ca72c1b8 Fix typo in documentation 2021-01-06 11:04:14 +01:00
Matthias
dfe9247c65 Merge pull request #4155 from freqtrade/fix_dry_open_order_update
Don't update open orders in dry-run mode
2021-01-06 10:52:39 +01:00
Matthias
91f8667881 DOn't update open orders in dry-run mode 2021-01-06 09:57:36 +01:00
Matthias
3d57a108d8 Merge pull request #4150 from hoeckxer/protection_documentation_clarification
Protections - clarification in documentation
2021-01-06 09:55:43 +01:00
Matthias
a906093153 FIx doc wording for all guards 2021-01-06 09:45:21 +01:00
hoeckxer
f7b055a58c Attempt to improve wording
Signed-off-by: hoeckxer <hawkeyenl@yahoo.com>
2021-01-06 09:26:03 +01:00
hoeckxer
95732e8991 Clarification in documentation
Signed-off-by: hoeckxer <hawkeyenl@yahoo.com>
2021-01-05 21:03:23 +01:00
hoeckxer
c0f170fdb9 Merge branch 'develop' into ignore_expired_candle 2021-01-05 21:00:08 +01:00
hoeckxer
5c34140a19 Adjusted documentation to reflect sub-key configuration 2021-01-05 20:59:31 +01:00
hoeckxer
65d91a3a58 isort fix 2021-01-05 15:36:34 +01:00
hoeckxer
573de1cf08 Fixed flake8 warnings 2021-01-05 15:30:29 +01:00
hoeckxer
67d84e7514 Merge branch 'ignore_expired_candle' of github.com:hoeckxer/freqtrade into ignore_expired_candle 2021-01-05 14:49:46 +01:00
hoeckxer
e3f3f36298 Changes based on review comments 2021-01-05 14:49:35 +01:00
Erwin Hoeckx
eaaaddac86 Update docs/configuration.md
Co-authored-by: Matthias <xmatthias@outlook.com>
2021-01-05 11:10:00 +01:00
Matthias
c010cdf894 Merge pull request #4138 from freqtrade/fastapi
use Fastapi instead of flask for API operations
2021-01-05 10:07:19 +01:00
hoeckxer
c9ed2137bb Simplified return statements
Signed-off-by: hoeckxer <hawkeyenl@yahoo.com>
2021-01-05 09:07:46 +01:00
Erwin Hoeckx
67306d943a Update interface.py
Simplified return value, thereby including the situation where the time simply hasn't expired yet
2021-01-05 07:33:34 +01:00
Erwin Hoeckx
9a93a0876a Update interface.py
Adjusted comment
2021-01-05 07:32:07 +01:00
hoeckxer
844df96ec7 Making changes so the build checks are satisified (imports & flake8)
Signed-off-by: hoeckxer <hawkeyenl@yahoo.com>
2021-01-05 07:06:53 +01:00
hoeckxer
614a996597 First commit about ignoring expired candle
Signed-off-by: hoeckxer <hawkeyenl@yahoo.com>
2021-01-04 20:49:24 +01:00
Matthias
cce4d7e42c Merge pull request #4145 from hoeckxer/custom_stoploss_doc_addition
Added an example with a positive offset for a custom stoploss
2021-01-04 19:19:53 +01:00
hoeckxer
1cf6e2c957 Changed documentation based on review comments
Signed-off-by: hoeckxer <hawkeyenl@yahoo.com>
2021-01-04 14:37:22 +01:00
hoeckxer
0704cfb05b Added an example with a positive offset for a custom stoploss
Signed-off-by: hoeckxer <hawkeyenl@yahoo.com>
2021-01-04 14:14:52 +01:00
Matthias
07bc0c3fce Improve merge_informative_pairs to properly merge correct timeframes
explanation in #4073, closes #4073
2021-01-04 13:49:38 +01:00
Matthias
d1804dee6b Add note about python-dev dependency 2021-01-04 09:40:17 +01:00
Matthias
9e84dd9274 Merge pull request #4141 from freqtrade/dependabot/pip/develop/scipy-1.6.0
Bump scipy from 1.5.4 to 1.6.0
2021-01-04 09:30:57 +01:00
Matthias
a7e3f9ef70 Merge pull request #4142 from freqtrade/dependabot/pip/develop/isort-5.7.0
Bump isort from 5.6.4 to 5.7.0
2021-01-04 09:27:04 +01:00
Matthias
a33f4fd9ca Merge pull request #4143 from freqtrade/dependabot/pip/develop/ccxt-1.40.14
Bump ccxt from 1.39.79 to 1.40.14
2021-01-04 08:30:42 +01:00
dependabot[bot]
9e435fba0b Bump ccxt from 1.39.79 to 1.40.14
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.39.79 to 1.40.14.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.39.79...1.40.14)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-04 05:39:14 +00:00
dependabot[bot]
7d06e61461 Bump scipy from 1.5.4 to 1.6.0
Bumps [scipy](https://github.com/scipy/scipy) from 1.5.4 to 1.6.0.
- [Release notes](https://github.com/scipy/scipy/releases)
- [Commits](https://github.com/scipy/scipy/compare/v1.5.4...v1.6.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-04 05:38:57 +00:00
dependabot[bot]
66391b80ae Bump isort from 5.6.4 to 5.7.0
Bumps [isort](https://github.com/pycqa/isort) from 5.6.4 to 5.7.0.
- [Release notes](https://github.com/pycqa/isort/releases)
- [Changelog](https://github.com/PyCQA/isort/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/pycqa/isort/compare/5.6.4...5.7.0)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-04 05:38:57 +00:00
Matthias
5ca2cd3a1e Change defaults to log only errors 2021-01-03 07:18:41 +01:00
Matthias
634d6f3898 Change logging to stderr 2021-01-03 07:15:45 +01:00
Matthias
26c3463403 Stake-amount supports unlimited, too 2021-01-03 06:49:10 +01:00
Matthias
cff50f9f66 Add response-model for show_config 2021-01-03 06:49:10 +01:00
Matthias
ca0bb7bbb8 Don't require RPC for strategy 2021-01-03 06:49:07 +01:00
Matthias
e6176d43f3 Optional RPC dependency 2021-01-03 06:49:07 +01:00
Matthias
3dc37dd79d Add types for deps 2021-01-03 06:49:07 +01:00
Matthias
336dd1a29c Rename api_models to api_schemas 2021-01-03 06:49:07 +01:00
Matthias
84ced92002 Fix mock-tests missing some fields 2021-01-03 06:49:07 +01:00
Matthias
29f4dd1dcd Enhance some response models 2021-01-03 06:49:07 +01:00
Matthias
718f2b24d2 Don't use relative imports 2021-01-03 06:49:07 +01:00
Matthias
b2ab553a31 Rename api_server2 module to apiserver 2021-01-03 06:49:04 +01:00
Matthias
eb20f6e7d0 Align auth token to flask version to prevent user-logout 2021-01-03 06:49:04 +01:00
Matthias
346542e5cd Remove flask dependency 2021-01-03 06:49:04 +01:00
Matthias
68d148e72d Allow configuration of openAPI interface 2021-01-03 06:49:04 +01:00
Matthias
1717121f10 Properly use JWT secret key 2021-01-03 06:49:04 +01:00
Matthias
790f833653 Some more tests around api_auth 2021-01-03 06:49:04 +01:00
Matthias
29ce323649 Fix wrong hyperoptlosstest 2021-01-03 06:49:04 +01:00
Matthias
776ce57f55 Remove api_server 2021-01-03 06:49:04 +01:00
Matthias
54a50b1fb4 Fix some tests 2021-01-03 06:49:04 +01:00
Matthias
9f873305eb Improve response models 2021-01-03 06:49:04 +01:00
Matthias
9350f505bc Implement missing methods 2021-01-03 06:49:04 +01:00
Matthias
e23898d17b Improve some tests 2021-01-03 06:49:04 +01:00
Matthias
9ee1d88355 Implement more endpoints 2021-01-03 06:49:04 +01:00
Matthias
73a29e6d74 Improve tests, implement more fastapi methods 2021-01-03 06:49:04 +01:00
Matthias
a18d66e108 Add more endpoints to fastapi 2021-01-03 06:49:04 +01:00
Matthias
f37ea4ba24 Fix some initial tests towards fastAPI 2021-01-03 06:49:04 +01:00
Matthias
4b86700a0f Implement more endpoints 2021-01-03 06:48:56 +01:00
Matthias
5e4c4cae06 Fix auth providers 2021-01-03 06:48:53 +01:00
Matthias
86d0700884 Move models to apimodels 2021-01-03 06:46:25 +01:00
Matthias
6594278509 Reorder endpoints 2021-01-03 06:46:25 +01:00
Matthias
eac74a9dec Implement auth in fastapi 2021-01-03 06:46:25 +01:00
Matthias
619b855d5f Add version endpoint 2021-01-03 06:46:25 +01:00
Matthias
a862f19f82 Allow retrieval of rpc and config via dependencies 2021-01-03 06:46:25 +01:00
Matthias
1e38fec61b Initial fastapi implementation (Ping working) 2021-01-03 06:46:25 +01:00
Matthias
31829d5250 Merge pull request #4134 from hoeckxer/documentation_imports_custom_stoploss
Added imports to documentation for clarification when using custom st…
2021-01-02 09:23:43 +01:00
Matthias
11f36fbaee Fix all custom stoploss samples 2021-01-02 09:14:31 +01:00
Erwin Hoeckx
67ced6a53c Update docs/strategy-advanced.md
Co-authored-by: Matthias <xmatthias@outlook.com>
2021-01-01 20:49:04 +01:00
hoeckxer
e5840abaf9 Added imports to documentation for clarification when using custom stoploss
Signed-off-by: hoeckxer <hawkeyenl@yahoo.com>
2020-12-31 21:05:47 +01:00
Matthias
885da85fce Merge branch 'stable' into develop 2020-12-31 10:39:20 +01:00
Matthias
9e3224ccc0 Merge pull request #4089 from freqtrade/feat/stoploss_custom
introduce custom stoploss
2020-12-31 10:33:33 +01:00
Matthias
12de29dd3e Merge pull request #4133 from freqtrade/dynamic_pairlist
Wildcard based blacklist
2020-12-31 10:02:30 +01:00
Matthias
512e163355 change docstring to better reflect what the method is for 2020-12-31 09:48:49 +01:00
Matthias
bd7600ff06 Small visual changes 2020-12-31 09:43:24 +01:00
Matthias
04624aae40 Add documentation for wildcard-blacklist 2020-12-30 12:30:50 +01:00
Matthias
0affacd39a Support invalid regex blacklist from config 2020-12-30 10:14:22 +01:00
Matthias
9feabe707f Fix RPC methods to allow wildcards (and validate wildcards) 2020-12-30 09:57:31 +01:00
Matthias
704cf14383 Add expand_pairlist method 2020-12-30 09:55:44 +01:00
Matthias
2fdda8e448 plot-profit should fail gracefully if no trade is within the selected timerange
closes #4119
2020-12-30 08:30:41 +01:00
Matthias
b8899b39ec Show advanced plot-config section again
closes #4132
2020-12-30 06:29:59 +01:00
Matthias
9d7e0514ff Merge pull request #4131 from freqtrade/fix/krakenbalance
Fix/krakenbalance
2020-12-30 06:23:25 +01:00
Matthias
b607740dd1 Fix kraken balance bug if open buy orders exist 2020-12-29 20:06:37 +01:00
Matthias
238e9aabb1 Add test showing wrong behaviour 2020-12-29 20:05:07 +01:00
Matthias
f97e810429 Merge pull request #4127 from freqtrade/new_release
New release 2020.12
2020-12-29 07:07:54 +01:00
Matthias
0925a3cd19 Reinstate header partials 2020-12-28 14:12:39 +01:00
Matthias
a2fdb9d2f6 Move jquery to the bottom 2020-12-28 14:12:39 +01:00
Matthias
f6e56027b1 Reinstate jquery 2020-12-28 14:12:39 +01:00
Matthias
625da69fcb Remove custom header section from docs 2020-12-28 14:12:15 +01:00
Matthias
dcc7d559ee Reinstate header partials 2020-12-28 14:08:57 +01:00
Matthias
ecea6c9526 Move jquery to the bottom 2020-12-28 14:02:30 +01:00
Matthias
accc59aa1b Reinstate jquery 2020-12-28 13:49:08 +01:00
Matthias
8366e67fee Remove custom header section from docs 2020-12-28 10:19:50 +01:00
Matthias
003552d78c Remove custom header section from docs 2020-12-28 10:19:24 +01:00
Matthias
d4e42987e2 Merge pull request #4126 from freqtrade/dependabot/pip/develop/pandas-1.2.0
Bump pandas from 1.1.5 to 1.2.0
2020-12-28 10:09:05 +01:00
Matthias
f80ffe279b Version bump 2020.12 2020-12-28 09:54:58 +01:00
Matthias
ea22588649 Merge branch 'stable' into new_release 2020-12-28 09:54:46 +01:00
Matthias
fe8898c7f8 Merge pull request #4124 from freqtrade/dependabot/pip/develop/scikit-learn-0.24.0
Bump scikit-learn from 0.23.2 to 0.24.0
2020-12-28 09:54:29 +01:00
Matthias
0d4cf32086 Slightly adapt to pandas incompatibility 2020-12-28 09:50:48 +01:00
dependabot[bot]
30087697e0 Bump pandas from 1.1.5 to 1.2.0
Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.1.5 to 1.2.0.
- [Release notes](https://github.com/pandas-dev/pandas/releases)
- [Changelog](https://github.com/pandas-dev/pandas/blob/master/RELEASE.md)
- [Commits](https://github.com/pandas-dev/pandas/compare/v1.1.5...v1.2.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-28 08:21:17 +00:00
Matthias
8dca3c84f8 Merge pull request #4122 from freqtrade/dependabot/pip/develop/ccxt-1.39.79
Bump ccxt from 1.39.52 to 1.39.79
2020-12-28 09:19:15 +01:00
Matthias
0fc504fc4e Merge pull request #4125 from freqtrade/dependabot/pip/develop/blosc-1.10.1
Bump blosc from 1.9.2 to 1.10.1
2020-12-28 09:18:14 +01:00
Matthias
25b872a9c8 Merge pull request #4123 from freqtrade/dependabot/pip/develop/pymdown-extensions-8.1
Bump pymdown-extensions from 8.0.1 to 8.1
2020-12-28 09:17:09 +01:00
Matthias
a328bf58f4 Merge pull request #4121 from freqtrade/dependabot/pip/develop/mkdocs-material-6.2.3
Bump mkdocs-material from 6.1.7 to 6.2.3
2020-12-28 09:16:46 +01:00
dependabot[bot]
f492609115 Bump blosc from 1.9.2 to 1.10.1
Bumps [blosc](https://github.com/blosc/python-blosc) from 1.9.2 to 1.10.1.
- [Release notes](https://github.com/blosc/python-blosc/releases)
- [Changelog](https://github.com/Blosc/python-blosc/blob/master/RELEASE_NOTES.rst)
- [Commits](https://github.com/blosc/python-blosc/compare/v1.9.2...v1.10.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-28 05:37:17 +00:00
dependabot[bot]
092ebf845d Bump scikit-learn from 0.23.2 to 0.24.0
Bumps [scikit-learn](https://github.com/scikit-learn/scikit-learn) from 0.23.2 to 0.24.0.
- [Release notes](https://github.com/scikit-learn/scikit-learn/releases)
- [Commits](https://github.com/scikit-learn/scikit-learn/compare/0.23.2...0.24.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-28 05:37:16 +00:00
dependabot[bot]
87b896879f Bump ccxt from 1.39.52 to 1.39.79
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.39.52 to 1.39.79.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.39.52...1.39.79)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-28 05:37:13 +00:00
dependabot[bot]
10840ec170 Bump pymdown-extensions from 8.0.1 to 8.1
Bumps [pymdown-extensions](https://github.com/facelessuser/pymdown-extensions) from 8.0.1 to 8.1.
- [Release notes](https://github.com/facelessuser/pymdown-extensions/releases)
- [Commits](https://github.com/facelessuser/pymdown-extensions/compare/8.0.1...8.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-28 05:37:13 +00:00
dependabot[bot]
b3e929d14b Bump mkdocs-material from 6.1.7 to 6.2.3
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 6.1.7 to 6.2.3.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/6.1.7...6.2.3)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-28 05:37:11 +00:00
Matthias
8cf3dbb682 Merge pull request #4110 from freqtrade/test/exchange_ccxt
add tests to verify exchange compatibility with ccxt
2020-12-26 16:32:25 +01:00
Matthias
7d2b9447d0 Update slack link 2020-12-26 15:30:55 +01:00
Matthias
4b910426ff Merge pull request #4113 from freqtrade/ref/rpc
Refactor RPC dependency tree
2020-12-25 10:10:50 +01:00
Matthias
a87c273903 Refactor RPC modules so handlers don't inherit RPC directly 2020-12-24 09:09:23 +01:00
Matthias
1508e08ea5 Move fiatconvert init to RPC class 2020-12-24 08:39:00 +01:00
Matthias
48f7997a77 Merge pull request #4112 from freqtrade/tests/telegram
simplify telegram tests
2020-12-24 08:24:11 +01:00
Matthias
5bf739b917 Simplify more telegram tests 2020-12-24 07:39:46 +01:00
Matthias
be4a4be7a3 Further simplify test_telegram 2020-12-24 07:29:26 +01:00
Matthias
4cbbb80bc3 Refactor test_telegram to simplify tests 2020-12-24 07:10:01 +01:00
Matthias
516e56bfaa Move init of _config to apiserver parent 2020-12-23 20:50:32 +01:00
Matthias
0b98f19f2c Merge pull request #4111 from freqtrade/move_pairlist_plugins
Move pairlists to be a plugin submodule
2020-12-23 19:10:08 +01:00
Matthias
f11fd2fee1 Sort imports 2020-12-23 17:00:02 +01:00
Matthias
67193bca3d Move pairlists to be a plugin submodule 2020-12-23 16:54:35 +01:00
Matthias
7cef5ac217 Merge pull request #4092 from MrKrautee/telegram
Telegram: specify custom keyboard in config
2020-12-23 16:25:37 +01:00
Matthias
1713841d0b Initialize markets at startup for ccxt tests 2020-12-23 16:20:25 +01:00
Matthias
721d0fb2a8 Improve wording of developer docs 2020-12-23 16:00:26 +01:00
Christof
74bcd82c3d Exception msg 2020-12-23 16:00:01 +01:00
Matthias
65d91b7cbb Add note on adding new exchange with compat tests 2020-12-23 15:53:41 +01:00
Matthias
5599490aa2 Adjust ohlcv test after rebase 2020-12-23 15:50:24 +01:00
Matthias
b39de171c8 Don't run longrun regularily 2020-12-23 15:46:08 +01:00
Matthias
0981287c62 Improve test syntax for ccxt_compat tests 2020-12-23 15:41:59 +01:00
Matthias
2016eea212 Fix some test-errors in ccxt_compat 2020-12-23 15:41:59 +01:00
Matthias
a6e6ce16b1 Fix test failures 2020-12-23 15:41:59 +01:00
Matthias
b7d4ff9c21 Add test for fetch_ohlcv (against exchange) 2020-12-23 15:41:59 +01:00
Matthias
7833d9935c Add dummy test for fetch_ohlcv 2020-12-23 15:41:59 +01:00
Matthias
79ed89e487 Add test for fee calculation 2020-12-23 15:41:59 +01:00
Matthias
38af1b2a5d Improve compat tests 2020-12-23 15:41:59 +01:00
Matthias
36d60fa8a8 First small compat test 2020-12-23 15:41:59 +01:00
Christof
b1fe5940fa check for Exception and log msgs 2020-12-22 13:01:01 +01:00
Christof
cd1a8e2c42 better error msg 2020-12-22 12:39:27 +01:00
Christof
be28b42bfa Exception for invalid keyboard config 2020-12-22 12:34:21 +01:00
Matthias
4dadfd199d Documentation syntax 2020-12-22 07:36:53 +01:00
Matthias
39579b6e5d Merge pull request #4093 from freqtrade/ohlcv_limit
set ohlcv limit on ccxt calls
2020-12-21 19:34:50 +01:00
Matthias
9d37ac9955 Merge pull request #4094 from MrKrautee/plot_area
Plot area between traces
2020-12-21 19:30:07 +01:00
Matthias
9c0850ff50 Merge pull request #4103 from MrKrautee/tg_docs
added /locks to command list
2020-12-21 10:22:54 +01:00
Christof
78dff3d510 docs: Note syntax 2020-12-21 10:22:24 +01:00
Christof
2787ba0809 added /locks to command list 2020-12-21 10:03:27 +01:00
Christof
277f3ff47b tests: cleaup 2020-12-21 09:52:10 +01:00
Matthias
5bd14bcccf Merge pull request #4100 from freqtrade/dependabot/pip/develop/pytest-mock-3.4.0
Bump pytest-mock from 3.3.1 to 3.4.0
2020-12-21 09:31:44 +01:00
Matthias
bbc049c838 Merge pull request #4102 from freqtrade/dependabot/pip/develop/sqlalchemy-1.3.22
Bump sqlalchemy from 1.3.20 to 1.3.22
2020-12-21 09:31:35 +01:00
dependabot[bot]
d25fe58574 Bump sqlalchemy from 1.3.20 to 1.3.22
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.3.20 to 1.3.22.
- [Release notes](https://github.com/sqlalchemy/sqlalchemy/releases)
- [Changelog](https://github.com/sqlalchemy/sqlalchemy/blob/master/CHANGES)
- [Commits](https://github.com/sqlalchemy/sqlalchemy/commits)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-21 08:19:10 +00:00
dependabot[bot]
8eb0130200 Bump pytest-mock from 3.3.1 to 3.4.0
Bumps [pytest-mock](https://github.com/pytest-dev/pytest-mock) from 3.3.1 to 3.4.0.
- [Release notes](https://github.com/pytest-dev/pytest-mock/releases)
- [Changelog](https://github.com/pytest-dev/pytest-mock/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest-mock/compare/v3.3.1...v3.4.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-21 08:18:53 +00:00
Matthias
fae631c026 Merge pull request #4096 from freqtrade/dependabot/pip/develop/pytest-6.2.1
Bump pytest from 6.2.0 to 6.2.1
2020-12-21 07:41:05 +01:00
Matthias
08a1f74748 Merge pull request #4098 from freqtrade/dependabot/pip/develop/flake8-tidy-imports-4.2.1
Bump flake8-tidy-imports from 4.2.0 to 4.2.1
2020-12-21 07:40:46 +01:00
Matthias
537c20ed87 Merge pull request #4097 from freqtrade/dependabot/pip/develop/questionary-1.9.0
Bump questionary from 1.8.1 to 1.9.0
2020-12-21 07:40:20 +01:00
Matthias
3a46f02682 Merge pull request #4101 from freqtrade/dependabot/pip/develop/ccxt-1.39.52
Bump ccxt from 1.39.33 to 1.39.52
2020-12-21 07:39:34 +01:00
Matthias
e587c005de Merge pull request #4099 from freqtrade/dependabot/pip/develop/joblib-1.0.0
Bump joblib from 0.17.0 to 1.0.0
2020-12-21 07:35:09 +01:00
Matthias
bc110cfe8f Merge pull request #4095 from freqtrade/dependabot/pip/develop/requests-2.25.1
Bump requests from 2.25.0 to 2.25.1
2020-12-21 07:34:30 +01:00
dependabot[bot]
3b67863914 Bump ccxt from 1.39.33 to 1.39.52
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.39.33 to 1.39.52.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.39.33...1.39.52)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-21 05:36:27 +00:00
dependabot[bot]
a2873096c8 Bump flake8-tidy-imports from 4.2.0 to 4.2.1
Bumps [flake8-tidy-imports](https://github.com/adamchainz/flake8-tidy-imports) from 4.2.0 to 4.2.1.
- [Release notes](https://github.com/adamchainz/flake8-tidy-imports/releases)
- [Changelog](https://github.com/adamchainz/flake8-tidy-imports/blob/master/HISTORY.rst)
- [Commits](https://github.com/adamchainz/flake8-tidy-imports/compare/4.2.0...4.2.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-21 05:36:24 +00:00
dependabot[bot]
5716202e45 Bump joblib from 0.17.0 to 1.0.0
Bumps [joblib](https://github.com/joblib/joblib) from 0.17.0 to 1.0.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/0.17.0...1.0.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-21 05:36:24 +00:00
dependabot[bot]
fe27206926 Bump questionary from 1.8.1 to 1.9.0
Bumps [questionary](https://github.com/tmbo/questionary) from 1.8.1 to 1.9.0.
- [Release notes](https://github.com/tmbo/questionary/releases)
- [Commits](https://github.com/tmbo/questionary/compare/1.8.1...1.9.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-21 05:36:12 +00:00
dependabot[bot]
a1755364e1 Bump pytest from 6.2.0 to 6.2.1
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.2.0 to 6.2.1.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/6.2.0...6.2.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-21 05:36:09 +00:00
dependabot[bot]
e7e687c8ec Bump requests from 2.25.0 to 2.25.1
Bumps [requests](https://github.com/psf/requests) from 2.25.0 to 2.25.1.
- [Release notes](https://github.com/psf/requests/releases)
- [Changelog](https://github.com/psf/requests/blob/master/HISTORY.md)
- [Commits](https://github.com/psf/requests/compare/v2.25.0...v2.25.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-21 05:36:08 +00:00
Christof
5423c21be0 keyboard type 2020-12-20 22:51:40 +01:00
Christof
f39dde121a moved keyboard config validation to __inti__ 2020-12-20 22:36:56 +01:00
Christof
18a24d75ef cleanup 2020-12-20 21:31:01 +01:00
Christof
3cb559994e some more test 2020-12-20 21:31:01 +01:00
Christof
c1b8ad7232 renaming, comments, cleanups 2020-12-20 21:31:01 +01:00
Christof
fabb31e1bc imports order 2020-12-20 21:31:01 +01:00
Christof
f120c8d6c7 documentation 2020-12-20 21:31:01 +01:00
Christof
f24626e139 removed too many blank lines 2020-12-20 21:31:01 +01:00
Christof
43091a26ce simple tests 2020-12-20 21:28:57 +01:00
Christof
8b24878023 plot_config documentation for fill_to, fill_label, fill_color 2020-12-20 21:28:57 +01:00
Christof
5b2902fcbc cleanup 2020-12-20 21:28:57 +01:00
Christof
16baca5eeb fixed: too complex warning 2020-12-20 21:28:57 +01:00
Christof
d901a86165 typo 2020-12-20 21:28:57 +01:00
Christof
75e4758936 changed config params, added fill area in subplots 2020-12-20 21:28:57 +01:00
Christof
cc39cf97dd revert to former create_plotconfig behaviour 2020-12-20 21:28:57 +01:00
Christof
4531c924da PEP8 2020-12-20 21:28:57 +01:00
Christof
fb3d82ccb9 cleanup 2020-12-20 21:28:57 +01:00
Christof
fdd4b40c34 fixed subplots, empty create plot_config if its not given by strategie 2020-12-20 21:28:57 +01:00
Christof
daa1727e2b Exeption for fill_area.traces 2020-12-20 21:28:57 +01:00
Christof
3fdfc06a1e label for fill_area added and documentation updated 2020-12-20 21:28:57 +01:00
Christof
ecadfdd98e fixed:advanced config. added. feature: fill area between traces by advanced configuration. 2020-12-20 21:28:57 +01:00
Christof
6b44545d37 sort order imports 2020-12-20 21:06:45 +01:00
Christof
799e6be2eb fix tests 2020-12-20 21:06:45 +01:00
Christof
621105df9a renaming shortcut_btns to keyboard 2020-12-20 21:06:45 +01:00
Christof
bf92099486 test for custom keyboard 2020-12-20 21:06:45 +01:00
Christof
5b3ffd5141 better log msg, comments 2020-12-20 21:06:45 +01:00
Christof
5e6897b278 documentation for custom keyboard 2020-12-20 21:06:45 +01:00
Christof
e92bcb00f6 telegram: specify custom shortcut bottons (keyboard) in config.json 2020-12-20 21:06:45 +01:00
Matthias
8d3f096a97 AgeFilter does not require tickers 2020-12-20 20:08:54 +01:00
Matthias
d7daa86434 Add bybit subclass 2020-12-20 19:59:46 +01:00
Matthias
bd0af1b300 Fix test warning 2020-12-20 19:38:12 +01:00
Matthias
7d2395ddb7 Add limit parameter to fetch_ohlcv 2020-12-20 19:33:04 +01:00
Matthias
fc0d14c1b5 Improve documentation 2020-12-20 19:14:18 +01:00
Matthias
676dd0d664 Improve documentation 2020-12-20 11:22:15 +01:00
Matthias
9d5961e224 Rename method to custom_stoploss 2020-12-20 11:17:50 +01:00
Matthias
277342f167 Rename flag to "use_custom_stoposs" 2020-12-20 11:12:22 +01:00
Matthias
8574751a07 Add stoploss_value to strategy template 2020-12-20 10:49:22 +01:00
Matthias
f8639fe938 Add more tests for custom_loss 2020-12-19 20:36:19 +01:00
Matthias
5f8610b28f Add explicit test for stop_loss_reached 2020-12-19 20:08:03 +01:00
Matthias
22d64553c9 Rename test file 2020-12-19 18:00:44 +01:00
Matthias
ea4238e860 cleanup some tests 2020-12-19 17:59:49 +01:00
Matthias
11e2915621 Fix documentation problem 2020-12-19 17:44:39 +01:00
Matthias
6892c08e9b Improve docstring 2020-12-19 13:18:06 +01:00
Matthias
b2c1098316 more docs for dynamic stoploss method 2020-12-19 12:03:18 +01:00
Matthias
f7b54c2415 Allow and document time-based custom stoploss
closes #3206
2020-12-19 11:46:49 +01:00
Matthias
f235ab8cf4 Fix some typos in docs 2020-12-19 11:39:21 +01:00
Matthias
18795844d8 Add initial set of custom stoploss documentation 2020-12-19 11:37:20 +01:00
Matthias
a414b57d54 Experiment with custom stoploss interface 2020-12-18 06:56:56 +01:00
Matthias
8f6aefb591 Extract stoploss assignment 2020-12-18 06:56:56 +01:00
Matthias
768a24c375 Add stoplossvalue interface 2020-12-18 06:56:56 +01:00
Matthias
b9f3410d8b Merge pull request #4082 from bigchakalaka/patch-2
Update strategy-customization.md
2020-12-18 06:16:13 +01:00
bigchakalaka
ca9fd08991 Update strategy-customization.md 2020-12-17 21:40:54 +01:00
Matthias
4e7f914e92 Improve test for AgeFilter, fix bug in Agefilter 2020-12-17 13:34:53 +01:00
Matthias
266031a6be Disallow PerformanceFilter for backtesting
closes #4072
2020-12-16 19:24:47 +01:00
Matthias
8441d0f60f Merge pull request #4069 from freqtrade/refactor_ohlcv_download
Refactor pairlist ohlcv download to use async
2020-12-16 19:11:49 +01:00
Matthias
d1fda28d2e Fix typehints 2020-12-15 20:59:58 +01:00
Matthias
011ba1d9ae Adapt tests to use async methods 2020-12-15 20:49:46 +01:00
Matthias
3c85d5201f Use async to get candle data for pairlists 2020-12-15 20:38:26 +01:00
Matthias
f320cb0d7a Merge pull request #4068 from freqtrade/docker_stage_pi
Docker stage pi
2020-12-15 19:10:10 +01:00
Matthias
4c0edd0461 Move dependencies to base image for RPI 2020-12-15 09:28:56 +01:00
Matthias
c8dde63227 Allow test-pairlist to run with verbosity 2020-12-15 09:23:40 +01:00
Matthias
69901c1314 Provide pair to _validate_pairs in pairlists 2020-12-15 09:23:40 +01:00
Matthias
39fec25ae0 add optional Cache arguments to refresh_pairs method 2020-12-15 09:23:27 +01:00
Matthias
dc92808335 Change PI dockerfile to use staged build 2020-12-15 06:44:08 +01:00
Matthias
ca9036ee1d Merge pull request #3840 from freqtrade/dependabot/docker/python-3.9.0-slim-buster
Bump python from 3.8.6-slim-buster to 3.9.1-slim-buster (build actions using 3.9)
2020-12-14 20:44:29 +01:00
Matthias
e240f0b372 Merge branch 'develop' into dependabot/docker/python-3.9.0-slim-buster 2020-12-14 20:18:34 +01:00
Matthias
8366e2fd89 Merge pull request #4064 from freqtrade/dependabot/pip/develop/pytest-6.2.0
Bump pytest from 6.1.2 to 6.2.0
2020-12-14 19:46:13 +01:00
Matthias
9f5c4ead15 Remove support for 3.6 2020-12-14 19:18:54 +01:00
Matthias
66d5271ada Don't build for 3.6 any longer 2020-12-14 19:10:24 +01:00
Matthias
ba869a330f Build 3.6 on github actions too 2020-12-14 19:05:41 +01:00
dependabot[bot]
8965b8a18d Bump pytest from 6.1.2 to 6.2.0
Bumps [pytest](https://github.com/pytest-dev/pytest) from 6.1.2 to 6.2.0.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/6.1.2...6.2.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-14 08:27:36 +00:00
Matthias
bf48df92c1 Merge pull request #4059 from freqtrade/dependabot/pip/develop/plotly-4.14.1
Bump plotly from 4.13.0 to 4.14.1
2020-12-14 07:42:24 +01:00
Matthias
e763aa04bd Merge pull request #4062 from freqtrade/dependabot/pip/develop/python-rapidjson-1.0
Bump python-rapidjson from 0.9.4 to 1.0
2020-12-14 07:40:49 +01:00
Matthias
36fba4826f Merge pull request #4058 from freqtrade/dependabot/pip/develop/cachetools-4.2.0
Bump cachetools from 4.1.1 to 4.2.0
2020-12-14 07:40:03 +01:00
Matthias
4813eec308 Merge pull request #4063 from freqtrade/dependabot/pip/develop/ccxt-1.39.33
Bump ccxt from 1.39.10 to 1.39.33
2020-12-14 07:39:30 +01:00
Matthias
aace993842 Merge pull request #4061 from freqtrade/dependabot/pip/develop/pandas-1.1.5
Bump pandas from 1.1.4 to 1.1.5
2020-12-14 07:38:45 +01:00
Matthias
834cf384f6 Merge pull request #4060 from freqtrade/dependabot/pip/develop/flake8-tidy-imports-4.2.0
Bump flake8-tidy-imports from 4.1.0 to 4.2.0
2020-12-14 07:37:00 +01:00
dependabot[bot]
a9b586d338 Bump ccxt from 1.39.10 to 1.39.33
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.39.10 to 1.39.33.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.39.10...1.39.33)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-14 05:44:16 +00:00
dependabot[bot]
44f295110b Bump python-rapidjson from 0.9.4 to 1.0
Bumps [python-rapidjson](https://github.com/python-rapidjson/python-rapidjson) from 0.9.4 to 1.0.
- [Release notes](https://github.com/python-rapidjson/python-rapidjson/releases)
- [Changelog](https://github.com/python-rapidjson/python-rapidjson/blob/master/CHANGES.rst)
- [Commits](https://github.com/python-rapidjson/python-rapidjson/compare/v0.9.4...v1.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-14 05:43:46 +00:00
dependabot[bot]
bdd895b8da Bump pandas from 1.1.4 to 1.1.5
Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.1.4 to 1.1.5.
- [Release notes](https://github.com/pandas-dev/pandas/releases)
- [Changelog](https://github.com/pandas-dev/pandas/blob/master/RELEASE.md)
- [Commits](https://github.com/pandas-dev/pandas/compare/v1.1.4...v1.1.5)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-14 05:43:42 +00:00
dependabot[bot]
a3139dd9d4 Bump flake8-tidy-imports from 4.1.0 to 4.2.0
Bumps [flake8-tidy-imports](https://github.com/adamchainz/flake8-tidy-imports) from 4.1.0 to 4.2.0.
- [Release notes](https://github.com/adamchainz/flake8-tidy-imports/releases)
- [Changelog](https://github.com/adamchainz/flake8-tidy-imports/blob/master/HISTORY.rst)
- [Commits](https://github.com/adamchainz/flake8-tidy-imports/compare/4.1.0...4.2.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-14 05:43:34 +00:00
dependabot[bot]
4cf16fa8d1 Bump plotly from 4.13.0 to 4.14.1
Bumps [plotly](https://github.com/plotly/plotly.py) from 4.13.0 to 4.14.1.
- [Release notes](https://github.com/plotly/plotly.py/releases)
- [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md)
- [Commits](https://github.com/plotly/plotly.py/compare/v4.13.0...v4.14.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-14 05:43:33 +00:00
dependabot[bot]
3bea9255e7 Bump cachetools from 4.1.1 to 4.2.0
Bumps [cachetools](https://github.com/tkem/cachetools) from 4.1.1 to 4.2.0.
- [Release notes](https://github.com/tkem/cachetools/releases)
- [Changelog](https://github.com/tkem/cachetools/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/tkem/cachetools/compare/v4.1.1...v4.2.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-14 05:43:33 +00:00
Matthias
dad427461d Downgrade dockerfile to 3.8.6 to avoid image bloat 2020-12-13 13:11:04 +01:00
Matthias
a4bfd0b0aa Split linux and OSX builds into 2 seperate, parallel jobs 2020-12-13 11:25:42 +01:00
Matthias
be555895b2 Merge branch 'develop' into dependabot/docker/python-3.9.0-slim-buster 2020-12-13 11:24:23 +01:00
Matthias
7c6357cc45 Merge pull request #4041 from freqtrade/plugins/protections_backtest
Introduce Protection Plugins
2020-12-13 11:08:24 +01:00
Matthias
657b002a81 Explicitly check for False in fetch_ticker 2020-12-13 10:59:29 +01:00
Matthias
9cd1be8f93 Update usage of open_trade_price to open_trade_value 2020-12-13 10:33:45 +01:00
Matthias
7eab33de08 Merge branch 'develop' into plugins/protections_backtest 2020-12-13 10:31:33 +01:00
Matthias
8a2fbf6592 Small cleanup of protection stuff 2020-12-13 10:16:09 +01:00
Matthias
1436dc58f5 Merge pull request #4057 from freqtrade/avoid_high_fee_wrong_reports
Avoid high fee wrong reports
2020-12-12 20:04:06 +01:00
Matthias
14647fb5f0 Add tests for update fee 2020-12-12 11:49:52 +01:00
Matthias
3ee7fe64ba Clean up some tests 2020-12-12 11:25:56 +01:00
Matthias
181b88dc75 Don't accept too high fees, assuming they are erroneous
Forces fallback to "detection from trades"
2020-12-12 10:52:27 +01:00
Matthias
aa4ac87fd4 Merge pull request #4052 from Samaoo/patch-4
Update backtesting.md
2020-12-12 10:48:23 +01:00
Samaoo
b45c2fb1d0 Update backtesting.md 2020-12-12 10:27:17 +01:00
Matthias
6107878f4e Bump ccxt to 1.39.10
closes #4051
2020-12-12 07:08:29 +01:00
Matthias
aab8e36e78 Merge pull request #4046 from dmmop/patch-1
Update dockerfile to multistage
2020-12-11 09:10:36 +01:00
Matthias
c784e5780e Merge pull request #4054 from freqtrade/models_open_price
Models open price
2020-12-11 06:31:02 +01:00
Matthias
95fd3824da Finish renamal of open_trade_price to open_value 2020-12-10 19:36:52 +01:00
Matthias
201cc67e05 Rename open_trade_price to "open_trade_value" 2020-12-10 19:21:20 +01:00
Matthias
76594d5dde Merge pull request #3799 from imxuwang/issue3783
Introduce Telegram /stats endpoint
2020-12-10 15:41:09 +01:00
Matthias
ca99d484fc Refactor to use list comprehension 2020-12-10 07:39:50 +01:00
Matthias
33f330256b Reorder commands on telegram init 2020-12-09 20:36:30 +01:00
Samaoo
af53dfbfab Update backtesting.md 2020-12-09 15:57:15 +01:00
Samaoo
f5817063b7 Update backtesting.md 2020-12-09 15:53:38 +01:00
David Martinez Martin
25f8e0cc57 Added git packages for future dependencies 2020-12-09 11:28:45 +01:00
David Martinez Martin
5708098256 Move ENV PATH to base image 2020-12-09 10:34:38 +01:00
Matthias
7126aa9514 Merge pull request #4047 from freqtrade/strategy_manatory
Ensure non-defined attributes fail correctly
2020-12-09 10:27:29 +01:00
Matthias
f1af2972e2 Ensure non-defined attributes fail correctly
Remove unnecessary check, as stoploss cannot be none (it's mandatory and
a number)
2020-12-09 07:55:08 +01:00
David Martinez Martin
e6b3e64534 Update dockerfile to multistage
This change reduce the image size from 727Mb to 469Mb.
2020-12-09 03:27:59 +01:00
Matthias
d9a86158f4 Add cmake to support raspberry 64bit installs 2020-12-08 19:46:54 +01:00
Matthias
ad7b29cc1d Merge pull request #4045 from Samaoo/patch-3
Update data-download.md
2020-12-08 19:14:17 +01:00
Samaoo
118a22d010 Update data-download.md 2020-12-08 18:04:26 +01:00
Matthias
9725b8e17c Update Dockerfile 2020-12-08 08:43:22 +01:00
Matthias
f897b683c7 Add seperate page describing plugins 2020-12-07 19:22:14 +01:00
Matthias
82bc6973fe Add last key to config_full 2020-12-07 16:16:33 +01:00
Matthias
c37bc307e2 Small finetunings to documentation 2020-12-07 16:12:03 +01:00
Matthias
b5289d5f0e Update full config with correct protection keys 2020-12-07 16:02:55 +01:00
Matthias
de2cc9708d Fix test leakage 2020-12-07 16:01:29 +01:00
Matthias
f047297995 Improve wording, fix bug 2020-12-07 15:48:06 +01:00
Matthias
3ab5514697 Add API endpoint for /stats 2020-12-07 15:07:08 +01:00
Matthias
81410fb404 Document /stats for telegram 2020-12-07 15:03:16 +01:00
Matthias
e873cafdc4 Beautify code a bit 2020-12-07 14:54:39 +01:00
Matthias
effc96e92b Improve tests for backtest protections 2020-12-07 11:39:01 +01:00
Matthias
5849d07497 Export locks as part of backtesting 2020-12-07 11:39:01 +01:00
Matthias
57a4044eb0 Enhance test verifying that locks are not replaced 2020-12-07 11:39:01 +01:00
Matthias
bb51da8297 Fix slow backtest due to protections 2020-12-07 11:39:01 +01:00
Matthias
75a5161650 Support multis-strategy backtests with protections 2020-12-07 11:39:01 +01:00
Matthias
a3f9cd2c26 Only load protections when necessary 2020-12-07 11:39:01 +01:00
Matthias
946fb09455 Update help command output 2020-12-07 11:39:01 +01:00
Matthias
e2d15f4082 Add parameter to enable protections for backtesting 2020-12-07 11:39:01 +01:00
Matthias
32189d27c8 Disable output from plugins in backtesting 2020-12-07 11:39:01 +01:00
Matthias
9f34aebdaa Allow closing trades without message 2020-12-07 11:39:01 +01:00
Matthias
b606936eb7 Make changes to backtesting to incorporate protections 2020-12-07 11:39:01 +01:00
Matthias
98c88fa58e Prepare protections for backtesting 2020-12-07 11:39:01 +01:00
Matthias
3426e99b8b Improve formatting of protection startup message 2020-12-07 11:37:57 +01:00
Matthias
64d6c7bb65 Update developer docs 2020-12-07 11:17:11 +01:00
Matthias
0e2a43ab4d Add duration_explanation functions 2020-12-07 11:12:09 +01:00
Matthias
c993831a04 Add protections to startup messages 2020-12-07 10:57:01 +01:00
Matthias
d4799e6aa3 Implement *candle definitions 2020-12-07 10:54:26 +01:00
Matthias
a93bb6853b Document *candles settings, implement validations 2020-12-07 10:47:13 +01:00
Matthias
eb952d77be Move lookback_period to parent __init__ 2020-12-07 08:27:14 +01:00
Matthias
f13e9ce5ed Improve docs 2020-12-07 08:23:10 +01:00
Matthias
b36f333b2f Add new protections to full sample, documentation 2020-12-07 08:23:10 +01:00
Matthias
f06b58dc91 Test MaxDrawdown desc 2020-12-07 08:23:10 +01:00
Matthias
089c463cfb Introduce max_drawdown protection 2020-12-07 08:23:10 +01:00
Matthias
9d6f3a89ef Improve docs and fix typos 2020-12-07 08:23:10 +01:00
Matthias
768d7fa196 Readd optional for get_pair_locks - it's necessary 2020-12-07 08:23:10 +01:00
Matthias
9947dcd1da Beta feature warning 2020-12-07 08:23:10 +01:00
Matthias
ad746627b3 Fix lock-loop 2020-12-07 08:23:10 +01:00
Matthias
397a15cb61 Improve protection documentation 2020-12-07 08:23:10 +01:00
Matthias
4351a26b4c Move stop_duration to parent class
avoids reimplementation and enhances standardization
2020-12-07 08:23:10 +01:00
Matthias
12e84bda1e Add developer docs for Protections 2020-12-07 08:23:10 +01:00
Matthias
6d0f16920f Get Longest lock logic 2020-12-07 08:23:10 +01:00
Matthias
dce2364672 Add stoploss per pair support 2020-12-07 08:23:10 +01:00
Matthias
dcdf4a0503 Improve tests 2020-12-07 08:23:10 +01:00
Matthias
32cde1cb7d Improve test for lowprofitpairs 2020-12-07 08:23:10 +01:00
Matthias
8f958ef723 Improve login-mixin structure 2020-12-07 08:23:10 +01:00
Matthias
8d9c66a638 Add LogginMixin to freqtradebot class to avoid over-logging 2020-12-07 08:23:10 +01:00
Matthias
be57ceb252 Remove confusing entry
(in this branch of the if statement, candle_date is empty
2020-12-07 08:23:10 +01:00
Matthias
5e3d2401f5 Only call stop methods when they actually support this method 2020-12-07 08:23:10 +01:00
Matthias
2cd54a5933 Allow disabling output from plugins 2020-12-07 08:23:10 +01:00
Matthias
8ebd6ad200 Rename login-mixin log method 2020-12-07 08:23:10 +01:00
Matthias
2e5b9fd4b2 format profit in low_profit_pairs 2020-12-07 08:23:10 +01:00
Matthias
e29d918ea5 Avoid double-locks also in per pair locks 2020-12-07 08:23:10 +01:00
Matthias
fc97266dd4 Add "now" to lock_pair method 2020-12-07 08:23:10 +01:00
Matthias
59091ef2b7 Add helper method to calculate protection until 2020-12-07 08:23:10 +01:00
Matthias
47cd856fea Include protection documentation 2020-12-07 08:23:10 +01:00
Matthias
5133675988 Apply all stops in the list, even if the first would apply already 2020-12-07 08:23:10 +01:00
Matthias
9484ee6690 Test for low_profit_pairs 2020-12-07 08:23:10 +01:00
Matthias
bb06365c50 Improve protection documentation 2020-12-07 08:23:10 +01:00
Matthias
1f703dc341 Improve protection documentation 2020-12-07 08:23:10 +01:00
Matthias
00d4820bc1 Add low_profit_pairs 2020-12-07 08:23:10 +01:00
Matthias
9f6c2a583f Better wording for config options 2020-12-07 08:23:10 +01:00
Matthias
8dbef6bbea Add test for cooldown period 2020-12-07 08:23:10 +01:00
Matthias
fe0afb9883 Implement calling of per-pair protection 2020-12-07 08:23:10 +01:00
Matthias
2a66c33a4e Add locks per pair 2020-12-07 08:23:10 +01:00
Matthias
ff7ba23477 Simplify enter_positions and add global pairlock check 2020-12-07 08:23:10 +01:00
Matthias
05be33ccd4 Simplify is_pair_locked 2020-12-07 08:23:10 +01:00
Matthias
56975db2ed Add more tests 2020-12-07 08:23:10 +01:00
Matthias
2b85e7eac3 Add initial tests for StoplossGuard protection 2020-12-07 08:23:10 +01:00
Matthias
816703b8e1 Improve protections work 2020-12-07 08:23:10 +01:00
Matthias
f39a534fc0 Implement global stop (First try) 2020-12-07 08:23:10 +01:00
Matthias
246b4a57a4 add small note to pairlist dev docs 2020-12-07 08:23:10 +01:00
Matthias
04878c3ce1 Rename test directory for pairlist 2020-12-07 08:23:10 +01:00
Matthias
3447f1ae53 Implement first stop method 2020-12-07 08:23:10 +01:00
Matthias
a0bd2ce837 Add first version of protection manager 2020-12-07 08:23:10 +01:00
Matthias
b6b9c8e5cc Move "slow-log" to it's own mixin 2020-12-07 08:23:10 +01:00
Matthias
f01d86060a Merge pull request #4040 from freqtrade/dependabot/pip/develop/ccxt-1.38.87
Bump ccxt from 1.38.55 to 1.38.87
2020-12-07 08:12:30 +01:00
Matthias
1fd652d3de Merge pull request #4039 from freqtrade/dependabot/pip/develop/mkdocs-material-6.1.7
Bump mkdocs-material from 6.1.6 to 6.1.7
2020-12-07 08:06:02 +01:00
dependabot[bot]
647e6509a4 Bump ccxt from 1.38.55 to 1.38.87
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.38.55 to 1.38.87.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.38.55...1.38.87)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-07 05:48:43 +00:00
dependabot[bot]
0c0eb8236d Bump mkdocs-material from 6.1.6 to 6.1.7
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 6.1.6 to 6.1.7.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/docs/changelog.md)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/6.1.6...6.1.7)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-07 05:48:23 +00:00
Matthias
51fbd0698c Move get_logs to be static method 2020-12-06 19:57:48 +01:00
Matthias
245c19f5e9 Add simple test for /stats call 2020-12-05 14:48:56 +01:00
Matthias
aa27c9ace2 Reorder methods in telegram
/stats is closely related to /profit
2020-12-05 14:39:50 +01:00
Matthias
143423145c Refactor most of the logic to rpc.py
this way /stats can be used by other RPC methods too
2020-12-05 14:38:42 +01:00
Matthias
c556d1b37e Make /stats working 2020-12-05 14:06:46 +01:00
Matthias
8f61b68b2a Merge branch 'develop' into pr/imxuwang/3799 2020-12-05 14:06:23 +01:00
Matthias
058d40a72c Fix telegram /daily command without arguments 2020-12-05 08:16:40 +01:00
Matthias
71e46794b4 Add updating documentation
closes #4036
2020-12-04 19:59:26 +01:00
Matthias
f37af9d98a Merge pull request #4033 from Samaoo/patch-2
Put dollar sign after amount in edge.md
2020-12-04 19:50:46 +01:00
Samaoo
7f453033a4 Update edge.md 2020-12-04 16:53:41 +01:00
Matthias
2fbbeb970b Gracefully handle cases where no buy price was found
closes #4030
2020-12-04 07:42:16 +01:00
Matthias
22595e6f92 Merge pull request #3929 from radwayne/roi_trailing_backtest
change backtesting behaviour if roi and trailing-stop happen at the same time
2020-12-03 19:40:46 +01:00
Matthias
01cb676f2c Merge pull request #4015 from freqtrade/dependabot/pip/develop/python-telegram-bot-13.1
Bump python-telegram-bot from 13.0 to 13.1
2020-12-02 19:02:10 +01:00
Matthias
9e063b9fc8 Merge pull request #4026 from Samaoo/patch-1
Update faq.md
2020-12-02 09:05:59 +01:00
Samaoo
9b4a81c0a4 Update faq.md 2020-12-02 08:40:49 +01:00
Matthias
c09c23eab1 Make sure non-int telegram values don't crash the bot 2020-12-02 07:51:59 +01:00
Matthias
d039ce1fb3 Update available columns for hyperopt
closes #4025
2020-12-02 06:46:18 +01:00
Matthias
4f8bc73d1a Merge pull request #4024 from Samaoo/patch-2
Update faq.md
2020-12-02 06:40:02 +01:00
Samaoo
3c4fe66d86 Update faq.md 2020-12-01 21:50:51 +01:00
Samaoo
4bc24ece41 Update faq.md 2020-12-01 21:49:50 +01:00
Samaoo
c1fffb9925 Update faq.md 2020-12-01 21:38:54 +01:00
Matthias
d6cc3d7374 Improve FAQ
related to question in #4023
2020-12-01 19:58:06 +01:00
Matthias
5dfa1807a3 Fix tests after small updates 2020-12-01 19:57:43 +01:00
Matthias
36b7edc342 Update typing errors 2020-12-01 19:57:09 +01:00
Matthias
de0c5f9133 Merge pull request #4022 from freqtrade/pi_setup
improve setup.sh script
2020-12-01 07:03:26 +01:00
Matthias
cec771b593 Ask for plotting dependency installation 2020-12-01 06:48:16 +01:00
Matthias
5f70d1f9a7 Ask for hyperopt installation during setup
closes #2871
2020-12-01 06:48:16 +01:00
Matthias
95b24ba8a9 Update setup.sh with some specifics 2020-12-01 06:48:12 +01:00
Matthias
202ca88e23 Changes to pi steup 2020-11-30 17:37:19 +01:00
dependabot[bot]
14d44b2cd6 Bump python-telegram-bot from 13.0 to 13.1
Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 13.0 to 13.1.
- [Release notes](https://github.com/python-telegram-bot/python-telegram-bot/releases)
- [Changelog](https://github.com/python-telegram-bot/python-telegram-bot/blob/master/CHANGES.rst)
- [Commits](https://github.com/python-telegram-bot/python-telegram-bot/compare/v13.0...v13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-30 08:02:09 +00:00
Matthias
dda5bcbc8d Merge pull request #4009 from mrsegen/patch-4
[Pairlist] Add PerformanceFilter
2020-11-30 07:48:15 +01:00
Matthias
5da41160bf Merge pull request #4017 from freqtrade/dependabot/pip/develop/ccxt-1.38.55
Bump ccxt from 1.38.13 to 1.38.55
2020-11-30 07:29:14 +01:00
Matthias
a22fd7eb3b Merge pull request #4016 from freqtrade/dependabot/pip/develop/plotly-4.13.0
Bump plotly from 4.12.0 to 4.13.0
2020-11-30 07:29:01 +01:00
dependabot[bot]
275cfb3a9c Bump ccxt from 1.38.13 to 1.38.55
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.38.13 to 1.38.55.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/doc/exchanges-by-country.rst)
- [Commits](https://github.com/ccxt/ccxt/compare/1.38.13...1.38.55)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-30 05:42:12 +00:00
dependabot[bot]
f17c7f0609 Bump plotly from 4.12.0 to 4.13.0
Bumps [plotly](https://github.com/plotly/plotly.py) from 4.12.0 to 4.13.0.
- [Release notes](https://github.com/plotly/plotly.py/releases)
- [Changelog](https://github.com/plotly/plotly.py/blob/master/CHANGELOG.md)
- [Commits](https://github.com/plotly/plotly.py/compare/v4.12.0...v4.13.0)

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-30 05:42:00 +00:00
Leif Segen
b7de18608d Trigger CI 2020-11-29 10:30:43 -06:00
Leif Segen
99abe52043 Trigger CI 2020-11-29 10:30:02 -06:00
Leif Segen
5f8e67d2b2 Update docs/includes/pairlists.md
Co-authored-by: Matthias <xmatthias@outlook.com>
2020-11-29 05:05:54 -06:00
Matthias
18de9cc5e5 Merge pull request #4012 from freqtrade/best_worst_pair
Enhance backtesting summary report
2020-11-29 10:54:09 +01:00
Leif Segen
90070f0dc5 Force test rerun 2020-11-28 17:17:40 -06:00
Leif Segen
1791495475 Trigger another run of tests 2020-11-28 16:50:44 -06:00
Leif Segen
4b6f5b92b5 Remove non-pertinent test case 2020-11-28 12:47:36 -06:00
Leif Segen
e7a035eefe Lint 2020-11-28 12:29:31 -06:00
Leif Segen
d6c9391924 Restoring expectation 2020-11-28 12:18:23 -06:00
Leif Segen
323c0657f8 Sort by profit after sort by count/pair 2020-11-28 12:17:03 -06:00
Leif Segen
6a74c57c3d Pair name-based sorting.
Attempt at more rational string sorting. Change test to show not working as expected.
2020-11-28 11:33:25 -06:00
Matthias
e40d97e05e Small formatting improvements 2020-11-28 17:52:29 +01:00
Matthias
5d3f59df90 Add best / worst trade 2020-11-28 17:45:56 +01:00
Matthias
a00f852cf9 Add best / worst pair to summary statistics 2020-11-28 17:37:10 +01:00
Leif Segen
03c5714399 Use explicit merge without depending on library detail. Add no trades case. 2020-11-28 09:45:17 -06:00
Leif Segen
e1d42ba78c Alphabetize 2020-11-28 09:44:01 -06:00
Matthias
56529180eb Further improve hyperopt docs 2020-11-28 16:42:08 +01:00
Matthias
ff286bd80c Slightly clarify hyperopt docs 2020-11-28 16:32:44 +01:00
Matthias
a47d8dbe56 Small refactor, avoiding duplicate calculation of profits 2020-11-28 11:35:29 +01:00
Matthias
829a47b187 Merge pull request #4006 from freqtrade/remove_deprecated_experimentals
Remove deprecated experimental settings
2020-11-28 10:34:47 +01:00
Matthias
4cb331b5ad Remove non-needed parameters from tests 2020-11-28 10:24:44 +01:00
Matthias
b7703e6428 Merge pull request #4007 from mrsegen/patch-2
Fix link
2020-11-28 10:22:58 +01:00
Leif Segen
37d2e476df isort imports 2020-11-28 01:59:30 -06:00
Leif Segen
f448564073 Lint 2020-11-28 01:49:46 -06:00
Leif Segen
ecce5265f5 Linting 2020-11-28 01:43:19 -06:00
Leif Segen
fefa500963 More lint 2020-11-28 01:34:40 -06:00
Leif Segen
966c6b308f Satisfy linter. 2020-11-28 01:34:18 -06:00
Leif Segen
1f7d681ddb Merge branch 'patch-4' of https://github.com/mrsegen/freqtrade into patch-4 2020-11-28 01:22:15 -06:00
Leif Segen
dbd50fdff6 Document filter. 2020-11-28 01:22:03 -06:00
Leif Segen
cfbd1c4c43 Merge branch 'develop' into patch-4 2020-11-28 01:17:34 -06:00
Leif Segen
662ec32073 Add test cases 2020-11-28 01:15:36 -06:00
Leif Segen
26855800a3 Remove unused seed 2020-11-28 00:39:18 -06:00
Leif Segen
4600bb807c Existing tests pass. 2020-11-28 00:38:06 -06:00
Leif Segen
9538fa1d72 Tweak main parameterized block for PerformanceFilter
Remove randomized exception that was geared toward ShuffleFilter. Remove case involvoing seed, also geared toward ShuffleFilter. Mock get_overall_performance().
2020-11-28 00:24:48 -06:00
Leif Segen
91b4c80d35 Remove unused parameters 2020-11-27 22:18:49 -06:00
Leif Segen
afb795b6f5 Remove unnecessary test
PerforamnceFilter doesn't use seeds, so no need to provide different ones.
2020-11-27 22:08:23 -06:00
Leif Segen
380cca2252 Remove unused imports 2020-11-27 22:00:48 -06:00
Leif Segen
3357350628 Revert unintended change 2020-11-27 22:00:36 -06:00
Leif Segen
c34150552f Revert unrelated change 2020-11-27 21:36:55 -06:00
Leif Segen
05686998bb Add starter entry in documentation 2020-11-27 21:26:42 -06:00
Leif Segen
7cbd89657f Initial step towards implementing proposed code 2020-11-27 21:24:40 -06:00
Leif Segen
89573348b6 Fix link 2020-11-27 20:37:52 -06:00
Matthias
af1b3721fb remove duplicate settings check 2020-11-27 20:28:17 +01:00
Matthias
95c3c45ec9 Remove long deprecated settings that moved from experimental to
ask_strategy
2020-11-27 20:24:32 +01:00
Leif Segen
46ec6f498c Correct link
Fix prior redirection to a non-working link: https://www.freqtrade.io/en/latest/telegram-usage/configuration/#understand-forcebuy_enable
2020-11-27 12:51:44 -06:00
Matthias
c69ce28b76 Update backtest assumption documentation 2020-11-27 09:26:58 +01:00
Matthias
fefb4b23d0 revise logic in should_sell 2020-11-27 09:24:53 +01:00
Matthias
4aa6ebee04 Add more tests for #2422 2020-11-27 09:17:25 +01:00
Matthias
57461a59f3 Update backtesting documentation with new logic 2020-11-27 08:30:17 +01:00
Matthias
81d08c4def Add detailed backtest test verifying the ROI / trailing stop collision 2020-11-27 08:24:56 +01:00
Leif Segen
d959eeb97d Merge pull request #2 from freqtrade/develop
catch up forked dev with original dev
2020-11-23 22:11:17 -06:00
Leif Segen
312533fded Match current dev file 2020-11-23 22:08:53 -06:00
Matthias
7a8b274a44 Merge branch 'develop' into pr/imxuwang/3799 2020-11-19 13:18:03 +01:00
Matthias
dd42d61d03 Run CI on 3.9 2020-11-17 19:44:39 +01:00
dependabot[bot]
181d3a3808 Bump python from 3.8.6-slim-buster to 3.9.0-slim-buster
Bumps python from 3.8.6-slim-buster to 3.9.0-slim-buster.

Signed-off-by: dependabot[bot] <support@github.com>
2020-11-17 19:44:05 +01:00
radwayne
8e03fee868 Update interface.py
Changed The should_sell() method, to handle the case where both ROI and trailing stoploss are reached in backtest.
2020-11-06 13:56:46 +01:00
Matthias
50e7418d24 Merge branch 'develop' into pr/imxuwang/3799 2020-10-22 08:05:09 +02:00
Matthias
2f91f87ad3 Merge branch 'develop' into pr/imxuwang/3799 2020-10-22 07:55:48 +02:00
Xu Wang
1c27aaab72 Declare type of 'dur'. 2020-10-18 20:24:13 +01:00
Xu Wang
355afc082e Add command 'stats' in expected test output. 2020-10-05 10:05:15 +01:00
Xu Wang
7bce2cd29d Add trade duration by win/loss. 2020-09-28 20:30:20 +01:00
Xu Wang
627e221b65 Use tabulate to create sell reason message. 2020-09-27 20:23:13 +01:00
Xu Wang
44ad0f631c Summarize trade reason for telegram command /stats. 2020-09-26 22:40:54 +01:00
Xu Wang
28411da83e Add the telegram command function template. 2020-09-22 22:28:12 +01:00
Leif Segen
1b4b10f8cd Update docs/installation.md
Address that numpy is required before `python3 -m pip install -r requirements.txt` can run.
2019-07-23 23:45:27 -05:00
188 changed files with 8263 additions and 3926 deletions

View File

@@ -3,13 +3,15 @@ FROM freqtradeorg/freqtrade:develop
# Install dependencies # Install dependencies
COPY requirements-dev.txt /freqtrade/ COPY requirements-dev.txt /freqtrade/
RUN apt-get update \ RUN apt-get update \
&& apt-get -y install git sudo vim \ && apt-get -y install git mercurial sudo vim \
&& apt-get clean \ && apt-get clean \
&& pip install autopep8 -r docs/requirements-docs.txt -r requirements-dev.txt --no-cache-dir \ && pip install autopep8 -r docs/requirements-docs.txt -r requirements-dev.txt --no-cache-dir \
&& useradd -u 1000 -U -m ftuser \ && useradd -u 1000 -U -m ftuser \
&& mkdir -p /home/ftuser/.vscode-server /home/ftuser/.vscode-server-insiders /home/ftuser/commandhistory \ && mkdir -p /home/ftuser/.vscode-server /home/ftuser/.vscode-server-insiders /home/ftuser/commandhistory \
&& echo "export PROMPT_COMMAND='history -a'" >> /home/ftuser/.bashrc \ && echo "export PROMPT_COMMAND='history -a'" >> /home/ftuser/.bashrc \
&& echo "export HISTFILE=~/commandhistory/.bash_history" >> /home/ftuser/.bashrc \ && echo "export HISTFILE=~/commandhistory/.bash_history" >> /home/ftuser/.bashrc \
&& mv /root/.local /home/ftuser/.local/ \
&& chown ftuser:ftuser -R /home/ftuser/.local/ \
&& chown ftuser: -R /home/ftuser/ && chown ftuser: -R /home/ftuser/
USER ftuser USER ftuser

View File

@@ -1,5 +1,5 @@
--- ---
name: BQuestion name: Question
about: Ask a question you could not find an answer in the docs about: Ask a question you could not find an answer in the docs
title: '' title: ''
labels: "Question" labels: "Question"

View File

@@ -14,13 +14,13 @@ on:
- cron: '0 5 * * 4' - cron: '0 5 * * 4'
jobs: jobs:
build: build_linux:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
strategy: strategy:
matrix: matrix:
os: [ ubuntu-18.04, ubuntu-20.04, macos-latest ] os: [ ubuntu-18.04, ubuntu-20.04 ]
python-version: [3.7, 3.8] python-version: [3.7, 3.8, 3.9]
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2
@@ -31,26 +31,19 @@ jobs:
python-version: ${{ matrix.python-version }} python-version: ${{ matrix.python-version }}
- name: Cache_dependencies - name: Cache_dependencies
uses: actions/cache@v1 uses: actions/cache@v2
id: cache id: cache
with: with:
path: ~/dependencies/ path: ~/dependencies/
key: ${{ runner.os }}-dependencies key: ${{ runner.os }}-dependencies
- name: pip cache (linux) - name: pip cache (linux)
uses: actions/cache@preview uses: actions/cache@v2
if: startsWith(matrix.os, 'ubuntu') if: startsWith(matrix.os, 'ubuntu')
with: with:
path: ~/.cache/pip path: ~/.cache/pip
key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip
- name: pip cache (macOS)
uses: actions/cache@preview
if: startsWith(matrix.os, 'macOS')
with:
path: ~/Library/Caches/pip
key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip
- name: TA binary *nix - name: TA binary *nix
if: steps.cache.outputs.cache-hit != 'true' if: steps.cache.outputs.cache-hit != 'true'
run: | run: |
@@ -68,6 +61,12 @@ jobs:
- name: Tests - name: Tests
run: | run: |
pytest --random-order --cov=freqtrade --cov-config=.coveragerc pytest --random-order --cov=freqtrade --cov-config=.coveragerc
if: matrix.python-version != '3.9'
- name: Tests incl. ccxt compatibility tests
run: |
pytest --random-order --cov=freqtrade --cov-config=.coveragerc --longrun
if: matrix.python-version == '3.9'
- name: Coveralls - name: Coveralls
if: (startsWith(matrix.os, 'ubuntu-20') && matrix.python-version == '3.8') if: (startsWith(matrix.os, 'ubuntu-20') && matrix.python-version == '3.8')
@@ -80,13 +79,13 @@ jobs:
- name: Backtesting - name: Backtesting
run: | run: |
cp config.json.example config.json cp config_bittrex.json.example config.json
freqtrade create-userdir --userdir user_data freqtrade create-userdir --userdir user_data
freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
- name: Hyperopt - name: Hyperopt
run: | run: |
cp config.json.example config.json cp config_bittrex.json.example config.json
freqtrade create-userdir --userdir user_data freqtrade create-userdir --userdir user_data
freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt --hyperopt-loss SharpeHyperOptLossDaily --print-all freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt --hyperopt-loss SharpeHyperOptLossDaily --print-all
@@ -113,6 +112,99 @@ jobs:
channel: '#notifications' channel: '#notifications'
url: ${{ secrets.SLACK_WEBHOOK }} url: ${{ secrets.SLACK_WEBHOOK }}
build_macos:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ macos-latest ]
python-version: [3.7, 3.8, 3.9]
steps:
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Cache_dependencies
uses: actions/cache@v2
id: cache
with:
path: ~/dependencies/
key: ${{ runner.os }}-dependencies
- name: pip cache (macOS)
uses: actions/cache@v2
if: startsWith(matrix.os, 'macOS')
with:
path: ~/Library/Caches/pip
key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip
- name: TA binary *nix
if: steps.cache.outputs.cache-hit != 'true'
run: |
cd build_helpers && ./install_ta-lib.sh ${HOME}/dependencies/; cd ..
- name: Installation - macOS
run: |
brew install hdf5 c-blosc
python -m pip install --upgrade pip
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
export TA_INCLUDE_PATH=${HOME}/dependencies/include
pip install -r requirements-dev.txt
pip install -e .
- name: Tests
run: |
pytest --random-order --cov=freqtrade --cov-config=.coveragerc
- name: Coveralls
if: (startsWith(matrix.os, 'ubuntu-20') && matrix.python-version == '3.8')
env:
# Coveralls token. Not used as secret due to github not providing secrets to forked repositories
COVERALLS_REPO_TOKEN: 6D1m0xupS3FgutfuGao8keFf9Hc0FpIXu
run: |
# Allow failure for coveralls
coveralls -v || true
- name: Backtesting
run: |
cp config_bittrex.json.example config.json
freqtrade create-userdir --userdir user_data
freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
- name: Hyperopt
run: |
cp config_bittrex.json.example config.json
freqtrade create-userdir --userdir user_data
freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt --hyperopt-loss SharpeHyperOptLossDaily --print-all
- name: Flake8
run: |
flake8
- name: Sort imports (isort)
run: |
isort --check .
- name: Mypy
run: |
mypy freqtrade scripts
- name: Slack Notification
uses: homoluctus/slatify@v1.8.0
if: failure() && ( github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork == false)
with:
type: ${{ job.status }}
job_name: '*Freqtrade CI ${{ matrix.os }}*'
mention: 'here'
mention_if: 'failure'
channel: '#notifications'
url: ${{ secrets.SLACK_WEBHOOK }}
build_windows: build_windows:
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
@@ -146,13 +238,13 @@ jobs:
- name: Backtesting - name: Backtesting
run: | run: |
cp config.json.example config.json cp config_bittrex.json.example config.json
freqtrade create-userdir --userdir user_data freqtrade create-userdir --userdir user_data
freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
- name: Hyperopt - name: Hyperopt
run: | run: |
cp config.json.example config.json cp config_bittrex.json.example config.json
freqtrade create-userdir --userdir user_data freqtrade create-userdir --userdir user_data
freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt --hyperopt-loss SharpeHyperOptLossDaily --print-all freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt --hyperopt-loss SharpeHyperOptLossDaily --print-all
@@ -215,7 +307,7 @@ jobs:
# Notify on slack only once - when CI completes (and after deploy) in case it's successfull # Notify on slack only once - when CI completes (and after deploy) in case it's successfull
notify-complete: notify-complete:
needs: [ build, build_windows, docs_check ] needs: [ build_linux, build_macos, build_windows, docs_check ]
runs-on: ubuntu-20.04 runs-on: ubuntu-20.04
steps: steps:
- name: Slack Notification - name: Slack Notification
@@ -228,8 +320,9 @@ jobs:
url: ${{ secrets.SLACK_WEBHOOK }} url: ${{ secrets.SLACK_WEBHOOK }}
deploy: deploy:
needs: [ build, build_windows, docs_check ] needs: [ build_linux, build_macos, build_windows, docs_check ]
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'
steps: steps:
- uses: actions/checkout@v2 - uses: actions/checkout@v2

1
.gitignore vendored
View File

@@ -8,6 +8,7 @@ user_data/*
user_data/notebooks/* user_data/notebooks/*
freqtrade-plot.html freqtrade-plot.html
freqtrade-profit-plot.html freqtrade-profit-plot.html
freqtrade/rpc/api_server/ui/*
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/

View File

@@ -4,5 +4,5 @@ build:
image: latest image: latest
python: python:
version: 3.6 version: 3.8
setup_py_install: false setup_py_install: false

View File

@@ -1,9 +1,9 @@
os: os:
- linux - linux
dist: xenial dist: bionic
language: python language: python
python: python:
- 3.6 - 3.8
services: services:
- docker - docker
env: env:
@@ -26,12 +26,12 @@ jobs:
# - coveralls || true # - coveralls || true
name: pytest name: pytest
- script: - script:
- cp config.json.example config.json - cp config_bittrex.json.example config.json
- freqtrade create-userdir --userdir user_data - freqtrade create-userdir --userdir user_data
- freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy - freqtrade backtesting --datadir tests/testdata --strategy SampleStrategy
name: backtest name: backtest
- script: - script:
- cp config.json.example config.json - cp config_bittrex.json.example config.json
- freqtrade create-userdir --userdir user_data - freqtrade create-userdir --userdir user_data
- freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt --hyperopt-loss SharpeHyperOptLossDaily - freqtrade hyperopt --datadir tests/testdata -e 5 --strategy SampleStrategy --hyperopt SampleHyperOpt --hyperopt-loss SharpeHyperOptLossDaily
name: hyperopt name: hyperopt

View File

@@ -12,7 +12,7 @@ Few pointers for contributions:
- New features need to contain unit tests, must conform to PEP8 (max-line-length = 100) and should be documented with the introduction PR. - New features need to contain unit tests, must conform to PEP8 (max-line-length = 100) and should be documented with the introduction PR.
- PR's can be declared as `[WIP]` - which signify Work in Progress Pull Requests (which are not finished). - PR's can be declared as `[WIP]` - which signify Work in Progress Pull Requests (which are not finished).
If you are unsure, discuss the feature on our [discord server](https://discord.gg/MA9v74M), on [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg) or in a [issue](https://github.com/freqtrade/freqtrade/issues) before a PR. If you are unsure, discuss the feature on our [discord server](https://discord.gg/MA9v74M), on [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw) or in a [issue](https://github.com/freqtrade/freqtrade/issues) before a PR.
## Getting started ## Getting started

View File

@@ -1,29 +1,48 @@
FROM python:3.8.6-slim-buster FROM python:3.9.2-slim-buster as base
RUN apt-get update \ # Setup env
&& apt-get -y install curl build-essential libssl-dev sqlite3 \ ENV LANG C.UTF-8
&& apt-get clean \ ENV LC_ALL C.UTF-8
&& pip install --upgrade pip ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONFAULTHANDLER 1
ENV PATH=/root/.local/bin:$PATH
# Prepare environment # Prepare environment
RUN mkdir /freqtrade RUN mkdir /freqtrade
WORKDIR /freqtrade WORKDIR /freqtrade
# Install dependencies
FROM base as python-deps
RUN apt-get update \
&& apt-get -y install curl build-essential libssl-dev git \
&& apt-get clean \
&& pip install --upgrade pip
# Install TA-lib # Install TA-lib
COPY build_helpers/* /tmp/ COPY build_helpers/* /tmp/
RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib* RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib*
ENV LD_LIBRARY_PATH /usr/local/lib ENV LD_LIBRARY_PATH /usr/local/lib
# Install dependencies # Install dependencies
COPY requirements.txt requirements-hyperopt.txt /freqtrade/ COPY requirements.txt requirements-hyperopt.txt /freqtrade/
RUN pip install numpy --no-cache-dir \ RUN pip install --user --no-cache-dir numpy \
&& pip install -r requirements-hyperopt.txt --no-cache-dir && pip install --user --no-cache-dir -r requirements-hyperopt.txt
# Copy dependencies to runtime-image
FROM base as runtime-image
COPY --from=python-deps /usr/local/lib /usr/local/lib
ENV LD_LIBRARY_PATH /usr/local/lib
COPY --from=python-deps /root/.local /root/.local
# Install and execute # Install and execute
COPY . /freqtrade/ COPY . /freqtrade/
RUN pip install -e . --no-cache-dir \ RUN pip install -e . --no-cache-dir \
&& mkdir /freqtrade/user_data/ && mkdir /freqtrade/user_data/ \
&& freqtrade install-ui
ENTRYPOINT ["freqtrade"] ENTRYPOINT ["freqtrade"]
# Default to trade mode # Default to trade mode
CMD [ "trade" ] CMD [ "trade" ]

View File

@@ -1,29 +1,49 @@
FROM --platform=linux/arm/v7 python:3.7.7-slim-buster FROM --platform=linux/arm/v7 python:3.7.9-slim-buster as base
RUN apt-get update \ # Setup env
&& apt-get -y install curl build-essential libssl-dev libffi-dev libatlas3-base libgfortran5 sqlite3 \ ENV LANG C.UTF-8
&& apt-get clean \ ENV LC_ALL C.UTF-8
&& pip install --upgrade pip \ ENV PYTHONDONTWRITEBYTECODE 1
&& echo "[global]\nextra-index-url=https://www.piwheels.org/simple" > /etc/pip.conf ENV PYTHONFAULTHANDLER 1
ENV PATH=/root/.local/bin:$PATH
# Prepare environment # Prepare environment
RUN mkdir /freqtrade RUN mkdir /freqtrade
WORKDIR /freqtrade WORKDIR /freqtrade
RUN apt-get update \
&& apt-get -y install libatlas3-base curl sqlite3 \
&& apt-get clean
# Install dependencies
FROM base as python-deps
RUN apt-get -y install build-essential libssl-dev libffi-dev libgfortran5 \
&& apt-get clean \
&& pip install --upgrade pip \
&& echo "[global]\nextra-index-url=https://www.piwheels.org/simple" > /etc/pip.conf
# Install TA-lib # Install TA-lib
COPY build_helpers/* /tmp/ COPY build_helpers/* /tmp/
RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib* RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib*
ENV LD_LIBRARY_PATH /usr/local/lib ENV LD_LIBRARY_PATH /usr/local/lib
# Install dependencies # Install dependencies
COPY requirements.txt /freqtrade/ COPY requirements.txt /freqtrade/
RUN pip install numpy --no-cache-dir \ RUN pip install --user --no-cache-dir numpy \
&& pip install -r requirements.txt --no-cache-dir && pip install --user --no-cache-dir -r requirements.txt
# Copy dependencies to runtime-image
FROM base as runtime-image
COPY --from=python-deps /usr/local/lib /usr/local/lib
ENV LD_LIBRARY_PATH /usr/local/lib
COPY --from=python-deps /root/.local /root/.local
# Install and execute # Install and execute
COPY . /freqtrade/ COPY . /freqtrade/
RUN pip install -e . --no-cache-dir RUN pip install -e . --no-cache-dir \
&& freqtrade install-ui
ENTRYPOINT ["freqtrade"] ENTRYPOINT ["freqtrade"]
# Default to trade mode # Default to trade mode
CMD [ "trade" ] CMD [ "trade" ]

View File

@@ -1,5 +1,6 @@
include LICENSE include LICENSE
include README.md include README.md
include config.json.example
recursive-include freqtrade *.py recursive-include freqtrade *.py
recursive-include freqtrade/templates/ *.j2 *.ipynb recursive-include freqtrade/templates/ *.j2 *.ipynb
include freqtrade/rpc/api_server/ui/fallback_file.html
include freqtrade/rpc/api_server/ui/favicon.ico

View File

@@ -22,12 +22,21 @@ expect.
We strongly recommend you to have coding and Python knowledge. Do not We strongly recommend you to have coding and Python knowledge. Do not
hesitate to read the source code and understand the mechanism of this bot. hesitate to read the source code and understand the mechanism of this bot.
## Exchange marketplaces supported ## Supported Exchange marketplaces
Please read the [exchange specific notes](docs/exchanges.md) to learn about eventual, special configurations needed for each exchange.
- [X] [Bittrex](https://bittrex.com/) - [X] [Bittrex](https://bittrex.com/)
- [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#blacklists)) - [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#blacklists))
- [X] [Kraken](https://kraken.com/) - [X] [Kraken](https://kraken.com/)
- [ ] [113 others to tests](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_ - [X] [FTX](https://ftx.com)
- [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
### Community tested
Exchanges confirmed working by the community:
- [X] [Bitvavo](https://bitvavo.com/)
## Documentation ## Documentation
@@ -37,9 +46,9 @@ Please find the complete documentation on our [website](https://www.freqtrade.io
## Features ## Features
- [x] **Based on Python 3.6+**: For botting on any operating system - Windows, macOS and Linux. - [x] **Based on Python 3.7+**: For botting on any operating system - Windows, macOS and Linux.
- [x] **Persistence**: Persistence is achieved through sqlite. - [x] **Persistence**: Persistence is achieved through sqlite.
- [x] **Dry-run**: Run the bot without playing money. - [x] **Dry-run**: Run the bot without paying money.
- [x] **Backtesting**: Run a simulation of your buy/sell strategy. - [x] **Backtesting**: Run a simulation of your buy/sell strategy.
- [x] **Strategy Optimization by machine learning**: Use machine learning to optimize your buy/sell strategy parameters with real exchange data. - [x] **Strategy Optimization by machine learning**: Use machine learning to optimize your buy/sell strategy parameters with real exchange data.
- [x] **Edge position sizing** Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. [Learn more](https://www.freqtrade.io/en/latest/edge/). - [x] **Edge position sizing** Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. [Learn more](https://www.freqtrade.io/en/latest/edge/).
@@ -113,7 +122,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. - `/stopbuy`: Stop entering new trades.
- `/status [table]`: Lists all open trades. - `/status <trade_id>|[table]`: Lists all or specific open trades.
- `/profit`: Lists cumulative profit from all finished trades - `/profit`: Lists cumulative profit from all finished trades
- `/forcesell <trade_id>|all`: Instantly sells the given trade (Ignoring `minimum_roi`). - `/forcesell <trade_id>|all`: Instantly sells the given trade (Ignoring `minimum_roi`).
- `/performance`: Show performance of each finished trade grouped by pair - `/performance`: Show performance of each finished trade grouped by pair
@@ -138,7 +147,7 @@ For any questions not covered by the documentation or for further information ab
Please check out our [discord server](https://discord.gg/MA9v74M). Please check out our [discord server](https://discord.gg/MA9v74M).
You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg). You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw).
### [Bugs / Issues](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue) ### [Bugs / Issues](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
@@ -169,7 +178,7 @@ to understand the requirements before sending your pull-requests.
Coding is not a necessity to contribute - maybe start with improving our documentation? Coding is not a necessity to contribute - maybe start with improving our documentation?
Issues labeled [good first issue](https://github.com/freqtrade/freqtrade/labels/good%20first%20issue) can be good first contributions, and will help get you familiar with the codebase. Issues labeled [good first issue](https://github.com/freqtrade/freqtrade/labels/good%20first%20issue) can be good first contributions, and will help get you familiar with the codebase.
**Note** before starting any major new feature work, *please open an issue describing what you are planning to do* or talk to us on [discord](https://discord.gg/MA9v74M) or [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg). This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it. **Note** before starting any major new feature work, *please open an issue describing what you are planning to do* or talk to us on [discord](https://discord.gg/MA9v74M) or [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw). This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it.
**Important:** Always create your PR against the `develop` branch, not `stable`. **Important:** Always create your PR against the `develop` branch, not `stable`.
@@ -187,9 +196,9 @@ To run this bot we recommend you a cloud instance with a minimum of:
### Software requirements ### Software requirements
- [Python 3.6.x](http://docs.python-guide.org/en/latest/starting/installation/) - [Python 3.7.x](http://docs.python-guide.org/en/latest/starting/installation/)
- [pip](https://pip.pypa.io/en/stable/installing/) - [pip](https://pip.pypa.io/en/stable/installing/)
- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) - [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
- [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html) - [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html)
- [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) (Recommended) - [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended)
- [Docker](https://www.docker.com/products/docker) (Recommended) - [Docker](https://www.docker.com/products/docker) (Recommended)

View File

@@ -30,7 +30,7 @@ if [ $? -ne 0 ]; then
fi fi
# Run backtest # Run backtest
docker run --rm -v $(pwd)/config.json.example:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy DefaultStrategy docker run --rm -v $(pwd)/config_bittrex.json.example:/freqtrade/config.json:ro -v $(pwd)/tests:/tests freqtrade:${TAG} backtesting --datadir /tests/testdata --strategy-path /tests/strategy/strats/ --strategy DefaultStrategy
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "failed running backtest" echo "failed running backtest"
@@ -51,6 +51,8 @@ fi
docker images docker images
docker push ${IMAGE_NAME} docker push ${IMAGE_NAME}
docker push ${IMAGE_NAME}:$TAG_PLOT
docker push ${IMAGE_NAME}:$TAG
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
echo "failed pushing repo" echo "failed pushing repo"
return 1 return 1

View File

@@ -12,15 +12,15 @@
"sell": 30 "sell": 30
}, },
"bid_strategy": { "bid_strategy": {
"use_order_book": false,
"ask_last_balance": 0.0, "ask_last_balance": 0.0,
"use_order_book": false,
"order_book_top": 1, "order_book_top": 1,
"check_depth_of_market": { "check_depth_of_market": {
"enabled": false, "enabled": false,
"bids_to_ask_delta": 1 "bids_to_ask_delta": 1
} }
}, },
"ask_strategy":{ "ask_strategy": {
"use_order_book": false, "use_order_book": false,
"order_book_min": 1, "order_book_min": 1,
"order_book_max": 1, "order_book_max": 1,
@@ -84,12 +84,13 @@
"enabled": false, "enabled": false,
"listen_ip_address": "127.0.0.1", "listen_ip_address": "127.0.0.1",
"listen_port": 8080, "listen_port": 8080,
"verbosity": "info", "verbosity": "error",
"jwt_secret_key": "somethingrandom", "jwt_secret_key": "somethingrandom",
"CORS_origins": [], "CORS_origins": [],
"username": "", "username": "freqtrader",
"password": "" "password": "SuperSecurePassword"
}, },
"bot_name": "freqtrade",
"initial_state": "running", "initial_state": "running",
"forcebuy_enable": false, "forcebuy_enable": false,
"internals": { "internals": {

View File

@@ -79,12 +79,13 @@
"enabled": false, "enabled": false,
"listen_ip_address": "127.0.0.1", "listen_ip_address": "127.0.0.1",
"listen_port": 8080, "listen_port": 8080,
"verbosity": "info", "verbosity": "error",
"jwt_secret_key": "somethingrandom", "jwt_secret_key": "somethingrandom",
"CORS_origins": [], "CORS_origins": [],
"username": "", "username": "freqtrader",
"password": "" "password": "SuperSecurePassword"
}, },
"bot_name": "freqtrade",
"initial_state": "running", "initial_state": "running",
"forcebuy_enable": false, "forcebuy_enable": false,
"internals": { "internals": {

View File

@@ -42,6 +42,7 @@
"order_book_max": 1, "order_book_max": 1,
"use_sell_signal": true, "use_sell_signal": true,
"sell_profit_only": false, "sell_profit_only": false,
"sell_profit_offset": 0.0,
"ignore_roi_if_buy_signal": false "ignore_roi_if_buy_signal": false
}, },
"order_types": { "order_types": {
@@ -75,8 +76,35 @@
"refresh_period": 1440 "refresh_period": 1440
} }
], ],
"protections": [
{
"method": "StoplossGuard",
"lookback_period_candles": 60,
"trade_limit": 4,
"stop_duration_candles": 60,
"only_per_pair": false
},
{
"method": "CooldownPeriod",
"stop_duration_candles": 20
},
{
"method": "MaxDrawdown",
"lookback_period_candles": 200,
"trade_limit": 20,
"stop_duration_candles": 10,
"max_allowed_drawdown": 0.2
},
{
"method": "LowProfitPairs",
"lookback_period_candles": 360,
"trade_limit": 1,
"stop_duration_candles": 2,
"required_profit": 0.02
}
],
"exchange": { "exchange": {
"name": "bittrex", "name": "binance",
"sandbox": false, "sandbox": false,
"key": "your_exchange_key", "key": "your_exchange_key",
"secret": "your_exchange_secret", "secret": "your_exchange_secret",
@@ -88,16 +116,21 @@
"aiohttp_trust_env": false "aiohttp_trust_env": false
}, },
"pair_whitelist": [ "pair_whitelist": [
"ALGO/BTC",
"ATOM/BTC",
"BAT/BTC",
"BCH/BTC",
"BRD/BTC",
"EOS/BTC",
"ETH/BTC", "ETH/BTC",
"IOTA/BTC",
"LINK/BTC",
"LTC/BTC", "LTC/BTC",
"ETC/BTC", "NEO/BTC",
"DASH/BTC", "NXS/BTC",
"ZEC/BTC", "XMR/BTC",
"XLM/BTC", "XRP/BTC",
"NXT/BTC", "XTZ/BTC"
"TRX/BTC",
"ADA/BTC",
"XMR/BTC"
], ],
"pair_blacklist": [ "pair_blacklist": [
"DOGE/BTC" "DOGE/BTC"
@@ -120,7 +153,7 @@
"remove_pumps": false "remove_pumps": false
}, },
"telegram": { "telegram": {
"enabled": true, "enabled": false,
"token": "your_telegram_token", "token": "your_telegram_token",
"chat_id": "your_telegram_chat_id", "chat_id": "your_telegram_chat_id",
"notification_settings": { "notification_settings": {
@@ -137,12 +170,14 @@
"enabled": false, "enabled": false,
"listen_ip_address": "127.0.0.1", "listen_ip_address": "127.0.0.1",
"listen_port": 8080, "listen_port": 8080,
"verbosity": "info", "verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "somethingrandom", "jwt_secret_key": "somethingrandom",
"CORS_origins": [], "CORS_origins": [],
"username": "freqtrader", "username": "freqtrader",
"password": "SuperSecurePassword" "password": "SuperSecurePassword"
}, },
"bot_name": "freqtrade",
"db_url": "sqlite:///tradesv3.sqlite", "db_url": "sqlite:///tradesv3.sqlite",
"initial_state": "running", "initial_state": "running",
"forcebuy_enable": false, "forcebuy_enable": false,

View File

@@ -89,12 +89,13 @@
"enabled": false, "enabled": false,
"listen_ip_address": "127.0.0.1", "listen_ip_address": "127.0.0.1",
"listen_port": 8080, "listen_port": 8080,
"verbosity": "info", "verbosity": "error",
"jwt_secret_key": "somethingrandom", "jwt_secret_key": "somethingrandom",
"CORS_origins": [], "CORS_origins": [],
"username": "", "username": "freqtrader",
"password": "" "password": "SuperSecurePassword"
}, },
"bot_name": "freqtrade",
"initial_state": "running", "initial_state": "running",
"forcebuy_enable": false, "forcebuy_enable": false,
"internals": { "internals": {

View File

@@ -9,11 +9,16 @@ services:
# Build step - only needed when additional dependencies are needed # Build step - only needed when additional dependencies are needed
# build: # build:
# context: . # context: .
# dockerfile: "./Dockerfile.technical" # dockerfile: "./docker/Dockerfile.technical"
restart: unless-stopped restart: unless-stopped
container_name: freqtrade container_name: freqtrade
volumes: volumes:
- "./user_data:/freqtrade/user_data" - "./user_data:/freqtrade/user_data"
# Expose api on port 8080 (localhost only)
# Please read the https://www.freqtrade.io/en/latest/rest-api/ documentation
# before enabling this.
# ports:
# - "127.0.0.1:8080:8080"
# Default command used when running `docker compose up` # Default command used when running `docker compose up`
command: > command: >
trade trade

View File

@@ -40,6 +40,11 @@ For the sample below, you then need to add the command line parameter `--hyperop
A sample of this can be found below, which is identical to the Default Hyperopt loss implementation. A full sample can be found in [userdata/hyperopts](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_loss.py). A sample of this can be found below, which is identical to the Default Hyperopt loss implementation. A full sample can be found in [userdata/hyperopts](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_loss.py).
``` python ``` python
from datetime import datetime
from typing import Dict
from pandas import DataFrame
from freqtrade.optimize.hyperopt import IHyperOptLoss from freqtrade.optimize.hyperopt import IHyperOptLoss
TARGET_TRADES = 600 TARGET_TRADES = 600
@@ -54,6 +59,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],
*args, **kwargs) -> float: *args, **kwargs) -> float:
""" """
Objective function, returns smaller number for better results Objective function, returns smaller number for better results
@@ -63,7 +69,7 @@ class SuperDuperHyperOptLoss(IHyperOptLoss):
* 0.25: Avoiding trade loss * 0.25: Avoiding trade loss
* 1.0 to total profit, compared to the expected value (`EXPECTED_MAX_PROFIT`) defined above * 1.0 to total profit, compared to the expected value (`EXPECTED_MAX_PROFIT`) defined above
""" """
total_profit = results['profit_percent'].sum() total_profit = results['profit_ratio'].sum()
trade_duration = results['trade_duration'].mean() trade_duration = results['trade_duration'].mean()
trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8) trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)
@@ -77,10 +83,12 @@ Currently, the arguments are:
* `results`: DataFrame containing the result * `results`: DataFrame containing the result
The following columns are available in results (corresponds to the output-file of backtesting when used with `--export trades`): The following columns are available in results (corresponds to the output-file of backtesting when used with `--export trades`):
`pair, profit_percent, profit_abs, open_time, close_time, open_index, close_index, trade_duration, open_at_end, open_rate, close_rate, sell_reason` `pair, profit_ratio, profit_abs, open_date, open_rate, fee_open, close_date, close_rate, fee_close, amount, trade_duration, is_open, sell_reason, stake_amount, min_rate, max_rate, stop_loss_ratio, stop_loss_abs`
* `trade_count`: Amount of trades (identical to `len(results)`) * `trade_count`: Amount of trades (identical to `len(results)`)
* `min_date`: Start date of the hyperopting TimeFrame * `min_date`: Start date of the timerange used
* `min_date`: End date of the hyperopting TimeFrame * `min_date`: End date of the timerange used
* `config`: Config object used (Note: Not all strategy-related parameters will be updated here if they are part of a hyperopt space).
* `processed`: Dict of Dataframes with the pair as keys containing the data used for backtesting.
This function needs to return a floating point number (`float`). Smaller numbers will be interpreted as better results. The parameters and balancing for this is up to you. This function needs to return a floating point number (`float`). Smaller numbers will be interpreted as better results. The parameters and balancing for this is up to you.

View File

@@ -5,6 +5,89 @@ This page explains how to validate your strategy performance by using Backtestin
Backtesting requires historic data to be available. Backtesting requires historic data to be available.
To learn how to get data for the pairs and exchange you're interested in, head over to the [Data Downloading](data-download.md) section of the documentation. To learn how to get data for the pairs and exchange you're interested in, head over to the [Data Downloading](data-download.md) section of the documentation.
## Backtesting command reference
```
usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [-s NAME]
[--strategy-path PATH] [-i TIMEFRAME]
[--timerange TIMERANGE]
[--data-format-ohlcv {json,jsongz,hdf5}]
[--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[--eps] [--dmmp] [--enable-protections]
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
[--export EXPORT] [--export-filename PATH]
optional arguments:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--data-format-ohlcv {json,jsongz,hdf5}
Storage format for downloaded candle (OHLCV) data.
(default: `None`).
--max-open-trades INT
Override the value of the `max_open_trades`
configuration setting.
--stake-amount STAKE_AMOUNT
Override the value of the `stake_amount` configuration
setting.
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
entry and exit).
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking).
--dmmp, --disable-max-market-positions
Disable applying `max_open_trades` during backtest
(same as setting `max_open_trades` to a very high
number).
--enable-protections, --enableprotections
Enable protections for backtesting.Will slow
backtesting down by a considerable amount, but will
include configured protections
--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]
Provide a space-separated list of strategies to
backtest. Please note that ticker-interval needs to be
set either in config or via command line. When using
this together with `--export trades`, the strategy-
name is injected into the filename (so `backtest-
data.json` becomes `backtest-data-
DefaultStrategy.json`
--export EXPORT Export backtest results, argument are: trades.
Example: `--export=trades`
--export-filename PATH
Save backtest results to the file with this filename.
Requires `--export` to be set as well. Example:
`--export-filename=user_data/backtest_results/backtest
_today.json`
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Strategy arguments:
-s NAME, --strategy NAME
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
```
## 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 Buy and Sell strategies and some historic data, you want to test it against
@@ -20,7 +103,7 @@ The result of backtesting will confirm if your bot has better odds of making a p
!!! Warning "Using dynamic pairlists for backtesting" !!! Warning "Using dynamic pairlists for backtesting"
Using dynamic pairlists is possible, however it relies on the current market conditions - which will not reflect the historic status of the pairlist. Using dynamic pairlists is possible, however it relies on the current market conditions - which will not reflect the historic status of the pairlist.
Also, when using pairlists other than StaticPairlist, reproducability of backtesting-results cannot be guaranteed. Also, when using pairlists other than StaticPairlist, reproducability of backtesting-results cannot be guaranteed.
Please read the [pairlists documentation](configuration.md#pairlists) for more information. Please read the [pairlists documentation](plugins.md#pairlists) for more information.
To achieve reproducible results, best generate a pairlist via the [`test-pairlist`](utils.md#test-pairlist) command and use that as static pairlist. To achieve reproducible results, best generate a pairlist via the [`test-pairlist`](utils.md#test-pairlist) command and use that as static pairlist.
@@ -165,10 +248,13 @@ A backtesting result will look like that:
| Max open trades | 3 | | Max open trades | 3 |
| | | | | |
| Total trades | 429 | | Total trades | 429 |
| First trade | 2019-01-01 18:30:00 |
| First trade Pair | EOS/USDT |
| Total Profit % | 152.41% | | Total Profit % | 152.41% |
| Trades per day | 3.575 | | Trades per day | 3.575 |
| | |
| Best Pair | LSK/BTC 26.26% |
| Worst Pair | ZEC/BTC -10.18% |
| Best Trade | LSK/BTC 4.25% |
| Worst Trade | ZEC/BTC -10.25% |
| Best day | 25.27% | | Best day | 25.27% |
| Worst day | -30.67% | | Worst day | -30.67% |
| Avg. Duration Winners | 4:23:00 | | Avg. Duration Winners | 4:23:00 |
@@ -238,10 +324,13 @@ It contains some useful key metrics about performance of your strategy on backte
| Max open trades | 3 | | Max open trades | 3 |
| | | | | |
| Total trades | 429 | | Total trades | 429 |
| First trade | 2019-01-01 18:30:00 |
| First trade Pair | EOS/USDT |
| Total Profit % | 152.41% | | Total Profit % | 152.41% |
| Trades per day | 3.575 | | Trades per day | 3.575 |
| | |
| Best Pair | LSK/BTC 26.26% |
| Worst Pair | ZEC/BTC -10.18% |
| Best Trade | LSK/BTC 4.25% |
| Worst Trade | ZEC/BTC -10.25% |
| Best day | 25.27% | | Best day | 25.27% |
| Worst day | -30.67% | | Worst day | -30.67% |
| Avg. Duration Winners | 4:23:00 | | Avg. Duration Winners | 4:23:00 |
@@ -256,12 +345,12 @@ It contains some useful key metrics about performance of your strategy on backte
``` ```
- `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option). - `Backtesting from` / `Backtesting to`: Backtesting range (usually defined with the `--timerange` option).
- `Max open trades`: Setting of `max_open_trades` (or `--max-open-trades`) - to clearly see settings for this. - `Max open trades`: Setting of `max_open_trades` (or `--max-open-trades`) - or number of pairs in the pairlist (whatever is lower).
- `Total trades`: Identical to the total trades of the backtest output table. - `Total trades`: Identical to the total trades of the backtest output table.
- `First trade`: First trade entered. - `Total Profit %`: Total profit. Aligned to the `TOTAL` row's `Tot Profit %` from the first table.
- `First trade pair`: Which pair was part of the first trade.
- `Total Profit %`: Total profit per stake amount. Aligned to the TOTAL column of the first table.
- `Trades per day`: Total trades divided by the backtesting duration in days (this will give you information about how many trades to expect from the strategy). - `Trades per day`: Total trades divided by the backtesting duration in days (this will give you information about how many trades to expect from the strategy).
- `Best Pair` / `Worst Pair`: Best and worst performing pair, and it's corresponding `Cum Profit %`.
- `Best Trade` / `Worst Trade`: Biggest winning trade and biggest losing trade
- `Best day` / `Worst day`: Best and worst day based on daily profit. - `Best day` / `Worst day`: Best and worst day based on daily profit.
- `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades. - `Avg. Duration Winners` / `Avg. Duration Loser`: Average durations for winning and losing trades.
- `Max Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced). - `Max Drawdown`: Maximum drawdown experienced. For example, the value of 50% means that from highest to subsequent lowest point, a 50% drop was experienced).
@@ -273,18 +362,24 @@ It contains some useful key metrics about performance of your strategy on backte
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:
- Buys happen at open-price - Buys happen at open-price
- Sell signal sells happen at open-price of the following candle - Sell-signal sells happen at open-price of the consecutive candle
- Low happens before high for stoploss, protecting capital first - Sell-signal is favored over Stoploss, because sell-signals are assumed to trigger on candle's open
- ROI - ROI
- sells are compared to high - but the ROI value is used (e.g. ROI = 2%, high=5% - so the sell will be at 2%) - sells are compared to high - but the ROI value is used (e.g. ROI = 2%, high=5% - so the sell will be at 2%)
- sells are never "below the candle", so a ROI of 2% may result in a sell at 2.4% if low was at 2.4% profit - sells are never "below the candle", so a ROI of 2% may result in a sell at 2.4% if low was at 2.4% profit
- Forcesells caused by `<N>=-1` ROI entries use low as sell value, unless N falls on the candle open (e.g. `120: -1` for 1h candles) - Forcesells caused by `<N>=-1` ROI entries use low as sell value, unless N falls on the candle open (e.g. `120: -1` for 1h candles)
- Stoploss sells happen exactly at stoploss price, even if low was lower - Stoploss sells happen exactly at stoploss price, even if low was lower, but the loss will be `2 * fees` higher than the stoploss price
- Stoploss is evaluated before ROI within one candle. So you can often see more trades with the `stoploss` sell reason comparing to the results obtained with the same strategy in the Dry Run/Live Trade modes
- Low happens before high for stoploss, protecting capital first
- Trailing stoploss - Trailing stoploss
- High happens first - adjusting stoploss - High happens first - adjusting stoploss
- Low uses the adjusted stoploss (so sells with large high-low difference are backtested correctly) - Low uses the adjusted stoploss (so sells with large high-low difference are backtested correctly)
- ROI applies before trailing-stop, ensuring profits are "top-capped" at ROI if both ROI and trailing stop applies
- Sell-reason does not explain if a trade was positive or negative, just what triggered the sell (this can look odd if negative ROI values are used) - Sell-reason does not explain if a trade was positive or negative, just what triggered the sell (this can look odd if negative ROI values are used)
- Stoploss (and trailing stoploss) is evaluated before ROI within one candle. So you can often see more trades with the `stoploss` and/or `trailing_stop` sell reason comparing to the results obtained with the same strategy in the Dry Run/Live Trade modes. - Evaluation sequence (if multiple signals happen on the same candle)
- ROI (if not stoploss)
- Sell-signal
- Stoploss
Taking these assumptions, backtesting tries to mirror real trading as closely as possible. However, backtesting will **never** replace running a strategy in dry-run mode. Taking these assumptions, backtesting tries to mirror real trading as closely as possible. However, backtesting will **never** replace running a strategy in dry-run mode.
Also, keep in mind that past results don't guarantee future success. Also, keep in mind that past results don't guarantee future success.

View File

@@ -4,6 +4,7 @@ This page provides you some basic concepts on how Freqtrade works and operates.
## Freqtrade terminology ## Freqtrade terminology
* Strategy: Your trading strategy, telling the bot what to do.
* Trade: Open position. * Trade: Open position.
* Open Order: Order which is currently placed on the exchange, and is not yet complete. * Open Order: Order which is currently placed on the exchange, and is not yet complete.
* Pair: Tradable pair, usually in the format of Quote/Base (e.g. XRP/USDT). * Pair: Tradable pair, usually in the format of Quote/Base (e.g. XRP/USDT).
@@ -49,8 +50,9 @@ This loop will be repeated again and again until the bot is stopped.
[backtesting](backtesting.md) or [hyperopt](hyperopt.md) do only part of the above logic, since most of the trading operations are fully simulated. [backtesting](backtesting.md) or [hyperopt](hyperopt.md) do only part of the above logic, since most of the trading operations are fully simulated.
* Load historic data for configured pairlist. * Load historic data for configured pairlist.
* Calculate indicators (calls `populate_indicators()`). * Calls `bot_loop_start()` once.
* Calls `populate_buy_trend()` and `populate_sell_trend()` * Calculate indicators (calls `populate_indicators()` once per pair).
* Calculate buy / sell signals (calls `populate_buy_trend()` and `populate_sell_trend()` once per pair)
* Loops per candle simulating entry and exit points. * Loops per candle simulating entry and exit points.
* Generate backtest report output * Generate backtest report output

View File

@@ -205,241 +205,6 @@ in production mode. Example command:
freqtrade trade -c config.json --db-url sqlite:///tradesv3.dry_run.sqlite freqtrade trade -c config.json --db-url sqlite:///tradesv3.dry_run.sqlite
``` ```
## Backtesting commands
Backtesting also uses the config specified via `-c/--config`.
```
usage: freqtrade backtesting [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [-s NAME]
[--strategy-path PATH] [-i TIMEFRAME]
[--timerange TIMERANGE] [--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[--eps] [--dmmp]
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
[--export EXPORT] [--export-filename PATH]
optional arguments:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--max-open-trades INT
Override the value of the `max_open_trades`
configuration setting.
--stake-amount STAKE_AMOUNT
Override the value of the `stake_amount` configuration
setting.
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
entry and exit).
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking).
--dmmp, --disable-max-market-positions
Disable applying `max_open_trades` during backtest
(same as setting `max_open_trades` to a very high
number).
--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]
Provide a space-separated list of strategies to
backtest. Please note that ticker-interval needs to be
set either in config or via command line. When using
this together with `--export trades`, the strategy-
name is injected into the filename (so `backtest-
data.json` becomes `backtest-data-
DefaultStrategy.json`
--export EXPORT Export backtest results, argument are: trades.
Example: `--export=trades`
--export-filename PATH
Save backtest results to the file with this filename.
Requires `--export` to be set as well. Example:
`--export-filename=user_data/backtest_results/backtest
_today.json`
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Strategy arguments:
-s NAME, --strategy NAME
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
```
### Getting historic data for backtesting
The first time your run Backtesting, you will need to download some historic data first.
This can be accomplished by using `freqtrade download-data`.
Check the corresponding [Data Downloading](data-download.md) section for more details
## Hyperopt commands
To optimize your strategy, you can use hyperopt parameter hyperoptimization
to find optimal parameter values for your strategy.
```
usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [-s NAME] [--strategy-path PATH]
[-i TIMEFRAME] [--timerange TIMERANGE]
[--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[--hyperopt NAME] [--hyperopt-path PATH] [--eps]
[-e INT]
[--spaces {all,buy,sell,roi,stoploss,trailing,default} [{all,buy,sell,roi,stoploss,trailing,default} ...]]
[--dmmp] [--print-all] [--no-color] [--print-json]
[-j JOBS] [--random-state INT] [--min-trades INT]
[--hyperopt-loss NAME]
optional arguments:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--max-open-trades INT
Override the value of the `max_open_trades`
configuration setting.
--stake-amount STAKE_AMOUNT
Override the value of the `stake_amount` configuration
setting.
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
entry and exit).
--hyperopt NAME Specify hyperopt class name which will be used by the
bot.
--hyperopt-path PATH Specify additional lookup path for Hyperopt and
Hyperopt Loss functions.
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking).
-e INT, --epochs INT Specify number of epochs (default: 100).
--spaces {all,buy,sell,roi,stoploss,trailing,default} [{all,buy,sell,roi,stoploss,trailing,default} ...]
Specify which parameters to hyperopt. Space-separated
list.
--dmmp, --disable-max-market-positions
Disable applying `max_open_trades` during backtest
(same as setting `max_open_trades` to a very high
number).
--print-all Print all results, not only the best ones.
--no-color Disable colorization of hyperopt results. May be
useful if you are redirecting output to a file.
--print-json Print output in JSON format.
-j JOBS, --job-workers JOBS
The number of concurrently running jobs for
hyperoptimization (hyperopt worker processes). If -1
(default), all CPUs are used, for -2, all CPUs but one
are used, etc. If 1 is given, no parallel computing
code is used at all.
--random-state INT Set random state to some positive integer for
reproducible hyperopt results.
--min-trades INT Set minimal desired number of trades for evaluations
in the hyperopt optimization path (default: 1).
--hyperopt-loss NAME Specify the class name of the hyperopt loss function
class (IHyperOptLoss). Different functions can
generate completely different results, since the
target for optimization is different. Built-in
Hyperopt-loss-functions are: ShortTradeDurHyperOptLoss,
OnlyProfitHyperOptLoss, SharpeHyperOptLoss,
SharpeHyperOptLossDaily, SortinoHyperOptLoss,
SortinoHyperOptLossDaily.
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Strategy arguments:
-s NAME, --strategy NAME
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
```
## Edge commands
To know your trade expectancy and winrate against historical data, you can use Edge.
```
usage: freqtrade edge [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [-s NAME] [--strategy-path PATH]
[-i TIMEFRAME] [--timerange TIMERANGE]
[--max-open-trades INT] [--stake-amount STAKE_AMOUNT]
[--fee FLOAT] [--stoplosses STOPLOSS_RANGE]
optional arguments:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--max-open-trades INT
Override the value of the `max_open_trades`
configuration setting.
--stake-amount STAKE_AMOUNT
Override the value of the `stake_amount` configuration
setting.
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
entry and exit).
--stoplosses STOPLOSS_RANGE
Defines a range of stoploss values against which edge
will assess the strategy. The format is "min,max,step"
(without any space). Example:
`--stoplosses=-0.01,-0.1,-0.001`
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Strategy arguments:
-s NAME, --strategy NAME
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
```
To understand edge and how to read the results, please read the [edge documentation](edge.md).
## Next step ## Next step
The optimal strategy of the bot will change with time depending of the market trends. The next step is to The optimal strategy of the bot will change with time depending of the market trends. The next step is to

View File

@@ -16,8 +16,7 @@ In some advanced use cases, multiple configuration files can be specified and us
If you used the [Quick start](installation.md/#quick-start) method for installing If you used the [Quick start](installation.md/#quick-start) method for installing
the bot, the installation script should have already created the default configuration file (`config.json`) for you. the bot, the installation script should have already created the default configuration file (`config.json`) for you.
If default configuration file is not created we recommend you to copy and use the `config.json.example` as a template If default configuration file is not created we recommend you to use `freqtrade new-config --config config.json` to generate a basic configuration file.
for your bot configuration.
The Freqtrade configuration file is to be written in the JSON format. The Freqtrade configuration file is to be written in the JSON format.
@@ -72,8 +71,10 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `ask_strategy.order_book_min` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. <br>*Defaults to `1`.* <br> **Datatype:** Positive Integer | `ask_strategy.order_book_min` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. <br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
| `ask_strategy.order_book_max` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. <br>*Defaults to `1`.* <br> **Datatype:** Positive Integer | `ask_strategy.order_book_max` | Bot will scan from the top min to max Order Book Asks searching for a profitable rate. <br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
| `ask_strategy.use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `true`.* <br> **Datatype:** Boolean | `ask_strategy.use_sell_signal` | Use sell signals produced by the strategy in addition to the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `true`.* <br> **Datatype:** Boolean
| `ask_strategy.sell_profit_only` | Wait until the bot makes a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean | `ask_strategy.sell_profit_only` | Wait until the bot reaches `ask_strategy.sell_profit_offset` before taking a sell decision. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
| `ask_strategy.sell_profit_offset` | Sell-signal is only active above this value. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0`.* <br> **Datatype:** Float (as ratio)
| `ask_strategy.ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean | `ask_strategy.ignore_roi_if_buy_signal` | Do not sell if the buy signal is still active. This setting takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
| `ask_strategy.ignore_buying_expired_candle_after` | Specifies the number of seconds until a buy signal is no longer used. <br> **Datatype:** Integer
| `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Dict | `order_types` | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Dict
| `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict | `order_time_in_force` | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict
| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> **Datatype:** String | `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> **Datatype:** String
@@ -81,16 +82,18 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String | `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String | `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String | `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `exchange.pair_whitelist` | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Not used by VolumePairList (see [below](#pairlists-and-pairlist-handlers)). <br> **Datatype:** List | `exchange.pair_whitelist` | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Supports regex pairs as `.*/BTC`. Not used by VolumePairList. [More information](plugins.md#pairlists-and-pairlist-handlers). <br> **Datatype:** List
| `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#pairlists-and-pairlist-handlers)). <br> **Datatype:** List | `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting. [More information](plugins.md#pairlists-and-pairlist-handlers). <br> **Datatype:** List
| `exchange.ccxt_config` | Additional CCXT parameters passed to both ccxt instances (sync and async). This is usually the correct place for ccxt configurations. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> **Datatype:** Dict | `exchange.ccxt_config` | Additional CCXT parameters passed to both ccxt instances (sync and async). This is usually the correct place for ccxt configurations. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> **Datatype:** Dict
| `exchange.ccxt_sync_config` | Additional CCXT parameters passed to the regular (sync) ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> **Datatype:** Dict | `exchange.ccxt_sync_config` | Additional CCXT parameters passed to the regular (sync) ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> **Datatype:** Dict
| `exchange.ccxt_async_config` | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> **Datatype:** Dict | `exchange.ccxt_async_config` | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation) <br> **Datatype:** Dict
| `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded. <br>*Defaults to `60` minutes.* <br> **Datatype:** Positive Integer | `exchange.markets_refresh_interval` | The interval in minutes in which markets are reloaded. <br>*Defaults to `60` minutes.* <br> **Datatype:** Positive Integer
| `exchange.skip_pair_validation` | Skip pairlist validation on startup.<br>*Defaults to `false`<br> **Datatype:** Boolean | `exchange.skip_pair_validation` | Skip pairlist validation on startup.<br>*Defaults to `false`<br> **Datatype:** Boolean
| `exchange.skip_open_order_update` | Skips open order updates on startup should the exchange cause problems. Only relevant in live conditions.<br>*Defaults to `false`<br> **Datatype:** Boolean
| `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation. | `edge.*` | Please refer to [edge configuration document](edge.md) for detailed explanation.
| `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now. <br>*Defaults to `true`.* <br> **Datatype:** Boolean | `experimental.block_bad_exchanges` | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
| `pairlists` | Define one or more pairlists to be used. [More information below](#pairlists-and-pairlist-handlers). <br>*Defaults to `StaticPairList`.* <br> **Datatype:** List of Dicts | `pairlists` | Define one or more pairlists to be used. [More information](plugins.md#pairlists-and-pairlist-handlers). <br>*Defaults to `StaticPairList`.* <br> **Datatype:** List of Dicts
| `protections` | Define one or more protections to be used. [More information](plugins.md#protections). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** List of Dicts
| `telegram.enabled` | Enable the usage of Telegram. <br> **Datatype:** Boolean | `telegram.enabled` | Enable the usage of Telegram. <br> **Datatype:** Boolean
| `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String | `telegram.token` | Your Telegram bot token. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String | `telegram.chat_id` | Your personal Telegram account id. Only required if `telegram.enabled` is `true`. <br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
@@ -107,13 +110,14 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `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
| `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
| `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite:///tradesv3.dryrun.sqlite` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances. <br> **Datatype:** String, SQLAlchemy connect string | `db_url` | Declares database URL to use. NOTE: This defaults to `sqlite:///tradesv3.dryrun.sqlite` if `dry_run` is `true`, and to `sqlite:///tradesv3.sqlite` for production instances. <br> **Datatype:** String, SQLAlchemy connect string
| `initial_state` | Defines the initial application state. More information below. <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`
| `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below. <br> **Datatype:** Boolean | `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below. <br> **Datatype:** Boolean
| `disable_dataframe_checks` | Disable checking the OHLCV dataframe returned from the strategy methods for correctness. Only use when intentionally changing the dataframe and understand what you are doing. [Strategy Override](#parameters-in-the-strategy).<br> *Defaults to `False`*. <br> **Datatype:** Boolean | `disable_dataframe_checks` | Disable checking the OHLCV dataframe returned from the strategy methods for correctness. Only use when intentionally changing the dataframe and understand what you are doing. [Strategy Override](#parameters-in-the-strategy).<br> *Defaults to `False`*. <br> **Datatype:** Boolean
| `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`. <br> **Datatype:** ClassName | `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`. <br> **Datatype:** ClassName
| `strategy_path` | Adds an additional strategy lookup path (must be a directory). <br> **Datatype:** String | `strategy_path` | Adds an additional strategy lookup path (must be a directory). <br> **Datatype:** String
| `internals.process_throttle_secs` | Set the process throttle. Value in second. <br>*Defaults to `5` seconds.* <br> **Datatype:** Positive Integer | `internals.process_throttle_secs` | Set the process throttle, or minimum loop duration for one bot iteration loop. Value in second. <br>*Defaults to `5` seconds.* <br> **Datatype:** Positive Integer
| `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages. <br>*Defaults to `60` seconds.* <br> **Datatype:** Positive Integer or 0 | `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages. <br>*Defaults to `60` seconds.* <br> **Datatype:** Positive Integer or 0
| `internals.sd_notify` | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details. <br> **Datatype:** Boolean | `internals.sd_notify` | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details. <br> **Datatype:** Boolean
| `logfile` | Specifies logfile name. Uses a rolling strategy for log file rotation for 10 files with the 1MB limit per file. <br> **Datatype:** String | `logfile` | Specifies logfile name. Uses a rolling strategy for log file rotation for 10 files with the 1MB limit per file. <br> **Datatype:** String
@@ -133,6 +137,7 @@ Values set in the configuration file always overwrite values set in the strategy
* `trailing_stop_positive` * `trailing_stop_positive`
* `trailing_stop_positive_offset` * `trailing_stop_positive_offset`
* `trailing_only_offset_is_reached` * `trailing_only_offset_is_reached`
* `use_custom_stoploss`
* `process_only_new_candles` * `process_only_new_candles`
* `order_types` * `order_types`
* `order_time_in_force` * `order_time_in_force`
@@ -140,9 +145,12 @@ Values set in the configuration file always overwrite values set in the strategy
* `stake_amount` * `stake_amount`
* `unfilledtimeout` * `unfilledtimeout`
* `disable_dataframe_checks` * `disable_dataframe_checks`
* `protections`
* `use_sell_signal` (ask_strategy) * `use_sell_signal` (ask_strategy)
* `sell_profit_only` (ask_strategy) * `sell_profit_only` (ask_strategy)
* `sell_profit_offset` (ask_strategy)
* `ignore_roi_if_buy_signal` (ask_strategy) * `ignore_roi_if_buy_signal` (ask_strategy)
* `ignore_buying_expired_candle_after` (ask_strategy)
### Configuring amount per trade ### Configuring amount per trade
@@ -239,37 +247,31 @@ If it is not set in either Strategy or Configuration, a default of 1000% `{"0":
!!! Note "Special case to forcesell after a specific time" !!! Note "Special case to forcesell after a specific time"
A special case presents using `"<N>": -1` as ROI. This forces the bot to sell a trade after N Minutes, no matter if it's positive or negative, so represents a time-limited force-sell. A special case presents using `"<N>": -1` as ROI. This forces the bot to sell a trade after N Minutes, no matter if it's positive or negative, so represents a time-limited force-sell.
### Understand stoploss
Go to the [stoploss documentation](stoploss.md) for more details.
### Understand trailing stoploss
Go to the [trailing stoploss Documentation](stoploss.md#trailing-stop-loss) for details on trailing stoploss.
### Understand initial_state
The `initial_state` configuration parameter is an optional field that defines the initial application state.
Possible values are `running` or `stopped`. (default=`running`)
If the value is `stopped` the bot has to be started with `/start` first.
### Understand forcebuy_enable ### Understand forcebuy_enable
The `forcebuy_enable` configuration parameter enables the usage of forcebuy commands via Telegram. The `forcebuy_enable` configuration parameter enables the usage of forcebuy commands via Telegram and REST API.
This is disabled for security reasons by default, and will show a warning message on startup if enabled. For security reasons, it's disabled by default, and freqtrade will show a warning message on startup if enabled.
For example, you can send `/forcebuy ETH/BTC` Telegram command when this feature if enabled to the bot, For example, you can send `/forcebuy ETH/BTC` to the bot, which will result in freqtrade buying the pair and holds it until a regular sell-signal (ROI, stoploss, /forcesell) appears.
who then buys the pair and holds it until a regular sell-signal (ROI, stoploss, /forcesell) appears.
This can be dangerous with some strategies, so use with care. This can be dangerous with some strategies, so use with care.
See [the telegram documentation](telegram-usage.md) for details on usage. See [the telegram documentation](telegram-usage.md) for details on usage.
### Understand process_throttle_secs ### Ignoring expired candles
The `process_throttle_secs` configuration parameter is an optional field that defines in seconds how long the bot should wait When working with larger timeframes (for example 1h or more) and using a low `max_open_trades` value, the last candle can be processed as soon as a trade slot becomes available. When processing the last candle, this can lead to a situation where it may not be desirable to use the buy signal on that candle. For example, when using a condition in your strategy where you use a cross-over, that point may have passed too long ago for you to start a trade on it.
before asking the strategy if we should buy or a sell an asset. After each wait period, the strategy is asked again for
every opened trade wether or not we should sell, and for all the remaining pairs (either the dynamic list of pairs or In these situations, you can enable the functionality to ignore candles that are beyond a specified period by setting `ask_strategy.ignore_buying_expired_candle_after` to a positive number, indicating the number of seconds after which the buy signal becomes expired.
the static list of pairs) if we should buy.
For example, if your strategy is using a 1h timeframe, and you only want to buy within the first 5 minutes when a new candle comes in, you can add the following configuration to your strategy:
``` json
"ask_strategy":{
"ignore_buying_expired_candle_after": 300,
"price_side": "bid",
// ...
},
```
### Understand order_types ### Understand order_types
@@ -447,136 +449,9 @@ The valid values are:
"BTC", "ETH", "XRP", "LTC", "BCH", "USDT" "BTC", "ETH", "XRP", "LTC", "BCH", "USDT"
``` ```
## Prices used for orders --8<-- "includes/pricing.md"
Prices for regular orders can be controlled via the parameter structures `bid_strategy` for buying and `ask_strategy` for selling. ## Using Dry-run mode
Prices are always retrieved right before an order is placed, either by querying the exchange tickers or by using the orderbook data.
!!! Note
Orderbook data used by Freqtrade are the data retrieved from exchange by the ccxt's function `fetch_order_book()`, i.e. are usually data from the L2-aggregated orderbook, while the ticker data are the structures returned by the ccxt's `fetch_ticker()`/`fetch_tickers()` functions. Refer to the ccxt library [documentation](https://github.com/ccxt/ccxt/wiki/Manual#market-data) for more details.
!!! Warning "Using market orders"
Please read the section [Market order pricing](#market-order-pricing) section when using market orders.
### Buy price
#### Check depth of market
When check depth of market is enabled (`bid_strategy.check_depth_of_market.enabled=True`), the buy signals are filtered based on the orderbook depth (sum of all amounts) for each orderbook side.
Orderbook `bid` (buy) side depth is then divided by the orderbook `ask` (sell) side depth and the resulting delta is compared to the value of the `bid_strategy.check_depth_of_market.bids_to_ask_delta` parameter. The buy order is only executed if the orderbook delta is greater than or equal to the configured delta value.
!!! Note
A delta value below 1 means that `ask` (sell) orderbook side depth is greater than the depth of the `bid` (buy) orderbook side, while a value greater than 1 means opposite (depth of the buy side is higher than the depth of the sell side).
#### Buy price side
The configuration setting `bid_strategy.price_side` defines the side of the spread the bot looks for when buying.
The following displays an orderbook.
``` explanation
...
103
102
101 # ask
-------------Current spread
99 # bid
98
97
...
```
If `bid_strategy.price_side` is set to `"bid"`, then the bot will use 99 as buying price.
In line with that, if `bid_strategy.price_side` is set to `"ask"`, then the bot will use 101 as buying price.
Using `ask` price often guarantees quicker filled orders, but the bot can also end up paying more than what would have been necessary.
Taker fees instead of maker fees will most likely apply even when using limit buy orders.
Also, prices at the "ask" side of the spread are higher than prices at the "bid" side in the orderbook, so the order behaves similar to a market order (however with a maximum price).
#### Buy price with Orderbook enabled
When buying with the orderbook enabled (`bid_strategy.use_order_book=True`), Freqtrade fetches the `bid_strategy.order_book_top` entries from the orderbook and then uses the entry specified as `bid_strategy.order_book_top` on the configured side (`bid_strategy.price_side`) of the orderbook. 1 specifies the topmost entry in the orderbook, while 2 would use the 2nd entry in the orderbook, and so on.
#### Buy price without Orderbook enabled
The following section uses `side` as the configured `bid_strategy.price_side`.
When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price.
The `bid_strategy.ask_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the `last` price and values between those interpolate between ask and last price.
### Sell price
#### Sell price side
The configuration setting `ask_strategy.price_side` defines the side of the spread the bot looks for when selling.
The following displays an orderbook:
``` explanation
...
103
102
101 # ask
-------------Current spread
99 # bid
98
97
...
```
If `ask_strategy.price_side` is set to `"ask"`, then the bot will use 101 as selling price.
In line with that, if `ask_strategy.price_side` is set to `"bid"`, then the bot will use 99 as selling price.
#### Sell price with Orderbook enabled
When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Freqtrade fetches the `ask_strategy.order_book_max` entries in the orderbook. Then each of the orderbook steps between `ask_strategy.order_book_min` and `ask_strategy.order_book_max` on the configured orderbook side are validated for a profitable sell-possibility based on the strategy configuration (`minimal_roi` conditions) and the sell order is placed at the first profitable spot.
!!! Note
Using `order_book_max` higher than `order_book_min` only makes sense when ask_strategy.price_side is set to `"ask"`.
The idea here is to place the sell order early, to be ahead in the queue.
A fixed slot (mirroring `bid_strategy.order_book_top`) can be defined by setting `ask_strategy.order_book_min` and `ask_strategy.order_book_max` to the same number.
!!! Warning "Order_book_max > 1 - increased risks for stoplosses!"
Using `ask_strategy.order_book_max` higher than 1 will increase the risk the stoploss on exchange is cancelled too early, since an eventual [stoploss on exchange](#understand-order_types) will be cancelled as soon as the order is placed.
Also, the sell order will remain on the exchange for `unfilledtimeout.sell` (or until it's filled) - which can lead to missed stoplosses (with or without using stoploss on exchange).
!!! Warning "Order_book_max > 1 in dry-run"
Using `ask_strategy.order_book_max` higher than 1 will result in improper dry-run results (significantly better than real orders executed on exchange), since dry-run assumes orders to be filled almost instantly.
It is therefore advised to not use this setting for dry-runs.
#### Sell price without Orderbook enabled
When not using orderbook (`ask_strategy.use_order_book=False`), the price at the `ask_strategy.price_side` side (defaults to `"ask"`) from the ticker will be used as the sell price.
### Market order pricing
When using market orders, prices should be configured to use the "correct" side of the orderbook to allow realistic pricing detection.
Assuming both buy and sell are using market orders, a configuration similar to the following might be used
``` jsonc
"order_types": {
"buy": "market",
"sell": "market"
// ...
},
"bid_strategy": {
"price_side": "ask",
// ...
},
"ask_strategy":{
"price_side": "bid",
// ...
},
```
Obviously, if only one side is using limit orders, different pricing combinations can be used.
--8<-- "includes/pairlists.md"
## Switch to Dry-run mode
We recommend starting the bot in the Dry-run mode to see how your bot will We recommend starting the bot in the Dry-run mode to see how your bot will
behave and what is the performance of your strategy. In the Dry-run mode the behave and what is the performance of your strategy. In the Dry-run mode the
@@ -609,9 +484,10 @@ Once you will be happy with your bot performance running in the Dry-run mode, yo
### Considerations for dry-run ### Considerations for dry-run
* API-keys may or may not be provided. Only Read-Only operations (i.e. operations that do not alter account state) on the exchange are performed in the dry-run mode. * API-keys may or may not be provided. Only Read-Only operations (i.e. operations that do not alter account state) on the exchange are performed in dry-run mode.
* Wallets (`/balance`) are simulated. * Wallets (`/balance`) are simulated based on `dry_run_wallet`.
* Orders are simulated, and will not be posted to the exchange. * Orders are simulated, and will not be posted to the exchange.
* Orders are assumed to fill immediately, and will never time out.
* In combination with `stoploss_on_exchange`, the stop_loss price is assumed to be filled. * In combination with `stoploss_on_exchange`, the stop_loss price is assumed to be filled.
* Open orders (not trades, which are stored in the database) are reset on bot restart. * Open orders (not trades, which are stored in the database) are reset on bot restart.
@@ -669,32 +545,6 @@ export HTTPS_PROXY="http://addr:port"
freqtrade freqtrade
``` ```
## Embedding Strategies
Freqtrade provides you with with an easy way to embed the strategy into your configuration file.
This is done by utilizing BASE64 encoding and providing this string at the strategy configuration field,
in your chosen config file.
### Encoding a string as BASE64
This is a quick example, how to generate the BASE64 string in python
```python
from base64 import urlsafe_b64encode
with open(file, 'r') as f:
content = f.read()
content = urlsafe_b64encode(content.encode('utf-8'))
```
The variable 'content', will contain the strategy file in a BASE64 encoded form. Which can now be set in your configurations file as following
```json
"strategy": "NameOfStrategy:BASE64String"
```
Please ensure that 'NameOfStrategy' is identical to the strategy name!
## 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

@@ -8,7 +8,7 @@ If no additional parameter is specified, freqtrade will download data for `"1m"`
Exchange and pairs will come from `config.json` (if specified using `-c/--config`). Exchange and pairs will come from `config.json` (if specified using `-c/--config`).
Otherwise `--exchange` becomes mandatory. Otherwise `--exchange` becomes mandatory.
You can use a relative timerange (`--days 20`) or an absolute starting point (`--timerange 20200101`). For incremental downloads, the relative approach should be used. You can use a relative timerange (`--days 20`) or an absolute starting point (`--timerange 20200101-`). For incremental downloads, the relative approach should be used.
!!! Tip "Tip: Updating existing data" !!! Tip "Tip: Updating existing data"
If you already have backtesting data available in your data-directory and would like to refresh this data up to today, use `--days xx` with a number slightly higher than the missing number of days. Freqtrade will keep the available data and only download the missing data. If you already have backtesting data available in your data-directory and would like to refresh this data up to today, use `--days xx` with a number slightly higher than the missing number of days. Freqtrade will keep the available data and only download the missing data.
@@ -264,7 +264,19 @@ If you are using Binance for example:
```bash ```bash
mkdir -p user_data/data/binance mkdir -p user_data/data/binance
cp freqtrade/tests/testdata/pairs.json user_data/data/binance cp tests/testdata/pairs.json user_data/data/binance
```
If you your configuration directory `user_data` was made by docker, you may get the following error:
```
cp: cannot create regular file 'user_data/data/binance/pairs.json': Permission denied
```
You can fix the permissions of your user-data directory as follows:
```
sudo chown -R $UID:$GID user_data
``` ```
The format of the `pairs.json` file is a simple json list. The format of the `pairs.json` file is a simple json list.
@@ -308,10 +320,13 @@ Since this data is large by default, the files use gzip by default. They are sto
To use this mode, simply add `--dl-trades` to your call. This will swap the download method to download trades, and resamples the data locally. To use this mode, simply add `--dl-trades` to your call. This will swap the download method to download trades, and resamples the data locally.
!!! Warning "do not use"
You should not use this unless you're a kraken user. Most other exchanges provide OHLCV data with sufficient history.
Example call: Example call:
```bash ```bash
freqtrade download-data --exchange binance --pairs XRP/ETH ETH/BTC --days 20 --dl-trades freqtrade download-data --exchange kraken --pairs XRP/EUR ETH/EUR --days 20 --dl-trades
``` ```
!!! Note !!! Note

View File

@@ -2,7 +2,7 @@
This page is intended for developers of Freqtrade, people who want to contribute to the Freqtrade codebase or documentation, or people who want to understand the source code of the application they're running. This page is intended for developers of Freqtrade, people who want to contribute to the Freqtrade codebase or documentation, or people who want to understand the source code of the application they're running.
All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We [track issues](https://github.com/freqtrade/freqtrade/issues) on [GitHub](https://github.com) and also have a dev channel on [discord](https://discord.gg/MA9v74M) or [slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg) where you can ask questions. All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We [track issues](https://github.com/freqtrade/freqtrade/issues) on [GitHub](https://github.com) and also have a dev channel on [discord](https://discord.gg/MA9v74M) or [slack](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw) where you can ask questions.
## Documentation ## Documentation
@@ -94,7 +94,9 @@ Below is an outline of exception inheritance hierarchy:
+---+ StrategyError +---+ StrategyError
``` ```
## Modules ---
## Plugins
### Pairlists ### Pairlists
@@ -119,6 +121,9 @@ The base-class provides an instance of the exchange (`self._exchange`) the pairl
self._pairlist_pos = pairlist_pos self._pairlist_pos = pairlist_pos
``` ```
!!! Tip
Don't forget to register your pairlist in `constants.py` under the variable `AVAILABLE_PAIRLISTS` - otherwise it will not be selectable.
Now, let's step through the methods which require actions: Now, let's step through the methods which require actions:
#### Pairlist configuration #### Pairlist configuration
@@ -170,6 +175,66 @@ In `VolumePairList`, this implements different methods of sorting, does early va
return pairs return pairs
``` ```
### Protections
Best read the [Protection documentation](plugins.md#protections) to understand protections.
This Guide is directed towards Developers who want to develop a new protection.
No protection should use datetime directly, but use the provided `date_now` variable for date calculations. This preserves the ability to backtest protections.
!!! Tip "Writing a new Protection"
Best copy one of the existing Protections to have a good example.
Don't forget to register your protection in `constants.py` under the variable `AVAILABLE_PROTECTIONS` - otherwise it will not be selectable.
#### Implementation of a new protection
All Protection implementations must have `IProtection` as parent class.
For that reason, they must implement the following methods:
* `short_desc()`
* `global_stop()`
* `stop_per_pair()`.
`global_stop()` and `stop_per_pair()` must return a ProtectionReturn tuple, which consists of:
* lock pair - boolean
* lock until - datetime - until when should the pair be locked (will be rounded up to the next new candle)
* reason - string, used for logging and storage in the database
The `until` portion should be calculated using the provided `calculate_lock_end()` method.
All Protections should use `"stop_duration"` / `"stop_duration_candles"` to define how long a a pair (or all pairs) should be locked.
The content of this is made available as `self._stop_duration` to the each Protection.
If your protection requires a look-back period, please use `"lookback_period"` / `"lockback_period_candles"` to keep all protections aligned.
#### Global vs. local stops
Protections can have 2 different ways to stop trading for a limited :
* Per pair (local)
* For all Pairs (globally)
##### Protections - per pair
Protections that implement the per pair approach must set `has_local_stop=True`.
The method `stop_per_pair()` will be called whenever a trade closed (sell order completed).
##### Protections - global protection
These Protections should do their evaluation across all pairs, and consequently will also lock all pairs from trading (called a global PairLock).
Global protection must set `has_global_stop=True` to be evaluated for global stops.
The method `global_stop()` will be called whenever a trade closed (sell order completed).
##### Protections - calculating lock end time
Protections should calculate the lock end time based on the last trade it considers.
This avoids re-locking should the lookback-period be longer than the actual lock period.
The `IProtection` parent class provides a helper method for this in `calculate_lock_end()`.
---
## Implement a new Exchange (WIP) ## Implement a new Exchange (WIP)
!!! Note !!! Note
@@ -177,6 +242,9 @@ In `VolumePairList`, this implements different methods of sorting, does early va
Most exchanges supported by CCXT should work out of the box. Most exchanges supported by CCXT should work out of the box.
To quickly test the public endpoints of an exchange, add a configuration for your exchange to `test_ccxt_compat.py` and run these tests with `pytest --longrun tests/exchange/test_ccxt_compat.py`.
Completing these tests successfully a good basis point (it's a requirement, actually), however these won't guarantee correct exchange functioning, as this only tests public endpoints, but no private endpoint (like generate order or similar).
### Stoploss On Exchange ### Stoploss On Exchange
Check if the new exchange supports Stoploss on Exchange orders through their API. Check if the new exchange supports Stoploss on Exchange orders through their API.

View File

@@ -1,201 +0,0 @@
## Freqtrade with docker without docker-compose
!!! Warning
The below documentation is provided for completeness and assumes that you are familiar with running docker containers. If you're just starting out with Docker, we recommend to follow the [Quickstart](docker.md) instructions.
### Download the official Freqtrade docker image
Pull the image from docker hub.
Branches / tags available can be checked out on [Dockerhub tags page](https://hub.docker.com/r/freqtradeorg/freqtrade/tags/).
```bash
docker pull freqtradeorg/freqtrade:stable
# Optionally tag the repository so the run-commands remain shorter
docker tag freqtradeorg/freqtrade:stable freqtrade
```
To update the image, simply run the above commands again and restart your running container.
Should you require additional libraries, please [build the image yourself](#build-your-own-docker-image).
!!! Note "Docker image update frequency"
The official docker images with tags `stable`, `develop` and `latest` are automatically rebuild once a week to keep the base image up-to-date.
In addition to that, every merge to `develop` will trigger a rebuild for `develop` and `latest`.
### Prepare the configuration files
Even though you will use docker, you'll still need some files from the github repository.
#### Clone the git repository
Linux/Mac/Windows with WSL
```bash
git clone https://github.com/freqtrade/freqtrade.git
```
Windows with docker
```bash
git clone --config core.autocrlf=input https://github.com/freqtrade/freqtrade.git
```
#### Copy `config.json.example` to `config.json`
```bash
cd freqtrade
cp -n config.json.example config.json
```
> To understand the configuration options, please refer to the [Bot Configuration](configuration.md) page.
#### Create your database file
=== "Dry-Run"
``` bash
touch tradesv3.dryrun.sqlite
```
=== "Production"
``` bash
touch tradesv3.sqlite
```
!!! Warning "Database File Path"
Make sure to use the path to the correct database file when starting the bot in Docker.
### Build your own Docker image
Best start by pulling the official docker image from dockerhub as explained [here](#download-the-official-docker-image) to speed up building.
To add additional libraries to your docker image, best check out [Dockerfile.technical](https://github.com/freqtrade/freqtrade/blob/develop/docker/Dockerfile.technical) which adds the [technical](https://github.com/freqtrade/technical) module to the image.
```bash
docker build -t freqtrade -f docker/Dockerfile.technical .
```
If you are developing using Docker, use `docker/Dockerfile.develop` to build a dev Docker image, which will also set up develop dependencies:
```bash
docker build -f docker/Dockerfile.develop -t freqtrade-dev .
```
!!! Warning "Include your config file manually"
For security reasons, your configuration file will not be included in the image, you will need to bind mount it. It is also advised to bind mount an SQLite database file (see [5. Run a restartable docker image](#run-a-restartable-docker-image)") to keep it between updates.
#### Verify the Docker image
After the build process you can verify that the image was created with:
```bash
docker images
```
The output should contain the freqtrade image.
### Run the Docker image
You can run a one-off container that is immediately deleted upon exiting with the following command (`config.json` must be in the current working directory):
```bash
docker run --rm -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
```
!!! Warning
In this example, the database will be created inside the docker instance and will be lost when you refresh your image.
#### Adjust timezone
By default, the container will use UTC timezone.
If you would like to change the timezone use the following commands:
=== "Linux"
``` bash
-v /etc/timezone:/etc/timezone:ro
# Complete command:
docker run --rm -v /etc/timezone:/etc/timezone:ro -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
```
=== "MacOS"
```bash
docker run --rm -e TZ=`ls -la /etc/localtime | cut -d/ -f8-9` -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
```
!!! Note "MacOS Issues"
The OSX Docker versions after 17.09.1 have a known issue whereby `/etc/localtime` cannot be shared causing Docker to not start.<br>
A work-around for this is to start with the MacOS command above
More information on this docker issue and work-around can be read [here](https://github.com/docker/for-mac/issues/2396).
### Run a restartable docker image
To run a restartable instance in the background (feel free to place your configuration and database files wherever it feels comfortable on your filesystem).
#### 1. Move your config file and database
The following will assume that you place your configuration / database files to `~/.freqtrade`, which is a hidden directory in your home directory. Feel free to use a different directory and replace the directory in the upcomming commands.
```bash
mkdir ~/.freqtrade
mv config.json ~/.freqtrade
mv tradesv3.sqlite ~/.freqtrade
```
#### 2. Run the docker image
```bash
docker run -d \
--name freqtrade \
-v ~/.freqtrade/config.json:/freqtrade/config.json \
-v ~/.freqtrade/user_data/:/freqtrade/user_data \
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
freqtrade trade --db-url sqlite:///tradesv3.sqlite --strategy MyAwesomeStrategy
```
!!! Note
When using docker, it's best to specify `--db-url` explicitly to ensure that the database URL and the mounted database file match.
!!! Note
All available bot command line parameters can be added to the end of the `docker run` command.
!!! Note
You can define a [restart policy](https://docs.docker.com/config/containers/start-containers-automatically/) in docker. It can be useful in some cases to use the `--restart unless-stopped` flag (crash of freqtrade or reboot of your system).
### Monitor your Docker instance
You can use the following commands to monitor and manage your container:
```bash
docker logs freqtrade
docker logs -f freqtrade
docker restart freqtrade
docker stop freqtrade
docker start freqtrade
```
For more information on how to operate Docker, please refer to the [official Docker documentation](https://docs.docker.com/).
!!! Note
You do not need to rebuild the image for configuration changes, it will suffice to edit `config.json` and restart the container.
### Backtest with docker
The following assumes that the download/setup of the docker image have been completed successfully.
Also, backtest-data should be available at `~/.freqtrade/user_data/`.
```bash
docker run -d \
--name freqtrade \
-v /etc/localtime:/etc/localtime:ro \
-v ~/.freqtrade/config.json:/freqtrade/config.json \
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
-v ~/.freqtrade/user_data/:/freqtrade/user_data/ \
freqtrade backtesting --strategy AwsomelyProfitableStrategy
```
Head over to the [Backtesting Documentation](backtesting.md) for more details.
!!! Note
Additional bot command line parameters can be appended after the image name (`freqtrade` in the above example).

View File

@@ -8,9 +8,7 @@ Start by downloading and installing Docker CE for your platform:
* [Windows](https://docs.docker.com/docker-for-windows/install/) * [Windows](https://docs.docker.com/docker-for-windows/install/)
* [Linux](https://docs.docker.com/install/) * [Linux](https://docs.docker.com/install/)
Optionally, [`docker-compose`](https://docs.docker.com/compose/install/) should be installed and available to follow the [docker quick start guide](#docker-quick-start). To simplify running freqtrade, please install [`docker-compose`](https://docs.docker.com/compose/install/) should be installed and available to follow the below [docker quick start guide](#docker-quick-start).
Once you have Docker installed, simply prepare the config file (e.g. `config.json`) and run the image for `freqtrade` as explained below.
## Freqtrade with docker-compose ## Freqtrade with docker-compose
@@ -71,7 +69,7 @@ The last 2 steps in the snippet create the directory with `user_data`, as well a
!!! Question "How to edit the bot configuration?" !!! Question "How to edit the bot configuration?"
You can edit the configuration at any time, which is available as `user_data/config.json` (within the directory `ft_userdata`) when using the above configuration. You can edit the configuration at any time, which is available as `user_data/config.json` (within the directory `ft_userdata`) when using the above configuration.
You can also change the both Strategy and commands by editing the `docker-compose.yml` file. You can also change the both Strategy and commands by editing the command section of your `docker-compose.yml` file.
#### Adding a custom strategy #### Adding a custom strategy
@@ -83,7 +81,8 @@ The `SampleStrategy` is run by default.
!!! Warning "`SampleStrategy` is just a demo!" !!! Warning "`SampleStrategy` is just a demo!"
The `SampleStrategy` is there for your reference and give you ideas for your own strategy. The `SampleStrategy` is there for your reference and give you ideas for your own strategy.
Please always backtest the strategy and use dry-run for some time before risking real money! Please always backtest your strategy and use dry-run for some time before risking real money!
You will find more information about Strategy development in the [Strategy documentation](strategy-customization.md).
Once this is done, you're ready to launch the bot in trading mode (Dry-run or Live-trading, depending on your answer to the corresponding question you made above). Once this is done, you're ready to launch the bot in trading mode (Dry-run or Live-trading, depending on your answer to the corresponding question you made above).
@@ -91,18 +90,23 @@ Once this is done, you're ready to launch the bot in trading mode (Dry-run or Li
docker-compose up -d docker-compose up -d
``` ```
#### Monitoring the bot
You can check for running instances with `docker-compose ps`.
This should list the service `freqtrade` as `running`. If that's not the case, best check the logs (see next point).
#### Docker-compose logs #### Docker-compose logs
Logs will be located at: `user_data/logs/freqtrade.log`. Logs will be written to: `user_data/logs/freqtrade.log`.
You can check the latest log with the command `docker-compose logs -f`. You can also check the latest log with the command `docker-compose logs -f`.
#### Database #### Database
The database will be at: `user_data/tradesv3.sqlite` The database will be located at: `user_data/tradesv3.sqlite`
#### Updating freqtrade with docker-compose #### Updating freqtrade with docker-compose
To update freqtrade when using `docker-compose` is as simple as running the following 2 commands: Updating freqtrade when using `docker-compose` is as simple as running the following 2 commands:
``` bash ``` bash
# Download the latest image # Download the latest image
@@ -120,10 +124,10 @@ This will first pull the latest image, and will then restart the container with
Advanced users may edit the docker-compose file further to include all possible options or arguments. Advanced users may edit the docker-compose file further to include all possible options or arguments.
All possible freqtrade arguments will be available by running `docker-compose run --rm freqtrade <command> <optional arguments>`. All freqtrade arguments will be available by running `docker-compose run --rm freqtrade <command> <optional arguments>`.
!!! Note "`docker-compose run --rm`" !!! Note "`docker-compose run --rm`"
Including `--rm` will clean up the container after completion, and is highly recommended for all modes except trading mode (running with `freqtrade trade` command). Including `--rm` will remove the container after completion, and is highly recommended for all modes except trading mode (running with `freqtrade trade` command).
#### Example: Download data with docker-compose #### Example: Download data with docker-compose
@@ -172,19 +176,19 @@ docker-compose run --rm freqtrade plot-dataframe --strategy AwesomeStrategy -p B
The output will be stored in the `user_data/plot` directory, and can be opened with any modern browser. The output will be stored in the `user_data/plot` directory, and can be opened with any modern browser.
## Data analayis using docker compose ## Data analysis using docker compose
Freqtrade provides a docker-compose file which starts up a jupyter lab server. Freqtrade provides a docker-compose file which starts up a jupyter lab server.
You can run this server using the following command: You can run this server using the following command:
``` bash ``` bash
docker-compose --rm -f docker/docker-compose-jupyter.yml up docker-compose -f docker/docker-compose-jupyter.yml up
``` ```
This will create a dockercontainer running jupyter lab, which will be accessible using `https://127.0.0.1:8888/lab`. This will create a docker-container running jupyter lab, which will be accessible using `https://127.0.0.1:8888/lab`.
Please use the link that's printed in the console after startup for simplified login. Please use the link that's printed in the console after startup for simplified login.
Since part of this image is built on your machine, it is recommended to rebuild the image from time to time to keep freqtrade (and dependencies) uptodate. Since part of this image is built on your machine, it is recommended to rebuild the image from time to time to keep freqtrade (and dependencies) up-to-date.
``` bash ``` bash
docker-compose -f docker/docker-compose-jupyter.yml build --no-cache docker-compose -f docker/docker-compose-jupyter.yml build --no-cache

View File

@@ -1,6 +1,6 @@
# Edge positioning # Edge positioning
The `Edge Positioning` module uses probability to calculate your win rate and risk reward ration. It will use these statistics to control your strategy trade entry points, position side and, stoploss. The `Edge Positioning` module uses probability to calculate your win rate and risk reward ratio. It will use these statistics to control your strategy trade entry points, position size and, stoploss.
!!! Warning !!! Warning
`Edge positioning` is not compatible with dynamic (volume-based) whitelist. `Edge positioning` is not compatible with dynamic (volume-based) whitelist.
@@ -9,6 +9,7 @@ The `Edge Positioning` module uses probability to calculate your win rate and ri
`Edge Positioning` only considers *its own* buy/sell/stoploss signals. It ignores the stoploss, trailing stoploss, and ROI settings in the strategy configuration file. `Edge Positioning` only considers *its own* buy/sell/stoploss signals. It ignores the stoploss, trailing stoploss, and ROI settings in the strategy configuration file.
`Edge Positioning` improves the performance of some trading strategies and *decreases* the performance of others. `Edge Positioning` improves the performance of some trading strategies and *decreases* the performance of others.
## Introduction ## Introduction
Trading strategies are not perfect. They are frameworks that are susceptible to the market and its indicators. Because the market is not at all predictable, sometimes a strategy will win and sometimes the same strategy will lose. Trading strategies are not perfect. They are frameworks that are susceptible to the market and its indicators. Because the market is not at all predictable, sometimes a strategy will win and sometimes the same strategy will lose.
@@ -23,8 +24,8 @@ The Edge Positioning module seeks to improve a strategy's winning probability an
We raise the following question[^1]: We raise the following question[^1]:
!!! Question "Which trade is a better option?" !!! Question "Which trade is a better option?"
a) A trade with 80% of chance of losing $100 and 20% chance of winning $200<br/> a) A trade with 80% of chance of losing 100\$ and 20% chance of winning 200\$<br/>
b) A trade with 100% of chance of losing $30 b) A trade with 100% of chance of losing 30\$
???+ Info "Answer" ???+ Info "Answer"
The expected value of *a)* is smaller than the expected value of *b)*.<br/> The expected value of *a)* is smaller than the expected value of *b)*.<br/>
@@ -34,8 +35,8 @@ We raise the following question[^1]:
Another way to look at it is to ask a similar question: Another way to look at it is to ask a similar question:
!!! Question "Which trade is a better option?" !!! Question "Which trade is a better option?"
a) A trade with 80% of chance of winning 100 and 20% chance of losing $200<br/> a) A trade with 80% of chance of winning 100\$ and 20% chance of losing 200\$<br/>
b) A trade with 100% of chance of winning $30 b) A trade with 100% of chance of winning 30\$
Edge positioning tries to answer the hard questions about risk/reward and position size automatically, seeking to minimizes the chances of losing of a given strategy. Edge positioning tries to answer the hard questions about risk/reward and position size automatically, seeking to minimizes the chances of losing of a given strategy.
@@ -55,7 +56,7 @@ Similarly, we can discover the set of losing trades $T_{lose}$ as follows:
$$ T_{lose} = \{o \in O | o \leq 0\} $$ $$ T_{lose} = \{o \in O | o \leq 0\} $$
!!! Example !!! Example
In a section where a strategy made three transactions $O = \{3.5, -1, 15, 0\}$:<br> In a section where a strategy made four transactions $O = \{3.5, -1, 15, 0\}$:<br>
$T_{win} = \{3.5, 15\}$<br> $T_{win} = \{3.5, 15\}$<br>
$T_{lose} = \{-1, 0\}$<br> $T_{lose} = \{-1, 0\}$<br>
@@ -82,7 +83,7 @@ Risk Reward Ratio ($R$) is a formula used to measure the expected gains of a giv
$$ R = \frac{\text{potential_profit}}{\text{potential_loss}} $$ $$ R = \frac{\text{potential_profit}}{\text{potential_loss}} $$
???+ Example "Worked example of $R$ calculation" ???+ Example "Worked example of $R$ calculation"
Let's say that you think that the price of *stonecoin* today is $10.0. You believe that, because they will start mining stonecoin, it will go up to $15.0 tomorrow. There is the risk that the stone is too hard, and the GPUs can't mine it, so the price might go to $0 tomorrow. You are planning to invest $100, which will give you 10 shares (100 / 10). Let's say that you think that the price of *stonecoin* today is 10.0\$. You believe that, because they will start mining stonecoin, it will go up to 15.0\$ tomorrow. There is the risk that the stone is too hard, and the GPUs can't mine it, so the price might go to 0\$ tomorrow. You are planning to invest 100\$, which will give you 10 shares (100 / 10).
Your potential profit is calculated as: Your potential profit is calculated as:
@@ -92,9 +93,9 @@ $$ R = \frac{\text{potential_profit}}{\text{potential_loss}} $$
&= 50 &= 50
\end{aligned}$ \end{aligned}$
Since the price might go to $0, the $100 dollars invested could turn into 0. Since the price might go to 0\$, the 100\$ dollars invested could turn into 0.
We do however use a stoploss of 15% - so in the worst case, we'll sell 15% below entry price (or at 8.5$). We do however use a stoploss of 15% - so in the worst case, we'll sell 15% below entry price (or at 8.5$\).
$\begin{aligned} $\begin{aligned}
\text{potential_loss} &= (\text{entry_price} - \text{stoploss}) * \frac{\text{investment}}{\text{entry_price}} \\ \text{potential_loss} &= (\text{entry_price} - \text{stoploss}) * \frac{\text{investment}}{\text{entry_price}} \\
@@ -109,7 +110,7 @@ $$ R = \frac{\text{potential_profit}}{\text{potential_loss}} $$
&= \frac{50}{15}\\ &= \frac{50}{15}\\
&= 3.33 &= 3.33
\end{aligned}$<br> \end{aligned}$<br>
What it effectively means is that the strategy have the potential to make 3.33$ for each $1 invested. What it effectively means is that the strategy have the potential to make 3.33\$ for each 1\$ invested.
On a long horizon, that is, on many trades, we can calculate the risk reward by dividing the strategy' average profit on winning trades by the strategy' average loss on losing trades. We can calculate the average profit, $\mu_{win}$, as follows: On a long horizon, that is, on many trades, we can calculate the risk reward by dividing the strategy' average profit on winning trades by the strategy' average loss on losing trades. We can calculate the average profit, $\mu_{win}$, as follows:
@@ -141,7 +142,7 @@ $$E = R * W - L$$
$E = R * W - L = 5 * 0.28 - 0.72 = 0.68$ $E = R * W - L = 5 * 0.28 - 0.72 = 0.68$
<br> <br>
The expectancy worked out in the example above means that, on average, this strategy' trades will return 1.68 times the size of its losses. Said another way, the strategy makes $1.68 for every $1 it loses, on average. The expectancy worked out in the example above means that, on average, this strategy' trades will return 1.68 times the size of its losses. Said another way, the strategy makes 1.68\$ for every 1\$ it loses, on average.
This is important for two reasons: First, it may seem obvious, but you know right away that you have a positive return. Second, you now have a number you can compare to other candidate systems to make decisions about which ones you employ. This is important for two reasons: First, it may seem obvious, but you know right away that you have a positive return. Second, you now have a number you can compare to other candidate systems to make decisions about which ones you employ.
@@ -206,7 +207,61 @@ Let's say the stake currency is **ETH** and there is $10$ **ETH** on the wallet.
- The strategy detects a sell signal in the **XLM/ETH** market. The bot exits **Trade 1** for a profit of $1$ **ETH**. The total capital in the wallet becomes $11$ **ETH** and the available capital for trading becomes $5.5$ **ETH**. - The strategy detects a sell signal in the **XLM/ETH** market. The bot exits **Trade 1** for a profit of $1$ **ETH**. The total capital in the wallet becomes $11$ **ETH** and the available capital for trading becomes $5.5$ **ETH**.
- **Trade 4** The strategy detects a new buy signal int the **XLM/ETH** market. `Edge Positioning` calculates the stoploss of $2%$, and the position size of $0.055 / 0.02 = 2.75$ **ETH**. - **Trade 4** The strategy detects a new buy signal int the **XLM/ETH** market. `Edge Positioning` calculates the stoploss of $2\%$, and the position size of $0.055 / 0.02 = 2.75$ **ETH**.
## Edge command reference
```
usage: freqtrade edge [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [-s NAME] [--strategy-path PATH]
[-i TIMEFRAME] [--timerange TIMERANGE]
[--max-open-trades INT] [--stake-amount STAKE_AMOUNT]
[--fee FLOAT] [--stoplosses STOPLOSS_RANGE]
optional arguments:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--max-open-trades INT
Override the value of the `max_open_trades`
configuration setting.
--stake-amount STAKE_AMOUNT
Override the value of the `stake_amount` configuration
setting.
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
entry and exit).
--stoplosses STOPLOSS_RANGE
Defines a range of stoploss values against which edge
will assess the strategy. The format is "min,max,step"
(without any space). Example:
`--stoplosses=-0.01,-0.1,-0.001`
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Strategy arguments:
-s NAME, --strategy NAME
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
```
## Configurations ## Configurations
@@ -222,7 +277,7 @@ Edge module has following configuration options:
| `stoploss_range_max` | Maximum stoploss. <br>*Defaults to `-0.10`.* <br> **Datatype:** Float | `stoploss_range_max` | Maximum stoploss. <br>*Defaults to `-0.10`.* <br> **Datatype:** Float
| `stoploss_range_step` | As an example if this is set to -0.01 then Edge will test the strategy for `[-0.01, -0,02, -0,03 ..., -0.09, -0.10]` ranges. <br> **Note** than having a smaller step means having a bigger range which could lead to slow calculation. <br> If you set this parameter to -0.001, you then slow down the Edge calculation by a factor of 10. <br>*Defaults to `-0.001`.* <br> **Datatype:** Float | `stoploss_range_step` | As an example if this is set to -0.01 then Edge will test the strategy for `[-0.01, -0,02, -0,03 ..., -0.09, -0.10]` ranges. <br> **Note** than having a smaller step means having a bigger range which could lead to slow calculation. <br> If you set this parameter to -0.001, you then slow down the Edge calculation by a factor of 10. <br>*Defaults to `-0.001`.* <br> **Datatype:** Float
| `minimum_winrate` | It filters out pairs which don't have at least minimum_winrate. <br>This comes handy if you want to be conservative and don't comprise win rate in favour of risk reward ratio. <br>*Defaults to `0.60`.* <br> **Datatype:** Float | `minimum_winrate` | It filters out pairs which don't have at least minimum_winrate. <br>This comes handy if you want to be conservative and don't comprise win rate in favour of risk reward ratio. <br>*Defaults to `0.60`.* <br> **Datatype:** Float
| `minimum_expectancy` | It filters out pairs which have the expectancy lower than this number. <br>Having an expectancy of 0.20 means if you put 10$ on a trade you expect a 12$ return. <br>*Defaults to `0.20`.* <br> **Datatype:** Float | `minimum_expectancy` | It filters out pairs which have the expectancy lower than this number. <br>Having an expectancy of 0.20 means if you put 10\$ on a trade you expect a 12\$ return. <br>*Defaults to `0.20`.* <br> **Datatype:** Float
| `min_trade_number` | When calculating *W*, *R* and *E* (expectancy) against historical data, you always want to have a minimum number of trades. The more this number is the more Edge is reliable. <br>Having a win rate of 100% on a single trade doesn't mean anything at all. But having a win rate of 70% over past 100 trades means clearly something. <br>*Defaults to `10` (it is highly recommended not to decrease this number).* <br> **Datatype:** Integer | `min_trade_number` | When calculating *W*, *R* and *E* (expectancy) against historical data, you always want to have a minimum number of trades. The more this number is the more Edge is reliable. <br>Having a win rate of 100% on a single trade doesn't mean anything at all. But having a win rate of 70% over past 100 trades means clearly something. <br>*Defaults to `10` (it is highly recommended not to decrease this number).* <br> **Datatype:** Integer
| `max_trade_duration_minute` | Edge will filter out trades with long duration. If a trade is profitable after 1 month, it is hard to evaluate the strategy based on it. But if most of trades are profitable and they have maximum duration of 30 minutes, then it is clearly a good sign.<br>**NOTICE:** While configuring this value, you should take into consideration your timeframe. As an example filtering out trades having duration less than one day for a strategy which has 4h interval does not make sense. Default value is set assuming your strategy interval is relatively small (1m or 5m, etc.).<br>*Defaults to `1440` (one day).* <br> **Datatype:** Integer | `max_trade_duration_minute` | Edge will filter out trades with long duration. If a trade is profitable after 1 month, it is hard to evaluate the strategy based on it. But if most of trades are profitable and they have maximum duration of 30 minutes, then it is clearly a good sign.<br>**NOTICE:** While configuring this value, you should take into consideration your timeframe. As an example filtering out trades having duration less than one day for a strategy which has 4h interval does not make sense. Default value is set assuming your strategy interval is relatively small (1m or 5m, etc.).<br>*Defaults to `1440` (one day).* <br> **Datatype:** Integer
| `remove_pumps` | Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off.<br>*Defaults to `false`.* <br> **Datatype:** Boolean | `remove_pumps` | Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off.<br>*Defaults to `false`.* <br> **Datatype:** Boolean

View File

@@ -2,30 +2,30 @@
## Beginner Tips & Tricks ## Beginner Tips & Tricks
* When you work with your strategy & hyperopt file you should use a proper code editor like vscode or Pycharm. A good code editor will provide syntax highlighting as well as line numbers, making it easy to find syntax errors (most likely, pointed out by Freqtrade during startup). * When you work with your strategy & hyperopt file you should use a proper code editor like VSCode or PyCharm. A good code editor will provide syntax highlighting as well as line numbers, making it easy to find syntax errors (most likely pointed out by Freqtrade during startup).
## Freqtrade common issues ## Freqtrade common issues
### The bot does not start ### The bot does not start
Running the bot with `freqtrade trade --config config.json` does show the output `freqtrade: command not found`. Running the bot with `freqtrade trade --config config.json` shows the output `freqtrade: command not found`.
This could have the following reasons: 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 work correctly.
* 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?
* Depending on the buy strategy, the amount of whitelisted coins, the * Depending on the buy strategy, the amount of whitelisted coins, the
situation of the market etc, it can take up to hours to find good entry situation of the market etc, it can take up to hours to find a good entry
position for a trade. Be patient! position for a trade. Be patient!
* Or it may because of a configuration error? Best check the logs, it's usually telling you if the bot is simply not getting buy signals (only heartbeat messages), or if there is something wrong (errors / exceptions in the log). * It may be because of a configuration error. It's best to check the logs, they usually tell you if the bot is simply not getting buy signals (only heartbeat messages), or if there is something wrong (errors / exceptions in the log).
### I have made 12 trades already, why is my total profit negative?! ### I have made 12 trades already, why is my total profit negative?
I understand your disappointment but unfortunately 12 trades is just I understand your disappointment but unfortunately 12 trades is just
not enough to say anything. If you run backtesting, you can see that our not enough to say anything. If you run backtesting, you can see that our
@@ -36,20 +36,17 @@ of course constantly aim to improve the bot but it will _always_ be a
gamble, which should leave you with modest wins on monthly basis but gamble, which should leave you with modest wins on monthly basis but
you can't say much from few trades. you can't say much from few trades.
### Id like to change the stake amount. Can I just stop the bot with /stop and then change the config.json and run it again? ### Id like to make changes to the config. Can I do that without having to kill the bot?
Not quite. Trades are persisted to a database but the configuration is Yes. You can edit your config and use the `/reload_config` command to reload the configuration. The bot will stop, reload the configuration and strategy and will restart with the new configuration and strategy.
currently only read when the bot is killed and restarted. `/stop` more
like pauses. You can stop your bot, adjust settings and start it again.
### I want to improve the bot with a new strategy ### I want to improve the bot with a new strategy
That's great. We have a nice backtesting and hyperoptimization setup. See That's great. We have a nice backtesting and hyperoptimization setup. See the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-commands).
the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-commands).
### Is there a setting to only SELL the coins being held and not perform anymore BUYS? ### Is there a setting to only SELL the coins being held and not perform anymore BUYS?
You can use the `/forcesell all` command from Telegram. You can use the `/stopbuy` command in Telegram to prevent future buys, followed by `/forcesell 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
@@ -59,7 +56,7 @@ Please look at the [advanced setup documentation Page](advanced-setup.md#running
This message is just a warning that the latest candles had missing candles in them. This message is just a warning that the latest candles had missing candles in them.
Depending on the exchange, this can indicate that the pair didn't have a trade for the timeframe you are using - and the exchange does only return candles with volume. Depending on the exchange, this can indicate that the pair didn't have a trade for the timeframe you are using - and the exchange does only return candles with volume.
On low volume pairs, this is a rather common occurance. On low volume pairs, this is a rather common occurrence.
If this happens for all pairs in the pairlist, this might indicate a recent exchange downtime. Please check your exchange's public channels for details. If this happens for all pairs in the pairlist, this might indicate a recent exchange downtime. Please check your exchange's public channels for details.
@@ -73,7 +70,7 @@ Read [the Bittrex section about restricted markets](exchanges.md#restricted-mark
### I'm getting the "Exchange Bittrex does not support market orders." message and cannot run my strategy ### I'm getting the "Exchange Bittrex does not support market orders." message and cannot run my strategy
As the message says, Bittrex does not support market orders and you have one of the [order types](configuration.md/#understand-order_types) set to "market". Probably your strategy was written with other exchanges in mind and sets "market" orders for "stoploss" orders, which is correct and preferable for most of the exchanges supporting market orders (but not for Bittrex). As the message says, Bittrex does not support market orders and you have one of the [order types](configuration.md/#understand-order_types) set to "market". Your strategy was probably written with other exchanges in mind and sets "market" orders for "stoploss" orders, which is correct and preferable for most of the exchanges supporting market orders (but not for Bittrex).
To fix it for Bittrex, redefine order types in the strategy to use "limit" instead of "market": To fix it for Bittrex, redefine order types in the strategy to use "limit" instead of "market":
@@ -85,7 +82,7 @@ To fix it for Bittrex, redefine order types in the strategy to use "limit" inste
} }
``` ```
Same fix should be done in the configuration file, if order types are defined in your custom config rather than in the strategy. The same fix should be applied in the configuration file, if order types are defined in your custom config rather than in the strategy.
### How do I search the bot logs for something? ### How do I search the bot logs for something?
@@ -127,10 +124,10 @@ On Windows, the `--logfile` option is also supported by Freqtrade and you can us
## Hyperopt module ## Hyperopt module
### How many epoch do I need to get a good Hyperopt result? ### How many epochs do I need to get a good Hyperopt result?
Per default Hyperopt called without the `-e`/`--epochs` command line option will only Per default Hyperopt called without the `-e`/`--epochs` command line option will only
run 100 epochs, means 100 evals of your triggers, guards, ... Too few run 100 epochs, means 100 evaluations of your triggers, guards, ... Too few
to find a great result (unless if you are very lucky), so you probably to find a great result (unless if you are very lucky), so you probably
have to run it for 10.000 or more. But it will take an eternity to have to run it for 10.000 or more. But it will take an eternity to
compute. compute.
@@ -140,32 +137,32 @@ Since hyperopt uses Bayesian search, running for too many epochs may not produce
It's therefore recommended to run between 500-1000 epochs over and over until you hit at least 10.000 epochs in total (or are satisfied with the result). You can best judge by looking at the results - if the bot keeps discovering better strategies, it's best to keep on going. It's therefore recommended to run between 500-1000 epochs over and over until you hit at least 10.000 epochs in total (or are satisfied with the result). You can best judge by looking at the results - if the bot keeps discovering better strategies, it's best to keep on going.
```bash ```bash
freqtrade hyperopt --hyperop SampleHyperopt --hyperopt-loss SharpeHyperOptLossDaily --strategy SampleStrategy -e 1000 freqtrade hyperopt --hyperopt SampleHyperopt --hyperopt-loss SharpeHyperOptLossDaily --strategy SampleStrategy -e 1000
``` ```
### Why does it take a long time to run hyperopt? ### Why does it take a long time to run hyperopt?
* Discovering a great strategy with Hyperopt takes time. Study www.freqtrade.io, the Freqtrade Documentation page, join the Freqtrade [Slack community](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg) - or the Freqtrade [discord community](https://discord.gg/X89cVG). While you patiently wait for the most advanced, free crypto bot in the world, to hand you a possible golden strategy specially designed just for you. * Discovering a great strategy with Hyperopt takes time. Study www.freqtrade.io, the Freqtrade Documentation page, join the Freqtrade [Slack community](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw) - or the Freqtrade [discord community](https://discord.gg/X89cVG). While you patiently wait for the most advanced, free crypto bot in the world, to hand you a possible golden strategy specially designed just for you.
* If you wonder why it can take from 20 minutes to days to do 1000 epochs here are some answers: * If you wonder why it can take from 20 minutes to days to do 1000 epochs here are some answers:
This answer was written during the release 0.15.1, when we had: This answer was written during the release 0.15.1, when we had:
- 8 triggers * 8 triggers
- 9 guards: let's say we evaluate even 10 values from each * 9 guards: let's say we evaluate even 10 values from each
- 1 stoploss calculation: let's say we want 10 values from that too to be evaluated * 1 stoploss calculation: let's say we want 10 values from that too to be evaluated
The following calculation is still very rough and not very precise The following calculation is still very rough and not very precise
but it will give the idea. With only these triggers and guards there is but it will give the idea. With only these triggers and guards there is
already 8\*10^9\*10 evaluations. A roughly total of 80 billion evals. already 8\*10^9\*10 evaluations. A roughly total of 80 billion evaluations.
Did you run 100 000 evals? Congrats, you've done roughly 1 / 100 000 th Did you run 100 000 evaluations? Congrats, you've done roughly 1 / 100 000 th
of the search space, assuming that the bot never tests the same parameters more than once. of the search space, assuming that the bot never tests the same parameters more than once.
* The time it takes to run 1000 hyperopt epochs depends on things like: The available cpu, hard-disk, ram, timeframe, timerange, indicator settings, indicator count, amount of coins that hyperopt test strategies on and the resulting trade count - which can be 650 trades in a year or 10.0000 trades depending if the strategy aims for big profits by trading rarely or for many low profit trades. * The time it takes to run 1000 hyperopt epochs depends on things like: The available cpu, hard-disk, ram, timeframe, timerange, indicator settings, indicator count, amount of coins that hyperopt test strategies on and the resulting trade count - which can be 650 trades in a year or 10.0000 trades depending if the strategy aims for big profits by trading rarely or for many low profit trades.
Example: 4% profit 650 times vs 0,3% profit a trade 10.000 times in a year. If we assume you set the --timerange to 365 days. Example: 4% profit 650 times vs 0,3% profit a trade 10.000 times in a year. If we assume you set the --timerange to 365 days.
Example: Example:
`freqtrade --config config.json --strategy SampleStrategy --hyperopt SampleHyperopt -e 1000 --timerange 20190601-20200601` `freqtrade --config config.json --strategy SampleStrategy --hyperopt SampleHyperopt -e 1000 --timerange 20190601-20200601`
## Edge module ## Edge module

View File

@@ -32,6 +32,107 @@ source .env/bin/activate
pip install -r requirements-hyperopt.txt pip install -r requirements-hyperopt.txt
``` ```
## Hyperopt command reference
```
usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [-s NAME] [--strategy-path PATH]
[-i TIMEFRAME] [--timerange TIMERANGE]
[--data-format-ohlcv {json,jsongz,hdf5}]
[--max-open-trades INT]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[--hyperopt NAME] [--hyperopt-path PATH] [--eps]
[--dmmp] [--enable-protections] [-e INT]
[--spaces {all,buy,sell,roi,stoploss,trailing,default} [{all,buy,sell,roi,stoploss,trailing,default} ...]]
[--print-all] [--no-color] [--print-json] [-j JOBS]
[--random-state INT] [--min-trades INT]
[--hyperopt-loss NAME]
optional arguments:
-h, --help show this help message and exit
-i TIMEFRAME, --timeframe TIMEFRAME, --ticker-interval TIMEFRAME
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--data-format-ohlcv {json,jsongz,hdf5}
Storage format for downloaded candle (OHLCV) data.
(default: `None`).
--max-open-trades INT
Override the value of the `max_open_trades`
configuration setting.
--stake-amount STAKE_AMOUNT
Override the value of the `stake_amount` configuration
setting.
--fee FLOAT Specify fee ratio. Will be applied twice (on trade
entry and exit).
--hyperopt NAME Specify hyperopt class name which will be used by the
bot.
--hyperopt-path PATH Specify additional lookup path for Hyperopt and
Hyperopt Loss functions.
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking).
--dmmp, --disable-max-market-positions
Disable applying `max_open_trades` during backtest
(same as setting `max_open_trades` to a very high
number).
--enable-protections, --enableprotections
Enable protections for backtesting.Will slow
backtesting down by a considerable amount, but will
include configured protections
-e INT, --epochs INT Specify number of epochs (default: 100).
--spaces {all,buy,sell,roi,stoploss,trailing,default} [{all,buy,sell,roi,stoploss,trailing,default} ...]
Specify which parameters to hyperopt. Space-separated
list.
--print-all Print all results, not only the best ones.
--no-color Disable colorization of hyperopt results. May be
useful if you are redirecting output to a file.
--print-json Print output in JSON format.
-j JOBS, --job-workers JOBS
The number of concurrently running jobs for
hyperoptimization (hyperopt worker processes). If -1
(default), all CPUs are used, for -2, all CPUs but one
are used, etc. If 1 is given, no parallel computing
code is used at all.
--random-state INT Set random state to some positive integer for
reproducible hyperopt results.
--min-trades INT Set minimal desired number of trades for evaluations
in the hyperopt optimization path (default: 1).
--hyperopt-loss NAME Specify the class name of the hyperopt loss function
class (IHyperOptLoss). Different functions can
generate completely different results, since the
target for optimization is different. Built-in
Hyperopt-loss-functions are:
ShortTradeDurHyperOptLoss, OnlyProfitHyperOptLoss,
SharpeHyperOptLoss, SharpeHyperOptLossDaily,
SortinoHyperOptLoss, SortinoHyperOptLossDaily
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are:
'syslog', 'journald'. See the documentation for more
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default:
`userdir/config.json` or `config.json` whichever
exists). Multiple --config options may be used. Can be
set to `-` to read config from stdin.
-d PATH, --datadir PATH
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
Strategy arguments:
-s NAME, --strategy NAME
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
```
## Prepare Hyperopting ## Prepare Hyperopting
Before we start digging into Hyperopt, we recommend you to take a look at Before we start digging into Hyperopt, we recommend you to take a look at
@@ -64,9 +165,9 @@ Depending on the space you want to optimize, only some of the below are required
Optional in hyperopt - can also be loaded from a strategy (recommended): Optional in hyperopt - can also be loaded from a strategy (recommended):
* copy `populate_indicators` from your strategy - otherwise default-strategy will be used * `populate_indicators` - fallback to create indicators
* copy `populate_buy_trend` from your strategy - otherwise default-strategy will be used * `populate_buy_trend` - fallback if not optimizing for buy space. should come from strategy
* copy `populate_sell_trend` from your strategy - otherwise default-strategy will be used * `populate_sell_trend` - fallback if not optimizing for sell space. should come from strategy
!!! Note !!! Note
You always have to provide a strategy to Hyperopt, even if your custom Hyperopt class contains all methods. You always have to provide a strategy to Hyperopt, even if your custom Hyperopt class contains all methods.
@@ -104,7 +205,7 @@ This command will create a new hyperopt file from a template, allowing you to ge
There are two places you need to change in your hyperopt file to add a new buy hyperopt for testing: There are two places you need to change in your hyperopt file to add a new buy hyperopt for testing:
* Inside `indicator_space()` - the parameters hyperopt shall be optimizing. * Inside `indicator_space()` - the parameters hyperopt shall be optimizing.
* Inside `populate_buy_trend()` - applying the parameters. * Within `buy_strategy_generator()` - populate the nested `populate_buy_trend()` to apply the parameters.
There you have two different types of indicators: 1. `guards` and 2. `triggers`. There you have two different types of indicators: 1. `guards` and 2. `triggers`.
@@ -128,7 +229,7 @@ Similar to the buy-signal above, sell-signals can also be optimized.
Place the corresponding settings into the following methods Place the corresponding settings into the following methods
* Inside `sell_indicator_space()` - the parameters hyperopt shall be optimizing. * Inside `sell_indicator_space()` - the parameters hyperopt shall be optimizing.
* Inside `populate_sell_trend()` - applying the parameters. * Within `sell_strategy_generator()` - populate the nested method `populate_sell_trend()` to apply the parameters.
The configuration and rules are the same than for buy signals. The configuration and rules are the same than for buy signals.
To avoid naming collisions in the search-space, please prefix all sell-spaces with `sell-`. To avoid naming collisions in the search-space, please prefix all sell-spaces with `sell-`.
@@ -173,6 +274,11 @@ one we call `trigger` and use it to decide which buy trigger we want to use.
So let's write the buy strategy using these values: So let's write the buy strategy using these values:
```python ```python
@staticmethod
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Define the buy strategy parameters to be used by Hyperopt.
"""
def populate_buy_trend(dataframe: DataFrame) -> DataFrame: def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
conditions = [] conditions = []
# GUARDS AND TRENDS # GUARDS AND TRENDS

View File

@@ -10,11 +10,20 @@ If multiple Pairlist Handlers are used, they are chained and a combination of al
Inactive markets are always removed from the resulting pairlist. Explicitly blacklisted pairs (those in the `pair_blacklist` configuration setting) are also always removed from the resulting pairlist. Inactive markets are always removed from the resulting pairlist. Explicitly blacklisted pairs (those in the `pair_blacklist` configuration setting) are also always removed from the resulting pairlist.
### Pair blacklist
The pair blacklist (configured via `exchange.pair_blacklist` in the configuration) disallows certain pairs from trading.
This can be as simple as excluding `DOGE/BTC` - which will remove exactly this pair.
The pair-blacklist does also support wildcards (in regex-style) - so `BNB/.*` will exclude ALL pairs that start with BNB.
You may also use something like `.*DOWN/BTC` or `.*UP/BTC` to exclude leveraged tokens (check Pair naming conventions for your exchange!)
### Available Pairlist Handlers ### Available Pairlist Handlers
* [`StaticPairList`](#static-pair-list) (default, if not configured differently) * [`StaticPairList`](#static-pair-list) (default, if not configured differently)
* [`VolumePairList`](#volume-pair-list) * [`VolumePairList`](#volume-pair-list)
* [`AgeFilter`](#agefilter) * [`AgeFilter`](#agefilter)
* [`PerformanceFilter`](#performancefilter)
* [`PrecisionFilter`](#precisionfilter) * [`PrecisionFilter`](#precisionfilter)
* [`PriceFilter`](#pricefilter) * [`PriceFilter`](#pricefilter)
* [`ShuffleFilter`](#shufflefilter) * [`ShuffleFilter`](#shufflefilter)
@@ -26,7 +35,7 @@ Inactive markets are always removed from the resulting pairlist. Explicitly blac
#### Static Pair List #### Static Pair List
By default, the `StaticPairList` method is used, which uses a statically defined pair whitelist from the configuration. By default, the `StaticPairList` method is used, which uses a statically defined pair whitelist from the configuration. The pairlist also supports wildcards (in regex-style) - so `.*/BTC` will include all pairs with BTC as a stake.
It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklist`. It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklist`.
@@ -64,6 +73,9 @@ The `refresh_period` setting allows to define the period (in seconds), at which
}], }],
``` ```
!!! Note
`VolumePairList` does not support backtesting mode.
#### AgeFilter #### AgeFilter
Removes pairs that have been listed on the exchange for less than `min_days_listed` days (defaults to `10`). Removes pairs that have been listed on the exchange for less than `min_days_listed` days (defaults to `10`).
@@ -74,6 +86,18 @@ be caught out buying before the pair has finished dropping in price.
This filter allows freqtrade to ignore pairs until they have been listed for at least `min_days_listed` days. This filter allows freqtrade to ignore pairs until they have been listed for at least `min_days_listed` days.
#### PerformanceFilter
Sorts pairs by past trade performance, as follows:
1. Positive performance.
2. No closed trades yet.
3. Negative performance.
Trade count is used as a tie breaker.
!!! Note
`PerformanceFilter` does not support backtesting mode.
#### PrecisionFilter #### PrecisionFilter
Filters low-value coins which would not allow setting stoplosses. Filters low-value coins which would not allow setting stoplosses.

127
docs/includes/pricing.md Normal file
View File

@@ -0,0 +1,127 @@
## Prices used for orders
Prices for regular orders can be controlled via the parameter structures `bid_strategy` for buying and `ask_strategy` for selling.
Prices are always retrieved right before an order is placed, either by querying the exchange tickers or by using the orderbook data.
!!! Note
Orderbook data used by Freqtrade are the data retrieved from exchange by the ccxt's function `fetch_order_book()`, i.e. are usually data from the L2-aggregated orderbook, while the ticker data are the structures returned by the ccxt's `fetch_ticker()`/`fetch_tickers()` functions. Refer to the ccxt library [documentation](https://github.com/ccxt/ccxt/wiki/Manual#market-data) for more details.
!!! Warning "Using market orders"
Please read the section [Market order pricing](#market-order-pricing) section when using market orders.
### Buy price
#### Check depth of market
When check depth of market is enabled (`bid_strategy.check_depth_of_market.enabled=True`), the buy signals are filtered based on the orderbook depth (sum of all amounts) for each orderbook side.
Orderbook `bid` (buy) side depth is then divided by the orderbook `ask` (sell) side depth and the resulting delta is compared to the value of the `bid_strategy.check_depth_of_market.bids_to_ask_delta` parameter. The buy order is only executed if the orderbook delta is greater than or equal to the configured delta value.
!!! Note
A delta value below 1 means that `ask` (sell) orderbook side depth is greater than the depth of the `bid` (buy) orderbook side, while a value greater than 1 means opposite (depth of the buy side is higher than the depth of the sell side).
#### Buy price side
The configuration setting `bid_strategy.price_side` defines the side of the spread the bot looks for when buying.
The following displays an orderbook.
``` explanation
...
103
102
101 # ask
-------------Current spread
99 # bid
98
97
...
```
If `bid_strategy.price_side` is set to `"bid"`, then the bot will use 99 as buying price.
In line with that, if `bid_strategy.price_side` is set to `"ask"`, then the bot will use 101 as buying price.
Using `ask` price often guarantees quicker filled orders, but the bot can also end up paying more than what would have been necessary.
Taker fees instead of maker fees will most likely apply even when using limit buy orders.
Also, prices at the "ask" side of the spread are higher than prices at the "bid" side in the orderbook, so the order behaves similar to a market order (however with a maximum price).
#### Buy price with Orderbook enabled
When buying with the orderbook enabled (`bid_strategy.use_order_book=True`), Freqtrade fetches the `bid_strategy.order_book_top` entries from the orderbook and then uses the entry specified as `bid_strategy.order_book_top` on the configured side (`bid_strategy.price_side`) of the orderbook. 1 specifies the topmost entry in the orderbook, while 2 would use the 2nd entry in the orderbook, and so on.
#### Buy price without Orderbook enabled
The following section uses `side` as the configured `bid_strategy.price_side`.
When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `side` price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `side` price is above the `last` price), it calculates a rate between `side` and `last` price.
The `bid_strategy.ask_last_balance` configuration parameter controls this. A value of `0.0` will use `side` price, while `1.0` will use the `last` price and values between those interpolate between ask and last price.
### Sell price
#### Sell price side
The configuration setting `ask_strategy.price_side` defines the side of the spread the bot looks for when selling.
The following displays an orderbook:
``` explanation
...
103
102
101 # ask
-------------Current spread
99 # bid
98
97
...
```
If `ask_strategy.price_side` is set to `"ask"`, then the bot will use 101 as selling price.
In line with that, if `ask_strategy.price_side` is set to `"bid"`, then the bot will use 99 as selling price.
#### Sell price with Orderbook enabled
When selling with the orderbook enabled (`ask_strategy.use_order_book=True`), Freqtrade fetches the `ask_strategy.order_book_max` entries in the orderbook. Then each of the orderbook steps between `ask_strategy.order_book_min` and `ask_strategy.order_book_max` on the configured orderbook side are validated for a profitable sell-possibility based on the strategy configuration (`minimal_roi` conditions) and the sell order is placed at the first profitable spot.
!!! Note
Using `order_book_max` higher than `order_book_min` only makes sense when ask_strategy.price_side is set to `"ask"`.
The idea here is to place the sell order early, to be ahead in the queue.
A fixed slot (mirroring `bid_strategy.order_book_top`) can be defined by setting `ask_strategy.order_book_min` and `ask_strategy.order_book_max` to the same number.
!!! Warning "Order_book_max > 1 - increased risks for stoplosses!"
Using `ask_strategy.order_book_max` higher than 1 will increase the risk the stoploss on exchange is cancelled too early, since an eventual [stoploss on exchange](#understand-order_types) will be cancelled as soon as the order is placed.
Also, the sell order will remain on the exchange for `unfilledtimeout.sell` (or until it's filled) - which can lead to missed stoplosses (with or without using stoploss on exchange).
!!! Warning "Order_book_max > 1 in dry-run"
Using `ask_strategy.order_book_max` higher than 1 will result in improper dry-run results (significantly better than real orders executed on exchange), since dry-run assumes orders to be filled almost instantly.
It is therefore advised to not use this setting for dry-runs.
#### Sell price without Orderbook enabled
When not using orderbook (`ask_strategy.use_order_book=False`), the price at the `ask_strategy.price_side` side (defaults to `"ask"`) from the ticker will be used as the sell price.
### Market order pricing
When using market orders, prices should be configured to use the "correct" side of the orderbook to allow realistic pricing detection.
Assuming both buy and sell are using market orders, a configuration similar to the following might be used
``` jsonc
"order_types": {
"buy": "market",
"sell": "market"
// ...
},
"bid_strategy": {
"price_side": "ask",
// ...
},
"ask_strategy":{
"price_side": "bid",
// ...
},
```
Obviously, if only one side is using limit orders, different pricing combinations can be used.

View File

@@ -0,0 +1,215 @@
## Protections
!!! Warning "Beta feature"
This feature is still in it's testing phase. Should you notice something you think is wrong please let us know via Discord, Slack or via Github Issue.
Protections will protect your strategy from unexpected events and market conditions by temporarily stop trading for either one pair, or for all pairs.
All protection end times are rounded up to the next candle to avoid sudden, unexpected intra-candle buys.
!!! Note
Not all Protections will work for all strategies, and parameters will need to be tuned for your strategy to improve performance.
To align your protection with your strategy, you can define protections in the strategy.
!!! Tip
Each Protection can be configured multiple times with different parameters, to allow different levels of protection (short-term / long-term).
!!! Note "Backtesting"
Protections are supported by backtesting and hyperopt, but must be explicitly enabled by using the `--enable-protections` flag.
### Available Protections
* [`StoplossGuard`](#stoploss-guard) Stop trading if a certain amount of stoploss occurred within a certain time window.
* [`MaxDrawdown`](#maxdrawdown) Stop trading if max-drawdown is reached.
* [`LowProfitPairs`](#low-profit-pairs) Lock pairs with low profits
* [`CooldownPeriod`](#cooldown-period) Don't enter a trade right after selling a trade.
### Common settings to all Protections
| Parameter| Description |
|------------|-------------|
| `method` | Protection name to use. <br> **Datatype:** String, selected from [available Protections](#available-protections)
| `stop_duration_candles` | For how many candles should the lock be set? <br> **Datatype:** Positive integer (in candles)
| `stop_duration` | how many minutes should protections be locked. <br>Cannot be used together with `stop_duration_candles`. <br> **Datatype:** Float (in minutes)
| `lookback_period_candles` | Only trades that completed within the last `lookback_period_candles` candles will be considered. This setting may be ignored by some Protections. <br> **Datatype:** Positive integer (in candles).
| `lookback_period` | Only trades that completed after `current_time - lookback_period` will be considered. <br>Cannot be used together with `lookback_period_candles`. <br>This setting may be ignored by some Protections. <br> **Datatype:** Float (in minutes)
| `trade_limit` | Number of trades required at minimum (not used by all Protections). <br> **Datatype:** Positive integer
!!! Note "Durations"
Durations (`stop_duration*` and `lookback_period*` can be defined in either minutes or candles).
For more flexibility when testing different timeframes, all below examples will use the "candle" definition.
#### Stoploss Guard
`StoplossGuard` selects all trades within `lookback_period` in minutes (or in candles when using `lookback_period_candles`).
If `trade_limit` or more trades resulted in stoploss, trading will stop for `stop_duration` in minutes (or in candles when using `stop_duration_candles`).
This applies across all pairs, unless `only_per_pair` is set to true, which will then only look at one pair at a time.
The below example stops trading for all pairs for 4 candles after the last trade if the bot hit stoploss 4 times within the last 24 candles.
```json
"protections": [
{
"method": "StoplossGuard",
"lookback_period_candles": 24,
"trade_limit": 4,
"stop_duration_candles": 4,
"only_per_pair": false
}
],
```
!!! Note
`StoplossGuard` considers all trades with the results `"stop_loss"`, `"stoploss_on_exchange"` and `"trailing_stop_loss"` if the resulting profit was negative.
`trade_limit` and `lookback_period` will need to be tuned for your strategy.
#### MaxDrawdown
`MaxDrawdown` uses all trades within `lookback_period` in minutes (or in candles when using `lookback_period_candles`) to determine the maximum drawdown. If the drawdown is below `max_allowed_drawdown`, trading will stop for `stop_duration` in minutes (or in candles when using `stop_duration_candles`) after the last trade - assuming that the bot needs some time to let markets recover.
The below sample stops trading for 12 candles if max-drawdown is > 20% considering all pairs - with a minimum of `trade_limit` trades - within the last 48 candles. If desired, `lookback_period` and/or `stop_duration` can be used.
```json
"protections": [
{
"method": "MaxDrawdown",
"lookback_period_candles": 48,
"trade_limit": 20,
"stop_duration_candles": 12,
"max_allowed_drawdown": 0.2
},
],
```
#### Low Profit Pairs
`LowProfitPairs` uses all trades for a pair within `lookback_period` in minutes (or in candles when using `lookback_period_candles`) to determine the overall profit ratio.
If that ratio is below `required_profit`, that pair will be locked for `stop_duration` in minutes (or in candles when using `stop_duration_candles`).
The below example will stop trading a pair for 60 minutes if the pair does not have a required profit of 2% (and a minimum of 2 trades) within the last 6 candles.
```json
"protections": [
{
"method": "LowProfitPairs",
"lookback_period_candles": 6,
"trade_limit": 2,
"stop_duration": 60,
"required_profit": 0.02
}
],
```
#### Cooldown Period
`CooldownPeriod` locks a pair for `stop_duration` in minutes (or in candles when using `stop_duration_candles`) after selling, avoiding a re-entry for this pair for `stop_duration` minutes.
The below example will stop trading a pair for 2 candles after closing a trade, allowing this pair to "cool down".
```json
"protections": [
{
"method": "CooldownPeriod",
"stop_duration_candles": 2
}
],
```
!!! Note
This Protection applies only at pair-level, and will never lock all pairs globally.
This Protection does not consider `lookback_period` as it only looks at the latest trade.
### Full example of Protections
All protections can be combined at will, also with different parameters, creating a increasing wall for under-performing pairs.
All protections are evaluated in the sequence they are defined.
The below example assumes a timeframe of 1 hour:
* Locks each pair after selling for an additional 5 candles (`CooldownPeriod`), giving other pairs a chance to get filled.
* Stops trading for 4 hours (`4 * 1h candles`) if the last 2 days (`48 * 1h candles`) had 20 trades, which caused a max-drawdown of more than 20%. (`MaxDrawdown`).
* Stops trading if more than 4 stoploss occur for all pairs within a 1 day (`24 * 1h candles`) limit (`StoplossGuard`).
* Locks all pairs that had 4 Trades within the last 6 hours (`6 * 1h candles`) with a combined profit ratio of below 0.02 (<2%) (`LowProfitPairs`).
* Locks all pairs for 2 candles that had a profit of below 0.01 (<1%) within the last 24h (`24 * 1h candles`), a minimum of 4 trades.
```json
"timeframe": "1h",
"protections": [
{
"method": "CooldownPeriod",
"stop_duration_candles": 5
},
{
"method": "MaxDrawdown",
"lookback_period_candles": 48,
"trade_limit": 20,
"stop_duration_candles": 4,
"max_allowed_drawdown": 0.2
},
{
"method": "StoplossGuard",
"lookback_period_candles": 24,
"trade_limit": 4,
"stop_duration_candles": 2,
"only_per_pair": false
},
{
"method": "LowProfitPairs",
"lookback_period_candles": 6,
"trade_limit": 2,
"stop_duration_candles": 60,
"required_profit": 0.02
},
{
"method": "LowProfitPairs",
"lookback_period_candles": 24,
"trade_limit": 4,
"stop_duration_candles": 2,
"required_profit": 0.01
}
],
```
You can use the same in your strategy, the syntax is only slightly different:
``` python
from freqtrade.strategy import IStrategy
class AwesomeStrategy(IStrategy)
timeframe = '1h'
protections = [
{
"method": "CooldownPeriod",
"stop_duration_candles": 5
},
{
"method": "MaxDrawdown",
"lookback_period_candles": 48,
"trade_limit": 20,
"stop_duration_candles": 4,
"max_allowed_drawdown": 0.2
},
{
"method": "StoplossGuard",
"lookback_period_candles": 24,
"trade_limit": 4,
"stop_duration_candles": 2,
"only_per_pair": False
},
{
"method": "LowProfitPairs",
"lookback_period_candles": 6,
"trade_limit": 2,
"stop_duration_candles": 60,
"required_profit": 0.02
},
{
"method": "LowProfitPairs",
"lookback_period_candles": 24,
"trade_limit": 4,
"stop_duration_candles": 2,
"required_profit": 0.01
}
]
# ...
```

View File

@@ -14,7 +14,7 @@
## Introduction ## Introduction
Freqtrade is a crypto-currency algorithmic trading software developed in python (3.6+) and supported on Windows, macOS and Linux. Freqtrade is a crypto-currency algorithmic trading software developed in python (3.7+) and supported on Windows, macOS and Linux.
!!! Danger "DISCLAIMER" !!! Danger "DISCLAIMER"
This software is for educational purposes only. Do not risk money which you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS. This software is for educational purposes only. Do not risk money which you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.
@@ -35,6 +35,22 @@ Freqtrade is a crypto-currency algorithmic trading software developed in python
- Control/Monitor: Use Telegram or a REST API (start/stop the bot, show profit/loss, daily summary, current open trades results, etc.). - Control/Monitor: Use Telegram or a REST API (start/stop the bot, show profit/loss, daily summary, current open trades results, etc.).
- Analyse: Further analysis can be performed on either Backtesting data or Freqtrade trading history (SQL database), including automated standard plots, and methods to load the data into [interactive environments](data-analysis.md). - Analyse: Further analysis can be performed on either Backtesting data or Freqtrade trading history (SQL database), including automated standard plots, and methods to load the data into [interactive environments](data-analysis.md).
## Supported exchange marketplaces
Please read the [exchange specific notes](exchanges.md) to learn about eventual, special configurations needed for each exchange.
- [X] [Binance](https://www.binance.com/) ([*Note for binance users](exchanges.md#blacklists))
- [X] [Bittrex](https://bittrex.com/)
- [X] [FTX](https://ftx.com)
- [X] [Kraken](https://kraken.com/)
- [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
### Community tested
Exchanges confirmed working by the community:
- [X] [Bitvavo](https://bitvavo.com/)
## Requirements ## Requirements
### Hardware requirements ### Hardware requirements
@@ -51,7 +67,7 @@ To run this bot we recommend you a linux cloud instance with a minimum of:
Alternatively Alternatively
- Python 3.6.x - Python 3.7+
- pip (pip3) - pip (pip3)
- git - git
- TA-Lib - TA-Lib
@@ -65,7 +81,7 @@ For any questions not covered by the documentation or for further information ab
Please check out our [discord server](https://discord.gg/MA9v74M). Please check out our [discord server](https://discord.gg/MA9v74M).
You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-jaut7r4m-Y17k4x5mcQES9a9swKuxbg). You can also join our [Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/zt-mm786y93-Fxo37glxMY9g8OQC5AoOIw).
## Ready to try? ## Ready to try?

View File

@@ -2,116 +2,79 @@
This page explains how to prepare your environment for running the bot. This page explains how to prepare your environment for running the bot.
Please consider using the prebuilt [docker images](docker.md) to get started quickly while trying out freqtrade evaluating how it operates. The freqtrade documentation describes various ways to install freqtrade
## Prerequisite * [Docker images](docker_quickstart.md) (separate page)
* [Script Installation](#script-installation)
* [Manual Installation](#manual-installation)
* [Installation with Conda](#installation-with-conda)
### Requirements Please consider using the prebuilt [docker images](docker_quickstart.md) to get started quickly while evaluating how freqtrade works.
Click each one for install guide: ------
* [Python >= 3.6.x](http://docs.python-guide.org/en/latest/starting/installation/) ## Information
* [pip](https://pip.pypa.io/en/stable/installing/)
* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended)
* [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html) (install instructions below)
We also recommend a [Telegram bot](telegram-usage.md#setup-your-telegram-bot), which is optional but recommended. For Windows installation, please use the [windows installation guide](windows_installation.md).
The easiest way to install and run Freqtrade is to clone the bot Github repository and then run the `./setup.sh` script, if it's available for your platform.
!!! Note "Version considerations"
When cloning the repository the default working branch has the name `develop`. This branch contains all last features (can be considered as relatively stable, thanks to automated tests).
The `stable` branch contains the code of the last release (done usually once per month on an approximately one week old snapshot of the `develop` branch to prevent packaging bugs, so potentially it's more stable).
!!! Note
Python3.7 or higher and the corresponding `pip` are assumed to be available. The install-script will warn you and stop if that's not the case. `git` is also needed to clone the Freqtrade repository.
Also, python headers (`python<yourversion>-dev` / `python<yourversion>-devel`) must be available for the installation to complete successfully.
!!! Warning "Up-to-date clock" !!! Warning "Up-to-date clock"
The clock on the system running the bot must be accurate, synchronized to a NTP server frequently enough to avoid problems with communication to the exchanges. The clock on the system running the bot must be accurate, synchronized to a NTP server frequently enough to avoid problems with communication to the exchanges.
## Quick start
Freqtrade provides the Linux/MacOS Easy Installation script to install all dependencies and help you configure the bot.
!!! Note
Windows installation is explained [here](#windows).
The easiest way to install and run Freqtrade is to clone the bot Github repository and then run the Easy Installation script, if it's available for your platform.
!!! Note "Version considerations"
When cloning the repository the default working branch has the name `develop`. This branch contains all last features (can be considered as relatively stable, thanks to automated tests). The `stable` branch contains the code of the last release (done usually once per month on an approximately one week old snapshot of the `develop` branch to prevent packaging bugs, so potentially it's more stable).
!!! Note
Python3.6 or higher and the corresponding `pip` are assumed to be available. The install-script will warn you and stop if that's not the case. `git` is also needed to clone the Freqtrade repository.
This can be achieved with the following commands:
```bash
git clone https://github.com/freqtrade/freqtrade.git
cd freqtrade
# git checkout stable # Optional, see (1)
./setup.sh --install
```
(1) This command switches the cloned repository to the use of the `stable` branch. It's not needed if you wish to stay on the `develop` branch. You may later switch between branches at any time with the `git checkout stable`/`git checkout develop` commands.
## Easy Installation Script (Linux/MacOS)
If you are on Debian, Ubuntu or MacOS Freqtrade provides the script to install, update, configure and reset the codebase of your bot.
```bash
$ ./setup.sh
usage:
-i,--install Install freqtrade from scratch
-u,--update Command git pull to update.
-r,--reset Hard reset your develop/stable branch.
-c,--config Easy config generator (Will override your existing file).
```
** --install **
With this option, the script will install the bot and most dependencies:
You will need to have git and python3.6+ installed beforehand for this to work.
* Mandatory software as: `ta-lib`
* Setup your virtualenv under `.env/`
This option is a combination of installation tasks, `--reset` and `--config`.
** --update **
This option will pull the last version of your current branch and update your virtualenv. Run the script with this option periodically to update your bot.
** --reset **
This option will hard reset your branch (only if you are on either `stable` or `develop`) and recreate your virtualenv.
** --config **
DEPRECATED - use `freqtrade new-config -c config.json` instead.
### Activate your virtual environment
Each time you open a new terminal, you must run `source .env/bin/activate`.
------ ------
## Custom Installation ## Requirements
We've included/collected install instructions for Ubuntu 16.04, MacOS, and Windows. These are guidelines and your success may vary with other distros. These requirements apply to both [Script Installation](#script-installation) and [Manual Installation](#manual-installation).
### Install guide
* [Python >= 3.7.x](http://docs.python-guide.org/en/latest/starting/installation/)
* [pip](https://pip.pypa.io/en/stable/installing/)
* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [virtualenv](https://virtualenv.pypa.io/en/stable/installation.html) (Recommended)
* [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html) (install instructions [below](#install-ta-lib))
### Install code
We've included/collected install instructions for Ubuntu, MacOS, and Windows. These are guidelines and your success may vary with other distros.
OS Specific steps are listed first, the [Common](#common) section below is necessary for all systems. OS Specific steps are listed first, the [Common](#common) section below is necessary for all systems.
!!! Note !!! Note
Python3.6 or higher and the corresponding pip are assumed to be available. Python3.7 or higher and the corresponding pip are assumed to be available.
=== "Ubuntu 16.04" === "Debian/Ubuntu"
#### Install necessary dependencies #### Install necessary dependencies
```bash ```bash
# update repository
sudo apt-get update sudo apt-get update
sudo apt-get install build-essential git
# install packages
sudo apt install -y python3-pip python3-venv python3-pandas python3-pip git
``` ```
=== "RaspberryPi/Raspbian" === "RaspberryPi/Raspbian"
The following assumes the latest [Raspbian Buster lite image](https://www.raspberrypi.org/downloads/raspbian/) from at least September 2019. The following assumes the latest [Raspbian Buster lite image](https://www.raspberrypi.org/downloads/raspbian/).
This image comes with python3.7 preinstalled, making it easy to get freqtrade up and running. This image comes with python3.7 preinstalled, making it easy to get freqtrade up and running.
Tested using a Raspberry Pi 3 with the Raspbian Buster lite image, all updates applied. Tested using a Raspberry Pi 3 with the Raspbian Buster lite image, all updates applied.
``` bash
sudo apt-get install python3-venv libatlas-base-dev ```bash
sudo apt-get install python3-venv libatlas-base-dev cmake
# Use pywheels.org to speed up installation
sudo echo "[global]\nextra-index-url=https://www.piwheels.org/simple" > tee /etc/pip.conf
git clone https://github.com/freqtrade/freqtrade.git git clone https://github.com/freqtrade/freqtrade.git
cd freqtrade cd freqtrade
@@ -120,16 +83,106 @@ OS Specific steps are listed first, the [Common](#common) section below is neces
!!! Note "Installation duration" !!! Note "Installation duration"
Depending on your internet speed and the Raspberry Pi version, installation can take multiple hours to complete. Depending on your internet speed and the Raspberry Pi version, installation can take multiple hours to complete.
Due to this, we recommend to use the pre-build docker-image for Raspberry, by following the [Docker quickstart documentation](docker_quickstart.md)
!!! Note !!! Note
The above does not install hyperopt dependencies. To install these, please use `python3 -m pip install -e .[hyperopt]`. The above does not install hyperopt dependencies. To install these, please use `python3 -m pip install -e .[hyperopt]`.
We do not advise to run hyperopt on a Raspberry Pi, since this is a very resource-heavy operation, which should be done on powerful machine. We do not advise to run hyperopt on a Raspberry Pi, since this is a very resource-heavy operation, which should be done on powerful machine.
### Common ------
#### 1. Install TA-Lib ## Freqtrade repository
Use the provided ta-lib installation script Freqtrade is an open source crypto-currency trading bot, whose code is hosted on `github.com`
```bash
# Download `develop` branch of freqtrade repository
git clone https://github.com/freqtrade/freqtrade.git
# Enter downloaded directory
cd freqtrade
# your choice (1): novice user
git checkout stable
# your choice (2): advanced user
git checkout develop
```
(1) This command switches the cloned repository to the use of the `stable` branch. It's not needed, if you wish to stay on the (2) `develop` branch.
You may later switch between branches at any time with the `git checkout stable`/`git checkout develop` commands.
------
## Script Installation
First of the ways to install Freqtrade, is to use provided the Linux/MacOS `./setup.sh` script, which install all dependencies and help you configure the bot.
Make sure you fulfill the [Requirements](#requirements) and have downloaded the [Freqtrade repository](#freqtrade-repository).
### Use /setup.sh -install (Linux/MacOS)
If you are on Debian, Ubuntu or MacOS, freqtrade provides the script to install freqtrade.
```bash
# --install, Install freqtrade from scratch
./setup.sh -i
```
### Activate your virtual environment
Each time you open a new terminal, you must run `source .env/bin/activate` to activate your virtual environment.
```bash
# then activate your .env
source ./.env/bin/activate
```
### Congratulations
[You are ready](#you-are-ready), and run the bot
### Other options of /setup.sh script
You can as well update, configure and reset the codebase of your bot with `./script.sh`
```bash
# --update, Command git pull to update.
./setup.sh -u
# --reset, Hard reset your develop/stable branch.
./setup.sh -r
```
```
** --install **
With this option, the script will install the bot and most dependencies:
You will need to have git and python3.7+ installed beforehand for this to work.
* Mandatory software as: `ta-lib`
* Setup your virtualenv under `.env/`
This option is a combination of installation tasks and `--reset`
** --update **
This option will pull the last version of your current branch and update your virtualenv. Run the script with this option periodically to update your bot.
** --reset **
This option will hard reset your branch (only if you are on either `stable` or `develop`) and recreate your virtualenv.
```
-----
## Manual Installation
Make sure you fulfill the [Requirements](#requirements) and have downloaded the [Freqtrade repository](#freqtrade-repository).
### Install TA-Lib
#### TA-Lib script installation
```bash ```bash
sudo ./build_helpers/install_ta-lib.sh sudo ./build_helpers/install_ta-lib.sh
@@ -154,77 +207,193 @@ cd ..
rm -rf ./ta-lib* rm -rf ./ta-lib*
``` ```
!!! Note #### Setup Python virtual environment (virtualenv)
An already downloaded version of ta-lib is included in the repository, as the sourceforge.net source seems to have problems frequently.
#### 2. Setup your Python virtual environment (virtualenv) You will run freqtrade in separated `virtual environment`
!!! Note
This step is optional but strongly recommended to keep your system organized
```bash ```bash
# create virtualenv in directory /freqtrade/.env
python3 -m venv .env python3 -m venv .env
# run virtualenv
source .env/bin/activate source .env/bin/activate
``` ```
#### 3. Install Freqtrade #### Install python dependencies
Clone the git repository:
```bash ```bash
git clone https://github.com/freqtrade/freqtrade.git
cd freqtrade
git checkout stable
```
#### 4. Install python dependencies
``` bash
python3 -m pip install --upgrade pip python3 -m pip install --upgrade pip
python3 -m pip install -e . python3 -m pip install -e .
``` ```
#### 5. Initialize the configuration ### Congratulations
```bash [You are ready](#you-are-ready), and run the bot
# Initialize the user_directory
freqtrade create-userdir --userdir user_data/
# Create a new configuration file #### (Optional) Post-installation Tasks
freqtrade new-config --config config.json
```
> *To edit the config please refer to [Bot Configuration](configuration.md).* !!! Note
If you run the bot on a server, you should consider using [Docker](docker_quickstart.md) or a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout.
#### 6. Run the Bot On Linux with software suite `systemd`, as an optional post-installation task, you may wish to setup the bot to run as a `systemd service` or configure it to send the log messages to the `syslog`/`rsyslog` or `journald` daemons. See [Advanced Logging](advanced-setup.md#advanced-logging) for details.
If this is the first time you run the bot, ensure you are running it in Dry-run `"dry_run": true,` otherwise it will start to buy and sell coins.
```bash
freqtrade trade -c config.json
```
*Note*: If you run the bot on a server, you should consider using [Docker](docker.md) or a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout.
#### 7. (Optional) Post-installation Tasks
On Linux, as an optional post-installation task, you may wish to setup the bot to run as a `systemd` service or configure it to send the log messages to the `syslog`/`rsyslog` or `journald` daemons. See [Advanced Logging](advanced-setup.md#advanced-logging) for details.
------ ------
### Anaconda ## Installation with Conda
Freqtrade can also be installed using Anaconda (or Miniconda). Freqtrade can also be installed with Miniconda or Anaconda. We recommend using Miniconda as it's installation footprint is smaller. Conda will automatically prepare and manage the extensive library-dependencies of the Freqtrade program.
!!! Note ### What is Conda?
This requires the [ta-lib](#1-install-ta-lib) C-library to be installed first. See below.
``` bash Conda is a package, dependency and environment manager for multiple programming languages: [conda docs](https://docs.conda.io/projects/conda/en/latest/index.html)
conda env create -f environment.yml
### Installation with conda
#### Install Conda
[Installing on linux](https://conda.io/projects/conda/en/latest/user-guide/install/linux.html#install-linux-silent)
[Installing on windows](https://conda.io/projects/conda/en/latest/user-guide/install/windows.html)
Answer all questions. After installation, it is mandatory to turn your terminal OFF and ON again.
#### Freqtrade download
Download and install freqtrade.
```bash
# download freqtrade
git clone https://github.com/freqtrade/freqtrade.git
# enter downloaded directory 'freqtrade'
cd freqtrade
``` ```
#### Freqtrade instal: Conda Environment
Prepare conda-freqtrade environment, using file `environment.yml`, which exist in main freqtrade directory
```bash
conda env create -n freqtrade-conda -f environment.yml
```
!!! Note "Creating Conda Environment"
The conda command `create -n` automatically installs all nested dependencies for the selected libraries, general structure of installation command is:
```bash
# choose your own packages
conda env create -n [name of the environment] [python version] [packages]
# point to file with packages
conda env create -n [name of the environment] -f [file]
```
#### Enter/exit freqtrade-conda environment
To check available environments, type
```bash
conda env list
```
Enter installed environment
```bash
# enter conda environment
conda activate freqtrade-conda
# exit conda environment - don't do it now
conda deactivate
```
Install last python dependencies with pip
```bash
python3 -m pip install --upgrade pip
python3 -m pip install -e .
```
### Congratulations
[You are ready](#you-are-ready), and run the bot
### Important shortcuts
```bash
# list installed conda environments
conda env list
# activate base environment
conda activate
# activate freqtrade-conda environment
conda activate freqtrade-conda
#deactivate any conda environments
conda deactivate
```
### Further info on anaconda
!!! Info "New heavy packages"
It may happen that creating a new Conda environment, populated with selected packages at the moment of creation takes less time than installing a large, heavy library or application, into previously set environment.
!!! Warning "pip install within conda"
The documentation of conda says that pip should NOT be used within conda, because internal problems can occur.
However, they are rare. [Anaconda Blogpost](https://www.anaconda.com/blog/using-pip-in-a-conda-environment)
Nevertheless, that is why, the `conda-forge` channel is preferred:
* more libraries are available (less need for `pip`)
* `conda-forge` works better with `pip`
* the libraries are newer
Happy trading!
----- -----
## Troubleshooting
## You are ready
You've made it this far, so you have successfully installed freqtrade.
### Initialize the configuration
```bash
# Step 1 - Initialize user folder
freqtrade create-userdir --userdir user_data
# Step 2 - Create a new configuration file
freqtrade new-config --config config.json
```
You are ready to run, read [Bot Configuration](configuration.md), remember to start with `dry_run: True` and verify that everything is working.
To learn how to setup your configuration, please refer to the [Bot Configuration](configuration.md) documentation page.
### Start the Bot
```bash
freqtrade trade --config config.json --strategy SampleStrategy
```
!!! Warning
You should read through the rest of the documentation, backtest the strategy you're going to use, and use dry-run before enabling trading with real money.
-----
## Troubleshooting
### Common problem: "command not found"
If you used (1)`Script` or (2)`Manual` installation, you need to run the bot in virtual environment. If you get error as below, make sure venv is active.
```bash
# if:
bash: freqtrade: command not found
# then activate your .env
source ./.env/bin/activate
```
### MacOS installation error ### MacOS installation error
@@ -233,13 +402,21 @@ Newer versions of MacOS may have installation failed with errors like `error: co
This error will require explicit installation of the SDK Headers, which are not installed by default in this version of MacOS. This error will require explicit installation of the SDK Headers, which are not installed by default in this version of MacOS.
For MacOS 10.14, this can be accomplished with the below command. For MacOS 10.14, this can be accomplished with the below command.
``` bash ```bash
open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg open /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg
``` ```
If this file is inexistent, then you're probably on a different version of MacOS, so you may need to consult the internet for specific resolution details. If this file is inexistent, then you're probably on a different version of MacOS, so you may need to consult the internet for specific resolution details.
----- ### MacOS installation error with python 3.9
Now you have an environment ready, the next step is When using python 3.9 on macOS, it's currently necessary to install some os-level modules to allow dependencies to compile.
[Bot Configuration](configuration.md). The errors you'll see happen during installation and are related to the installation of `tables` or `blosc`.
You can install the necessary libraries with the following command:
```bash
brew install hdf5 c-blosc
```
After this, please run the installation (script) again.

View File

@@ -1,54 +1,51 @@
{#-
This file was automatically generated - do not edit
-#}
{% set site_url = config.site_url | d(nav.homepage.url, true) | url %}
{% if not config.use_directory_urls and site_url[0] == site_url[-1] == "." %}
{% set site_url = site_url ~ "/index.html" %}
{% endif %}
<header class="md-header" data-md-component="header"> <header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid"> <nav class="md-header-nav md-grid" aria-label="{{ lang.t('header.title') }}">
<div class="md-flex"> <a href="{{ site_url }}" title="{{ config.site_name | e }}" class="md-header-nav__button md-logo"
<div class="md-flex__cell md-flex__cell--shrink"> aria-label="{{ config.site_name }}">
<a href="{{ config.site_url | default(nav.homepage.url, true) | url }}" title="{{ config.site_name }}" {% include "partials/logo.html" %}
class="md-header-nav__button md-logo"> </a>
{% if config.theme.logo.icon %} <label class="md-header-nav__button md-icon" for="__drawer">
<i class="md-icon">{{ config.theme.logo.icon }}</i> {% include ".icons/material/menu" ~ ".svg" %}
{% else %} </label>
<img src="{{ config.theme.logo | url }}" width="24" height="24"> <div class="md-header-nav__title" data-md-component="header-title">
{% endif %} <div class="md-header-nav__ellipsis">
</a> <div class="md-header-nav__topic">
</div> <span class="md-ellipsis">
<div class="md-flex__cell md-flex__cell--shrink"> {{ config.site_name }}
<label class="md-icon md-icon--menu md-header-nav__button" for="__drawer"></label> </span>
</div>
<div class="md-flex__cell md-flex__cell--stretch">
<div class="md-flex__ellipsis md-header-nav__title" data-md-component="title">
{% block site_name %}
{% if config.site_name == page.title %}
{{ config.site_name }}
{% else %}
<span class="md-header-nav__topic">
{{ config.site_name }}
</span>
<span class="md-header-nav__topic">
{{ page.title }}
</span>
{% endif %}
{% endblock %}
</div>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
{% block search_box %}
{% if "search" in config["plugins"] %}
<label class="md-icon md-icon--search md-header-nav__button" for="__search"></label>
{% include "partials/search.html" %}
{% endif %}
{% endblock %}
</div>
{% if config.repo_url %}
<div class="md-flex__cell md-flex__cell--shrink">
<div class="md-header-nav__source">
{% include "partials/source.html" %}
</div>
</div>
{% endif %}
</div> </div>
</nav> <div class="md-header-nav__topic">
<!-- Place this tag in your head or just before your close body tag. --> <span class="md-ellipsis">
<script async defer src="https://buttons.github.io/buttons.js"></script> {% if page and page.meta and page.meta.title %}
<script src="https://code.jquery.com/jquery-3.4.1.min.js" {{ page.meta.title }}
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script> {% else %}
{{ page.title }}
{% endif %}
</span>
</div>
</div>
</div>
{% if "search" in config["plugins"] %}
<label class="md-header-nav__button md-icon" for="__search">
{% include ".icons/material/magnify.svg" %}
</label>
{% include "partials/search.html" %}
{% endif %}
{% if config.repo_url %}
<div class="md-header-nav__source">
{% include "partials/source.html" %}
</div>
{% endif %}
</nav>
<!-- Place this tag in your head or just before your close body tag. -->
<script async defer src="https://buttons.github.io/buttons.js"></script>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha256-CSXorXvZcTkaix6Yvo6HppcZGetbYMGWSFlBw8HfCJo=" crossorigin="anonymous"></script>
</header> </header>

View File

@@ -168,6 +168,7 @@ Additional features when using plot_config include:
* Specify colors per indicator * Specify colors per indicator
* Specify additional subplots * Specify additional subplots
* Specify indicator pairs to fill area in between
The sample plot configuration below specifies fixed colors for the indicators. Otherwise consecutive plots may produce different colorschemes each time, making comparisons difficult. The sample plot configuration below specifies fixed colors for the indicators. Otherwise consecutive plots may produce different colorschemes each time, making comparisons difficult.
It also allows multiple subplots to display both MACD and RSI at the same time. It also allows multiple subplots to display both MACD and RSI at the same time.
@@ -183,23 +184,34 @@ Sample configuration with inline comments explaining the process:
'ema50': {'color': '#CCCCCC'}, 'ema50': {'color': '#CCCCCC'},
# By omitting color, a random color is selected. # By omitting color, a random color is selected.
'sar': {}, 'sar': {},
# fill area between senkou_a and senkou_b
'senkou_a': {
'color': 'green', #optional
'fill_to': 'senkou_b',
'fill_label': 'Ichimoku Cloud', #optional
'fill_color': 'rgba(255,76,46,0.2)', #optional
},
# plot senkou_b, too. Not only the area to it.
'senkou_b': {}
}, },
'subplots': { 'subplots': {
# Create subplot MACD # Create subplot MACD
"MACD": { "MACD": {
'macd': {'color': 'blue'}, 'macd': {'color': 'blue', 'fill_to': 'macdhist'},
'macdsignal': {'color': 'orange'}, 'macdsignal': {'color': 'orange'}
}, },
# Additional subplot RSI # Additional subplot RSI
"RSI": { "RSI": {
'rsi': {'color': 'red'}, 'rsi': {'color': 'red'}
} }
} }
} }
``` ```
!!! Note !!! Note
The above configuration assumes that `ema10`, `ema50`, `macd`, `macdsignal` and `rsi` are columns in the DataFrame created by the strategy. The above configuration assumes that `ema10`, `ema50`, `senkou_a`, `senkou_b`,
`macd`, `macdsignal`, `macdhist` and `rsi` are columns in the DataFrame created by the strategy.
## Plot profit ## Plot profit

3
docs/plugins.md Normal file
View File

@@ -0,0 +1,3 @@
# Plugins
--8<-- "includes/pairlists.md"
--8<-- "includes/protections.md"

View File

@@ -1,3 +1,3 @@
mkdocs-material==6.1.6 mkdocs-material==6.2.8
mdx_truly_sane_lists==1.2 mdx_truly_sane_lists==1.2
pymdown-extensions==8.0.1 pymdown-extensions==8.1.1

View File

@@ -1,4 +1,19 @@
# REST API Usage # REST API & FreqUI
## FreqUI
Freqtrade provides a builtin webserver, which can serve [FreqUI](https://github.com/freqtrade/frequi), the freqtrade UI.
By default, the UI is not included in the installation (except for docker images), and must be installed explicitly with `freqtrade install-ui`.
This same command can also be used to update freqUI, should there be a new release.
Once the bot is started in trade / dry-run mode (with `freqtrade trade`) - the UI will be available under the configured port below (usually `http://127.0.0.1:8080`).
!!! info "Alpha release"
FreqUI is still considered an alpha release - if you encounter bugs or inconsistencies please open a [FreqUI issue](https://github.com/freqtrade/frequi/issues/new/choose).
!!! Note "developers"
Developers should not use this method, but instead use the method described in the [freqUI repository](https://github.com/freqtrade/frequi) to get the source-code of freqUI.
## Configuration ## Configuration
@@ -11,7 +26,8 @@ Sample configuration:
"enabled": true, "enabled": true,
"listen_ip_address": "127.0.0.1", "listen_ip_address": "127.0.0.1",
"listen_port": 8080, "listen_port": 8080,
"verbosity": "info", "verbosity": "error",
"enable_openapi": false,
"jwt_secret_key": "somethingrandom", "jwt_secret_key": "somethingrandom",
"CORS_origins": [], "CORS_origins": [],
"username": "Freqtrader", "username": "Freqtrader",
@@ -22,9 +38,6 @@ Sample configuration:
!!! Danger "Security warning" !!! Danger "Security warning"
By default, the configuration listens on localhost only (so it's not reachable from other systems). We strongly recommend to not expose this API to the internet and choose a strong, unique password, since others will potentially be able to control your bot. By default, the configuration listens on localhost only (so it's not reachable from other systems). We strongly recommend to not expose this API to the internet and choose a strong, unique password, since others will potentially be able to control your bot.
!!! Danger "Password selection"
Please make sure to select a very strong, unique password to protect your bot from unauthorized access.
You can then access the API by going to `http://127.0.0.1:8080/api/v1/ping` in a browser to check if the API is running correctly. You can then access the API by going to `http://127.0.0.1:8080/api/v1/ping` in a browser to check if the API is running correctly.
This should return the response: This should return the response:
@@ -34,16 +47,22 @@ This should return the response:
All other endpoints return sensitive info and require authentication and are therefore not available through a web browser. All other endpoints return sensitive info and require authentication and are therefore not available through a web browser.
To generate a secure password, either use a password manager, or use the below code snipped. ### Security
To generate a secure password, best use a password manager, or use the below code.
``` python ``` python
import secrets import secrets
secrets.token_hex() secrets.token_hex()
``` ```
!!! Hint !!! Hint "JWT token"
Use the same method to also generate a JWT secret key (`jwt_secret_key`). Use the same method to also generate a JWT secret key (`jwt_secret_key`).
!!! Danger "Password selection"
Please make sure to select a very strong, unique password to protect your bot from unauthorized access.
Also change `jwt_secret_key` to something random (no need to remember this, but it'll be used to encrypt your session, so it better be something unique!).
### Configuration with docker ### Configuration with docker
If you run your bot using docker, you'll need to have the bot listen to incoming connections. The security is then handled by docker. If you run your bot using docker, you'll need to have the bot listen to incoming connections. The security is then handled by docker.
@@ -56,28 +75,20 @@ If you run your bot using docker, you'll need to have the bot listen to incoming
}, },
``` ```
Add the following to your docker command: Uncomment the following from your docker-compose file:
``` bash ```yml
-p 127.0.0.1:8080:8080 ports:
``` - "127.0.0.1:8080:8080"
A complete sample-command may then look as follows:
```bash
docker run -d \
--name freqtrade \
-v ~/.freqtrade/config.json:/freqtrade/config.json \
-v ~/.freqtrade/user_data/:/freqtrade/user_data \
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
-p 127.0.0.1:8080:8080 \
freqtrade trade --db-url sqlite:///tradesv3.sqlite --strategy MyAwesomeStrategy
``` ```
!!! Danger "Security warning" !!! Danger "Security warning"
By using `-p 8080:8080` the API is 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.
## Consuming the API
## Rest API
### Consuming the API
You can consume the API by using the script `scripts/rest_client.py`. You can consume the API by using the script `scripts/rest_client.py`.
The client script only requires the `requests` module, so Freqtrade does not need to be installed on the system. The client script only requires the `requests` module, so Freqtrade does not need to be installed on the system.
@@ -88,7 +99,7 @@ python3 scripts/rest_client.py <command> [optional parameters]
By default, the script assumes `127.0.0.1` (localhost) and port `8080` to be used, however you can specify a configuration file to override this behaviour. By default, the script assumes `127.0.0.1` (localhost) and port `8080` to be used, however you can specify a configuration file to override this behaviour.
### Minimalistic client config #### Minimalistic client config
``` json ``` json
{ {
@@ -104,7 +115,7 @@ By default, the script assumes `127.0.0.1` (localhost) and port `8080` to be use
python3 scripts/rest_client.py --config rest_config.json <command> [optional parameters] python3 scripts/rest_client.py --config rest_config.json <command> [optional parameters]
``` ```
## Available endpoints ### Available endpoints
| Command | Description | | Command | Description |
|----------|-------------| |----------|-------------|
@@ -127,6 +138,7 @@ python3 scripts/rest_client.py --config rest_config.json <command> [optional par
| `performance` | Show performance of each finished trade grouped by pair. | `performance` | Show performance of each finished trade grouped by pair.
| `balance` | Show account balance per currency. | `balance` | Show account balance per currency.
| `daily <n>` | Shows profit or loss per day, over the last n days (n defaults to 7). | `daily <n>` | Shows profit or loss per day, over the last n days (n defaults to 7).
| `stats` | Display a summary of profit / loss reasons as well as average holding times.
| `whitelist` | Show the current whitelist. | `whitelist` | Show the current whitelist.
| `blacklist [pair]` | Show the current blacklist, or adds a pair to the blacklist. | `blacklist [pair]` | Show the current blacklist, or adds a pair to the blacklist.
| `edge` | Show validated pairs by Edge if it is enabled. | `edge` | Show validated pairs by Edge if it is enabled.
@@ -229,6 +241,9 @@ show_config
start start
Start the bot if it's in the stopped state. Start the bot if it's in the stopped state.
stats
Return the stats report (durations, sell-reasons).
status status
Get the status of open trades. Get the status of open trades.
@@ -259,7 +274,12 @@ whitelist
``` ```
## Advanced API usage using JWT tokens ### OpenAPI interface
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.
### Advanced API usage using JWT tokens
!!! Note !!! Note
The below should be done in an application (a Freqtrade REST API client, which fetches info via API), and is not intended to be used on a regular basis. The below should be done in an application (a Freqtrade REST API client, which fetches info via API), and is not intended to be used on a regular basis.
@@ -284,9 +304,9 @@ Since the access token has a short timeout (15 min) - the `token/refresh` reques
{"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1ODkxMTk5NzQsIm5iZiI6MTU4OTExOTk3NCwianRpIjoiMDBjNTlhMWUtMjBmYS00ZTk0LTliZjAtNWQwNTg2MTdiZDIyIiwiZXhwIjoxNTg5MTIwODc0LCJpZGVudGl0eSI6eyJ1IjoiRnJlcXRyYWRlciJ9LCJmcmVzaCI6ZmFsc2UsInR5cGUiOiJhY2Nlc3MifQ.1seHlII3WprjjclY6DpRhen0rqdF4j6jbvxIhUFaSbs"} {"access_token":"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE1ODkxMTk5NzQsIm5iZiI6MTU4OTExOTk3NCwianRpIjoiMDBjNTlhMWUtMjBmYS00ZTk0LTliZjAtNWQwNTg2MTdiZDIyIiwiZXhwIjoxNTg5MTIwODc0LCJpZGVudGl0eSI6eyJ1IjoiRnJlcXRyYWRlciJ9LCJmcmVzaCI6ZmFsc2UsInR5cGUiOiJhY2Nlc3MifQ.1seHlII3WprjjclY6DpRhen0rqdF4j6jbvxIhUFaSbs"}
``` ```
## CORS ### CORS
All web-based frontends are subject to [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) - Cross-Origin Resource Sharing. All web-based front-ends are subject to [CORS](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) - Cross-Origin Resource Sharing.
Since most of the requests to the Freqtrade API must be authenticated, a proper CORS policy is key to avoid security problems. Since most of the requests to the Freqtrade API must be authenticated, a proper CORS policy is key to avoid security problems.
Also, the standard disallows `*` CORS policies for requests with credentials, so this setting must be set appropriately. Also, the standard disallows `*` CORS policies for requests with credentials, so this setting must be set appropriately.

View File

@@ -78,6 +78,7 @@ At this stage the bot contains the following stoploss support modes:
2. Trailing stop loss. 2. Trailing stop loss.
3. Trailing stop loss, custom positive loss. 3. Trailing stop loss, custom positive loss.
4. Trailing stop loss only once the trade has reached a certain offset. 4. Trailing stop loss only once the trade has reached a certain offset.
5. [Custom stoploss function](strategy-advanced.md#custom-stoploss)
### Static Stop Loss ### Static Stop Loss

View File

@@ -8,11 +8,185 @@ If you're just getting started, please be familiar with the methods described in
!!! Note !!! Note
All callback methods described below should only be implemented in a strategy if they are actually used. All callback methods described below should only be implemented in a strategy if they are actually used.
!!! Tip
You can get a strategy template containing all below methods by running `freqtrade new-strategy --strategy MyAwesomeStrategy --template advanced`
## Custom stoploss
A stoploss can only ever move upwards - so if you set it to an absolute profit of 2%, you can never move it below this price.
Also, the traditional `stoploss` value serves as an absolute lower level and will be instated as the initial stoploss.
The usage of the custom stoploss method must be enabled by setting `use_custom_stoploss=True` on the strategy object.
The method must return a stoploss value (float / number) with a relative ratio below the current price.
E.g. `current_profit = 0.05` (5% profit) - stoploss returns `0.02` - then you "locked in" a profit of 3% (`0.05 - 0.02 = 0.03`).
To simulate a regular trailing stoploss of 4% (trailing 4% behind the maximum reached price) you would use the following very simple method:
``` python
# additional imports required
from datetime import datetime
from freqtrade.persistence import Trade
class AwesomeStrategy(IStrategy):
# ... populate_* methods
use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
"""
Custom stoploss logic, returning the new distance relative to current_rate (as ratio).
e.g. returning -0.05 would create a stoploss 5% below current_rate.
The custom stoploss can never be below self.stoploss, which serves as a hard maximum loss.
For full documentation please go to https://www.freqtrade.io/en/latest/strategy-advanced/
When not implemented by a strategy, returns the initial stoploss value
Only called when use_custom_stoploss is set to True.
:param pair: Pair that's currently analyzed
:param trade: trade object.
:param current_time: datetime object, containing the current datetime
:param current_rate: Rate, calculated based on pricing settings in ask_strategy.
:param current_profit: Current profit (as ratio), calculated based on current_rate.
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
:return float: New stoploss value, relative to the currentrate
"""
return -0.04
```
Stoploss on exchange works similar to `trailing_stop`, and the stoploss on exchange is updated as configured in `stoploss_on_exchange_interval` ([More details about stoploss on exchange](stoploss.md#stop-loss-on-exchange-freqtrade)).
!!! Note "Use of dates"
All time-based calculations should be done based on `current_time` - using `datetime.now()` or `datetime.utcnow()` is discouraged, as this will break backtesting support.
!!! Tip "Trailing stoploss"
It's recommended to disable `trailing_stop` when using custom stoploss values. Both can work in tandem, but you might encounter the trailing stop to move the price higher while your custom function would not want this, causing conflicting behavior.
### Custom stoploss examples
The next section will show some examples on what's possible with the custom stoploss function.
Of course, many more things are possible, and all examples can be combined at will.
#### Time based trailing stop
Use the initial stoploss for the first 60 minutes, after this change to 10% trailing stoploss, and after 2 hours (120 minutes) we use a 5% trailing stoploss.
``` python
from datetime import datetime, timedelta
from freqtrade.persistence import Trade
class AwesomeStrategy(IStrategy):
# ... populate_* methods
use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
# Make sure you have the longest interval first - these conditions are evaluated from top to bottom.
if current_time - timedelta(minutes=120) > trade.open_date:
return -0.05
elif current_time - timedelta(minutes=60) > trade.open_date:
return -0.10
return 1
```
#### Different stoploss per pair
Use a different stoploss depending on the pair.
In this example, we'll trail the highest price with 10% trailing stoploss for `ETH/BTC` and `XRP/BTC`, with 5% trailing stoploss for `LTC/BTC` and with 15% for all other pairs.
``` python
from datetime import datetime
from freqtrade.persistence import Trade
class AwesomeStrategy(IStrategy):
# ... populate_* methods
use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
if pair in ('ETH/BTC', 'XRP/BTC'):
return -0.10
elif pair in ('LTC/BTC'):
return -0.05
return -0.15
```
#### Trailing stoploss with positive offset
Use the initial stoploss until the profit is above 4%, then use a trailing stoploss of 50% of the current profit with a minimum of 2.5% and a maximum of 5%.
Please note that the stoploss can only increase, values lower than the current stoploss are ignored.
``` python
from datetime import datetime, timedelta
from freqtrade.persistence import Trade
class AwesomeStrategy(IStrategy):
# ... populate_* methods
use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
if current_profit < 0.04:
return -1 # return a value bigger than the inital stoploss to keep using the inital stoploss
# After reaching the desired offset, allow the stoploss to trail by half the profit
desired_stoploss = current_profit / 2
# Use a minimum of 2.5% and a maximum of 5%
return max(min(desired_stoploss, 0.05), 0.025)
```
#### Absolute stoploss
The below example sets absolute profit levels based on the current profit.
* Use the regular stoploss until 20% profit is reached
* Once profit is > 40%, stoploss will be at 25%, locking in at least 25% of the profit.
* Once profit is > 25% - stoploss will be 15%.
* Once profit is > 20% - stoploss will be set to 7%.
``` python
from datetime import datetime
from freqtrade.persistence import Trade
class AwesomeStrategy(IStrategy):
# ... populate_* methods
use_custom_stoploss = True
def custom_stoploss(self, pair: str, trade: 'Trade', current_time: datetime,
current_rate: float, current_profit: float, **kwargs) -> float:
# Calculate as `-desired_stop_from_open + current_profit` to get the distance between current_profit and initial price
if current_profit > 0.40:
return (-0.25 + current_profit)
if current_profit > 0.25:
return (-0.15 + current_profit)
if current_profit > 0.20:
return (-0.7 + current_profit)
return 1
```
---
## Custom order timeout rules ## Custom order timeout rules
Simple, timebased order-timeouts can be configured either via strategy or in the configuration in the `unfilledtimeout` section. Simple, time-based order-timeouts can be configured either via strategy or in the configuration in the `unfilledtimeout` section.
However, freqtrade also offers a custom callback for both ordertypes, which allows you to decide based on custom criteria if a order did time out or not. However, freqtrade also offers a custom callback for both order types, which allows you to decide based on custom criteria if a order did time out or not.
!!! Note !!! Note
Unfilled order timeouts are not relevant during backtesting or hyperopt, and are only relevant during real (live) trading. Therefore these methods are only called in these circumstances. Unfilled order timeouts are not relevant during backtesting or hyperopt, and are only relevant during real (live) trading. Therefore these methods are only called in these circumstances.
@@ -28,7 +202,7 @@ The function must return either `True` (cancel order) or `False` (keep order ali
from datetime import datetime, timedelta from datetime import datetime, timedelta
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
class Awesomestrategy(IStrategy): class AwesomeStrategy(IStrategy):
# ... populate_* methods # ... populate_* methods
@@ -67,7 +241,7 @@ class Awesomestrategy(IStrategy):
from datetime import datetime from datetime import datetime
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
class Awesomestrategy(IStrategy): class AwesomeStrategy(IStrategy):
# ... populate_* methods # ... populate_* methods
@@ -95,6 +269,8 @@ class Awesomestrategy(IStrategy):
return False return False
``` ```
---
## Bot loop start callback ## Bot loop start callback
A simple callback which is called once at the start of every bot throttling iteration. A simple callback which is called once at the start of every bot throttling iteration.
@@ -103,7 +279,7 @@ This can be used to perform calculations which are pair independent (apply to al
``` python ``` python
import requests import requests
class Awesomestrategy(IStrategy): class AwesomeStrategy(IStrategy):
# ... populate_* methods # ... populate_* methods
@@ -128,7 +304,7 @@ class Awesomestrategy(IStrategy):
`confirm_trade_entry()` can be used to abort a trade entry at the latest second (maybe because the price is not what we expect). `confirm_trade_entry()` can be used to abort a trade entry at the latest second (maybe because the price is not what we expect).
``` python ``` python
class Awesomestrategy(IStrategy): class AwesomeStrategy(IStrategy):
# ... populate_* methods # ... populate_* methods
@@ -164,7 +340,7 @@ class Awesomestrategy(IStrategy):
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
class Awesomestrategy(IStrategy): class AwesomeStrategy(IStrategy):
# ... populate_* methods # ... populate_* methods
@@ -200,6 +376,8 @@ class Awesomestrategy(IStrategy):
``` ```
---
## Derived strategies ## Derived strategies
The strategies can be derived from other strategies. This avoids duplication of your custom strategy code. You can use this technique to override small parts of your main strategy, leaving the rest untouched: The strategies can be derived from other strategies. This avoids duplication of your custom strategy code. You can use this technique to override small parts of your main strategy, leaving the rest untouched:
@@ -219,4 +397,41 @@ class MyAwesomeStrategy2(MyAwesomeStrategy):
trailing_stop = True trailing_stop = True
``` ```
Both attributes and methods may be overriden, altering behavior of the original strategy in a way you need. Both attributes and methods may be overridden, altering behavior of the original strategy in a way you need.
!!! Note "Parent-strategy in different files"
If you have the parent-strategy in a different file, you'll need to add the following to the top of your "child"-file to ensure proper loading, otherwise freqtrade may not be able to load the parent strategy correctly.
``` python
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent))
from myawesomestrategy import MyAwesomeStrategy
```
## Embedding Strategies
Freqtrade provides you with with an easy way to embed the strategy into your configuration file.
This is done by utilizing BASE64 encoding and providing this string at the strategy configuration field,
in your chosen config file.
### Encoding a string as BASE64
This is a quick example, how to generate the BASE64 string in python
```python
from base64 import urlsafe_b64encode
with open(file, 'r') as f:
content = f.read()
content = urlsafe_b64encode(content.encode('utf-8'))
```
The variable 'content', will contain the strategy file in a BASE64 encoded form. Which can now be set in your configurations file as following
```json
"strategy": "NameOfStrategy:BASE64String"
```
Please ensure that 'NameOfStrategy' is identical to the strategy name!

View File

@@ -147,7 +147,7 @@ Let's try to backtest 1 month (January 2019) of 5m candles using an example stra
freqtrade backtesting --timerange 20190101-20190201 --timeframe 5m freqtrade backtesting --timerange 20190101-20190201 --timeframe 5m
``` ```
Assuming `startup_candle_count` is set to 100, backtesting knows it needs 100 candles to generate valid buy signals. It will load data from `20190101 - (100 * 5m)` - which is ~2019-12-31 15:30:00. Assuming `startup_candle_count` is set to 100, backtesting knows it needs 100 candles to generate valid buy signals. It will load data from `20190101 - (100 * 5m)` - which is ~2018-12-31 15:30:00.
If this data is available, indicators will be calculated with this extended timerange. The instable startup period (up to 2019-01-01 00:00:00) will then be removed before starting backtesting. If this data is available, indicators will be calculated with this extended timerange. The instable startup period (up to 2019-01-01 00:00:00) will then be removed before starting backtesting.
!!! Note !!! Note
@@ -309,17 +309,17 @@ Storing information can be accomplished by creating a new dictionary within the
The name of the variable can be chosen at will, but should be prefixed with `cust_` to avoid naming collisions with predefined strategy variables. The name of the variable can be chosen at will, but should be prefixed with `cust_` to avoid naming collisions with predefined strategy variables.
```python ```python
class Awesomestrategy(IStrategy): class AwesomeStrategy(IStrategy):
# Create custom dictionary # Create custom dictionary
cust_info = {} cust_info = {}
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Check if the entry already exists # Check if the entry already exists
if not metadata["pair"] in self._cust_info: if not metadata["pair"] in self.cust_info:
# Create empty entry for this pair # Create empty entry for this pair
self._cust_info[metadata["pair"]] = {} self.cust_info[metadata["pair"]] = {}
if "crosstime" in self.cust_info[metadata["pair"]: if "crosstime" in self.cust_info[metadata["pair"]]:
self.cust_info[metadata["pair"]]["crosstime"] += 1 self.cust_info[metadata["pair"]]["crosstime"] += 1
else: else:
self.cust_info[metadata["pair"]]["crosstime"] = 1 self.cust_info[metadata["pair"]]["crosstime"] = 1
@@ -444,14 +444,19 @@ It can also be used in specific callbacks to get the signal that caused the acti
``` python ``` python
# fetch current dataframe # fetch current dataframe
if self.dp: if self.dp:
dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=metadata['pair'], if self.dp.runmode.value in ('live', 'dry_run'):
timeframe=self.timeframe) dataframe, last_updated = self.dp.get_analyzed_dataframe(pair=metadata['pair'],
timeframe=self.timeframe)
``` ```
!!! Note "No data available" !!! Note "No data available"
Returns an empty dataframe if the requested pair was not cached. Returns an empty dataframe if the requested pair was not cached.
This should not happen when using whitelisted pairs. This should not happen when using whitelisted pairs.
!!! Warning "Warning about backtesting"
This method will return an empty dataframe during backtesting.
### *orderbook(pair, maximum)* ### *orderbook(pair, maximum)*
``` python ``` python
@@ -462,8 +467,8 @@ if self.dp:
dataframe['best_ask'] = ob['asks'][0][0] dataframe['best_ask'] = ob['asks'][0][0]
``` ```
!!! Warning !!! Warning "Warning about backtesting"
The order book is not part of the historic data which means backtesting and hyperopt will not work correctly if this method is used. The order book is not part of the historic data which means backtesting and hyperopt will not work correctly if this method is used, as the method will return uptodate values.
### *ticker(pair)* ### *ticker(pair)*
@@ -653,7 +658,7 @@ The following example queries for the current pair and trades from today, howeve
if self.config['runmode'].value in ('live', 'dry_run'): if self.config['runmode'].value in ('live', 'dry_run'):
trades = Trade.get_trades([Trade.pair == metadata['pair'], trades = Trade.get_trades([Trade.pair == metadata['pair'],
Trade.open_date > datetime.utcnow() - timedelta(days=1), Trade.open_date > datetime.utcnow() - timedelta(days=1),
Trade.is_open == False, Trade.is_open.is_(False),
]).order_by(Trade.close_date).all() ]).order_by(Trade.close_date).all()
# Summarize profit for this pair. # Summarize profit for this pair.
curdayprofit = sum(trade.close_profit for trade in trades) curdayprofit = sum(trade.close_profit for trade in trades)
@@ -719,7 +724,7 @@ if self.config['runmode'].value in ('live', 'dry_run'):
# fetch closed trades for the last 2 days # fetch closed trades for the last 2 days
trades = Trade.get_trades([Trade.pair == metadata['pair'], trades = Trade.get_trades([Trade.pair == metadata['pair'],
Trade.open_date > datetime.utcnow() - timedelta(days=2), Trade.open_date > datetime.utcnow() - timedelta(days=2),
Trade.is_open == False, Trade.is_open.is_(False),
]).all() ]).all()
# Analyze the conditions you'd like to lock the pair .... will probably be different for every strategy # Analyze the conditions you'd like to lock the pair .... will probably be different for every strategy
sumprofit = sum(trade.close_profit for trade in trades) sumprofit = sum(trade.close_profit for trade in trades)

View File

@@ -24,7 +24,7 @@ config["strategy"] = "SampleStrategy"
# Location of the data # Location of the data
data_location = Path(config['user_data_dir'], 'data', 'binance') data_location = Path(config['user_data_dir'], 'data', 'binance')
# Pair to analyze - Only use one pair here # Pair to analyze - Only use one pair here
pair = "BTC_USDT" pair = "BTC/USDT"
``` ```
@@ -34,7 +34,9 @@ from freqtrade.data.history import load_pair_history
candles = load_pair_history(datadir=data_location, candles = load_pair_history(datadir=data_location,
timeframe=config["timeframe"], timeframe=config["timeframe"],
pair=pair) pair=pair,
data_format = "hdf5",
)
# Confirm success # Confirm success
print("Loaded " + str(len(candles)) + f" rows of data for {pair} from {data_location}") print("Loaded " + str(len(candles)) + f" rows of data for {pair} from {data_location}")

View File

@@ -87,6 +87,41 @@ Example configuration showing the different settings:
}, },
``` ```
## Create a custom keyboard (command shortcut buttons)
Telegram allows us to create a custom keyboard with buttons for commands.
The default custom keyboard looks like this.
```python
[
["/daily", "/profit", "/balance"], # row 1, 3 commands
["/status", "/status table", "/performance"], # row 2, 3 commands
["/count", "/start", "/stop", "/help"] # row 3, 4 commands
]
```
### Usage
You can create your own keyboard in `config.json`:
``` json
"telegram": {
"enabled": true,
"token": "your_telegram_token",
"chat_id": "your_telegram_chat_id",
"keyboard": [
["/daily", "/stats", "/balance", "/profit"],
["/status table", "/performance"],
["/reload_config", "/count", "/logs"]
]
},
```
!!! Note "Supported Commands"
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`
## Telegram commands ## Telegram commands
Per default, the Telegram bot shows predefined commands. Some commands Per default, the Telegram bot shows predefined commands. Some commands
@@ -102,10 +137,12 @@ official commands. You can ask at any moment for help with `/help`.
| `/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.
| `/status` | Lists all open trades | `/status` | Lists all open trades
| `/status <trade_id>` | Lists one or more specific trade. Separate multiple <trade_id> with a blank space.
| `/status table` | List all open trades in a table format. Pending buy orders are marked with an asterisk (*) Pending sell orders are marked with a double asterisk (**) | `/status table` | List all open trades in a table format. Pending buy orders are marked with an asterisk (*) Pending sell orders are marked with a double asterisk (**)
| `/trades [limit]` | List all recently closed trades in a table format. | `/trades [limit]` | List all recently closed trades in a table format.
| `/delete <trade_id>` | Delete a specific trade from the Database. Tries to close open orders. Requires manual handling of this trade on the exchange. | `/delete <trade_id>` | Delete a specific trade from the Database. Tries to close open orders. Requires manual handling of this trade on the exchange.
| `/count` | Displays number of trades used and available | `/count` | Displays number of trades used and available
| `/locks` | Show currently locked pairs.
| `/profit` | Display a summary of your profit/loss from close trades and some stats about your performance | `/profit` | Display a summary of your profit/loss from close trades and some stats about your performance
| `/forcesell <trade_id>` | Instantly sells the given trade (Ignoring `minimum_roi`). | `/forcesell <trade_id>` | Instantly sells the given trade (Ignoring `minimum_roi`).
| `/forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`). | `/forcesell all` | Instantly sells all open trades (Ignoring `minimum_roi`).
@@ -113,6 +150,7 @@ official commands. You can ask at any moment for help with `/help`.
| `/performance` | Show performance of each finished trade grouped by pair | `/performance` | Show performance of each finished trade grouped by pair
| `/balance` | Show account balance per currency | `/balance` | Show account balance per currency
| `/daily <n>` | Shows profit or loss per day, over the last n days (n defaults to 7) | `/daily <n>` | Shows profit or loss per day, over the last n days (n defaults to 7)
| `/stats` | Shows Wins / losses by Sell reason as well as Avg. holding durations for buys and sells
| `/whitelist` | Show the current whitelist | `/whitelist` | Show the current whitelist
| `/blacklist [pair]` | Show the current blacklist, or adds a pair to the blacklist. | `/blacklist [pair]` | Show the current blacklist, or adds a pair to the blacklist.
| `/edge` | Show validated pairs by Edge if it is enabled. | `/edge` | Show validated pairs by Edge if it is enabled.
@@ -207,7 +245,7 @@ Return a summary of your profit/loss and performance.
Note that for this to work, `forcebuy_enable` needs to be set to true. Note that for this to work, `forcebuy_enable` needs to be set to true.
[More details](configuration.md/#understand-forcebuy_enable) [More details](configuration.md#understand-forcebuy_enable)
### /performance ### /performance

31
docs/updating.md Normal file
View File

@@ -0,0 +1,31 @@
# How to update
To update your freqtrade installation, please use one of the below methods, corresponding to your installation method.
## docker-compose
!!! Note "Legacy installations using the `master` image"
We're switching from master to stable for the release Images - please adjust your docker-file and replace `freqtradeorg/freqtrade:master` with `freqtradeorg/freqtrade:stable`
``` bash
docker-compose pull
docker-compose up -d
```
## Installation via setup script
``` bash
./setup.sh --update
```
!!! Note
Make sure to run this command with your virtual environment disabled!
## Plain native installation
Please ensure that you're also updating dependencies - otherwise things might break without you noticing.
``` bash
git pull
pip install -U -r requirements.txt
```

View File

@@ -391,7 +391,7 @@ $ freqtrade list-markets --exchange kraken --all
## Test pairlist ## Test pairlist
Use the `test-pairlist` subcommand to test the configuration of [dynamic pairlists](configuration.md#pairlists). Use the `test-pairlist` subcommand to test the configuration of [dynamic pairlists](plugins.md#pairlists).
Requires a configuration with specified `pairlists` attribute. 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.
@@ -415,7 +415,7 @@ optional arguments:
### Examples ### Examples
Show whitelist when using a [dynamic pairlist](configuration.md#pairlists). Show whitelist when using a [dynamic pairlist](plugins.md#pairlists).
``` ```
freqtrade test-pairlist --config config.json --quote USDT BTC freqtrade test-pairlist --config config.json --quote USDT BTC

View File

@@ -1,4 +1,4 @@
We **strongly** recommend that Windows users use [Docker](docker.md) as this will work much easier and smoother (also more secure). We **strongly** recommend that Windows users use [Docker](docker_quickstart.md) as this will work much easier and smoother (also more secure).
If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work. If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work.
Otherwise, try the instructions below. Otherwise, try the instructions below.
@@ -52,6 +52,6 @@ error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++
Unfortunately, many packages requiring compilation don't provide a pre-built wheel. It is therefore mandatory to have a C/C++ compiler installed and available for your python environment to use. Unfortunately, many packages requiring compilation don't provide a pre-built wheel. It is therefore mandatory to have a C/C++ compiler installed and available for your python environment to use.
The easiest way is to download install Microsoft Visual Studio Community [here](https://visualstudio.microsoft.com/downloads/) and make sure to install "Common Tools for Visual C++" to enable building C code on Windows. Unfortunately, this is a heavy download / dependency (~4Gb) so you might want to consider WSL or [docker](docker.md) first. The easiest way is to download install Microsoft Visual Studio Community [here](https://visualstudio.microsoft.com/downloads/) and make sure to install "Common Tools for Visual C++" to enable building C code on Windows. Unfortunately, this is a heavy download / dependency (~4Gb) so you might want to consider WSL or [docker compose](docker_quickstart.md) first.
--- ---

View File

@@ -1,60 +1,71 @@
name: freqtrade name: freqtrade
channels: channels:
- defaults
- conda-forge - conda-forge
# - defaults
dependencies: dependencies:
# Required for app # 1/4 req main
- python>=3.6 - python>=3.7
- pip - numpy
- wheel - pandas
- numpy - pip
- pandas
- SQLAlchemy - aiohttp
- arrow - SQLAlchemy
- requests - python-telegram-bot
- urllib3 - arrow
- wrapt - cachetools
- jsonschema - requests
- tabulate - urllib3
- python-rapidjson - wrapt
- flask - jsonschema
- python-dotenv
- cachetools
- python-telegram-bot
# Optional for plotting
- plotly
# Optional for hyperopt
- scipy
- scikit-optimize
- scikit-learn
- filelock
- joblib
# Optional for development
- flake8
- pytest
- pytest-mock
- pytest-asyncio
- pytest-cov
- coveralls
- mypy
# Useful for jupyter
- jupyter
- ipykernel
- isort
- yapf
- pip:
# Required for app
- cython
- pycoingecko
- ccxt
- TA-Lib - TA-Lib
- py_find_1st - tabulate
- jinja2
- blosc
- sdnotify - sdnotify
# Optional for develpment - fastapi
- flake8-tidy-imports - uvicorn
- flake8-type-annotations - pyjwt
- pytest-random-order - colorama
- -e . - questionary
- prompt-toolkit
# ============================
# 2/4 req dev
- coveralls
- flake8
- mypy
- pytest
- pytest-asyncio
- pytest-cov
- pytest-mock
- isort
- nbconvert
# ============================
# 3/4 req hyperopt
- scipy
- scikit-learn
- filelock
- scikit-optimize
- joblib
- progressbar2
# ============================
# 4/4 req plot
- plotly
- jupyter
- pip:
- pycoingecko
- py_find_1st
- tables
- pytest-random-order
- flake8-type-annotations
- ccxt
- flake8-tidy-imports
- -e .
# - python-rapidjso

View File

@@ -1,5 +1,5 @@
""" Freqtrade bot """ """ Freqtrade bot """
__version__ = '2020.11' __version__ = '2021.2'
if __version__ == 'develop': if __version__ == 'develop':

View File

@@ -3,7 +3,7 @@
__main__.py for Freqtrade __main__.py for Freqtrade
To launch Freqtrade as a module To launch Freqtrade as a module
> python -m freqtrade (with Python >= 3.6) > python -m freqtrade (with Python >= 3.7)
""" """
from freqtrade import main from freqtrade import main

View File

@@ -10,8 +10,8 @@ from freqtrade.commands.arguments import Arguments
from freqtrade.commands.build_config_commands import start_new_config from freqtrade.commands.build_config_commands import start_new_config
from freqtrade.commands.data_commands import (start_convert_data, start_download_data, from freqtrade.commands.data_commands import (start_convert_data, start_download_data,
start_list_data) start_list_data)
from freqtrade.commands.deploy_commands import (start_create_userdir, start_new_hyperopt, from freqtrade.commands.deploy_commands import (start_create_userdir, start_install_ui,
start_new_strategy) start_new_hyperopt, start_new_strategy)
from freqtrade.commands.hyperopt_commands import start_hyperopt_list, start_hyperopt_show from freqtrade.commands.hyperopt_commands import start_hyperopt_list, start_hyperopt_show
from freqtrade.commands.list_commands import (start_list_exchanges, start_list_hyperopts, from freqtrade.commands.list_commands import (start_list_exchanges, start_list_hyperopts,
start_list_markets, start_list_strategies, start_list_markets, start_list_strategies,

View File

@@ -20,11 +20,13 @@ ARGS_COMMON_OPTIMIZE = ["timeframe", "timerange", "dataformat_ohlcv",
"max_open_trades", "stake_amount", "fee"] "max_open_trades", "stake_amount", "fee"]
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions", ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions",
"enable_protections",
"strategy_list", "export", "exportfilename"] "strategy_list", "export", "exportfilename"]
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path", ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
"position_stacking", "epochs", "spaces", "position_stacking", "use_max_market_positions",
"use_max_market_positions", "print_all", "enable_protections",
"epochs", "spaces", "print_all",
"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"] "hyperopt_loss"]
@@ -42,7 +44,8 @@ ARGS_LIST_TIMEFRAMES = ["exchange", "print_one_column"]
ARGS_LIST_PAIRS = ["exchange", "print_list", "list_pairs_print_json", "print_one_column", ARGS_LIST_PAIRS = ["exchange", "print_list", "list_pairs_print_json", "print_one_column",
"print_csv", "base_currencies", "quote_currencies", "list_pairs_all"] "print_csv", "base_currencies", "quote_currencies", "list_pairs_all"]
ARGS_TEST_PAIRLIST = ["config", "quote_currencies", "print_one_column", "list_pairs_print_json"] ARGS_TEST_PAIRLIST = ["verbosity", "config", "quote_currencies", "print_one_column",
"list_pairs_print_json"]
ARGS_CREATE_USERDIR = ["user_data_dir", "reset"] ARGS_CREATE_USERDIR = ["user_data_dir", "reset"]
@@ -67,6 +70,8 @@ ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit",
ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url", ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url",
"trade_source", "timeframe"] "trade_source", "timeframe"]
ARGS_INSTALL_UI = ["erase_ui_only"]
ARGS_SHOW_TRADES = ["db_url", "trade_ids", "print_json"] ARGS_SHOW_TRADES = ["db_url", "trade_ids", "print_json"]
ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable",
@@ -164,8 +169,8 @@ class Arguments:
from freqtrade.commands import (start_backtesting, start_convert_data, start_create_userdir, from freqtrade.commands import (start_backtesting, start_convert_data, start_create_userdir,
start_download_data, start_edge, start_hyperopt, start_download_data, start_edge, start_hyperopt,
start_hyperopt_list, start_hyperopt_show, start_list_data, start_hyperopt_list, start_hyperopt_show, start_install_ui,
start_list_exchanges, start_list_hyperopts, start_list_data, start_list_exchanges, start_list_hyperopts,
start_list_markets, start_list_strategies, start_list_markets, start_list_strategies,
start_list_timeframes, start_new_config, start_new_hyperopt, start_list_timeframes, start_new_config, start_new_hyperopt,
start_new_strategy, start_plot_dataframe, start_plot_profit, start_new_strategy, start_plot_dataframe, start_plot_profit,
@@ -352,6 +357,14 @@ class Arguments:
test_pairlist_cmd.set_defaults(func=start_test_pairlist) test_pairlist_cmd.set_defaults(func=start_test_pairlist)
self._build_args(optionlist=ARGS_TEST_PAIRLIST, parser=test_pairlist_cmd) self._build_args(optionlist=ARGS_TEST_PAIRLIST, parser=test_pairlist_cmd)
# Add install-ui subcommand
install_ui_cmd = subparsers.add_parser(
'install-ui',
help='Install FreqUI',
)
install_ui_cmd.set_defaults(func=start_install_ui)
self._build_args(optionlist=ARGS_INSTALL_UI, parser=install_ui_cmd)
# Add Plotting subcommand # Add Plotting subcommand
plot_dataframe_cmd = subparsers.add_parser( plot_dataframe_cmd = subparsers.add_parser(
'plot-dataframe', 'plot-dataframe',

View File

@@ -144,6 +144,14 @@ AVAILABLE_CLI_OPTIONS = {
action='store_false', action='store_false',
default=True, default=True,
), ),
"enable_protections": Arg(
'--enable-protections', '--enableprotections',
help='Enable protections for backtesting.'
'Will slow backtesting down by a considerable amount, but will include '
'configured protections',
action='store_true',
default=False,
),
"strategy_list": Arg( "strategy_list": Arg(
'--strategy-list', '--strategy-list',
help='Provide a space-separated list of strategies to backtest. ' help='Provide a space-separated list of strategies to backtest. '
@@ -379,6 +387,12 @@ AVAILABLE_CLI_OPTIONS = {
help='Clean all existing data for the selected exchange/pairs/timeframes.', help='Clean all existing data for the selected exchange/pairs/timeframes.',
action='store_true', action='store_true',
), ),
"erase_ui_only": Arg(
'--erase',
help="Clean UI folder, don't download new version.",
action='store_true',
default=False,
),
# Templating options # Templating options
"template": Arg( "template": Arg(
'--template', '--template',

View File

@@ -10,6 +10,7 @@ from freqtrade.data.history import (convert_trades_to_ohlcv, refresh_backtest_oh
refresh_backtest_trades_data) refresh_backtest_trades_data)
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes from freqtrade.exchange import timeframe_to_minutes
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.resolvers import ExchangeResolver from freqtrade.resolvers import ExchangeResolver
from freqtrade.state import RunMode from freqtrade.state import RunMode
@@ -42,15 +43,17 @@ def start_download_data(args: Dict[str, Any]) -> None:
"Downloading data requires a list of pairs. " "Downloading data requires a list of pairs. "
"Please check the documentation on how to configure this.") "Please check the documentation on how to configure this.")
logger.info(f"About to download pairs: {config['pairs']}, "
f"intervals: {config['timeframes']} to {config['datadir']}")
pairs_not_available: List[str] = [] pairs_not_available: List[str] = []
# Init exchange # Init exchange
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False) exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False)
# Manual validations of relevant settings # Manual validations of relevant settings
exchange.validate_pairs(config['pairs']) exchange.validate_pairs(config['pairs'])
expanded_pairs = expand_pairlist(config['pairs'], list(exchange.markets))
logger.info(f"About to download pairs: {expanded_pairs}, "
f"intervals: {config['timeframes']} to {config['datadir']}")
for timeframe in config['timeframes']: for timeframe in config['timeframes']:
exchange.validate_timeframes(timeframe) exchange.validate_timeframes(timeframe)
@@ -58,20 +61,20 @@ def start_download_data(args: Dict[str, Any]) -> None:
if config.get('download_trades'): if config.get('download_trades'):
pairs_not_available = refresh_backtest_trades_data( pairs_not_available = refresh_backtest_trades_data(
exchange, pairs=config['pairs'], datadir=config['datadir'], exchange, pairs=expanded_pairs, datadir=config['datadir'],
timerange=timerange, erase=bool(config.get('erase')), timerange=timerange, erase=bool(config.get('erase')),
data_format=config['dataformat_trades']) data_format=config['dataformat_trades'])
# Convert downloaded trade data to different timeframes # Convert downloaded trade data to different timeframes
convert_trades_to_ohlcv( convert_trades_to_ohlcv(
pairs=config['pairs'], timeframes=config['timeframes'], pairs=expanded_pairs, timeframes=config['timeframes'],
datadir=config['datadir'], timerange=timerange, erase=bool(config.get('erase')), datadir=config['datadir'], timerange=timerange, erase=bool(config.get('erase')),
data_format_ohlcv=config['dataformat_ohlcv'], data_format_ohlcv=config['dataformat_ohlcv'],
data_format_trades=config['dataformat_trades'], data_format_trades=config['dataformat_trades'],
) )
else: else:
pairs_not_available = refresh_backtest_ohlcv_data( pairs_not_available = refresh_backtest_ohlcv_data(
exchange, pairs=config['pairs'], timeframes=config['timeframes'], exchange, pairs=expanded_pairs, timeframes=config['timeframes'],
datadir=config['datadir'], timerange=timerange, erase=bool(config.get('erase')), datadir=config['datadir'], timerange=timerange, erase=bool(config.get('erase')),
data_format=config['dataformat_ohlcv']) data_format=config['dataformat_ohlcv'])

View File

@@ -1,7 +1,9 @@
import logging import logging
import sys import sys
from pathlib import Path from pathlib import Path
from typing import Any, Dict from typing import Any, Dict, Optional, Tuple
import requests
from freqtrade.configuration import setup_utils_configuration from freqtrade.configuration import setup_utils_configuration
from freqtrade.configuration.directory_operations import copy_sample_files, create_userdata_dir from freqtrade.configuration.directory_operations import copy_sample_files, create_userdata_dir
@@ -137,3 +139,87 @@ def start_new_hyperopt(args: Dict[str, Any]) -> None:
deploy_new_hyperopt(args['hyperopt'], new_path, args['template']) deploy_new_hyperopt(args['hyperopt'], new_path, args['template'])
else: else:
raise OperationalException("`new-hyperopt` requires --hyperopt to be set.") raise OperationalException("`new-hyperopt` requires --hyperopt to be set.")
def clean_ui_subdir(directory: Path):
if directory.is_dir():
logger.info("Removing UI directory content.")
for p in reversed(list(directory.glob('**/*'))): # iterate contents from leaves to root
if p.name in ('.gitkeep', 'fallback_file.html'):
continue
if p.is_file():
p.unlink()
elif p.is_dir():
p.rmdir()
def read_ui_version(dest_folder: Path) -> Optional[str]:
file = dest_folder / '.uiversion'
if not file.is_file():
return None
with file.open('r') as f:
return f.read()
def download_and_install_ui(dest_folder: Path, dl_url: str, version: str):
from io import BytesIO
from zipfile import ZipFile
logger.info(f"Downloading {dl_url}")
resp = requests.get(dl_url).content
dest_folder.mkdir(parents=True, exist_ok=True)
with ZipFile(BytesIO(resp)) as zf:
for fn in zf.filelist:
with zf.open(fn) as x:
destfile = dest_folder / fn.filename
if fn.is_dir():
destfile.mkdir(exist_ok=True)
else:
destfile.write_bytes(x.read())
with (dest_folder / '.uiversion').open('w') as f:
f.write(version)
def get_ui_download_url() -> Tuple[str, str]:
base_url = 'https://api.github.com/repos/freqtrade/frequi/'
# Get base UI Repo path
resp = requests.get(f"{base_url}releases")
resp.raise_for_status()
r = resp.json()
latest_version = r[0]['name']
assets = r[0].get('assets', [])
dl_url = ''
if assets and len(assets) > 0:
dl_url = assets[0]['browser_download_url']
# URL not found - try assets url
if not dl_url:
assets = r[0]['assets_url']
resp = requests.get(assets)
r = resp.json()
dl_url = r[0]['browser_download_url']
return dl_url, latest_version
def start_install_ui(args: Dict[str, Any]) -> None:
dest_folder = Path(__file__).parents[1] / 'rpc/api_server/ui/installed/'
# First make sure the assets are removed.
dl_url, latest_version = get_ui_download_url()
curr_version = read_ui_version(dest_folder)
if curr_version == latest_version and not args.get('erase_ui_only'):
logger.info(f"UI already up-to-date, FreqUI Version {curr_version}.")
return
clean_ui_subdir(dest_folder)
if args.get('erase_ui_only'):
logger.info("Erased UI directory content. Not downloading new version.")
else:
# Download a new version
download_and_install_ui(dest_folder, dl_url, latest_version)

View File

@@ -15,7 +15,7 @@ def start_test_pairlist(args: Dict[str, Any]) -> None:
""" """
Test Pairlist configuration Test Pairlist configuration
""" """
from freqtrade.pairlist.pairlistmanager import PairListManager from freqtrade.plugins.pairlistmanager import PairListManager
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE) config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False) exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False)

View File

@@ -54,7 +54,7 @@ def validate_config_schema(conf: Dict[str, Any]) -> Dict[str, Any]:
return conf return conf
except ValidationError as e: except ValidationError as e:
logger.critical( logger.critical(
f"Invalid configuration. See config.json.example. Reason: {e}" f"Invalid configuration. Reason: {e}"
) )
raise ValidationError( raise ValidationError(
best_match(Draft4Validator(conf_schema).iter_errors(conf)).message best_match(Draft4Validator(conf_schema).iter_errors(conf)).message
@@ -74,6 +74,7 @@ def validate_config_consistency(conf: Dict[str, Any]) -> None:
_validate_trailing_stoploss(conf) _validate_trailing_stoploss(conf)
_validate_edge(conf) _validate_edge(conf)
_validate_whitelist(conf) _validate_whitelist(conf)
_validate_protections(conf)
_validate_unlimited_amount(conf) _validate_unlimited_amount(conf)
# validate configuration before returning # validate configuration before returning
@@ -155,3 +156,22 @@ def _validate_whitelist(conf: Dict[str, Any]) -> None:
if (pl.get('method') == 'StaticPairList' if (pl.get('method') == 'StaticPairList'
and not conf.get('exchange', {}).get('pair_whitelist')): and not conf.get('exchange', {}).get('pair_whitelist')):
raise OperationalException("StaticPairList requires pair_whitelist to be set.") raise OperationalException("StaticPairList requires pair_whitelist to be set.")
def _validate_protections(conf: Dict[str, Any]) -> None:
"""
Validate protection configuration validity
"""
for prot in conf.get('protections', []):
if ('stop_duration' in prot and 'stop_duration_candles' in prot):
raise OperationalException(
"Protections must specify either `stop_duration` or `stop_duration_candles`.\n"
f"Please fix the protection {prot.get('method')}"
)
if ('lookback_period' in prot and 'lookback_period_candles' in prot):
raise OperationalException(
"Protections must specify either `lookback_period` or `lookback_period_candles`.\n"
f"Please fix the protection {prot.get('method')}"
)

View File

@@ -211,6 +211,9 @@ class Configuration:
self._args_to_config(config, argname='position_stacking', self._args_to_config(config, argname='position_stacking',
logstring='Parameter --enable-position-stacking detected ...') logstring='Parameter --enable-position-stacking detected ...')
self._args_to_config(
config, argname='enable_protections',
logstring='Parameter --enable-protections detected, enabling Protections. ...')
# Setting max_open_trades to infinite if -1 # Setting max_open_trades to infinite if -1
if config.get('max_open_trades') == -1: if config.get('max_open_trades') == -1:
config['max_open_trades'] = float('inf') config['max_open_trades'] = float('inf')

View File

@@ -26,6 +26,24 @@ def check_conflicting_settings(config: Dict[str, Any],
) )
def process_removed_setting(config: Dict[str, Any],
section1: str, name1: str,
section2: str, name2: str) -> None:
"""
:param section1: Removed section
:param name1: Removed setting name
:param section2: new section for this key
:param name2: new setting name
"""
section1_config = config.get(section1, {})
if name1 in section1_config:
raise OperationalException(
f"Setting `{section1}.{name1}` has been moved to `{section2}.{name2}. "
f"Please delete it from your configuration and use the `{section2}.{name2}` "
"setting instead."
)
def process_deprecated_setting(config: Dict[str, Any], def process_deprecated_setting(config: Dict[str, Any],
section1: str, name1: str, section1: str, name1: str,
section2: str, name2: str) -> None: section2: str, name2: str) -> None:
@@ -44,19 +62,18 @@ def process_deprecated_setting(config: Dict[str, Any],
def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None: def process_temporary_deprecated_settings(config: Dict[str, Any]) -> None:
check_conflicting_settings(config, 'ask_strategy', 'use_sell_signal', # Kept for future deprecated / moved settings
'experimental', 'use_sell_signal') # check_conflicting_settings(config, 'ask_strategy', 'use_sell_signal',
check_conflicting_settings(config, 'ask_strategy', 'sell_profit_only', # 'experimental', 'use_sell_signal')
'experimental', 'sell_profit_only') # process_deprecated_setting(config, 'ask_strategy', 'use_sell_signal',
check_conflicting_settings(config, 'ask_strategy', 'ignore_roi_if_buy_signal', # 'experimental', 'use_sell_signal')
'experimental', 'ignore_roi_if_buy_signal')
process_deprecated_setting(config, 'ask_strategy', 'use_sell_signal', process_removed_setting(config, 'experimental', 'use_sell_signal',
'experimental', 'use_sell_signal') 'ask_strategy', 'use_sell_signal')
process_deprecated_setting(config, 'ask_strategy', 'sell_profit_only', process_removed_setting(config, 'experimental', 'sell_profit_only',
'experimental', 'sell_profit_only') 'ask_strategy', 'sell_profit_only')
process_deprecated_setting(config, 'ask_strategy', 'ignore_roi_if_buy_signal', process_removed_setting(config, 'experimental', 'ignore_roi_if_buy_signal',
'experimental', 'ignore_roi_if_buy_signal') 'ask_strategy', 'ignore_roi_if_buy_signal')
if (config.get('edge', {}).get('enabled', False) if (config.get('edge', {}).get('enabled', False)
and 'capital_available_percentage' in config.get('edge', {})): and 'capital_available_percentage' in config.get('edge', {})):

View File

@@ -24,8 +24,10 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss',
'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily', 'SharpeHyperOptLoss', 'SharpeHyperOptLossDaily',
'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily'] 'SortinoHyperOptLoss', 'SortinoHyperOptLossDaily']
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList',
'AgeFilter', 'PrecisionFilter', 'PriceFilter', 'AgeFilter', 'PerformanceFilter', 'PrecisionFilter',
'RangeStabilityFilter', 'ShuffleFilter', 'SpreadFilter'] 'PriceFilter', 'RangeStabilityFilter', 'ShuffleFilter',
'SpreadFilter']
AVAILABLE_PROTECTIONS = ['CooldownPeriod', 'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard']
AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5'] AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5']
DRY_RUN_WALLET = 1000 DRY_RUN_WALLET = 1000
DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S' DATETIME_PRINT_FORMAT = '%Y-%m-%d %H:%M:%S'
@@ -43,6 +45,16 @@ USERPATH_NOTEBOOKS = 'notebooks'
TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent'] TELEGRAM_SETTING_OPTIONS = ['on', 'off', 'silent']
# Define decimals per coin for outputs
# Only used for outputs.
DECIMAL_PER_COIN_FALLBACK = 3 # Should be low to avoid listing all possible FIAT's
DECIMALS_PER_COIN = {
'BTC': 8,
'ETH': 5,
}
# Soure files with destination directories within user-directory # Soure files with destination directories within user-directory
USER_DATA_FILES = { USER_DATA_FILES = {
'sample_strategy.py': USERPATH_STRATEGIES, 'sample_strategy.py': USERPATH_STRATEGIES,
@@ -114,6 +126,7 @@ CONF_SCHEMA = {
'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1}, 'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1},
'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1}, 'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1},
'trailing_only_offset_is_reached': {'type': 'boolean'}, 'trailing_only_offset_is_reached': {'type': 'boolean'},
'bot_name': {'type': 'string'},
'unfilledtimeout': { 'unfilledtimeout': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
@@ -152,6 +165,7 @@ CONF_SCHEMA = {
'order_book_max': {'type': 'integer', 'minimum': 1, 'maximum': 50}, 'order_book_max': {'type': 'integer', 'minimum': 1, 'maximum': 50},
'use_sell_signal': {'type': 'boolean'}, 'use_sell_signal': {'type': 'boolean'},
'sell_profit_only': {'type': 'boolean'}, 'sell_profit_only': {'type': 'boolean'},
'sell_profit_offset': {'type': 'number', 'minimum': 0.0},
'ignore_roi_if_buy_signal': {'type': 'boolean'} 'ignore_roi_if_buy_signal': {'type': 'boolean'}
} }
}, },
@@ -182,9 +196,6 @@ CONF_SCHEMA = {
'experimental': { 'experimental': {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'use_sell_signal': {'type': 'boolean'},
'sell_profit_only': {'type': 'boolean'},
'ignore_roi_if_buy_signal': {'type': 'boolean'},
'block_bad_exchanges': {'type': 'boolean'} 'block_bad_exchanges': {'type': 'boolean'}
} }
}, },
@@ -194,7 +205,21 @@ CONF_SCHEMA = {
'type': 'object', 'type': 'object',
'properties': { 'properties': {
'method': {'type': 'string', 'enum': AVAILABLE_PAIRLISTS}, 'method': {'type': 'string', 'enum': AVAILABLE_PAIRLISTS},
'config': {'type': 'object'} },
'required': ['method'],
}
},
'protections': {
'type': 'array',
'items': {
'type': 'object',
'properties': {
'method': {'type': 'string', 'enum': AVAILABLE_PROTECTIONS},
'stop_duration': {'type': 'number', 'minimum': 0.0},
'stop_duration_candles': {'type': 'number', 'minimum': 0},
'trade_limit': {'type': 'number', 'minimum': 1},
'lookback_period': {'type': 'number', 'minimum': 1},
'lookback_period_candles': {'type': 'number', 'minimum': 1},
}, },
'required': ['method'], 'required': ['method'],
} }

View File

@@ -2,9 +2,8 @@
Helpers when analyzing backtest data Helpers when analyzing backtest data
""" """
import logging import logging
from datetime import timezone
from pathlib import Path from pathlib import Path
from typing import Any, Dict, Optional, Tuple, Union from typing import Any, Dict, List, Optional, Tuple, Union
import numpy as np import numpy as np
import pandas as pd import pandas as pd
@@ -16,9 +15,22 @@ from freqtrade.persistence import Trade, init_db
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# must align with columns in backtest.py # Old format - maybe remove?
BT_DATA_COLUMNS = ["pair", "profit_percent", "open_date", "close_date", "index", "trade_duration", BT_DATA_COLUMNS_OLD = ["pair", "profit_percent", "open_date", "close_date", "index",
"open_rate", "close_rate", "open_at_end", "sell_reason"] "trade_duration", "open_rate", "close_rate", "open_at_end", "sell_reason"]
# Mid-term format, crated by BacktestResult Named Tuple
BT_DATA_COLUMNS_MID = ['pair', 'profit_percent', 'open_date', 'close_date', 'trade_duration',
'open_rate', 'close_rate', 'open_at_end', 'sell_reason', 'fee_open',
'fee_close', 'amount', 'profit_abs', 'profit_ratio']
# Newest format
BT_DATA_COLUMNS = ['pair', 'stake_amount', 'amount', 'open_date', 'close_date',
'open_rate', 'close_rate',
'fee_open', 'fee_close', 'trade_duration',
'profit_ratio', 'profit_abs', 'sell_reason',
'initial_stop_loss_abs', 'initial_stop_loss_ratio', 'stop_loss_abs',
'stop_loss_ratio', 'min_rate', 'max_rate', 'is_open', ]
def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str: def get_latest_optimize_filename(directory: Union[Path, str], variant: str) -> str:
@@ -154,7 +166,7 @@ def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = Non
) )
else: else:
# old format - only with lists. # old format - only with lists.
df = pd.DataFrame(data, columns=BT_DATA_COLUMNS) df = pd.DataFrame(data, columns=BT_DATA_COLUMNS_OLD)
df['open_date'] = pd.to_datetime(df['open_date'], df['open_date'] = pd.to_datetime(df['open_date'],
unit='s', unit='s',
@@ -166,7 +178,10 @@ def load_backtest_data(filename: Union[Path, str], strategy: Optional[str] = Non
utc=True, utc=True,
infer_datetime_format=True infer_datetime_format=True
) )
# Create compatibility with new format
df['profit_abs'] = df['close_rate'] - df['open_rate'] df['profit_abs'] = df['close_rate'] - df['open_rate']
if 'profit_ratio' not in df.columns:
df['profit_ratio'] = df['profit_percent']
df = df.sort_values("open_date").reset_index(drop=True) df = df.sort_values("open_date").reset_index(drop=True)
return df return df
@@ -209,6 +224,20 @@ def evaluate_result_multi(results: pd.DataFrame, timeframe: str,
return df_final[df_final['open_trades'] > max_open_trades] return df_final[df_final['open_trades'] > max_open_trades]
def trade_list_to_dataframe(trades: List[Trade]) -> pd.DataFrame:
"""
Convert list of Trade objects to pandas Dataframe
:param trades: List of trade objects
:return: Dataframe with BT_DATA_COLUMNS
"""
df = pd.DataFrame.from_records([t.to_json() for t in trades], columns=BT_DATA_COLUMNS)
if len(df) > 0:
df.loc[:, 'close_date'] = pd.to_datetime(df['close_date'], utc=True)
df.loc[:, 'open_date'] = pd.to_datetime(df['open_date'], utc=True)
df.loc[:, 'close_rate'] = df['close_rate'].astype('float64')
return df
def load_trades_from_db(db_url: str, strategy: Optional[str] = None) -> pd.DataFrame: def load_trades_from_db(db_url: str, strategy: Optional[str] = None) -> pd.DataFrame:
""" """
Load trades from a DB (using dburl) Load trades from a DB (using dburl)
@@ -219,36 +248,10 @@ def load_trades_from_db(db_url: str, strategy: Optional[str] = None) -> pd.DataF
""" """
init_db(db_url, clean_open_orders=False) init_db(db_url, clean_open_orders=False)
columns = ["pair", "open_date", "close_date", "profit", "profit_percent",
"open_rate", "close_rate", "amount", "trade_duration", "sell_reason",
"fee_open", "fee_close", "open_rate_requested", "close_rate_requested",
"stake_amount", "max_rate", "min_rate", "id", "exchange",
"stop_loss", "initial_stop_loss", "strategy", "timeframe"]
filters = [] filters = []
if strategy: if strategy:
filters.append(Trade.strategy == strategy) filters.append(Trade.strategy == strategy)
trades = trade_list_to_dataframe(Trade.get_trades(filters).all())
trades = pd.DataFrame([(t.pair,
t.open_date.replace(tzinfo=timezone.utc),
t.close_date.replace(tzinfo=timezone.utc) if t.close_date else None,
t.calc_profit(), t.calc_profit_ratio(),
t.open_rate, t.close_rate, t.amount,
(round((t.close_date.timestamp() - t.open_date.timestamp()) / 60, 2)
if t.close_date else None),
t.sell_reason,
t.fee_open, t.fee_close,
t.open_rate_requested,
t.close_rate_requested,
t.stake_amount,
t.max_rate,
t.min_rate,
t.id, t.exchange,
t.stop_loss, t.initial_stop_loss,
t.strategy, t.timeframe
)
for t in Trade.get_trades(filters).all()],
columns=columns)
return trades return trades
@@ -309,7 +312,7 @@ def calculate_market_change(data: Dict[str, pd.DataFrame], column: str = "close"
end = df[column].dropna().iloc[-1] end = df[column].dropna().iloc[-1]
tmp_means.append((end - start) / start) tmp_means.append((end - start) / start)
return np.mean(tmp_means) return float(np.mean(tmp_means))
def combine_dataframes_with_mean(data: Dict[str, pd.DataFrame], def combine_dataframes_with_mean(data: Dict[str, pd.DataFrame],
@@ -334,7 +337,7 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str,
""" """
Adds a column `col_name` with the cumulative profit for the given trades array. Adds a column `col_name` with the cumulative profit for the given trades array.
:param df: DataFrame with date index :param df: DataFrame with date index
:param trades: DataFrame containing trades (requires columns close_date and profit_percent) :param trades: DataFrame containing trades (requires columns close_date and profit_ratio)
:param col_name: Column name that will be assigned the results :param col_name: Column name that will be assigned the results
:param timeframe: Timeframe used during the operations :param timeframe: Timeframe used during the operations
:return: Returns df with one additional column, col_name, containing the cumulative profit. :return: Returns df with one additional column, col_name, containing the cumulative profit.
@@ -346,8 +349,8 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str,
timeframe_minutes = timeframe_to_minutes(timeframe) timeframe_minutes = timeframe_to_minutes(timeframe)
# Resample to timeframe to make sure trades match candles # Resample to timeframe to make sure trades match candles
_trades_sum = trades.resample(f'{timeframe_minutes}min', on='close_date' _trades_sum = trades.resample(f'{timeframe_minutes}min', on='close_date'
)[['profit_percent']].sum() )[['profit_ratio']].sum()
df.loc[:, col_name] = _trades_sum.cumsum() df.loc[:, col_name] = _trades_sum['profit_ratio'].cumsum()
# Set first value to 0 # Set first value to 0
df.loc[df.iloc[0].name, col_name] = 0 df.loc[df.iloc[0].name, col_name] = 0
# FFill to get continuous # FFill to get continuous
@@ -356,13 +359,13 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str,
def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date', def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date',
value_col: str = 'profit_percent' value_col: str = 'profit_ratio'
) -> Tuple[float, pd.Timestamp, pd.Timestamp]: ) -> Tuple[float, pd.Timestamp, pd.Timestamp]:
""" """
Calculate max drawdown and the corresponding close dates Calculate max drawdown and the corresponding close dates
:param trades: DataFrame containing trades (requires columns close_date and profit_percent) :param trades: DataFrame containing trades (requires columns close_date and profit_ratio)
:param date_col: Column in DataFrame to use for dates (defaults to 'close_date') :param date_col: Column in DataFrame to use for dates (defaults to 'close_date')
:param value_col: Column in DataFrame to use for values (defaults to 'profit_percent') :param value_col: Column in DataFrame to use for values (defaults to 'profit_ratio')
:return: Tuple (float, highdate, lowdate) with absolute max drawdown, high and low time :return: Tuple (float, highdate, lowdate) with absolute max drawdown, high and low time
:raise: ValueError if trade-dataframe was found empty. :raise: ValueError if trade-dataframe was found empty.
""" """
@@ -380,3 +383,21 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date'
high_date = profit_results.loc[max_drawdown_df.iloc[:idxmin]['high_value'].idxmax(), date_col] high_date = profit_results.loc[max_drawdown_df.iloc[:idxmin]['high_value'].idxmax(), date_col]
low_date = profit_results.loc[idxmin, date_col] low_date = profit_results.loc[idxmin, date_col]
return abs(min(max_drawdown_df['drawdown'])), high_date, low_date return abs(min(max_drawdown_df['drawdown'])), high_date, low_date
def calculate_csum(trades: pd.DataFrame) -> Tuple[float, float]:
"""
Calculate min/max cumsum of trades, to show if the wallet/stake amount ratio is sane
:param trades: DataFrame containing trades (requires columns close_date and profit_percent)
:return: Tuple (float, float) with cumsum of profit_abs
:raise: ValueError if trade-dataframe was found empty.
"""
if len(trades) == 0:
raise ValueError("Trade dataframe empty.")
csum_df = pd.DataFrame()
csum_df['sum'] = trades['profit_abs'].cumsum()
csum_min = csum_df['sum'].min()
csum_max = csum_df['sum'].max()
return csum_min, csum_max

View File

@@ -86,8 +86,12 @@ class JsonDataHandler(IDataHandler):
filename = self._pair_data_filename(self._datadir, pair, timeframe) filename = self._pair_data_filename(self._datadir, pair, timeframe)
if not filename.exists(): if not filename.exists():
return DataFrame(columns=self._columns) return DataFrame(columns=self._columns)
pairdata = read_json(filename, orient='values') try:
pairdata.columns = self._columns pairdata = read_json(filename, orient='values')
pairdata.columns = self._columns
except ValueError:
logger.error(f"Could not load data for {pair}.")
return DataFrame(columns=self._columns)
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['date'] = to_datetime(pairdata['date'], pairdata['date'] = to_datetime(pairdata['date'],

View File

@@ -12,6 +12,7 @@ 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
from freqtrade.data.history import get_timerange, load_data, refresh_data from freqtrade.data.history import get_timerange, load_data, refresh_data
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.strategy.interface import SellType from freqtrade.strategy.interface import SellType
@@ -80,10 +81,12 @@ class Edge:
if config.get('fee'): if config.get('fee'):
self.fee = config['fee'] self.fee = config['fee']
else: else:
self.fee = self.exchange.get_fee(symbol=self.config['exchange']['pair_whitelist'][0]) self.fee = self.exchange.get_fee(symbol=expand_pairlist(
self.config['exchange']['pair_whitelist'], list(self.exchange.markets))[0])
def calculate(self) -> bool: def calculate(self) -> bool:
pairs = self.config['exchange']['pair_whitelist'] pairs = expand_pairlist(self.config['exchange']['pair_whitelist'],
list(self.exchange.markets))
heartbeat = self.edge_config.get('process_throttle_secs') heartbeat = self.edge_config.get('process_throttle_secs')
if (self._last_updated > 0) and ( if (self._last_updated > 0) and (
@@ -101,6 +104,7 @@ class Edge:
exchange=self.exchange, exchange=self.exchange,
timeframe=self.strategy.timeframe, timeframe=self.strategy.timeframe,
timerange=self._timerange, timerange=self._timerange,
data_format=self.config.get('dataformat_ohlcv', 'json'),
) )
data = load_data( data = load_data(
@@ -156,7 +160,8 @@ class Edge:
available_capital = (total_capital + capital_in_trade) * self._capital_ratio available_capital = (total_capital + capital_in_trade) * self._capital_ratio
allowed_capital_at_risk = available_capital * self._allowed_risk allowed_capital_at_risk = available_capital * self._allowed_risk
max_position_size = abs(allowed_capital_at_risk / stoploss) max_position_size = abs(allowed_capital_at_risk / stoploss)
position_size = min(max_position_size, free_capital) # Position size must be below available capital.
position_size = min(min(max_position_size, free_capital), available_capital)
if pair in self._cached_pairs: if pair in self._cached_pairs:
logger.info( logger.info(
'winrate: %s, expectancy: %s, position size: %s, pair: %s,' 'winrate: %s, expectancy: %s, position size: %s, pair: %s,'

View File

@@ -6,6 +6,7 @@ from freqtrade.exchange.exchange import Exchange
from freqtrade.exchange.bibox import Bibox from freqtrade.exchange.bibox import Bibox
from freqtrade.exchange.binance import Binance from freqtrade.exchange.binance import Binance
from freqtrade.exchange.bittrex import Bittrex from freqtrade.exchange.bittrex import Bittrex
from freqtrade.exchange.bybit import Bybit
from freqtrade.exchange.exchange import (available_exchanges, ccxt_exchanges, from freqtrade.exchange.exchange import (available_exchanges, ccxt_exchanges,
get_exchange_bad_reason, is_exchange_bad, get_exchange_bad_reason, is_exchange_bad,
is_exchange_known_ccxt, is_exchange_officially_supported, is_exchange_known_ccxt, is_exchange_officially_supported,

View File

@@ -18,6 +18,7 @@ class Binance(Exchange):
_ft_has: Dict = { _ft_has: Dict = {
"stoploss_on_exchange": True, "stoploss_on_exchange": True,
"order_time_in_force": ['gtc', 'fok', 'ioc'], "order_time_in_force": ['gtc', 'fok', 'ioc'],
"ohlcv_candle_limit": 1000,
"trades_pagination": "id", "trades_pagination": "id",
"trades_pagination_arg": "fromId", "trades_pagination_arg": "fromId",
"l2_limit_range": [5, 10, 20, 50, 100, 500, 1000], "l2_limit_range": [5, 10, 20, 50, 100, 500, 1000],

View File

@@ -19,5 +19,11 @@ class Bittrex(Exchange):
""" """
_ft_has: Dict = { _ft_has: Dict = {
"ohlcv_candle_limit_per_timeframe": {
'1m': 1440,
'5m': 288,
'1h': 744,
'1d': 365,
},
"l2_limit_range": [1, 25, 500], "l2_limit_range": [1, 25, 500],
} }

View File

@@ -0,0 +1,24 @@
""" Bybit exchange subclass """
import logging
from typing import Dict
from freqtrade.exchange import Exchange
logger = logging.getLogger(__name__)
class Bybit(Exchange):
"""
Bybit exchange class. Contains adjustments needed for Freqtrade to work
with this exchange.
Please note that this exchange is not included in the list of exchanges
officially supported by the Freqtrade development team. So some features
may still not work as expected.
"""
# fetchCurrencies API point requires authentication for Bybit,
_ft_has: Dict = {
"ohlcv_candle_limit": 200,
}

View File

@@ -21,6 +21,7 @@ BAD_EXCHANGES = {
"hitbtc": "This API cannot be used with Freqtrade. " "hitbtc": "This API cannot be used with Freqtrade. "
"Use `hitbtc2` exchange id to access this exchange.", "Use `hitbtc2` exchange id to access this exchange.",
"phemex": "Does not provide history. ", "phemex": "Does not provide history. ",
"poloniex": "Does not provide fetch_order endpoint to fetch both open and closed orders.",
**dict.fromkeys([ **dict.fromkeys([
'adara', 'adara',
'anxpro', 'anxpro',

View File

@@ -3,6 +3,7 @@
Cryptocurrency Exchanges support Cryptocurrency Exchanges support
""" """
import asyncio import asyncio
import http
import inspect import inspect
import logging import logging
from copy import deepcopy from copy import deepcopy
@@ -17,7 +18,7 @@ from ccxt.base.decimal_to_precision import (ROUND_DOWN, ROUND_UP, TICK_SIZE, TRU
decimal_to_precision) decimal_to_precision)
from pandas import DataFrame from pandas import DataFrame
from freqtrade.constants import ListPairsWithTimeframes from freqtrade.constants import DEFAULT_AMOUNT_RESERVE_PERCENT, ListPairsWithTimeframes
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.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError, from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFundsError,
InvalidOrderException, OperationalException, RetryableOrderError, InvalidOrderException, OperationalException, RetryableOrderError,
@@ -25,6 +26,7 @@ from freqtrade.exceptions import (DDosProtection, ExchangeError, InsufficientFun
from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, BAD_EXCHANGES, retrier, from freqtrade.exchange.common import (API_FETCH_ORDER_RETRY_COUNT, BAD_EXCHANGES, retrier,
retrier_async) retrier_async)
from freqtrade.misc import deep_merge_dicts, safe_value_fallback2 from freqtrade.misc import deep_merge_dicts, safe_value_fallback2
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
CcxtModuleType = Any CcxtModuleType = Any
@@ -33,6 +35,12 @@ CcxtModuleType = Any
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Workaround for adding samesite support to pre 3.8 python
# Only applies to python3.7, and only on certain exchanges (kraken)
# Replicates the fix from starlette (which is actually causing this problem)
http.cookies.Morsel._reserved["samesite"] = "SameSite" # type: ignore
class Exchange: class Exchange:
_config: Dict = {} _config: Dict = {}
@@ -65,6 +73,7 @@ class Exchange:
""" """
self._api: ccxt.Exchange = None self._api: ccxt.Exchange = None
self._api_async: ccxt_async.Exchange = None self._api_async: ccxt_async.Exchange = None
self._markets: Dict = {}
self._config.update(config) self._config.update(config)
@@ -92,7 +101,6 @@ class Exchange:
logger.info("Overriding exchange._ft_has with config params, result: %s", self._ft_has) logger.info("Overriding exchange._ft_has with config params, result: %s", self._ft_has)
# Assign this directly for easy access # Assign this directly for easy access
self._ohlcv_candle_limit = self._ft_has['ohlcv_candle_limit']
self._ohlcv_partial_candle = self._ft_has['ohlcv_partial_candle'] self._ohlcv_partial_candle = self._ft_has['ohlcv_partial_candle']
self._trades_pagination = self._ft_has['trades_pagination'] self._trades_pagination = self._ft_has['trades_pagination']
@@ -128,7 +136,8 @@ class Exchange:
self.validate_pairs(config['exchange']['pair_whitelist']) self.validate_pairs(config['exchange']['pair_whitelist'])
self.validate_ordertypes(config.get('order_types', {})) self.validate_ordertypes(config.get('order_types', {}))
self.validate_order_time_in_force(config.get('order_time_in_force', {})) self.validate_order_time_in_force(config.get('order_time_in_force', {}))
self.validate_required_startup_candles(config.get('startup_candle_count', 0)) self.validate_required_startup_candles(config.get('startup_candle_count', 0),
config.get('timeframe', ''))
# Converts the interval provided in minutes in config to seconds # Converts the interval provided in minutes in config to seconds
self.markets_refresh_interval: int = exchange_config.get( self.markets_refresh_interval: int = exchange_config.get(
@@ -189,26 +198,32 @@ class Exchange:
def timeframes(self) -> List[str]: def timeframes(self) -> List[str]:
return list((self._api.timeframes or {}).keys()) return list((self._api.timeframes or {}).keys())
@property
def ohlcv_candle_limit(self) -> int:
"""exchange ohlcv candle limit"""
return int(self._ohlcv_candle_limit)
@property @property
def markets(self) -> Dict: def markets(self) -> Dict:
"""exchange ccxt markets""" """exchange ccxt markets"""
if not self._api.markets: if not self._markets:
logger.info("Markets were not loaded. Loading them now..") logger.info("Markets were not loaded. Loading them now..")
self._load_markets() self._load_markets()
return self._api.markets return self._markets
@property @property
def precisionMode(self) -> str: def precisionMode(self) -> str:
"""exchange ccxt precisionMode""" """exchange ccxt precisionMode"""
return self._api.precisionMode return self._api.precisionMode
def ohlcv_candle_limit(self, timeframe: str) -> int:
"""
Exchange ohlcv candle limit
Uses ohlcv_candle_limit_per_timeframe if the exchange has different limts
per timeframe (e.g. bittrex), otherwise falls back to ohlcv_candle_limit
:param timeframe: Timeframe to check
:return: Candle limit as integer
"""
return int(self._ft_has.get('ohlcv_candle_limit_per_timeframe', {}).get(
timeframe, self._ft_has.get('ohlcv_candle_limit')))
def get_markets(self, base_currencies: List[str] = None, quote_currencies: List[str] = None, def get_markets(self, base_currencies: List[str] = None, quote_currencies: List[str] = None,
pairs_only: bool = False, active_only: bool = False) -> Dict: pairs_only: bool = False, active_only: bool = False) -> Dict[str, Any]:
""" """
Return exchange ccxt markets, filtered out by base currency and quote currency Return exchange ccxt markets, filtered out by base currency and quote currency
if this was requested in parameters. if this was requested in parameters.
@@ -290,7 +305,7 @@ class Exchange:
def _load_markets(self) -> None: def _load_markets(self) -> None:
""" Initialize markets both sync and async """ """ Initialize markets both sync and async """
try: try:
self._api.load_markets() self._markets = self._api.load_markets()
self._load_async_markets() self._load_async_markets()
self._last_markets_refresh = arrow.utcnow().int_timestamp self._last_markets_refresh = arrow.utcnow().int_timestamp
except ccxt.BaseError as e: except ccxt.BaseError as e:
@@ -305,7 +320,7 @@ class Exchange:
return None return None
logger.debug("Performing scheduled market reload..") logger.debug("Performing scheduled market reload..")
try: try:
self._api.load_markets(reload=True) self._markets = self._api.load_markets(reload=True)
# Also reload async markets to avoid issues with newly listed pairs # Also reload async markets to avoid issues with newly listed pairs
self._load_async_markets(reload=True) self._load_async_markets(reload=True)
self._last_markets_refresh = arrow.utcnow().int_timestamp self._last_markets_refresh = arrow.utcnow().int_timestamp
@@ -335,8 +350,9 @@ class Exchange:
if not self.markets: if not self.markets:
logger.warning('Unable to validate pairs (assuming they are correct).') logger.warning('Unable to validate pairs (assuming they are correct).')
return return
extended_pairs = expand_pairlist(pairs, list(self.markets), keep_invalid=True)
invalid_pairs = [] invalid_pairs = []
for pair in pairs: for pair in extended_pairs:
# Note: ccxt has BaseCurrency/QuoteCurrency format for pairs # Note: ccxt has BaseCurrency/QuoteCurrency format for pairs
# TODO: add a support for having coins in BTC/USDT format # TODO: add a support for having coins in BTC/USDT format
if self.markets and pair not in self.markets: if self.markets and pair not in self.markets:
@@ -418,15 +434,16 @@ class Exchange:
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.')
def validate_required_startup_candles(self, startup_candles: int) -> None: def validate_required_startup_candles(self, startup_candles: int, timeframe: str) -> None:
""" """
Checks if required startup_candles is more than ohlcv_candle_limit. Checks if required startup_candles is more than ohlcv_candle_limit().
Requires a grace-period of 5 candles - so a startup-period up to 494 is allowed by default. Requires a grace-period of 5 candles - so a startup-period up to 494 is allowed by default.
""" """
if startup_candles + 5 > self._ft_has['ohlcv_candle_limit']: candle_limit = self.ohlcv_candle_limit(timeframe)
if startup_candles + 5 > candle_limit:
raise OperationalException( raise OperationalException(
f"This strategy requires {startup_candles} candles to start. " f"This strategy requires {startup_candles} candles to start. "
f"{self.name} only provides {self._ft_has['ohlcv_candle_limit']}.") f"{self.name} only provides {candle_limit} for {timeframe}.")
def exchange_has(self, endpoint: str) -> bool: def exchange_has(self, endpoint: str) -> bool:
""" """
@@ -487,6 +504,41 @@ class Exchange:
else: else:
return 1 / pow(10, precision) return 1 / pow(10, precision)
def get_min_pair_stake_amount(self, pair: str, price: float,
stoploss: float) -> Optional[float]:
try:
market = self.markets[pair]
except KeyError:
raise ValueError(f"Can't get market information for symbol {pair}")
if 'limits' not in market:
return None
min_stake_amounts = []
limits = market['limits']
if ('cost' in limits and 'min' in limits['cost']
and limits['cost']['min'] is not None):
min_stake_amounts.append(limits['cost']['min'])
if ('amount' in limits and 'min' in limits['amount']
and limits['amount']['min'] is not None):
min_stake_amounts.append(limits['amount']['min'] * price)
if not min_stake_amounts:
return None
# reserve some percent defined in config (5% default) + stoploss
amount_reserve_percent = 1.0 - self._config.get('amount_reserve_percent',
DEFAULT_AMOUNT_RESERVE_PERCENT)
amount_reserve_percent += stoploss
# it should not be more than 50%
amount_reserve_percent = max(amount_reserve_percent, 0.5)
# The value returned should satisfy both limits: for amount (base currency) and
# for cost (quote, stake currency), so max() is used here.
# See also #2575 at github.
return max(min_stake_amounts) / amount_reserve_percent
def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float, def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float,
rate: float, params: Dict = {}) -> Dict[str, Any]: rate: float, params: Dict = {}) -> Dict[str, Any]:
order_id = f'dry_run_{side}_{datetime.now().timestamp()}' order_id = f'dry_run_{side}_{datetime.now().timestamp()}'
@@ -658,7 +710,8 @@ class Exchange:
@retrier @retrier
def fetch_ticker(self, pair: str) -> dict: def fetch_ticker(self, pair: str) -> dict:
try: try:
if pair not in self._api.markets or not self._api.markets[pair].get('active'): if (pair not in self.markets or
self.markets[pair].get('active', False) is False):
raise ExchangeError(f"Pair {pair} not available") raise ExchangeError(f"Pair {pair} not available")
data = self._api.fetch_ticker(pair) data = self._api.fetch_ticker(pair)
return data return data
@@ -675,7 +728,7 @@ class Exchange:
""" """
Get candle history using asyncio and returns the list of candles. Get candle history using asyncio and returns the list of candles.
Handles all async work for this. Handles all async work for this.
Async over one pair, assuming we get `self._ohlcv_candle_limit` candles per call. Async over one pair, assuming we get `self.ohlcv_candle_limit()` candles per call.
:param pair: Pair to download :param pair: Pair to download
:param timeframe: Timeframe to get data for :param timeframe: Timeframe to get data for
:param since_ms: Timestamp in milliseconds to get history from :param since_ms: Timestamp in milliseconds to get history from
@@ -705,7 +758,7 @@ class Exchange:
Download historic ohlcv Download historic ohlcv
""" """
one_call = timeframe_to_msecs(timeframe) * self._ohlcv_candle_limit one_call = timeframe_to_msecs(timeframe) * self.ohlcv_candle_limit(timeframe)
logger.debug( logger.debug(
"one_call: %s msecs (%s)", "one_call: %s msecs (%s)",
one_call, one_call,
@@ -732,13 +785,17 @@ class Exchange:
logger.info("Downloaded data for %s with length %s.", pair, len(data)) logger.info("Downloaded data for %s with length %s.", pair, len(data))
return data return data
def refresh_latest_ohlcv(self, pair_list: ListPairsWithTimeframes) -> List[Tuple[str, List]]: def refresh_latest_ohlcv(self, pair_list: ListPairsWithTimeframes, *,
since_ms: Optional[int] = None, cache: bool = True
) -> Dict[Tuple[str, str], DataFrame]:
""" """
Refresh in-memory OHLCV asynchronously and set `_klines` with the result Refresh in-memory OHLCV asynchronously and set `_klines` with the result
Loops asynchronously over pair_list and downloads all pairs async (semi-parallel). Loops asynchronously over pair_list and downloads all pairs async (semi-parallel).
Only used in the dataprovider.refresh() method. Only used in the dataprovider.refresh() method.
:param pair_list: List of 2 element tuples containing pair, interval to refresh :param pair_list: List of 2 element tuples containing pair, interval to refresh
:return: TODO: return value is only used in the tests, get rid of it :param since_ms: time since when to download, in milliseconds
:param cache: Assign result to _klines. Usefull for one-off downloads like for pairlists
:return: Dict of [{(pair, timeframe): Dataframe}]
""" """
logger.debug("Refreshing candle (OHLCV) data for %d pairs", len(pair_list)) logger.debug("Refreshing candle (OHLCV) data for %d pairs", len(pair_list))
@@ -748,7 +805,8 @@ class Exchange:
for pair, timeframe in set(pair_list): for pair, timeframe in set(pair_list):
if (not ((pair, timeframe) in self._klines) if (not ((pair, timeframe) in self._klines)
or self._now_is_time_to_refresh(pair, timeframe)): or self._now_is_time_to_refresh(pair, timeframe)):
input_coroutines.append(self._async_get_candle_history(pair, timeframe)) input_coroutines.append(self._async_get_candle_history(pair, timeframe,
since_ms=since_ms))
else: else:
logger.debug( logger.debug(
"Using cached candle (OHLCV) data for pair %s, timeframe %s ...", "Using cached candle (OHLCV) data for pair %s, timeframe %s ...",
@@ -758,6 +816,7 @@ class Exchange:
results = asyncio.get_event_loop().run_until_complete( results = asyncio.get_event_loop().run_until_complete(
asyncio.gather(*input_coroutines, return_exceptions=True)) asyncio.gather(*input_coroutines, return_exceptions=True))
results_df = {}
# handle caching # handle caching
for res in results: for res in results:
if isinstance(res, Exception): if isinstance(res, Exception):
@@ -769,11 +828,13 @@ class Exchange:
if ticks: if ticks:
self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000 self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000
# keeping parsed dataframe in cache # keeping parsed dataframe in cache
self._klines[(pair, timeframe)] = ohlcv_to_dataframe( ohlcv_df = ohlcv_to_dataframe(
ticks, timeframe, pair=pair, fill_missing=True, ticks, timeframe, pair=pair, fill_missing=True,
drop_incomplete=self._ohlcv_partial_candle) drop_incomplete=self._ohlcv_partial_candle)
results_df[(pair, timeframe)] = ohlcv_df
return results if cache:
self._klines[(pair, timeframe)] = ohlcv_df
return results_df
def _now_is_time_to_refresh(self, pair: str, timeframe: str) -> bool: def _now_is_time_to_refresh(self, pair: str, timeframe: str) -> bool:
# Timeframe in seconds # Timeframe in seconds
@@ -798,7 +859,8 @@ class Exchange:
) )
data = await self._api_async.fetch_ohlcv(pair, timeframe=timeframe, data = await self._api_async.fetch_ohlcv(pair, timeframe=timeframe,
since=since_ms) since=since_ms,
limit=self.ohlcv_candle_limit(timeframe))
# Some exchanges sort OHLCV in ASC order and others in DESC. # Some exchanges sort OHLCV in ASC order and others in DESC.
# Ex: Bittrex returns the list of OHLCV in ASC order (oldest first, newest last) # Ex: Bittrex returns the list of OHLCV in ASC order (oldest first, newest last)
@@ -926,7 +988,7 @@ class Exchange:
while True: while True:
t = await self._async_fetch_trades(pair, since=since) t = await self._async_fetch_trades(pair, since=since)
if len(t): if len(t):
since = t[-1][1] since = t[-1][0]
trades.extend(t) trades.extend(t)
# Reached the end of the defined-download period # Reached the end of the defined-download period
if until and t[-1][0] > until: if until and t[-1][0] > until:
@@ -971,7 +1033,7 @@ class Exchange:
""" """
Get trade history data using asyncio. Get trade history data using asyncio.
Handles all async work and returns the list of candles. Handles all async work and returns the list of candles.
Async over one pair, assuming we get `self._ohlcv_candle_limit` candles per call. Async over one pair, assuming we get `self.ohlcv_candle_limit()` candles per call.
:param pair: Pair to download :param pair: Pair to download
:param since: Timestamp in milliseconds to get history from :param since: Timestamp in milliseconds to get history from
:param until: Timestamp in milliseconds. Defaults to current timestamp if not defined. :param until: Timestamp in milliseconds. Defaults to current timestamp if not defined.

View File

@@ -18,6 +18,7 @@ class Kraken(Exchange):
_params: Dict = {"trading_agreement": "agree"} _params: Dict = {"trading_agreement": "agree"}
_ft_has: Dict = { _ft_has: Dict = {
"stoploss_on_exchange": True, "stoploss_on_exchange": True,
"ohlcv_candle_limit": 720,
"trades_pagination": "id", "trades_pagination": "id",
"trades_pagination_arg": "since", "trades_pagination_arg": "since",
} }
@@ -47,7 +48,7 @@ class Kraken(Exchange):
orders = self._api.fetch_open_orders() orders = self._api.fetch_open_orders()
order_list = [(x["symbol"].split("/")[0 if x["side"] == "sell" else 1], order_list = [(x["symbol"].split("/")[0 if x["side"] == "sell" else 1],
x["remaining"], x["remaining"] if x["side"] == "sell" else x["remaining"] * x["price"],
# Don't remove the below comment, this can be important for debuggung # Don't remove the below comment, this can be important for debuggung
# x["side"], x["amount"], # x["side"], x["amount"],
) for x in orders] ) for x in orders]

View File

@@ -19,10 +19,12 @@ from freqtrade.data.dataprovider import DataProvider
from freqtrade.edge import Edge from freqtrade.edge import Edge
from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError, from freqtrade.exceptions import (DependencyException, ExchangeError, InsufficientFundsError,
InvalidOrderException, PricingError) InvalidOrderException, PricingError)
from freqtrade.exchange import timeframe_to_minutes from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
from freqtrade.misc import safe_value_fallback, safe_value_fallback2 from freqtrade.misc import safe_value_fallback, safe_value_fallback2
from freqtrade.pairlist.pairlistmanager import PairListManager from freqtrade.mixins import LoggingMixin
from freqtrade.persistence import Order, PairLocks, Trade, cleanup_db, init_db from freqtrade.persistence import Order, PairLocks, Trade, cleanup_db, init_db
from freqtrade.plugins.pairlistmanager import PairListManager
from freqtrade.plugins.protectionmanager import ProtectionManager
from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.rpc import RPCManager, RPCMessageType from freqtrade.rpc import RPCManager, RPCMessageType
from freqtrade.state import State from freqtrade.state import State
@@ -34,7 +36,7 @@ from freqtrade.wallets import Wallets
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class FreqtradeBot: class FreqtradeBot(LoggingMixin):
""" """
Freqtrade is the main class of the bot. Freqtrade is the main class of the bot.
This is from here the bot start its logic. This is from here the bot start its logic.
@@ -78,6 +80,8 @@ class FreqtradeBot:
self.dataprovider = DataProvider(self.config, self.exchange, self.pairlists) self.dataprovider = DataProvider(self.config, self.exchange, self.pairlists)
self.protections = ProtectionManager(self.config)
# Attach Dataprovider to Strategy baseclass # Attach Dataprovider to Strategy baseclass
IStrategy.dp = self.dataprovider IStrategy.dp = self.dataprovider
# Attach Wallets to Strategy baseclass # Attach Wallets to Strategy baseclass
@@ -101,6 +105,7 @@ class FreqtradeBot:
self.rpc: RPCManager = RPCManager(self) self.rpc: RPCManager = RPCManager(self)
# Protect sell-logic from forcesell and viceversa # Protect sell-logic from forcesell and viceversa
self._sell_lock = Lock() self._sell_lock = Lock()
LoggingMixin.__init__(self, logger, timeframe_to_seconds(self.strategy.timeframe))
def notify_status(self, msg: str) -> None: def notify_status(self, msg: str) -> None:
""" """
@@ -132,7 +137,7 @@ class FreqtradeBot:
Called on startup and after reloading the bot - triggers notifications and Called on startup and after reloading the bot - triggers notifications and
performs startup tasks performs startup tasks
""" """
self.rpc.startup_messages(self.config, self.pairlists) self.rpc.startup_messages(self.config, self.pairlists, self.protections)
if not self.edge: if not self.edge:
# Adjust stoploss if it was changed # Adjust stoploss if it was changed
Trade.stoploss_reinitialization(self.strategy.stoploss) Trade.stoploss_reinitialization(self.strategy.stoploss)
@@ -174,6 +179,7 @@ class FreqtradeBot:
# Without this, freqtrade my try to recreate stoploss_on_exchange orders # Without this, freqtrade my try to recreate stoploss_on_exchange orders
# while selling is in process, since telegram messages arrive in an different thread. # while selling is in process, since telegram messages arrive in an different thread.
with self._sell_lock: with self._sell_lock:
trades = Trade.get_open_trades()
# First process current opened trades (positions) # First process current opened trades (positions)
self.exit_positions(trades) self.exit_positions(trades)
@@ -195,7 +201,7 @@ class FreqtradeBot:
Notify the user when the bot is stopped Notify the user when the bot is stopped
and there are still open trades active. and there are still open trades active.
""" """
open_trades = Trade.get_trades([Trade.is_open == 1]).all() open_trades = Trade.get_trades([Trade.is_open.is_(True)]).all()
if len(open_trades) != 0: if len(open_trades) != 0:
msg = { msg = {
@@ -228,7 +234,7 @@ class FreqtradeBot:
_whitelist.extend([trade.pair for trade in trades if trade.pair not in _whitelist]) _whitelist.extend([trade.pair for trade in trades if trade.pair not in _whitelist])
return _whitelist return _whitelist
def get_free_open_trades(self): def get_free_open_trades(self) -> int:
""" """
Return the number of free open trades slots or 0 if Return the number of free open trades slots or 0 if
max number of open trades reached max number of open trades reached
@@ -241,6 +247,10 @@ class FreqtradeBot:
Updates open orders based on order list kept in the database. Updates open orders based on order list kept in the database.
Mainly updates the state of orders - but may also close trades Mainly updates the state of orders - but may also close trades
""" """
if self.config['dry_run'] or self.config['exchange'].get('skip_open_order_update', False):
# Updating open orders in dry-run does not make sense and will fail.
return
orders = Order.get_open_orders() orders = Order.get_open_orders()
logger.info(f"Updating {len(orders)} open orders.") logger.info(f"Updating {len(orders)} open orders.")
for order in orders: for order in orders:
@@ -251,6 +261,7 @@ class FreqtradeBot:
self.update_trade_state(order.trade, order.order_id, fo) self.update_trade_state(order.trade, order.order_id, fo)
except ExchangeError as e: except ExchangeError as e:
logger.warning(f"Error updating Order {order.order_id} due to {e}") logger.warning(f"Error updating Order {order.order_id} due to {e}")
def update_closed_trades_without_assigned_fees(self): def update_closed_trades_without_assigned_fees(self):
@@ -258,6 +269,10 @@ class FreqtradeBot:
Update closed trades without close fees assigned. Update closed trades without close fees assigned.
Only acts when Orders are in the database, otherwise the last orderid is unknown. Only acts when Orders are in the database, otherwise the last orderid is unknown.
""" """
if self.config['dry_run']:
# Updating open orders in dry-run does not make sense and will fail.
return
trades: List[Trade] = Trade.get_sold_trades_without_assigned_fees() trades: List[Trade] = Trade.get_sold_trades_without_assigned_fees()
for trade in trades: for trade in trades:
@@ -358,6 +373,15 @@ class FreqtradeBot:
logger.info("No currency pair in active pair whitelist, " logger.info("No currency pair in active pair whitelist, "
"but checking to sell open trades.") "but checking to sell open trades.")
return trades_created return trades_created
if PairLocks.is_global_lock():
lock = PairLocks.get_pair_longest_lock('*')
if lock:
self.log_once(f"Global pairlock active until "
f"{lock.lock_end_time.strftime(constants.DATETIME_PRINT_FORMAT)}. "
"Not creating new trades.", logger.info)
else:
self.log_once("Global pairlock active. Not creating new trades.", logger.info)
return trades_created
# Create entity and execute trade for each pair from whitelist # Create entity and execute trade for each pair from whitelist
for pair in whitelist: for pair in whitelist:
try: try:
@@ -366,8 +390,7 @@ class FreqtradeBot:
logger.warning('Unable to create trade for %s: %s', pair, exception) logger.warning('Unable to create trade for %s: %s', pair, exception)
if not trades_created: if not trades_created:
logger.debug("Found no buy signals for whitelisted currencies. " logger.debug("Found no buy signals for whitelisted currencies. Trying again...")
"Trying again...")
return trades_created return trades_created
@@ -417,118 +440,6 @@ class FreqtradeBot:
return used_rate return used_rate
def get_trade_stake_amount(self, pair: str) -> float:
"""
Calculate stake amount for the trade
:return: float: Stake amount
:raise: DependencyException if the available stake amount is too low
"""
stake_amount: float
# Ensure wallets are uptodate.
self.wallets.update()
if self.edge:
stake_amount = self.edge.stake_amount(
pair,
self.wallets.get_free(self.config['stake_currency']),
self.wallets.get_total(self.config['stake_currency']),
Trade.total_open_trades_stakes()
)
else:
stake_amount = self.config['stake_amount']
if stake_amount == constants.UNLIMITED_STAKE_AMOUNT:
stake_amount = self._calculate_unlimited_stake_amount()
return self._check_available_stake_amount(stake_amount)
def _get_available_stake_amount(self) -> float:
"""
Return the total currently available balance in stake currency,
respecting tradable_balance_ratio.
Calculated as
<open_trade stakes> + free amount ) * tradable_balance_ratio - <open_trade stakes>
"""
val_tied_up = Trade.total_open_trades_stakes()
# Ensure <tradable_balance_ratio>% is used from the overall balance
# Otherwise we'd risk lowering stakes with each open trade.
# (tied up + current free) * ratio) - tied up
available_amount = ((val_tied_up + self.wallets.get_free(self.config['stake_currency'])) *
self.config['tradable_balance_ratio']) - val_tied_up
return available_amount
def _calculate_unlimited_stake_amount(self) -> float:
"""
Calculate stake amount for "unlimited" stake amount
:return: 0 if max number of trades reached, else stake_amount to use.
"""
free_open_trades = self.get_free_open_trades()
if not free_open_trades:
return 0
available_amount = self._get_available_stake_amount()
return available_amount / free_open_trades
def _check_available_stake_amount(self, stake_amount: float) -> float:
"""
Check if stake amount can be fulfilled with the available balance
for the stake currency
:return: float: Stake amount
"""
available_amount = self._get_available_stake_amount()
if self.config['amend_last_stake_amount']:
# Remaining amount needs to be at least stake_amount * last_stake_amount_min_ratio
# Otherwise the remaining amount is too low to trade.
if available_amount > (stake_amount * self.config['last_stake_amount_min_ratio']):
stake_amount = min(stake_amount, available_amount)
else:
stake_amount = 0
if available_amount < stake_amount:
raise DependencyException(
f"Available balance ({available_amount} {self.config['stake_currency']}) is "
f"lower than stake amount ({stake_amount} {self.config['stake_currency']})"
)
return stake_amount
def _get_min_pair_stake_amount(self, pair: str, price: float) -> Optional[float]:
try:
market = self.exchange.markets[pair]
except KeyError:
raise ValueError(f"Can't get market information for symbol {pair}")
if 'limits' not in market:
return None
min_stake_amounts = []
limits = market['limits']
if ('cost' in limits and 'min' in limits['cost']
and limits['cost']['min'] is not None):
min_stake_amounts.append(limits['cost']['min'])
if ('amount' in limits and 'min' in limits['amount']
and limits['amount']['min'] is not None):
min_stake_amounts.append(limits['amount']['min'] * price)
if not min_stake_amounts:
return None
# reserve some percent defined in config (5% default) + stoploss
amount_reserve_percent = 1.0 - self.config.get('amount_reserve_percent',
constants.DEFAULT_AMOUNT_RESERVE_PERCENT)
if self.strategy.stoploss is not None:
amount_reserve_percent += self.strategy.stoploss
# it should not be more than 50%
amount_reserve_percent = max(amount_reserve_percent, 0.5)
# The value returned should satisfy both limits: for amount (base currency) and
# for cost (quote, stake currency), so max() is used here.
# See also #2575 at github.
return max(min_stake_amounts) / amount_reserve_percent
def create_trade(self, pair: str) -> bool: def create_trade(self, pair: str) -> bool:
""" """
Check the implemented trading strategy for buy signals. Check the implemented trading strategy for buy signals.
@@ -541,9 +452,15 @@ class FreqtradeBot:
logger.debug(f"create_trade for pair {pair}") logger.debug(f"create_trade for pair {pair}")
analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(pair, self.strategy.timeframe) analyzed_df, _ = self.dataprovider.get_analyzed_dataframe(pair, self.strategy.timeframe)
if self.strategy.is_pair_locked( nowtime = analyzed_df.iloc[-1]['date'] if len(analyzed_df) > 0 else None
pair, analyzed_df.iloc[-1]['date'] if len(analyzed_df) > 0 else None): if self.strategy.is_pair_locked(pair, nowtime):
logger.info(f"Pair {pair} is currently locked.") lock = PairLocks.get_pair_longest_lock(pair, nowtime)
if lock:
self.log_once(f"Pair {pair} is still locked until "
f"{lock.lock_end_time.strftime(constants.DATETIME_PRINT_FORMAT)}.",
logger.info)
else:
self.log_once(f"Pair {pair} is still locked.", logger.info)
return False return False
# get_free_open_trades is checked before create_trade is called # get_free_open_trades is checked before create_trade is called
@@ -556,7 +473,8 @@ class FreqtradeBot:
(buy, sell) = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df) (buy, sell) = self.strategy.get_signal(pair, self.strategy.timeframe, analyzed_df)
if buy and not sell: if buy and not sell:
stake_amount = self.get_trade_stake_amount(pair) stake_amount = self.wallets.get_trade_stake_amount(pair, self.get_free_open_trades(),
self.edge)
if not stake_amount: if not stake_amount:
logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.") logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.")
return False return False
@@ -616,7 +534,11 @@ class FreqtradeBot:
# Calculate price # Calculate price
buy_limit_requested = self.get_buy_rate(pair, True) buy_limit_requested = self.get_buy_rate(pair, True)
min_stake_amount = self._get_min_pair_stake_amount(pair, buy_limit_requested) if not buy_limit_requested:
raise PricingError('Could not determine buy price.')
min_stake_amount = self.exchange.get_min_pair_stake_amount(pair, buy_limit_requested,
self.strategy.stoploss)
if min_stake_amount is not None and min_stake_amount > stake_amount: if min_stake_amount is not None and min_stake_amount > stake_amount:
logger.warning( logger.warning(
f"Can't open a new trade for {pair}: stake amount " f"Can't open a new trade for {pair}: stake amount "
@@ -968,7 +890,8 @@ class FreqtradeBot:
logger.warning('Stoploss order was cancelled, but unable to recreate one.') logger.warning('Stoploss order was cancelled, but unable to recreate one.')
# Finally we check if stoploss on exchange should be moved up because of trailing. # Finally we check if stoploss on exchange should be moved up because of trailing.
if stoploss_order and self.config.get('trailing_stop', False): if stoploss_order and (self.config.get('trailing_stop', False)
or self.config.get('use_custom_stoploss', False)):
# if trailing stoploss is enabled we check if stoploss value has changed # if trailing stoploss is enabled we check if stoploss value has changed
# in which case we cancel stoploss order and put another one with new # in which case we cancel stoploss order and put another one with new
# value immediately # value immediately
@@ -1148,7 +1071,9 @@ class FreqtradeBot:
if not self.exchange.check_order_canceled_empty(order): if not self.exchange.check_order_canceled_empty(order):
try: try:
# if trade is not partially completed, just delete the order # if trade is not partially completed, just delete the order
self.exchange.cancel_order(trade.open_order_id, trade.pair) co = self.exchange.cancel_order_with_result(trade.open_order_id, trade.pair,
trade.amount)
trade.update_order(co)
except InvalidOrderException: except InvalidOrderException:
logger.exception(f"Could not cancel sell order {trade.open_order_id}") logger.exception(f"Could not cancel sell order {trade.open_order_id}")
return 'error cancelling order' return 'error cancelling order'
@@ -1156,6 +1081,7 @@ class FreqtradeBot:
else: else:
reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE'] reason = constants.CANCEL_REASON['CANCELLED_ON_EXCHANGE']
logger.info('Sell order %s for %s.', reason, trade) logger.info('Sell order %s for %s.', reason, trade)
trade.update_order(order)
trade.close_rate = None trade.close_rate = None
trade.close_rate_requested = None trade.close_rate_requested = None
@@ -1258,6 +1184,7 @@ class FreqtradeBot:
trade.orders.append(order_obj) trade.orders.append(order_obj)
trade.open_order_id = order['id'] trade.open_order_id = order['id']
trade.sell_order_status = ''
trade.close_rate_requested = limit trade.close_rate_requested = limit
trade.sell_reason = sell_reason.value trade.sell_reason = sell_reason.value
# In case of market sell orders the order can be closed immediately # In case of market sell orders the order can be closed immediately
@@ -1393,7 +1320,7 @@ class FreqtradeBot:
abs_tol=constants.MATH_CLOSE_PREC): abs_tol=constants.MATH_CLOSE_PREC):
order['amount'] = new_amount order['amount'] = new_amount
order.pop('filled', None) order.pop('filled', None)
trade.recalc_open_trade_price() trade.recalc_open_trade_value()
except DependencyException as exception: except DependencyException as exception:
logger.warning("Could not update trade amount: %s", exception) logger.warning("Could not update trade amount: %s", exception)
@@ -1405,6 +1332,8 @@ class FreqtradeBot:
# Updating wallets when order is closed # Updating wallets when order is closed
if not trade.is_open: if not trade.is_open:
self.protections.stop_per_pair(trade.pair)
self.protections.global_stop()
self.wallets.update() self.wallets.update()
return False return False
@@ -1446,13 +1375,16 @@ class FreqtradeBot:
fee_cost, fee_currency, fee_rate = self.exchange.extract_cost_curr_rate(order) fee_cost, fee_currency, fee_rate = self.exchange.extract_cost_curr_rate(order)
logger.info(f"Fee for Trade {trade} [{order.get('side')}]: " logger.info(f"Fee for Trade {trade} [{order.get('side')}]: "
f"{fee_cost:.8g} {fee_currency} - rate: {fee_rate}") f"{fee_cost:.8g} {fee_currency} - rate: {fee_rate}")
if fee_rate is None or fee_rate < 0.02:
trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', '')) # Reject all fees that report as > 2%.
if trade_base_currency == fee_currency: # These are most likely caused by a parsing bug in ccxt
# Apply fee to amount # due to multiple trades (https://github.com/ccxt/ccxt/issues/8025)
return self.apply_fee_conditional(trade, trade_base_currency, trade.update_fee(fee_cost, fee_currency, fee_rate, order.get('side', ''))
amount=order_amount, fee_abs=fee_cost) if trade_base_currency == fee_currency:
return order_amount # Apply fee to amount
return self.apply_fee_conditional(trade, trade_base_currency,
amount=order_amount, fee_abs=fee_cost)
return order_amount
return self.fee_detection_from_trades(trade, order, order_amount) return self.fee_detection_from_trades(trade, order, order_amount)
def fee_detection_from_trades(self, trade: Trade, order: Dict, order_amount: float) -> float: def fee_detection_from_trades(self, trade: Trade, order: Dict, order_amount: float) -> float:

View File

@@ -9,8 +9,8 @@ from typing import Any, List
# check min. python version # check min. python version
if sys.version_info < (3, 6): if sys.version_info < (3, 7):
sys.exit("Freqtrade requires Python version >= 3.6") sys.exit("Freqtrade requires Python version >= 3.7")
from freqtrade.commands import Arguments from freqtrade.commands import Arguments
from freqtrade.exceptions import FreqtradeException, OperationalException from freqtrade.exceptions import FreqtradeException, OperationalException

View File

@@ -9,13 +9,37 @@ from pathlib import Path
from typing import Any from typing import Any
from typing.io import IO from typing.io import IO
import numpy as np
import rapidjson import rapidjson
from freqtrade.constants import DECIMAL_PER_COIN_FALLBACK, DECIMALS_PER_COIN
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
def decimals_per_coin(coin: str):
"""
Helper method getting decimal amount for this coin
example usage: f".{decimals_per_coin('USD')}f"
:param coin: Which coin are we printing the price / value for
"""
return DECIMALS_PER_COIN.get(coin, DECIMAL_PER_COIN_FALLBACK)
def round_coin_value(value: float, coin: str, show_coin_name=True) -> str:
"""
Get price value for this coin
:param value: Value to be printed
:param coin: Which coin are we printing the price / value for
:param show_coin_name: Return string in format: "222.22 USDT" or "222.22"
:return: Formatted / rounded value (with or without coin name)
"""
if show_coin_name:
return f"{value:.{decimals_per_coin(coin)}f} {coin}"
else:
return f"{value:.{decimals_per_coin(coin)}f}"
def shorten_date(_date: str) -> str: def shorten_date(_date: str) -> str:
""" """
Trim the date so it fits on small screens Trim the date so it fits on small screens
@@ -28,20 +52,6 @@ def shorten_date(_date: str) -> str:
return new_date return new_date
############################################
# Used by scripts #
# Matplotlib doesn't support ::datetime64, #
# so we need to convert it into ::datetime #
############################################
def datesarray_to_datetimearray(dates: np.ndarray) -> np.ndarray:
"""
Convert an pandas-array of timestamps into
An numpy-array of datetimes
:return: numpy-array of datetime
"""
return dates.dt.to_pydatetime()
def file_dump_json(filename: Path, data: Any, is_zip: bool = False, log: bool = True) -> None: def file_dump_json(filename: Path, data: Any, is_zip: bool = False, log: bool = True) -> None:
""" """
Dump JSON data into a file Dump JSON data into a file

View File

@@ -0,0 +1,2 @@
# flake8: noqa: F401
from freqtrade.mixins.logging_mixin import LoggingMixin

View File

@@ -0,0 +1,38 @@
from typing import Callable
from cachetools import TTLCache, cached
class LoggingMixin():
"""
Logging Mixin
Shows similar messages only once every `refresh_period`.
"""
# Disable output completely
show_output = True
def __init__(self, logger, refresh_period: int = 3600):
"""
:param refresh_period: in seconds - Show identical messages in this intervals
"""
self.logger = logger
self.refresh_period = refresh_period
self._log_cache: TTLCache = TTLCache(maxsize=1024, ttl=self.refresh_period)
def log_once(self, message: str, logmethod: Callable) -> None:
"""
Logs message - not more often than "refresh_period" to avoid log spamming
Logs the log-message as debug as well to simplify debugging.
:param message: String containing the message to be sent to the function.
:param logmethod: Function that'll be called. Most likely `logger.info`.
:return: None.
"""
@cached(cache=self._log_cache)
def _log_once(message: str):
logmethod(message)
# Log as debug first
self.logger.debug(message)
# Call hidden function.
if self.show_output:
_log_once(message)

View File

@@ -6,24 +6,28 @@ This module contains the backtesting logic
import logging import logging
from collections import defaultdict from collections import defaultdict
from copy import deepcopy from copy import deepcopy
from datetime import datetime, timedelta from datetime import datetime, timedelta, timezone
from typing import Any, Dict, List, NamedTuple, Optional, Tuple from typing import Any, Dict, List, Optional, Tuple
from pandas import DataFrame from pandas import DataFrame
from freqtrade.configuration import TimeRange, remove_credentials, validate_config_consistency from freqtrade.configuration import TimeRange, remove_credentials, validate_config_consistency
from freqtrade.constants import DATETIME_PRINT_FORMAT from freqtrade.constants import DATETIME_PRINT_FORMAT
from freqtrade.data import history from freqtrade.data import history
from freqtrade.data.btanalysis import trade_list_to_dataframe
from freqtrade.data.converter import trim_dataframe from freqtrade.data.converter import trim_dataframe
from freqtrade.data.dataprovider import DataProvider from freqtrade.data.dataprovider import DataProvider
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
from freqtrade.mixins import LoggingMixin
from freqtrade.optimize.optimize_reports import (generate_backtest_stats, show_backtest_results, from freqtrade.optimize.optimize_reports import (generate_backtest_stats, show_backtest_results,
store_backtest_stats) store_backtest_stats)
from freqtrade.pairlist.pairlistmanager import PairListManager from freqtrade.persistence import PairLocks, Trade
from freqtrade.persistence import Trade from freqtrade.plugins.pairlistmanager import PairListManager
from freqtrade.plugins.protectionmanager import ProtectionManager
from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.strategy.interface import IStrategy, SellCheckTuple, SellType from freqtrade.strategy.interface import IStrategy, SellCheckTuple, SellType
from freqtrade.strategy.strategy_wrapper import strategy_safe_wrapper
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -38,25 +42,6 @@ LOW_IDX = 5
HIGH_IDX = 6 HIGH_IDX = 6
class BacktestResult(NamedTuple):
"""
NamedTuple Defining BacktestResults inputs.
"""
pair: str
profit_percent: float
profit_abs: float
open_date: datetime
open_rate: float
open_fee: float
close_date: datetime
close_rate: float
close_fee: float
amount: float
trade_duration: float
open_at_end: bool
sell_reason: SellType
class Backtesting: class Backtesting:
""" """
Backtesting class, this class contains all the logic to run a backtest Backtesting class, this class contains all the logic to run a backtest
@@ -67,11 +52,15 @@ class Backtesting:
""" """
def __init__(self, config: Dict[str, Any]) -> None: def __init__(self, config: Dict[str, Any]) -> None:
LoggingMixin.show_output = False
self.config = config self.config = config
# Reset keys for backtesting # Reset keys for backtesting
remove_credentials(self.config) remove_credentials(self.config)
self.strategylist: List[IStrategy] = [] self.strategylist: List[IStrategy] = []
self.all_results: Dict[str, Dict] = {}
self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config) self.exchange = ExchangeResolver.load_exchange(self.config['exchange']['name'], self.config)
dataprovider = DataProvider(self.config, self.exchange) dataprovider = DataProvider(self.config, self.exchange)
@@ -98,6 +87,8 @@ class Backtesting:
self.pairlists = PairListManager(self.exchange, self.config) self.pairlists = PairListManager(self.exchange, self.config)
if 'VolumePairList' in self.pairlists.name_list: if 'VolumePairList' in self.pairlists.name_list:
raise OperationalException("VolumePairList not allowed for backtesting.") raise OperationalException("VolumePairList not allowed for backtesting.")
if 'PerformanceFilter' in self.pairlists.name_list:
raise OperationalException("PerformanceFilter not allowed for backtesting.")
if len(self.strategylist) > 1 and 'PrecisionFilter' in self.pairlists.name_list: if len(self.strategylist) > 1 and 'PrecisionFilter' in self.pairlists.name_list:
raise OperationalException( raise OperationalException(
@@ -115,11 +106,24 @@ class Backtesting:
else: else:
self.fee = self.exchange.get_fee(symbol=self.pairlists.whitelist[0]) self.fee = self.exchange.get_fee(symbol=self.pairlists.whitelist[0])
Trade.use_db = False
Trade.reset_trades()
PairLocks.timeframe = self.config['timeframe']
PairLocks.use_db = False
PairLocks.reset_locks()
if self.config.get('enable_protections', False):
self.protections = ProtectionManager(self.config)
# Get maximum required startup period # Get maximum required startup period
self.required_startup = max([strat.startup_candle_count for strat in self.strategylist]) self.required_startup = max([strat.startup_candle_count for strat in self.strategylist])
# Load one (first) strategy # Load one (first) strategy
self._set_strategy(self.strategylist[0]) self._set_strategy(self.strategylist[0])
def __del__(self):
LoggingMixin.show_output = True
PairLocks.use_db = True
Trade.use_db = True
def _set_strategy(self, strategy): def _set_strategy(self, strategy):
""" """
Load strategy into backtesting Load strategy into backtesting
@@ -131,6 +135,10 @@ class Backtesting:
self.strategy.order_types['stoploss_on_exchange'] = False self.strategy.order_types['stoploss_on_exchange'] = False
def load_bt_data(self) -> Tuple[Dict[str, DataFrame], TimeRange]: def load_bt_data(self) -> Tuple[Dict[str, DataFrame], TimeRange]:
"""
Loads backtest data and returns the data combined with the timerange
as tuple.
"""
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')))
@@ -156,6 +164,18 @@ class Backtesting:
return data, timerange return data, timerange
def prepare_backtest(self, enable_protections):
"""
Backtesting setup method - called once for every call to "backtest()".
"""
PairLocks.use_db = False
PairLocks.timeframe = self.config['timeframe']
Trade.use_db = False
if enable_protections:
# Reset persisted data - used for protections only
PairLocks.reset_locks()
Trade.reset_trades()
def _get_ohlcv_as_lists(self, processed: Dict[str, DataFrame]) -> Dict[str, Tuple]: def _get_ohlcv_as_lists(self, processed: Dict[str, DataFrame]) -> Dict[str, Tuple]:
""" """
Helper function to convert a processed dataframes into lists for performance reasons. Helper function to convert a processed dataframes into lists for performance reasons.
@@ -226,7 +246,7 @@ class Backtesting:
else: else:
return sell_row[OPEN_IDX] return sell_row[OPEN_IDX]
def _get_sell_trade_entry(self, trade: Trade, sell_row: Tuple) -> Optional[BacktestResult]: def _get_sell_trade_entry(self, trade: Trade, sell_row: Tuple) -> Optional[Trade]:
sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], sell_row[DATE_IDX], sell = self.strategy.should_sell(trade, sell_row[OPEN_IDX], sell_row[DATE_IDX],
sell_row[BUY_IDX], sell_row[SELL_IDX], sell_row[BUY_IDX], sell_row[SELL_IDX],
@@ -235,24 +255,15 @@ class Backtesting:
trade_dur = int((sell_row[DATE_IDX] - trade.open_date).total_seconds() // 60) trade_dur = int((sell_row[DATE_IDX] - trade.open_date).total_seconds() // 60)
closerate = self._get_close_rate(sell_row, trade, sell, trade_dur) closerate = self._get_close_rate(sell_row, trade, sell, trade_dur)
return BacktestResult(pair=trade.pair, trade.close_date = sell_row[DATE_IDX]
profit_percent=trade.calc_profit_ratio(rate=closerate), trade.sell_reason = sell.sell_type
profit_abs=trade.calc_profit(rate=closerate), trade.close(closerate, show_msg=False)
open_date=trade.open_date, return trade
open_rate=trade.open_rate,
open_fee=self.fee,
close_date=sell_row[DATE_IDX],
close_rate=closerate,
close_fee=self.fee,
amount=trade.amount,
trade_duration=trade_dur,
open_at_end=False,
sell_reason=sell.sell_type
)
return None return None
def handle_left_open(self, open_trades: Dict[str, List[Trade]], def handle_left_open(self, open_trades: Dict[str, List[Trade]],
data: Dict[str, List[Tuple]]) -> List[BacktestResult]: data: Dict[str, List[Tuple]]) -> List[Trade]:
""" """
Handling of left open trades at the end of backtesting Handling of left open trades at the end of backtesting
""" """
@@ -261,29 +272,18 @@ class Backtesting:
if len(open_trades[pair]) > 0: if len(open_trades[pair]) > 0:
for trade in open_trades[pair]: for trade in open_trades[pair]:
sell_row = data[pair][-1] sell_row = data[pair][-1]
trade_entry = BacktestResult(pair=trade.pair,
profit_percent=trade.calc_profit_ratio( trade.close_date = sell_row[DATE_IDX]
rate=sell_row[OPEN_IDX]), trade.sell_reason = SellType.FORCE_SELL
profit_abs=trade.calc_profit(sell_row[OPEN_IDX]), trade.close(sell_row[OPEN_IDX], show_msg=False)
open_date=trade.open_date, trade.is_open = True
open_rate=trade.open_rate, trades.append(trade)
open_fee=self.fee,
close_date=sell_row[DATE_IDX],
close_rate=sell_row[OPEN_IDX],
close_fee=self.fee,
amount=trade.amount,
trade_duration=int((
sell_row[DATE_IDX] - trade.open_date
).total_seconds() // 60),
open_at_end=True,
sell_reason=SellType.FORCE_SELL
)
trades.append(trade_entry)
return trades return trades
def backtest(self, processed: Dict, stake_amount: float, def backtest(self, processed: Dict, stake_amount: float,
start_date: datetime, end_date: datetime, start_date: datetime, end_date: datetime,
max_open_trades: int = 0, position_stacking: bool = False) -> DataFrame: max_open_trades: int = 0, position_stacking: bool = False,
enable_protections: bool = False) -> DataFrame:
""" """
Implement backtesting functionality Implement backtesting functionality
@@ -297,13 +297,15 @@ class Backtesting:
:param end_date: backtesting timerange end datetime :param end_date: backtesting timerange end datetime
:param max_open_trades: maximum number of concurrent trades, <= 0 means unlimited :param max_open_trades: maximum number of concurrent trades, <= 0 means unlimited
:param position_stacking: do we allow position stacking? :param position_stacking: do we allow position stacking?
:param enable_protections: Should protections be enabled?
:return: DataFrame with trades (results of backtesting) :return: DataFrame with trades (results of backtesting)
""" """
logger.debug(f"Run backtest, stake_amount: {stake_amount}, " logger.debug(f"Run backtest, stake_amount: {stake_amount}, "
f"start_date: {start_date}, end_date: {end_date}, " f"start_date: {start_date}, end_date: {end_date}, "
f"max_open_trades: {max_open_trades}, position_stacking: {position_stacking}" f"max_open_trades: {max_open_trades}, position_stacking: {position_stacking}"
) )
trades = [] trades: List[Trade] = []
self.prepare_backtest(enable_protections)
# Use dict of lists with data for performance # Use dict of lists with data for performance
# (looping lists is a lot faster than pandas DataFrames) # (looping lists is a lot faster than pandas DataFrames)
@@ -342,7 +344,8 @@ class Backtesting:
if ((position_stacking or len(open_trades[pair]) == 0) if ((position_stacking or len(open_trades[pair]) == 0)
and (max_open_trades <= 0 or open_trade_count_start < max_open_trades) and (max_open_trades <= 0 or open_trade_count_start < max_open_trades)
and tmp != end_date and tmp != end_date
and row[BUY_IDX] == 1 and row[SELL_IDX] != 1): and row[BUY_IDX] == 1 and row[SELL_IDX] != 1
and not PairLocks.is_pair_locked(pair, row[DATE_IDX])):
# Enter trade # Enter trade
trade = Trade( trade = Trade(
pair=pair, pair=pair,
@@ -361,6 +364,7 @@ class Backtesting:
open_trade_count += 1 open_trade_count += 1
# logger.debug(f"{pair} - Backtesting emulates creation of new trade: {trade}.") # logger.debug(f"{pair} - Backtesting emulates creation of new trade: {trade}.")
open_trades[pair].append(trade) open_trades[pair].append(trade)
Trade.trades.append(trade)
for trade in open_trades[pair]: for trade in open_trades[pair]:
# since indexes has been incremented before, we need to go one step back to # since indexes has been incremented before, we need to go one step back to
@@ -372,13 +376,63 @@ class Backtesting:
open_trade_count -= 1 open_trade_count -= 1
open_trades[pair].remove(trade) open_trades[pair].remove(trade)
trades.append(trade_entry) trades.append(trade_entry)
if enable_protections:
self.protections.stop_per_pair(pair, row[DATE_IDX])
self.protections.global_stop(tmp)
# Move time one configured time_interval ahead. # Move time one configured time_interval ahead.
tmp += timedelta(minutes=self.timeframe_min) tmp += timedelta(minutes=self.timeframe_min)
trades += self.handle_left_open(open_trades, data=data) trades += self.handle_left_open(open_trades, data=data)
return DataFrame.from_records(trades, columns=BacktestResult._fields) return trade_list_to_dataframe(trades)
def backtest_one_strategy(self, strat: IStrategy, data: Dict[str, Any], timerange: TimeRange):
logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())
backtest_start_time = datetime.now(timezone.utc)
self._set_strategy(strat)
strategy_safe_wrapper(self.strategy.bot_loop_start, supress_error=True)()
# Use max_open_trades in backtesting, except --disable-max-market-positions is set
if self.config.get('use_max_market_positions', True):
# Must come from strategy config, as the strategy may modify this setting.
max_open_trades = self.strategy.config['max_open_trades']
else:
logger.info(
'Ignoring max_open_trades (--disable-max-market-positions was used) ...')
max_open_trades = 0
# need to reprocess data every time to populate signals
preprocessed = self.strategy.ohlcvdata_to_dataframe(data)
# Trim startup period from analyzed dataframe
for pair, df in preprocessed.items():
preprocessed[pair] = trim_dataframe(df, timerange)
min_date, max_date = history.get_timerange(preprocessed)
logger.info(f'Backtesting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} '
f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} '
f'({(max_date - min_date).days} days)..')
# Execute backtest and store results
results = self.backtest(
processed=preprocessed,
stake_amount=self.config['stake_amount'],
start_date=min_date.datetime,
end_date=max_date.datetime,
max_open_trades=max_open_trades,
position_stacking=self.config.get('position_stacking', False),
enable_protections=self.config.get('enable_protections', False),
)
backtest_end_time = datetime.now(timezone.utc)
self.all_results[self.strategy.get_strategy_name()] = {
'results': results,
'config': self.strategy.config,
'locks': PairLocks.locks,
'backtest_start_time': int(backtest_start_time.timestamp()),
'backtest_end_time': int(backtest_end_time.timestamp()),
}
return min_date, max_date
def start(self) -> None: def start(self) -> None:
""" """
@@ -387,53 +441,15 @@ class Backtesting:
""" """
data: Dict[str, Any] = {} data: Dict[str, Any] = {}
logger.info('Using stake_currency: %s ...', self.config['stake_currency'])
logger.info('Using stake_amount: %s ...', self.config['stake_amount'])
position_stacking = self.config.get('position_stacking', False)
data, timerange = self.load_bt_data() data, timerange = self.load_bt_data()
all_results = {} min_date = None
max_date = None
for strat in self.strategylist: for strat in self.strategylist:
logger.info("Running backtesting for Strategy %s", strat.get_strategy_name()) min_date, max_date = self.backtest_one_strategy(strat, data, timerange)
self._set_strategy(strat)
# Use max_open_trades in backtesting, except --disable-max-market-positions is set stats = generate_backtest_stats(data, self.all_results,
if self.config.get('use_max_market_positions', True): min_date=min_date, max_date=max_date)
# Must come from strategy config, as the strategy may modify this setting.
max_open_trades = self.strategy.config['max_open_trades']
else:
logger.info(
'Ignoring max_open_trades (--disable-max-market-positions was used) ...')
max_open_trades = 0
# need to reprocess data every time to populate signals
preprocessed = self.strategy.ohlcvdata_to_dataframe(data)
# Trim startup period from analyzed dataframe
for pair, df in preprocessed.items():
preprocessed[pair] = trim_dataframe(df, timerange)
min_date, max_date = history.get_timerange(preprocessed)
logger.info(f'Backtesting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} '
f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} '
f'({(max_date - min_date).days} days)..')
# Execute backtest and print results
results = self.backtest(
processed=preprocessed,
stake_amount=self.config['stake_amount'],
start_date=min_date.datetime,
end_date=max_date.datetime,
max_open_trades=max_open_trades,
position_stacking=position_stacking,
)
all_results[self.strategy.get_strategy_name()] = {
'results': results,
'config': self.strategy.config,
}
stats = generate_backtest_stats(data, all_results, min_date=min_date, max_date=max_date)
if self.config.get('export', False): if self.config.get('export', False):
store_backtest_stats(self.config['exportfilename'], stats) store_backtest_stats(self.config['exportfilename'], stats)

View File

@@ -42,7 +42,7 @@ class ShortTradeDurHyperOptLoss(IHyperOptLoss):
* 0.25: Avoiding trade loss * 0.25: Avoiding trade loss
* 1.0 to total profit, compared to the expected value (`EXPECTED_MAX_PROFIT`) defined above * 1.0 to total profit, compared to the expected value (`EXPECTED_MAX_PROFIT`) defined above
""" """
total_profit = results['profit_percent'].sum() total_profit = results['profit_ratio'].sum()
trade_duration = results['trade_duration'].mean() trade_duration = results['trade_duration'].mean()
trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8) trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)

View File

@@ -542,12 +542,15 @@ class Hyperopt:
end_date=max_date.datetime, end_date=max_date.datetime,
max_open_trades=self.max_open_trades, max_open_trades=self.max_open_trades,
position_stacking=self.position_stacking, position_stacking=self.position_stacking,
enable_protections=self.config.get('enable_protections', False),
) )
return self._get_results_dict(backtesting_results, min_date, max_date, return self._get_results_dict(backtesting_results, min_date, max_date,
params_dict, params_details) params_dict, params_details,
processed=processed)
def _get_results_dict(self, backtesting_results, min_date, max_date, def _get_results_dict(self, backtesting_results, min_date, max_date,
params_dict, params_details): params_dict, params_details, processed: Dict[str, DataFrame]):
results_metrics = self._calculate_results_metrics(backtesting_results) results_metrics = self._calculate_results_metrics(backtesting_results)
results_explanation = self._format_results_explanation_string(results_metrics) results_explanation = self._format_results_explanation_string(results_metrics)
@@ -561,7 +564,8 @@ class Hyperopt:
loss: float = MAX_LOSS loss: float = MAX_LOSS
if trade_count >= self.config['hyperopt_min_trades']: if trade_count >= self.config['hyperopt_min_trades']:
loss = self.calculate_loss(results=backtesting_results, trade_count=trade_count, loss = self.calculate_loss(results=backtesting_results, trade_count=trade_count,
min_date=min_date.datetime, max_date=max_date.datetime) min_date=min_date.datetime, max_date=max_date.datetime,
config=self.config, processed=processed)
return { return {
'loss': loss, 'loss': loss,
'params_dict': params_dict, 'params_dict': params_dict,
@@ -572,20 +576,20 @@ class Hyperopt:
} }
def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict: def _calculate_results_metrics(self, backtesting_results: DataFrame) -> Dict:
wins = len(backtesting_results[backtesting_results.profit_percent > 0]) wins = len(backtesting_results[backtesting_results['profit_ratio'] > 0])
draws = len(backtesting_results[backtesting_results.profit_percent == 0]) draws = len(backtesting_results[backtesting_results['profit_ratio'] == 0])
losses = len(backtesting_results[backtesting_results.profit_percent < 0]) losses = len(backtesting_results[backtesting_results['profit_ratio'] < 0])
return { return {
'trade_count': len(backtesting_results.index), 'trade_count': len(backtesting_results.index),
'wins': wins, 'wins': wins,
'draws': draws, 'draws': draws,
'losses': losses, 'losses': losses,
'winsdrawslosses': f"{wins:>4} {draws:>4} {losses:>4}", 'winsdrawslosses': f"{wins:>4} {draws:>4} {losses:>4}",
'avg_profit': backtesting_results.profit_percent.mean() * 100.0, 'avg_profit': backtesting_results['profit_ratio'].mean() * 100.0,
'median_profit': backtesting_results.profit_percent.median() * 100.0, 'median_profit': backtesting_results['profit_ratio'].median() * 100.0,
'total_profit': backtesting_results.profit_abs.sum(), 'total_profit': backtesting_results['profit_abs'].sum(),
'profit': backtesting_results.profit_percent.sum() * 100.0, 'profit': backtesting_results['profit_ratio'].sum() * 100.0,
'duration': backtesting_results.trade_duration.mean(), 'duration': backtesting_results['trade_duration'].mean(),
} }
def _format_results_explanation_string(self, results_metrics: Dict) -> str: def _format_results_explanation_string(self, results_metrics: Dict) -> str:
@@ -648,7 +652,7 @@ class Hyperopt:
# Trim startup period from analyzed dataframe # Trim startup period from analyzed dataframe
for pair, df in preprocessed.items(): for pair, df in preprocessed.items():
preprocessed[pair] = trim_dataframe(df, timerange) preprocessed[pair] = trim_dataframe(df, timerange)
min_date, max_date = get_timerange(data) min_date, max_date = get_timerange(preprocessed)
logger.info(f'Hyperopting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} ' logger.info(f'Hyperopting with data from {min_date.strftime(DATETIME_PRINT_FORMAT)} '
f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} ' f'up to {max_date.strftime(DATETIME_PRINT_FORMAT)} '

View File

@@ -5,6 +5,7 @@ This module defines the interface for the loss-function for hyperopt
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from datetime import datetime from datetime import datetime
from typing import Dict
from pandas import DataFrame from pandas import DataFrame
@@ -19,7 +20,9 @@ class IHyperOptLoss(ABC):
@staticmethod @staticmethod
@abstractmethod @abstractmethod
def hyperopt_loss_function(results: DataFrame, trade_count: int, def hyperopt_loss_function(results: DataFrame, trade_count: int,
min_date: datetime, max_date: datetime, *args, **kwargs) -> float: min_date: datetime, max_date: datetime,
config: Dict, processed: Dict[str, DataFrame],
*args, **kwargs) -> float:
""" """
Objective function, returns smaller number for better results Objective function, returns smaller number for better results
""" """

View File

@@ -34,5 +34,5 @@ class OnlyProfitHyperOptLoss(IHyperOptLoss):
""" """
Objective function, returns smaller number for better results. Objective function, returns smaller number for better results.
""" """
total_profit = results['profit_percent'].sum() total_profit = results['profit_ratio'].sum()
return 1 - total_profit / EXPECTED_MAX_PROFIT return 1 - total_profit / EXPECTED_MAX_PROFIT

View File

@@ -28,7 +28,7 @@ class SharpeHyperOptLoss(IHyperOptLoss):
Uses Sharpe Ratio calculation. Uses Sharpe Ratio calculation.
""" """
total_profit = results["profit_percent"] total_profit = results["profit_ratio"]
days_period = (max_date - min_date).days days_period = (max_date - min_date).days
# adding slippage of 0.1% per trade # adding slippage of 0.1% per trade

View File

@@ -34,9 +34,9 @@ class SharpeHyperOptLossDaily(IHyperOptLoss):
annual_risk_free_rate = 0.0 annual_risk_free_rate = 0.0
risk_free_rate = annual_risk_free_rate / days_in_year risk_free_rate = annual_risk_free_rate / days_in_year
# apply slippage per trade to profit_percent # apply slippage per trade to profit_ratio
results.loc[:, 'profit_percent_after_slippage'] = \ results.loc[:, 'profit_ratio_after_slippage'] = \
results['profit_percent'] - slippage_per_trade_ratio results['profit_ratio'] - slippage_per_trade_ratio
# create the index within the min_date and end max_date # create the index within the min_date and end max_date
t_index = date_range(start=min_date, end=max_date, freq=resample_freq, t_index = date_range(start=min_date, end=max_date, freq=resample_freq,
@@ -44,10 +44,10 @@ class SharpeHyperOptLossDaily(IHyperOptLoss):
sum_daily = ( sum_daily = (
results.resample(resample_freq, on='close_date').agg( results.resample(resample_freq, on='close_date').agg(
{"profit_percent_after_slippage": sum}).reindex(t_index).fillna(0) {"profit_ratio_after_slippage": sum}).reindex(t_index).fillna(0)
) )
total_profit = sum_daily["profit_percent_after_slippage"] - risk_free_rate total_profit = sum_daily["profit_ratio_after_slippage"] - risk_free_rate
expected_returns_mean = total_profit.mean() expected_returns_mean = total_profit.mean()
up_stdev = total_profit.std() up_stdev = total_profit.std()

View File

@@ -28,7 +28,7 @@ class SortinoHyperOptLoss(IHyperOptLoss):
Uses Sortino Ratio calculation. Uses Sortino Ratio calculation.
""" """
total_profit = results["profit_percent"] total_profit = results["profit_ratio"]
days_period = (max_date - min_date).days days_period = (max_date - min_date).days
# adding slippage of 0.1% per trade # adding slippage of 0.1% per trade
@@ -36,7 +36,7 @@ class SortinoHyperOptLoss(IHyperOptLoss):
expected_returns_mean = total_profit.sum() / days_period expected_returns_mean = total_profit.sum() / days_period
results['downside_returns'] = 0 results['downside_returns'] = 0
results.loc[total_profit < 0, 'downside_returns'] = results['profit_percent'] results.loc[total_profit < 0, 'downside_returns'] = results['profit_ratio']
down_stdev = np.std(results['downside_returns']) down_stdev = np.std(results['downside_returns'])
if down_stdev != 0: if down_stdev != 0:

View File

@@ -36,9 +36,9 @@ class SortinoHyperOptLossDaily(IHyperOptLoss):
days_in_year = 365 days_in_year = 365
minimum_acceptable_return = 0.0 minimum_acceptable_return = 0.0
# apply slippage per trade to profit_percent # apply slippage per trade to profit_ratio
results.loc[:, 'profit_percent_after_slippage'] = \ results.loc[:, 'profit_ratio_after_slippage'] = \
results['profit_percent'] - slippage_per_trade_ratio results['profit_ratio'] - slippage_per_trade_ratio
# create the index within the min_date and end max_date # create the index within the min_date and end max_date
t_index = date_range(start=min_date, end=max_date, freq=resample_freq, t_index = date_range(start=min_date, end=max_date, freq=resample_freq,
@@ -46,17 +46,17 @@ class SortinoHyperOptLossDaily(IHyperOptLoss):
sum_daily = ( sum_daily = (
results.resample(resample_freq, on='close_date').agg( results.resample(resample_freq, on='close_date').agg(
{"profit_percent_after_slippage": sum}).reindex(t_index).fillna(0) {"profit_ratio_after_slippage": sum}).reindex(t_index).fillna(0)
) )
total_profit = sum_daily["profit_percent_after_slippage"] - minimum_acceptable_return total_profit = sum_daily["profit_ratio_after_slippage"] - minimum_acceptable_return
expected_returns_mean = total_profit.mean() expected_returns_mean = total_profit.mean()
sum_daily['downside_returns'] = 0 sum_daily['downside_returns'] = 0
sum_daily.loc[total_profit < 0, 'downside_returns'] = total_profit sum_daily.loc[total_profit < 0, 'downside_returns'] = total_profit
total_downside = sum_daily['downside_returns'] total_downside = sum_daily['downside_returns']
# Here total_downside contains min(0, P - MAR) values, # Here total_downside contains min(0, P - MAR) values,
# where P = sum_daily["profit_percent_after_slippage"] # where P = sum_daily["profit_ratio_after_slippage"]
down_stdev = math.sqrt((total_downside**2).sum() / len(total_downside)) down_stdev = math.sqrt((total_downside**2).sum() / len(total_downside))
if down_stdev != 0: if down_stdev != 0:

View File

@@ -9,8 +9,9 @@ from pandas import DataFrame
from tabulate import tabulate from tabulate import tabulate
from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN
from freqtrade.data.btanalysis import calculate_market_change, calculate_max_drawdown from freqtrade.data.btanalysis import (calculate_csum, calculate_market_change,
from freqtrade.misc import file_dump_json calculate_max_drawdown)
from freqtrade.misc import decimals_per_coin, file_dump_json, round_coin_value
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -38,11 +39,12 @@ def store_backtest_stats(recordfilename: Path, stats: Dict[str, DataFrame]) -> N
file_dump_json(latest_filename, {'latest_backtest': str(filename.name)}) file_dump_json(latest_filename, {'latest_backtest': str(filename.name)})
def _get_line_floatfmt() -> List[str]: def _get_line_floatfmt(stake_currency: str) -> List[str]:
""" """
Generate floatformat (goes in line with _generate_result_line()) Generate floatformat (goes in line with _generate_result_line())
""" """
return ['s', 'd', '.2f', '.2f', '.8f', '.2f', 'd', 'd', 'd', 'd'] return ['s', 'd', '.2f', '.2f', f'.{decimals_per_coin(stake_currency)}f',
'.2f', 'd', 'd', 'd', 'd']
def _get_line_header(first_column: str, stake_currency: str) -> List[str]: def _get_line_header(first_column: str, stake_currency: str) -> List[str]:
@@ -58,16 +60,19 @@ def _generate_result_line(result: DataFrame, max_open_trades: int, first_column:
""" """
Generate one result dict, with "first_column" as key. Generate one result dict, with "first_column" as key.
""" """
profit_sum = result['profit_ratio'].sum()
profit_total = profit_sum / max_open_trades
return { return {
'key': first_column, 'key': first_column,
'trades': len(result), 'trades': len(result),
'profit_mean': result['profit_percent'].mean() if len(result) > 0 else 0.0, 'profit_mean': result['profit_ratio'].mean() if len(result) > 0 else 0.0,
'profit_mean_pct': result['profit_percent'].mean() * 100.0 if len(result) > 0 else 0.0, 'profit_mean_pct': result['profit_ratio'].mean() * 100.0 if len(result) > 0 else 0.0,
'profit_sum': result['profit_percent'].sum(), 'profit_sum': profit_sum,
'profit_sum_pct': result['profit_percent'].sum() * 100.0, 'profit_sum_pct': round(profit_sum * 100.0, 2),
'profit_total_abs': result['profit_abs'].sum(), 'profit_total_abs': result['profit_abs'].sum(),
'profit_total': result['profit_percent'].sum() / max_open_trades, 'profit_total': profit_total,
'profit_total_pct': result['profit_percent'].sum() * 100.0 / max_open_trades, 'profit_total_pct': round(profit_total * 100.0, 2),
'duration_avg': str(timedelta( 'duration_avg': str(timedelta(
minutes=round(result['trade_duration'].mean())) minutes=round(result['trade_duration'].mean()))
) if not result.empty else '0:00', ) if not result.empty else '0:00',
@@ -121,9 +126,9 @@ def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List
for reason, count in results['sell_reason'].value_counts().iteritems(): for reason, count in results['sell_reason'].value_counts().iteritems():
result = results.loc[results['sell_reason'] == reason] result = results.loc[results['sell_reason'] == reason]
profit_mean = result['profit_percent'].mean() profit_mean = result['profit_ratio'].mean()
profit_sum = result["profit_percent"].sum() profit_sum = result['profit_ratio'].sum()
profit_percent_tot = result['profit_percent'].sum() / max_open_trades profit_total = profit_sum / max_open_trades
tabular_data.append( tabular_data.append(
{ {
@@ -137,8 +142,8 @@ def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List
'profit_sum': profit_sum, 'profit_sum': profit_sum,
'profit_sum_pct': round(profit_sum * 100, 2), 'profit_sum_pct': round(profit_sum * 100, 2),
'profit_total_abs': result['profit_abs'].sum(), 'profit_total_abs': result['profit_abs'].sum(),
'profit_total': profit_percent_tot, 'profit_total': profit_total,
'profit_total_pct': round(profit_percent_tot * 100, 2), 'profit_total_pct': round(profit_total * 100, 2),
} }
) )
return tabular_data return tabular_data
@@ -147,7 +152,7 @@ def generate_sell_reason_stats(max_open_trades: int, results: DataFrame) -> List
def generate_strategy_metrics(all_results: Dict) -> List[Dict]: def generate_strategy_metrics(all_results: Dict) -> List[Dict]:
""" """
Generate summary per strategy Generate summary per strategy
:param all_results: Dict of <Strategyname: BacktestResult> containing results for all strategies :param all_results: Dict of <Strategyname: DataFrame> containing results for all strategies
:return: List of Dicts containing the metrics per Strategy :return: List of Dicts containing the metrics per Strategy
""" """
@@ -196,15 +201,15 @@ def generate_daily_stats(results: DataFrame) -> Dict[str, Any]:
'winner_holding_avg': timedelta(), 'winner_holding_avg': timedelta(),
'loser_holding_avg': timedelta(), 'loser_holding_avg': timedelta(),
} }
daily_profit = results.resample('1d', on='close_date')['profit_percent'].sum() daily_profit = results.resample('1d', on='close_date')['profit_ratio'].sum()
worst = min(daily_profit) worst = min(daily_profit)
best = max(daily_profit) best = max(daily_profit)
winning_days = sum(daily_profit > 0) winning_days = sum(daily_profit > 0)
draw_days = sum(daily_profit == 0) draw_days = sum(daily_profit == 0)
losing_days = sum(daily_profit < 0) losing_days = sum(daily_profit < 0)
winning_trades = results.loc[results['profit_percent'] > 0] winning_trades = results.loc[results['profit_ratio'] > 0]
losing_trades = results.loc[results['profit_percent'] < 0] losing_trades = results.loc[results['profit_ratio'] < 0]
return { return {
'backtest_best_day': best, 'backtest_best_day': best,
@@ -240,7 +245,7 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
if not isinstance(results, DataFrame): if not isinstance(results, DataFrame):
continue continue
config = content['config'] config = content['config']
max_open_trades = config['max_open_trades'] max_open_trades = min(config['max_open_trades'], len(btdata.keys()))
stake_currency = config['stake_currency'] stake_currency = config['stake_currency']
pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency, pair_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
@@ -250,22 +255,28 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
results=results) results=results)
left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency, left_open_results = generate_pair_metrics(btdata, stake_currency=stake_currency,
max_open_trades=max_open_trades, max_open_trades=max_open_trades,
results=results.loc[results['open_at_end']], results=results.loc[results['is_open']],
skip_nan=True) skip_nan=True)
daily_stats = generate_daily_stats(results) daily_stats = generate_daily_stats(results)
best_pair = max([pair for pair in pair_results if pair['key'] != 'TOTAL'],
key=lambda x: x['profit_sum']) if len(pair_results) > 1 else None
worst_pair = min([pair for pair in pair_results if pair['key'] != 'TOTAL'],
key=lambda x: x['profit_sum']) if len(pair_results) > 1 else None
results['open_timestamp'] = results['open_date'].astype(int64) // 1e6 results['open_timestamp'] = results['open_date'].astype(int64) // 1e6
results['close_timestamp'] = results['close_date'].astype(int64) // 1e6 results['close_timestamp'] = results['close_date'].astype(int64) // 1e6
backtest_days = (max_date - min_date).days backtest_days = (max_date - min_date).days
strat_stats = { strat_stats = {
'trades': results.to_dict(orient='records'), 'trades': results.to_dict(orient='records'),
'locks': [lock.to_json() for lock in content['locks']],
'best_pair': best_pair,
'worst_pair': worst_pair,
'results_per_pair': pair_results, 'results_per_pair': pair_results,
'sell_reason_summary': sell_reason_stats, 'sell_reason_summary': sell_reason_stats,
'left_open_trades': left_open_results, 'left_open_trades': left_open_results,
'total_trades': len(results), 'total_trades': len(results),
'profit_mean': results['profit_percent'].mean() if len(results) > 0 else 0, 'profit_mean': results['profit_ratio'].mean() if len(results) > 0 else 0,
'profit_total': results['profit_percent'].sum(), 'profit_total': results['profit_ratio'].sum() / max_open_trades,
'profit_total_abs': results['profit_abs'].sum(), 'profit_total_abs': results['profit_abs'].sum(),
'backtest_start': min_date.datetime, 'backtest_start': min_date.datetime,
'backtest_start_ts': min_date.int_timestamp * 1000, 'backtest_start_ts': min_date.int_timestamp * 1000,
@@ -273,23 +284,32 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
'backtest_end_ts': max_date.int_timestamp * 1000, 'backtest_end_ts': max_date.int_timestamp * 1000,
'backtest_days': backtest_days, 'backtest_days': backtest_days,
'backtest_run_start_ts': content['backtest_start_time'],
'backtest_run_end_ts': content['backtest_end_time'],
'trades_per_day': round(len(results) / backtest_days, 2) if backtest_days > 0 else 0, 'trades_per_day': round(len(results) / backtest_days, 2) if backtest_days > 0 else 0,
'market_change': market_change, 'market_change': market_change,
'pairlist': list(btdata.keys()), 'pairlist': list(btdata.keys()),
'stake_amount': config['stake_amount'], 'stake_amount': config['stake_amount'],
'stake_currency': config['stake_currency'], 'stake_currency': config['stake_currency'],
'max_open_trades': (config['max_open_trades'] 'max_open_trades': max_open_trades,
if config['max_open_trades'] != float('inf') else -1), 'max_open_trades_setting': (config['max_open_trades']
if config['max_open_trades'] != float('inf') else -1),
'timeframe': config['timeframe'], 'timeframe': config['timeframe'],
'timerange': config.get('timerange', ''),
'enable_protections': config.get('enable_protections', False),
'strategy_name': strategy,
# Parameters relevant for backtesting # Parameters relevant for backtesting
'stoploss': config['stoploss'], 'stoploss': config['stoploss'],
'trailing_stop': config.get('trailing_stop', False), 'trailing_stop': config.get('trailing_stop', False),
'trailing_stop_positive': config.get('trailing_stop_positive'), 'trailing_stop_positive': config.get('trailing_stop_positive'),
'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset', 0.0), 'trailing_stop_positive_offset': config.get('trailing_stop_positive_offset', 0.0),
'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached', False), 'trailing_only_offset_is_reached': config.get('trailing_only_offset_is_reached', False),
'use_custom_stoploss': config.get('use_custom_stoploss', False),
'minimal_roi': config['minimal_roi'], 'minimal_roi': config['minimal_roi'],
'use_sell_signal': config['ask_strategy']['use_sell_signal'], 'use_sell_signal': config['ask_strategy']['use_sell_signal'],
'sell_profit_only': config['ask_strategy']['sell_profit_only'], 'sell_profit_only': config['ask_strategy']['sell_profit_only'],
'sell_profit_offset': config['ask_strategy']['sell_profit_offset'],
'ignore_roi_if_buy_signal': config['ask_strategy']['ignore_roi_if_buy_signal'], 'ignore_roi_if_buy_signal': config['ask_strategy']['ignore_roi_if_buy_signal'],
**daily_stats, **daily_stats,
} }
@@ -297,7 +317,7 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
try: try:
max_drawdown, drawdown_start, drawdown_end = calculate_max_drawdown( max_drawdown, drawdown_start, drawdown_end = calculate_max_drawdown(
results, value_col='profit_percent') results, value_col='profit_ratio')
strat_stats.update({ strat_stats.update({
'max_drawdown': max_drawdown, 'max_drawdown': max_drawdown,
'drawdown_start': drawdown_start, 'drawdown_start': drawdown_start,
@@ -305,6 +325,13 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
'drawdown_end': drawdown_end, 'drawdown_end': drawdown_end,
'drawdown_end_ts': drawdown_end.timestamp() * 1000, 'drawdown_end_ts': drawdown_end.timestamp() * 1000,
}) })
csum_min, csum_max = calculate_csum(results)
strat_stats.update({
'csum_min': csum_min,
'csum_max': csum_max
})
except ValueError: except ValueError:
strat_stats.update({ strat_stats.update({
'max_drawdown': 0.0, 'max_drawdown': 0.0,
@@ -312,6 +339,8 @@ def generate_backtest_stats(btdata: Dict[str, DataFrame],
'drawdown_start_ts': 0, 'drawdown_start_ts': 0,
'drawdown_end': datetime(1970, 1, 1, tzinfo=timezone.utc), 'drawdown_end': datetime(1970, 1, 1, tzinfo=timezone.utc),
'drawdown_end_ts': 0, 'drawdown_end_ts': 0,
'csum_min': 0,
'csum_max': 0
}) })
strategy_results = generate_strategy_metrics(all_results=all_results) strategy_results = generate_strategy_metrics(all_results=all_results)
@@ -334,7 +363,7 @@ def text_table_bt_results(pair_results: List[Dict[str, Any]], stake_currency: st
""" """
headers = _get_line_header('Pair', stake_currency) headers = _get_line_header('Pair', stake_currency)
floatfmt = _get_line_floatfmt() floatfmt = _get_line_floatfmt(stake_currency)
output = [[ output = [[
t['key'], t['trades'], t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'], t['key'], t['trades'], t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'],
t['profit_total_pct'], t['duration_avg'], t['wins'], t['draws'], t['losses'] t['profit_total_pct'], t['duration_avg'], t['wins'], t['draws'], t['losses']
@@ -365,7 +394,9 @@ def text_table_sell_reason(sell_reason_stats: List[Dict[str, Any]], stake_curren
output = [[ output = [[
t['sell_reason'], t['trades'], t['wins'], t['draws'], t['losses'], t['sell_reason'], t['trades'], t['wins'], t['draws'], t['losses'],
t['profit_mean_pct'], t['profit_sum_pct'], t['profit_total_abs'], t['profit_total_pct'], t['profit_mean_pct'], t['profit_sum_pct'],
round_coin_value(t['profit_total_abs'], stake_currency, False),
t['profit_total_pct'],
] for t in sell_reason_stats] ] for t in sell_reason_stats]
return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right") return tabulate(output, headers=headers, tablefmt="orgtbl", stralign="right")
@@ -375,10 +406,10 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str:
Generate summary table per strategy Generate summary table per strategy
:param stake_currency: stake-currency - used to correctly name headers :param stake_currency: stake-currency - used to correctly name headers
:param max_open_trades: Maximum allowed open trades used for backtest :param max_open_trades: Maximum allowed open trades used for backtest
:param all_results: Dict of <Strategyname: BacktestResult> containing results for all strategies :param all_results: Dict of <Strategyname: DataFrame> containing results for all strategies
:return: pretty printed table with tabulate as string :return: pretty printed table with tabulate as string
""" """
floatfmt = _get_line_floatfmt() floatfmt = _get_line_floatfmt(stake_currency)
headers = _get_line_header('Strategy', stake_currency) headers = _get_line_header('Strategy', stake_currency)
output = [[ output = [[
@@ -392,17 +423,25 @@ def text_table_strategy(strategy_results, stake_currency: str) -> str:
def text_table_add_metrics(strat_results: Dict) -> str: def text_table_add_metrics(strat_results: Dict) -> str:
if len(strat_results['trades']) > 0: if len(strat_results['trades']) > 0:
min_trade = min(strat_results['trades'], key=lambda x: x['open_date']) best_trade = max(strat_results['trades'], key=lambda x: x['profit_ratio'])
worst_trade = min(strat_results['trades'], key=lambda x: x['profit_ratio'])
metrics = [ metrics = [
('Backtesting from', strat_results['backtest_start'].strftime(DATETIME_PRINT_FORMAT)), ('Backtesting from', strat_results['backtest_start'].strftime(DATETIME_PRINT_FORMAT)),
('Backtesting to', strat_results['backtest_end'].strftime(DATETIME_PRINT_FORMAT)), ('Backtesting to', strat_results['backtest_end'].strftime(DATETIME_PRINT_FORMAT)),
('Max open trades', strat_results['max_open_trades']), ('Max open trades', strat_results['max_open_trades']),
('', ''), # Empty line to improve readability ('', ''), # Empty line to improve readability
('Total trades', strat_results['total_trades']), ('Total trades', strat_results['total_trades']),
('First trade', min_trade['open_date'].strftime(DATETIME_PRINT_FORMAT)),
('First trade Pair', min_trade['pair']),
('Total Profit %', f"{round(strat_results['profit_total'] * 100, 2)}%"), ('Total Profit %', f"{round(strat_results['profit_total'] * 100, 2)}%"),
('Trades per day', strat_results['trades_per_day']), ('Trades per day', strat_results['trades_per_day']),
('', ''), # Empty line to improve readability
('Best Pair', f"{strat_results['best_pair']['key']} "
f"{round(strat_results['best_pair']['profit_sum_pct'], 2)}%"),
('Worst Pair', f"{strat_results['worst_pair']['key']} "
f"{round(strat_results['worst_pair']['profit_sum_pct'], 2)}%"),
('Best trade', f"{best_trade['pair']} {round(best_trade['profit_ratio'] * 100, 2)}%"),
('Worst trade', f"{worst_trade['pair']} "
f"{round(worst_trade['profit_ratio'] * 100, 2)}%"),
('Best day', f"{round(strat_results['backtest_best_day'] * 100, 2)}%"), ('Best day', f"{round(strat_results['backtest_best_day'] * 100, 2)}%"),
('Worst day', f"{round(strat_results['backtest_worst_day'] * 100, 2)}%"), ('Worst day', f"{round(strat_results['backtest_worst_day'] * 100, 2)}%"),
('Days win/draw/lose', f"{strat_results['winning_days']} / " ('Days win/draw/lose', f"{strat_results['winning_days']} / "
@@ -410,6 +449,12 @@ def text_table_add_metrics(strat_results: Dict) -> str:
('Avg. Duration Winners', f"{strat_results['winner_holding_avg']}"), ('Avg. Duration Winners', f"{strat_results['winner_holding_avg']}"),
('Avg. Duration Loser', f"{strat_results['loser_holding_avg']}"), ('Avg. Duration Loser', f"{strat_results['loser_holding_avg']}"),
('', ''), # Empty line to improve readability ('', ''), # Empty line to improve readability
('Abs Profit Min', round_coin_value(strat_results['csum_min'],
strat_results['stake_currency'])),
('Abs Profit Max', round_coin_value(strat_results['csum_max'],
strat_results['stake_currency'])),
('Max Drawdown', f"{round(strat_results['max_drawdown'] * 100, 2)}%"), ('Max Drawdown', f"{round(strat_results['max_drawdown'] * 100, 2)}%"),
('Drawdown Start', strat_results['drawdown_start'].strftime(DATETIME_PRINT_FORMAT)), ('Drawdown Start', strat_results['drawdown_start'].strftime(DATETIME_PRINT_FORMAT)),
('Drawdown End', strat_results['drawdown_end'].strftime(DATETIME_PRINT_FORMAT)), ('Drawdown End', strat_results['drawdown_end'].strftime(DATETIME_PRINT_FORMAT)),

View File

@@ -53,11 +53,11 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
else: else:
timeframe = get_column_def(cols, 'timeframe', 'null') timeframe = get_column_def(cols, 'timeframe', 'null')
open_trade_price = get_column_def(cols, 'open_trade_price', open_trade_value = get_column_def(cols, 'open_trade_value',
f'amount * open_rate * (1 + {fee_open})') f'amount * open_rate * (1 + {fee_open})')
close_profit_abs = get_column_def( close_profit_abs = get_column_def(
cols, 'close_profit_abs', cols, 'close_profit_abs',
f"(amount * close_rate * (1 - {fee_close})) - {open_trade_price}") f"(amount * close_rate * (1 - {fee_close})) - {open_trade_value}")
sell_order_status = get_column_def(cols, 'sell_order_status', 'null') sell_order_status = get_column_def(cols, 'sell_order_status', 'null')
amount_requested = get_column_def(cols, 'amount_requested', 'amount') amount_requested = get_column_def(cols, 'amount_requested', 'amount')
@@ -79,7 +79,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct, stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
stoploss_order_id, stoploss_last_update, stoploss_order_id, stoploss_last_update,
max_rate, min_rate, sell_reason, sell_order_status, strategy, max_rate, min_rate, sell_reason, sell_order_status, strategy,
timeframe, open_trade_price, close_profit_abs timeframe, open_trade_value, close_profit_abs
) )
select id, lower(exchange), select id, lower(exchange),
case case
@@ -102,7 +102,7 @@ def migrate_trades_table(decl_base, inspector, engine, table_back_name: str, col
{max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason, {max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason,
{sell_order_status} sell_order_status, {sell_order_status} sell_order_status,
{strategy} strategy, {timeframe} timeframe, {strategy} strategy, {timeframe} timeframe,
{open_trade_price} open_trade_price, {close_profit_abs} close_profit_abs {open_trade_value} open_trade_value, {close_profit_abs} close_profit_abs
from {table_back_name} from {table_back_name}
""") """)
@@ -134,7 +134,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
table_back_name = get_backup_name(tabs, 'trades_bak') table_back_name = get_backup_name(tabs, 'trades_bak')
# Check for latest column # Check for latest column
if not has_column(cols, 'amount_requested'): if not has_column(cols, 'open_trade_value'):
logger.info(f'Running database migration for trades - backup: {table_back_name}') logger.info(f'Running database migration for trades - backup: {table_back_name}')
migrate_trades_table(decl_base, inspector, engine, table_back_name, cols) migrate_trades_table(decl_base, inspector, engine, table_back_name, cols)
# Reread columns - the above recreated the table! # Reread columns - the above recreated the table!

View File

@@ -171,6 +171,10 @@ class Order(_DECL_BASE):
""" """
Get all non-closed orders - useful when trying to batch-update orders Get all non-closed orders - useful when trying to batch-update orders
""" """
if not isinstance(order, dict):
logger.warning(f"{order} is not a valid response object.")
return
filtered_orders = [o for o in orders if o.order_id == order.get('id')] filtered_orders = [o for o in orders if o.order_id == order.get('id')]
if filtered_orders: if filtered_orders:
oobj = filtered_orders[0] oobj = filtered_orders[0]
@@ -202,6 +206,10 @@ class Trade(_DECL_BASE):
""" """
__tablename__ = 'trades' __tablename__ = 'trades'
use_db: bool = True
# Trades container for backtesting
trades: List['Trade'] = []
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
orders = relationship("Order", order_by="Order.id", cascade="all, delete-orphan") orders = relationship("Order", order_by="Order.id", cascade="all, delete-orphan")
@@ -217,8 +225,8 @@ class Trade(_DECL_BASE):
fee_close_currency = Column(String, nullable=True) fee_close_currency = Column(String, nullable=True)
open_rate = Column(Float) open_rate = Column(Float)
open_rate_requested = Column(Float) open_rate_requested = Column(Float)
# open_trade_price - calculated via _calc_open_trade_price # open_trade_value - calculated via _calc_open_trade_value
open_trade_price = Column(Float) open_trade_value = Column(Float)
close_rate = Column(Float) close_rate = Column(Float)
close_rate_requested = Column(Float) close_rate_requested = Column(Float)
close_profit = Column(Float) close_profit = Column(Float)
@@ -252,7 +260,7 @@ class Trade(_DECL_BASE):
def __init__(self, **kwargs): def __init__(self, **kwargs):
super().__init__(**kwargs) super().__init__(**kwargs)
self.recalc_open_trade_price() self.recalc_open_trade_value()
def __repr__(self): def __repr__(self):
open_since = self.open_date.strftime(DATETIME_PRINT_FORMAT) if self.is_open else 'closed' open_since = self.open_date.strftime(DATETIME_PRINT_FORMAT) if self.is_open else 'closed'
@@ -284,7 +292,7 @@ class Trade(_DECL_BASE):
'open_timestamp': int(self.open_date.replace(tzinfo=timezone.utc).timestamp() * 1000), 'open_timestamp': int(self.open_date.replace(tzinfo=timezone.utc).timestamp() * 1000),
'open_rate': self.open_rate, 'open_rate': self.open_rate,
'open_rate_requested': self.open_rate_requested, 'open_rate_requested': self.open_rate_requested,
'open_trade_price': round(self.open_trade_price, 8), 'open_trade_value': round(self.open_trade_value, 8),
'close_date_hum': (arrow.get(self.close_date).humanize() 'close_date_hum': (arrow.get(self.close_date).humanize()
if self.close_date else None), if self.close_date else None),
@@ -298,6 +306,11 @@ class Trade(_DECL_BASE):
'close_profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None, 'close_profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None,
'close_profit_abs': self.close_profit_abs, # Deprecated 'close_profit_abs': self.close_profit_abs, # Deprecated
'trade_duration_s': (int((self.close_date - self.open_date).total_seconds())
if self.close_date else None),
'trade_duration': (int((self.close_date - self.open_date).total_seconds() // 60)
if self.close_date else None),
'profit_ratio': self.close_profit, 'profit_ratio': self.close_profit,
'profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None, 'profit_pct': round(self.close_profit * 100, 2) if self.close_profit else None,
'profit_abs': self.close_profit_abs, 'profit_abs': self.close_profit_abs,
@@ -323,6 +336,14 @@ class Trade(_DECL_BASE):
'open_order_id': self.open_order_id, 'open_order_id': self.open_order_id,
} }
@staticmethod
def reset_trades() -> None:
"""
Resets all trades. Only active for backtesting mode.
"""
if not Trade.use_db:
Trade.trades = []
def adjust_min_max_rates(self, current_price: float) -> None: def adjust_min_max_rates(self, current_price: float) -> None:
""" """
Adjust the max_rate and min_rate. Adjust the max_rate and min_rate.
@@ -330,6 +351,12 @@ class Trade(_DECL_BASE):
self.max_rate = max(current_price, self.max_rate or self.open_rate) self.max_rate = max(current_price, self.max_rate or self.open_rate)
self.min_rate = min(current_price, self.min_rate or self.open_rate) self.min_rate = min(current_price, self.min_rate or self.open_rate)
def _set_new_stoploss(self, new_loss: float, stoploss: float):
"""Assign new stop value"""
self.stop_loss = new_loss
self.stop_loss_pct = -1 * abs(stoploss)
self.stoploss_last_update = datetime.utcnow()
def adjust_stop_loss(self, current_price: float, stoploss: float, def adjust_stop_loss(self, current_price: float, stoploss: float,
initial: bool = False) -> None: initial: bool = False) -> None:
""" """
@@ -348,19 +375,15 @@ class Trade(_DECL_BASE):
# no stop loss assigned yet # no stop loss assigned yet
if not self.stop_loss: if not self.stop_loss:
logger.debug(f"{self.pair} - Assigning new stoploss...") logger.debug(f"{self.pair} - Assigning new stoploss...")
self.stop_loss = new_loss self._set_new_stoploss(new_loss, stoploss)
self.stop_loss_pct = -1 * abs(stoploss)
self.initial_stop_loss = new_loss self.initial_stop_loss = new_loss
self.initial_stop_loss_pct = -1 * abs(stoploss) self.initial_stop_loss_pct = -1 * abs(stoploss)
self.stoploss_last_update = datetime.utcnow()
# evaluate if the stop loss needs to be updated # evaluate if the stop loss needs to be updated
else: else:
if new_loss > self.stop_loss: # stop losses only walk up, never down! if new_loss > self.stop_loss: # stop losses only walk up, never down!
logger.debug(f"{self.pair} - Adjusting stoploss...") logger.debug(f"{self.pair} - Adjusting stoploss...")
self.stop_loss = new_loss self._set_new_stoploss(new_loss, stoploss)
self.stop_loss_pct = -1 * abs(stoploss)
self.stoploss_last_update = datetime.utcnow()
else: else:
logger.debug(f"{self.pair} - Keeping current stoploss...") logger.debug(f"{self.pair} - Keeping current stoploss...")
@@ -389,7 +412,7 @@ class Trade(_DECL_BASE):
# Update open rate and actual amount # Update open rate and actual amount
self.open_rate = Decimal(safe_value_fallback(order, 'average', 'price')) self.open_rate = Decimal(safe_value_fallback(order, 'average', 'price'))
self.amount = Decimal(safe_value_fallback(order, 'filled', 'amount')) self.amount = Decimal(safe_value_fallback(order, 'filled', 'amount'))
self.recalc_open_trade_price() self.recalc_open_trade_value()
if self.is_open: if self.is_open:
logger.info(f'{order_type.upper()}_BUY has been fulfilled for {self}.') logger.info(f'{order_type.upper()}_BUY has been fulfilled for {self}.')
self.open_order_id = None self.open_order_id = None
@@ -407,7 +430,7 @@ class Trade(_DECL_BASE):
raise ValueError(f'Unknown order type: {order_type}') raise ValueError(f'Unknown order type: {order_type}')
cleanup_db() cleanup_db()
def close(self, rate: float) -> None: def close(self, rate: float, *, show_msg: bool = True) -> None:
""" """
Sets close_rate to the given rate, calculates total profit Sets close_rate to the given rate, calculates total profit
and marks trade as closed and marks trade as closed
@@ -419,10 +442,11 @@ class Trade(_DECL_BASE):
self.is_open = False self.is_open = False
self.sell_order_status = 'closed' self.sell_order_status = 'closed'
self.open_order_id = None self.open_order_id = None
logger.info( if show_msg:
'Marking %s as closed as the trade is fulfilled and found no open orders for it.', logger.info(
self 'Marking %s as closed as the trade is fulfilled and found no open orders for it.',
) self
)
def update_fee(self, fee_cost: float, fee_currency: Optional[str], fee_rate: Optional[float], def update_fee(self, fee_cost: float, fee_currency: Optional[str], fee_rate: Optional[float],
side: str) -> None: side: str) -> None:
@@ -464,7 +488,7 @@ class Trade(_DECL_BASE):
Trade.session.delete(self) Trade.session.delete(self)
Trade.session.flush() Trade.session.flush()
def _calc_open_trade_price(self) -> float: def _calc_open_trade_value(self) -> float:
""" """
Calculate the open_rate including open_fee. Calculate the open_rate including open_fee.
:return: Price in of the open trade incl. Fees :return: Price in of the open trade incl. Fees
@@ -473,14 +497,14 @@ class Trade(_DECL_BASE):
fees = buy_trade * Decimal(self.fee_open) fees = buy_trade * Decimal(self.fee_open)
return float(buy_trade + fees) return float(buy_trade + fees)
def recalc_open_trade_price(self) -> None: def recalc_open_trade_value(self) -> None:
""" """
Recalculate open_trade_price. Recalculate open_trade_value.
Must be called whenever open_rate or fee_open is changed. Must be called whenever open_rate or fee_open is changed.
""" """
self.open_trade_price = self._calc_open_trade_price() self.open_trade_value = self._calc_open_trade_value()
def calc_close_trade_price(self, rate: Optional[float] = None, def calc_close_trade_value(self, rate: Optional[float] = None,
fee: Optional[float] = None) -> float: fee: Optional[float] = None) -> float:
""" """
Calculate the close_rate including fee Calculate the close_rate including fee
@@ -507,11 +531,11 @@ class Trade(_DECL_BASE):
If rate is not set self.close_rate will be used If rate is not set self.close_rate will be used
:return: profit in stake currency as float :return: profit in stake currency as float
""" """
close_trade_price = self.calc_close_trade_price( close_trade_value = self.calc_close_trade_value(
rate=(rate or self.close_rate), rate=(rate or self.close_rate),
fee=(fee or self.fee_close) fee=(fee or self.fee_close)
) )
profit = close_trade_price - self.open_trade_price profit = close_trade_value - self.open_trade_value
return float(f"{profit:.8f}") return float(f"{profit:.8f}")
def calc_profit_ratio(self, rate: Optional[float] = None, def calc_profit_ratio(self, rate: Optional[float] = None,
@@ -523,11 +547,11 @@ class Trade(_DECL_BASE):
:param fee: fee to use on the close rate (optional). :param fee: fee to use on the close rate (optional).
:return: profit ratio as float :return: profit ratio as float
""" """
close_trade_price = self.calc_close_trade_price( close_trade_value = self.calc_close_trade_value(
rate=(rate or self.close_rate), rate=(rate or self.close_rate),
fee=(fee or self.fee_close) fee=(fee or self.fee_close)
) )
profit_ratio = (close_trade_price / self.open_trade_price) - 1 profit_ratio = (close_trade_value / self.open_trade_value) - 1
return float(f"{profit_ratio:.8f}") return float(f"{profit_ratio:.8f}")
def select_order(self, order_side: str, is_open: Optional[bool]) -> Optional[Order]: def select_order(self, order_side: str, is_open: Optional[bool]) -> Optional[Order]:
@@ -562,6 +586,43 @@ class Trade(_DECL_BASE):
else: else:
return Trade.query return Trade.query
@staticmethod
def get_trades_proxy(*, pair: str = None, is_open: bool = None,
open_date: datetime = None, close_date: datetime = None,
) -> List['Trade']:
"""
Helper function to query Trades.
Returns a List of trades, filtered on the parameters given.
In live mode, converts the filter to a database query and returns all rows
In Backtest mode, uses filters on Trade.trades to get the result.
:return: unsorted List[Trade]
"""
if Trade.use_db:
trade_filter = []
if pair:
trade_filter.append(Trade.pair == pair)
if open_date:
trade_filter.append(Trade.open_date > open_date)
if close_date:
trade_filter.append(Trade.close_date > close_date)
if is_open is not None:
trade_filter.append(Trade.is_open.is_(is_open))
return Trade.get_trades(trade_filter).all()
else:
# Offline mode - without database
sel_trades = [trade for trade in Trade.trades]
if pair:
sel_trades = [trade for trade in sel_trades if trade.pair == pair]
if open_date:
sel_trades = [trade for trade in sel_trades if trade.open_date > open_date]
if close_date:
sel_trades = [trade for trade in sel_trades if trade.close_date
and trade.close_date > close_date]
if is_open is not None:
sel_trades = [trade for trade in sel_trades if trade.is_open == is_open]
return sel_trades
@staticmethod @staticmethod
def get_open_trades() -> List[Any]: def get_open_trades() -> List[Any]:
""" """
@@ -688,7 +749,7 @@ class PairLock(_DECL_BASE):
@staticmethod @staticmethod
def query_pair_locks(pair: Optional[str], now: datetime) -> Query: def query_pair_locks(pair: Optional[str], now: datetime) -> Query:
""" """
Get all locks for this pair Get all currently active locks for this pair
:param pair: Pair to check for. Returns all current locks if pair is empty :param pair: Pair to check for. Returns all current locks if pair is empty
:param now: Datetime object (generated via datetime.now(timezone.utc)). :param now: Datetime object (generated via datetime.now(timezone.utc)).
""" """

View File

@@ -22,10 +22,27 @@ class PairLocks():
timeframe: str = '' timeframe: str = ''
@staticmethod @staticmethod
def lock_pair(pair: str, until: datetime, reason: str = None) -> None: def reset_locks() -> None:
"""
Resets all locks. Only active for backtesting mode.
"""
if not PairLocks.use_db:
PairLocks.locks = []
@staticmethod
def lock_pair(pair: str, until: datetime, reason: str = None, *, now: datetime = None) -> None:
"""
Create PairLock from now to "until".
Uses database by default, unless PairLocks.use_db is set to False,
in which case a list is maintained.
:param pair: pair to lock. use '*' to lock all pairs
:param until: End time of the lock. Will be rounded up to the next candle.
:param reason: Reason string that will be shown as reason for the lock
:param now: Current timestamp. Used to determine lock start time.
"""
lock = PairLock( lock = PairLock(
pair=pair, pair=pair,
lock_time=datetime.now(timezone.utc), lock_time=now or datetime.now(timezone.utc),
lock_end_time=timeframe_to_next_date(PairLocks.timeframe, until), lock_end_time=timeframe_to_next_date(PairLocks.timeframe, until),
reason=reason, reason=reason,
active=True active=True
@@ -57,6 +74,15 @@ class PairLocks():
)] )]
return locks return locks
@staticmethod
def get_pair_longest_lock(pair: str, now: Optional[datetime] = None) -> Optional[PairLock]:
"""
Get the lock that expires the latest for the pair given.
"""
locks = PairLocks.get_pair_locks(pair, now)
locks = sorted(locks, key=lambda l: l.lock_end_time, reverse=True)
return locks[0] if locks else None
@staticmethod @staticmethod
def unlock_pair(pair: str, now: Optional[datetime] = None) -> None: def unlock_pair(pair: str, now: Optional[datetime] = None) -> None:
""" """

View File

@@ -13,6 +13,7 @@ from freqtrade.data.history import get_timerange, load_data
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_prev_date, timeframe_to_seconds from freqtrade.exchange import timeframe_to_prev_date, timeframe_to_seconds
from freqtrade.misc import pair_to_filename from freqtrade.misc import pair_to_filename
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
from freqtrade.resolvers import ExchangeResolver, StrategyResolver from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.strategy import IStrategy from freqtrade.strategy import IStrategy
@@ -29,16 +30,16 @@ except ImportError:
exit(1) exit(1)
def init_plotscript(config, startup_candles: int = 0): def init_plotscript(config, markets: List, startup_candles: int = 0):
""" """
Initialize objects needed for plotting Initialize objects needed for plotting
:return: Dict with candle (OHLCV) data, trades and pairs :return: Dict with candle (OHLCV) data, trades and pairs
""" """
if "pairs" in config: if "pairs" in config:
pairs = config['pairs'] pairs = expand_pairlist(config['pairs'], markets)
else: else:
pairs = config['exchange']['pair_whitelist'] pairs = expand_pairlist(config['exchange']['pair_whitelist'], markets)
# Set timerange to use # Set timerange to use
timerange = TimeRange.parse_timerange(config.get('timerange')) timerange = TimeRange.parse_timerange(config.get('timerange'))
@@ -52,7 +53,7 @@ def init_plotscript(config, startup_candles: int = 0):
data_format=config.get('dataformat_ohlcv', 'json'), data_format=config.get('dataformat_ohlcv', 'json'),
) )
if startup_candles: if startup_candles and data:
min_date, max_date = get_timerange(data) min_date, max_date = get_timerange(data)
logger.info(f"Loading data from {min_date} to {max_date}") logger.info(f"Loading data from {min_date} to {max_date}")
timerange.adjust_start_if_necessary(timeframe_to_seconds(config.get('timeframe', '5m')), timerange.adjust_start_if_necessary(timeframe_to_seconds(config.get('timeframe', '5m')),
@@ -66,14 +67,16 @@ def init_plotscript(config, startup_candles: int = 0):
if not filename.is_dir() and not filename.is_file(): if not filename.is_dir() and not filename.is_file():
logger.warning("Backtest file is missing skipping trades.") logger.warning("Backtest file is missing skipping trades.")
no_trades = True no_trades = True
try:
trades = load_trades( trades = load_trades(
config['trade_source'], config['trade_source'],
db_url=config.get('db_url'), db_url=config.get('db_url'),
exportfilename=filename, exportfilename=filename,
no_trades=no_trades, no_trades=no_trades,
strategy=config.get('strategy'), strategy=config.get('strategy'),
) )
except ValueError as e:
raise OperationalException(e) from e
trades = trim_dataframe(trades, timerange, 'open_date') trades = trim_dataframe(trades, timerange, 'open_date')
return {"ohlcv": data, return {"ohlcv": data,
@@ -174,10 +177,10 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
# Trades can be empty # Trades can be empty
if trades is not None and len(trades) > 0: if trades is not None and len(trades) > 0:
# Create description for sell summarizing the trade # Create description for sell summarizing the trade
trades['desc'] = trades.apply(lambda row: f"{round(row['profit_percent'] * 100, 1)}%, " trades['desc'] = trades.apply(lambda row: f"{round(row['profit_ratio'] * 100, 1)}%, "
f"{row['sell_reason']}, " f"{row['sell_reason']}, "
f"{row['trade_duration']} min", f"{row['trade_duration']} min",
axis=1) axis=1)
trade_buys = go.Scatter( trade_buys = go.Scatter(
x=trades["open_date"], x=trades["open_date"],
y=trades["open_rate"], y=trades["open_rate"],
@@ -194,9 +197,9 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
) )
trade_sells = go.Scatter( trade_sells = go.Scatter(
x=trades.loc[trades['profit_percent'] > 0, "close_date"], x=trades.loc[trades['profit_ratio'] > 0, "close_date"],
y=trades.loc[trades['profit_percent'] > 0, "close_rate"], y=trades.loc[trades['profit_ratio'] > 0, "close_rate"],
text=trades.loc[trades['profit_percent'] > 0, "desc"], text=trades.loc[trades['profit_ratio'] > 0, "desc"],
mode='markers', mode='markers',
name='Sell - Profit', name='Sell - Profit',
marker=dict( marker=dict(
@@ -207,9 +210,9 @@ def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
) )
) )
trade_sells_loss = go.Scatter( trade_sells_loss = go.Scatter(
x=trades.loc[trades['profit_percent'] <= 0, "close_date"], x=trades.loc[trades['profit_ratio'] <= 0, "close_date"],
y=trades.loc[trades['profit_percent'] <= 0, "close_rate"], y=trades.loc[trades['profit_ratio'] <= 0, "close_rate"],
text=trades.loc[trades['profit_percent'] <= 0, "desc"], text=trades.loc[trades['profit_ratio'] <= 0, "desc"],
mode='markers', mode='markers',
name='Sell - Loss', name='Sell - Loss',
marker=dict( marker=dict(
@@ -263,6 +266,65 @@ def create_plotconfig(indicators1: List[str], indicators2: List[str],
return plot_config return plot_config
def plot_area(fig, row: int, data: pd.DataFrame, indicator_a: str,
indicator_b: str, label: str = "",
fill_color: str = "rgba(0,176,246,0.2)") -> make_subplots:
""" Creates a plot for the area between two traces and adds it to fig.
:param fig: Plot figure to append to
:param row: row number for this plot
:param data: candlestick DataFrame
:param indicator_a: indicator name as populated in stragetie
:param indicator_b: indicator name as populated in stragetie
:param label: label for the filled area
:param fill_color: color to be used for the filled area
:return: fig with added filled_traces plot
"""
if indicator_a in data and indicator_b in data:
# make lines invisible to get the area plotted, only.
line = {'color': 'rgba(255,255,255,0)'}
# TODO: Figure out why scattergl causes problems plotly/plotly.js#2284
trace_a = go.Scatter(x=data.date, y=data[indicator_a],
showlegend=False,
line=line)
trace_b = go.Scatter(x=data.date, y=data[indicator_b], name=label,
fill="tonexty", fillcolor=fill_color,
line=line)
fig.add_trace(trace_a, row, 1)
fig.add_trace(trace_b, row, 1)
return fig
def add_areas(fig, row: int, data: pd.DataFrame, indicators) -> make_subplots:
""" Adds all area plots (specified in plot_config) to fig.
:param fig: Plot figure to append to
:param row: row number for this plot
:param data: candlestick DataFrame
:param indicators: dict with indicators. ie.: plot_config['main_plot'] or
plot_config['subplots'][subplot_label]
:return: fig with added filled_traces plot
"""
for indicator, ind_conf in indicators.items():
if 'fill_to' in ind_conf:
indicator_b = ind_conf['fill_to']
if indicator in data and indicator_b in data:
label = ind_conf.get('fill_label',
f'{indicator}<>{indicator_b}')
fill_color = ind_conf.get('fill_color', 'rgba(0,176,246,0.2)')
fig = plot_area(fig, row, data, indicator, indicator_b,
label=label, fill_color=fill_color)
elif indicator not in data:
logger.info(
'Indicator "%s" ignored. Reason: This indicator is not '
'found in your strategy.', indicator
)
elif indicator_b not in data:
logger.info(
'fill_to: "%s" ignored. Reason: This indicator is not '
'in your strategy.', indicator_b
)
return fig
def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFrame = None, *, def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFrame = None, *,
indicators1: List[str] = [], indicators1: List[str] = [],
indicators2: List[str] = [], indicators2: List[str] = [],
@@ -280,7 +342,6 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
:return: Plotly figure :return: Plotly figure
""" """
plot_config = create_plotconfig(indicators1, indicators2, plot_config) plot_config = create_plotconfig(indicators1, indicators2, plot_config)
rows = 2 + len(plot_config['subplots']) rows = 2 + len(plot_config['subplots'])
row_widths = [1 for _ in plot_config['subplots']] row_widths = [1 for _ in plot_config['subplots']]
# Define the graph # Define the graph
@@ -346,36 +407,20 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
fig.add_trace(sells, 1, 1) fig.add_trace(sells, 1, 1)
else: else:
logger.warning("No sell-signals found.") logger.warning("No sell-signals found.")
# Add Bollinger Bands
# TODO: Figure out why scattergl causes problems plotly/plotly.js#2284 fig = plot_area(fig, 1, data, 'bb_lowerband', 'bb_upperband',
if 'bb_lowerband' in data and 'bb_upperband' in data: label="Bollinger Band")
bb_lower = go.Scatter( # prevent bb_lower and bb_upper from plotting
x=data.date, try:
y=data.bb_lowerband, del plot_config['main_plot']['bb_lowerband']
showlegend=False, del plot_config['main_plot']['bb_upperband']
line={'color': 'rgba(255,255,255,0)'}, except KeyError:
) pass
bb_upper = go.Scatter( # main plot goes to row 1
x=data.date,
y=data.bb_upperband,
name='Bollinger Band',
fill="tonexty",
fillcolor="rgba(0,176,246,0.2)",
line={'color': 'rgba(255,255,255,0)'},
)
fig.add_trace(bb_lower, 1, 1)
fig.add_trace(bb_upper, 1, 1)
if ('bb_upperband' in plot_config['main_plot']
and 'bb_lowerband' in plot_config['main_plot']):
del plot_config['main_plot']['bb_upperband']
del plot_config['main_plot']['bb_lowerband']
# Add indicators to main plot
fig = add_indicators(fig=fig, row=1, indicators=plot_config['main_plot'], data=data) fig = add_indicators(fig=fig, row=1, indicators=plot_config['main_plot'], data=data)
fig = add_areas(fig, 1, data, plot_config['main_plot'])
fig = plot_trades(fig, trades) fig = plot_trades(fig, trades)
# sub plot: Volume goes to row 2
# Volume goes to row 2
volume = go.Bar( volume = go.Bar(
x=data['date'], x=data['date'],
y=data['volume'], y=data['volume'],
@@ -384,13 +429,14 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
marker_line_color='DarkSlateGrey' marker_line_color='DarkSlateGrey'
) )
fig.add_trace(volume, 2, 1) fig.add_trace(volume, 2, 1)
# add each sub plot to a separate row
# Add indicators to separate row for i, label in enumerate(plot_config['subplots']):
for i, name in enumerate(plot_config['subplots']): sub_config = plot_config['subplots'][label]
fig = add_indicators(fig=fig, row=3 + i, row = 3 + i
indicators=plot_config['subplots'][name], fig = add_indicators(fig=fig, row=row, indicators=sub_config,
data=data) data=data)
# fill area between indicators ( 'fill_to': 'other_indicator')
fig = add_areas(fig, row, data, sub_config)
return fig return fig
@@ -401,6 +447,8 @@ def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame],
# Trim trades to available OHLCV data # Trim trades to available OHLCV data
trades = extract_trades_of_period(df_comb, trades, date_index=True) trades = extract_trades_of_period(df_comb, trades, date_index=True)
if len(trades) == 0:
raise OperationalException('No trades found in selected timerange.')
# Add combined cumulative profit # Add combined cumulative profit
df_comb = create_cum_profit(df_comb, trades, 'cum_profit', timeframe) df_comb = create_cum_profit(df_comb, trades, 'cum_profit', timeframe)
@@ -482,7 +530,7 @@ def load_and_plot_trades(config: Dict[str, Any]):
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config) exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config)
IStrategy.dp = DataProvider(config, exchange) IStrategy.dp = DataProvider(config, exchange)
plot_elements = init_plotscript(config, strategy.startup_candle_count) plot_elements = init_plotscript(config, list(exchange.markets), strategy.startup_candle_count)
timerange = plot_elements['timerange'] timerange = plot_elements['timerange']
trades = plot_elements['trades'] trades = plot_elements['trades']
pair_counter = 0 pair_counter = 0
@@ -517,7 +565,8 @@ def plot_profit(config: Dict[str, Any]) -> None:
But should be somewhat proportional, and therefor useful But should be somewhat proportional, and therefor useful
in helping out to find a good algorithm. in helping out to find a good algorithm.
""" """
plot_elements = init_plotscript(config) exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config)
plot_elements = init_plotscript(config, list(exchange.markets))
trades = plot_elements['trades'] trades = plot_elements['trades']
# Filter trades to relevant pairs # Filter trades to relevant pairs
# Remove open pairs - we don't know the profit yet so can't calculate profit for these. # Remove open pairs - we don't know the profit yet so can't calculate profit for these.

View File

@@ -2,13 +2,15 @@
Minimum age (days listed) pair list filter Minimum age (days listed) pair list filter
""" """
import logging import logging
from typing import Any, Dict from copy import deepcopy
from typing import Any, Dict, List, Optional
import arrow import arrow
from pandas import DataFrame
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.misc import plural from freqtrade.misc import plural
from freqtrade.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -28,10 +30,10 @@ class AgeFilter(IPairList):
if self._min_days_listed < 1: if self._min_days_listed < 1:
raise OperationalException("AgeFilter requires min_days_listed to be >= 1") raise OperationalException("AgeFilter requires min_days_listed to be >= 1")
if self._min_days_listed > exchange.ohlcv_candle_limit: if self._min_days_listed > exchange.ohlcv_candle_limit('1d'):
raise OperationalException("AgeFilter requires min_days_listed to not exceed " raise OperationalException("AgeFilter requires min_days_listed to not exceed "
"exchange max request size " "exchange max request size "
f"({exchange.ohlcv_candle_limit})") f"({exchange.ohlcv_candle_limit('1d')})")
@property @property
def needstickers(self) -> bool: def needstickers(self) -> bool:
@@ -40,7 +42,7 @@ class AgeFilter(IPairList):
If no Pairlist requires tickers, an empty Dict is passed If no Pairlist requires tickers, an empty Dict is passed
as tickers argument to filter_pairlist as tickers argument to filter_pairlist
""" """
return True return False
def short_desc(self) -> str: def short_desc(self) -> str:
""" """
@@ -49,36 +51,49 @@ class AgeFilter(IPairList):
return (f"{self.name} - Filtering pairs with age less than " return (f"{self.name} - Filtering pairs with age less than "
f"{self._min_days_listed} {plural(self._min_days_listed, 'day')}.") f"{self._min_days_listed} {plural(self._min_days_listed, 'day')}.")
def _validate_pair(self, ticker: Dict) -> bool: def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
""" """
Validate age for the ticker :param pairlist: pairlist to filter or sort
:param ticker: ticker dict as returned from ccxt.load_markets() :param tickers: Tickers (from exchange.get_tickers()). May be cached.
:return: True if the pair can stay, False if it should be removed :return: new allowlist
""" """
needed_pairs = [(p, '1d') for p in pairlist if p not in self._symbolsChecked]
# Check symbol in cache if not needed_pairs:
if ticker['symbol'] in self._symbolsChecked: return pairlist
return True
since_ms = int(arrow.utcnow() since_ms = int(arrow.utcnow()
.floor('day') .floor('day')
.shift(days=-self._min_days_listed) .shift(days=-self._min_days_listed - 1)
.float_timestamp) * 1000 .float_timestamp) * 1000
candles = self._exchange.refresh_latest_ohlcv(needed_pairs, since_ms=since_ms, cache=False)
if self._enabled:
for p in deepcopy(pairlist):
daily_candles = candles[(p, '1d')] if (p, '1d') in candles else None
if not self._validate_pair_loc(p, daily_candles):
pairlist.remove(p)
logger.info(f"Validated {len(pairlist)} pairs.")
return pairlist
daily_candles = self._exchange.get_historic_ohlcv(pair=ticker['symbol'], def _validate_pair_loc(self, pair: str, daily_candles: Optional[DataFrame]) -> bool:
timeframe='1d', """
since_ms=since_ms) Validate age for the ticker
:param pair: Pair that's currently validated
:param ticker: ticker dict as returned from ccxt.load_markets()
:return: True if the pair can stay, false if it should be removed
"""
# Check symbol in cache
if pair in self._symbolsChecked:
return True
if daily_candles is not None: if daily_candles is not None:
if len(daily_candles) > self._min_days_listed: if len(daily_candles) > self._min_days_listed:
# We have fetched at least the minimum required number of daily candles # We have fetched at least the minimum required number of daily candles
# Add to cache, store the time we last checked this symbol # Add to cache, store the time we last checked this symbol
self._symbolsChecked[ticker['symbol']] = int(arrow.utcnow().float_timestamp) * 1000 self._symbolsChecked[pair] = int(arrow.utcnow().float_timestamp) * 1000
return True return True
else: else:
self.log_on_refresh(logger.info, f"Removed {ticker['symbol']} from whitelist, " self.log_once(f"Removed {pair} from whitelist, because age "
f"because age {len(daily_candles)} is less than " f"{len(daily_candles)} is less than {self._min_days_listed} "
f"{self._min_days_listed} " f"{plural(self._min_days_listed, 'day')}", logger.info)
f"{plural(self._min_days_listed, 'day')}")
return False return False
return False return False

View File

@@ -6,16 +6,15 @@ from abc import ABC, abstractmethod, abstractproperty
from copy import deepcopy from copy import deepcopy
from typing import Any, Dict, List from typing import Any, Dict, List
from cachetools import TTLCache, cached
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.exchange import market_is_active from freqtrade.exchange import market_is_active
from freqtrade.mixins import LoggingMixin
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class IPairList(ABC): class IPairList(LoggingMixin, ABC):
def __init__(self, exchange, pairlistmanager, def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any], config: Dict[str, Any], pairlistconfig: Dict[str, Any],
@@ -36,7 +35,7 @@ class IPairList(ABC):
self._pairlist_pos = pairlist_pos self._pairlist_pos = pairlist_pos
self.refresh_period = self._pairlistconfig.get('refresh_period', 1800) self.refresh_period = self._pairlistconfig.get('refresh_period', 1800)
self._last_refresh = 0 self._last_refresh = 0
self._log_cache: TTLCache = TTLCache(maxsize=1024, ttl=self.refresh_period) LoggingMixin.__init__(self, logger, self.refresh_period)
@property @property
def name(self) -> str: def name(self) -> str:
@@ -46,24 +45,6 @@ class IPairList(ABC):
""" """
return self.__class__.__name__ return self.__class__.__name__
def log_on_refresh(self, logmethod, message: str) -> None:
"""
Logs message - not more often than "refresh_period" to avoid log spamming
Logs the log-message as debug as well to simplify debugging.
:param logmethod: Function that'll be called. Most likely `logger.info`.
:param message: String containing the message to be sent to the function.
:return: None.
"""
@cached(cache=self._log_cache)
def _log_on_refresh(message: str):
logmethod(message)
# Log as debug first
logger.debug(message)
# Call hidden function.
_log_on_refresh(message)
@abstractproperty @abstractproperty
def needstickers(self) -> bool: def needstickers(self) -> bool:
""" """
@@ -79,13 +60,14 @@ class IPairList(ABC):
-> Please overwrite in subclasses -> Please overwrite in subclasses
""" """
def _validate_pair(self, ticker) -> bool: def _validate_pair(self, pair: str, ticker: Dict[str, Any]) -> bool:
""" """
Check one pair against Pairlist Handler's specific conditions. Check one pair against Pairlist Handler's specific conditions.
Either implement it in the Pairlist Handler or override the generic Either implement it in the Pairlist Handler or override the generic
filter_pairlist() method. filter_pairlist() method.
:param pair: Pair that's currently validated
:param ticker: ticker dict as returned from ccxt.load_markets() :param ticker: ticker dict as returned from ccxt.load_markets()
:return: True if the pair can stay, false if it should be removed :return: True if the pair can stay, false if it should be removed
""" """
@@ -128,7 +110,7 @@ class IPairList(ABC):
# Copy list since we're modifying this list # Copy list since we're modifying this list
for p in deepcopy(pairlist): for p in deepcopy(pairlist):
# Filter out assets # Filter out assets
if not self._validate_pair(tickers[p]): if not self._validate_pair(p, tickers[p] if p in tickers else {}):
pairlist.remove(p) pairlist.remove(p)
return pairlist return pairlist
@@ -142,10 +124,21 @@ class IPairList(ABC):
""" """
return self._pairlistmanager.verify_blacklist(pairlist, logmethod) return self._pairlistmanager.verify_blacklist(pairlist, logmethod)
def verify_whitelist(self, pairlist: List[str], logmethod,
keep_invalid: bool = False) -> List[str]:
"""
Proxy method to verify_whitelist for easy access for child classes.
:param pairlist: Pairlist to validate
:param logmethod: Function that'll be called, `logger.info` or `logger.warning`
:param keep_invalid: If sets to True, drops invalid pairs silently while expanding regexes.
:return: pairlist - whitelisted pairs
"""
return self._pairlistmanager.verify_whitelist(pairlist, logmethod, keep_invalid)
def _whitelist_for_active_markets(self, pairlist: List[str]) -> List[str]: def _whitelist_for_active_markets(self, pairlist: List[str]) -> List[str]:
""" """
Check available markets and remove pair from whitelist if necessary Check available markets and remove pair from whitelist if necessary
:param whitelist: the sorted list of pairs the user might want to trade :param pairlist: the sorted list of pairs the user might want to trade
:return: the list of pairs the user wants to trade without those unavailable or :return: the list of pairs the user wants to trade without those unavailable or
black_listed black_listed
""" """
@@ -175,7 +168,7 @@ class IPairList(ABC):
# Check if market is active # Check if market is active
market = markets[pair] market = markets[pair]
if not market_is_active(market): if not market_is_active(market):
logger.info(f"Ignoring {pair} from whitelist. Market is not active.") self.log_once(f"Ignoring {pair} from whitelist. Market is not active.", logger.info)
continue continue
if pair not in sanitized_whitelist: if pair not in sanitized_whitelist:
sanitized_whitelist.append(pair) sanitized_whitelist.append(pair)

View File

@@ -0,0 +1,66 @@
"""
Performance pair list filter
"""
import logging
from typing import Any, Dict, List
import pandas as pd
from freqtrade.persistence import Trade
from freqtrade.plugins.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__)
class PerformanceFilter(IPairList):
def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
@property
def needstickers(self) -> bool:
"""
Boolean property defining if tickers are necessary.
If no Pairlist requries tickers, an empty List is passed
as tickers argument to filter_pairlist
"""
return False
def short_desc(self) -> str:
"""
Short allowlist method description - used for startup-messages
"""
return f"{self.name} - Sorting pairs by performance."
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
"""
Filters and sorts pairlist and returns the allowlist again.
Called on each bot iteration - please use internal caching if necessary
:param pairlist: pairlist to filter or sort
:param tickers: Tickers (from exchange.get_tickers()). May be cached.
:return: new allowlist
"""
# Get the trading performance for pairs from database
performance = pd.DataFrame(Trade.get_overall_performance())
# Skip performance-based sorting if no performance data is available
if len(performance) == 0:
return pairlist
# Get pairlist from performance dataframe values
list_df = pd.DataFrame({'pair': pairlist})
# Set initial value for pairs with no trades to 0
# Sort the list using:
# - primarily performance (high to low)
# - then count (low to high, so as to favor same performance with fewer trades)
# - then pair name alphametically
sorted_df = list_df.merge(performance, on='pair', how='left')\
.fillna(0).sort_values(by=['count', 'pair'], ascending=True)\
.sort_values(by=['profit'], ascending=False)
pairlist = sorted_df['pair'].tolist()
return pairlist

View File

@@ -5,7 +5,7 @@ import logging
from typing import Any, Dict from typing import Any, Dict
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -43,25 +43,25 @@ class PrecisionFilter(IPairList):
""" """
return f"{self.name} - Filtering untradable pairs." return f"{self.name} - Filtering untradable pairs."
def _validate_pair(self, ticker: dict) -> bool: def _validate_pair(self, pair: str, ticker: Dict[str, Any]) -> bool:
""" """
Check if pair has enough room to add a stoploss to avoid "unsellable" buys of very Check if pair has enough room to add a stoploss to avoid "unsellable" buys of very
low value pairs. low value pairs.
:param pair: Pair that's currently validated
:param ticker: ticker dict as returned from ccxt.load_markets() :param ticker: ticker dict as returned from ccxt.load_markets()
:return: True if the pair can stay, False if it should be removed :return: True if the pair can stay, false if it should be removed
""" """
stop_price = ticker['ask'] * self._stoploss stop_price = ticker['ask'] * self._stoploss
# Adjust stop-prices to precision # Adjust stop-prices to precision
sp = self._exchange.price_to_precision(ticker["symbol"], stop_price) sp = self._exchange.price_to_precision(pair, stop_price)
stop_gap_price = self._exchange.price_to_precision(ticker["symbol"], stop_price * 0.99) stop_gap_price = self._exchange.price_to_precision(pair, stop_price * 0.99)
logger.debug(f"{ticker['symbol']} - {sp} : {stop_gap_price}") logger.debug(f"{ticker['symbol']} - {sp} : {stop_gap_price}")
if sp <= stop_gap_price: if sp <= stop_gap_price:
self.log_on_refresh(logger.info, self.log_once(f"Removed {ticker['symbol']} from whitelist, because "
f"Removed {ticker['symbol']} from whitelist, " f"stop price {sp} would be <= stop limit {stop_gap_price}", logger.info)
f"because stop price {sp} would be <= stop limit {stop_gap_price}")
return False return False
return True return True

View File

@@ -5,7 +5,7 @@ import logging
from typing import Any, Dict from typing import Any, Dict
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -57,39 +57,40 @@ class PriceFilter(IPairList):
return f"{self.name} - No price filters configured." return f"{self.name} - No price filters configured."
def _validate_pair(self, ticker) -> bool: def _validate_pair(self, pair: str, ticker: Dict[str, Any]) -> bool:
""" """
Check if if one price-step (pip) is > than a certain barrier. Check if if one price-step (pip) is > than a certain barrier.
:param pair: Pair that's currently validated
:param ticker: ticker dict as returned from ccxt.load_markets() :param ticker: ticker dict as returned from ccxt.load_markets()
:return: True if the pair can stay, false if it should be removed :return: True if the pair can stay, false if it should be removed
""" """
if ticker['last'] is None or ticker['last'] == 0: if ticker['last'] is None or ticker['last'] == 0:
self.log_on_refresh(logger.info, self.log_once(f"Removed {pair} from whitelist, because "
f"Removed {ticker['symbol']} from whitelist, because " "ticker['last'] is empty (Usually no trade in the last 24h).",
"ticker['last'] is empty (Usually no trade in the last 24h).") logger.info)
return False return False
# Perform low_price_ratio check. # Perform low_price_ratio check.
if self._low_price_ratio != 0: if self._low_price_ratio != 0:
compare = self._exchange.price_get_one_pip(ticker['symbol'], ticker['last']) compare = self._exchange.price_get_one_pip(pair, ticker['last'])
changeperc = compare / ticker['last'] changeperc = compare / ticker['last']
if changeperc > self._low_price_ratio: if changeperc > self._low_price_ratio:
self.log_on_refresh(logger.info, f"Removed {ticker['symbol']} from whitelist, " self.log_once(f"Removed {pair} from whitelist, "
f"because 1 unit is {changeperc * 100:.3f}%") f"because 1 unit is {changeperc * 100:.3f}%", logger.info)
return False return False
# Perform min_price check. # Perform min_price check.
if self._min_price != 0: if self._min_price != 0:
if ticker['last'] < self._min_price: if ticker['last'] < self._min_price:
self.log_on_refresh(logger.info, f"Removed {ticker['symbol']} from whitelist, " self.log_once(f"Removed {pair} from whitelist, "
f"because last price < {self._min_price:.8f}") f"because last price < {self._min_price:.8f}", logger.info)
return False return False
# Perform max_price check. # Perform max_price check.
if self._max_price != 0: if self._max_price != 0:
if ticker['last'] > self._max_price: if ticker['last'] > self._max_price:
self.log_on_refresh(logger.info, f"Removed {ticker['symbol']} from whitelist, " self.log_once(f"Removed {ticker['symbol']} from whitelist, "
f"because last price > {self._max_price:.8f}") f"because last price > {self._max_price:.8f}", logger.info)
return False return False
return True return True

View File

@@ -5,7 +5,7 @@ import logging
import random import random
from typing import Any, Dict, List from typing import Any, Dict, List
from freqtrade.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)

View File

@@ -4,7 +4,7 @@ Spread pair list filter
import logging import logging
from typing import Any, Dict from typing import Any, Dict
from freqtrade.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -36,19 +36,22 @@ class SpreadFilter(IPairList):
return (f"{self.name} - Filtering pairs with ask/bid diff above " return (f"{self.name} - Filtering pairs with ask/bid diff above "
f"{self._max_spread_ratio * 100}%.") f"{self._max_spread_ratio * 100}%.")
def _validate_pair(self, ticker: dict) -> bool: def _validate_pair(self, pair: str, ticker: Dict[str, Any]) -> bool:
""" """
Validate spread for the ticker Validate spread for the ticker
:param pair: Pair that's currently validated
:param ticker: ticker dict as returned from ccxt.load_markets() :param ticker: ticker dict as returned from ccxt.load_markets()
:return: True if the pair can stay, False if it should be removed :return: True if the pair can stay, false if it should be removed
""" """
if 'bid' in ticker and 'ask' in ticker: if 'bid' in ticker and 'ask' in ticker and ticker['ask']:
spread = 1 - ticker['bid'] / ticker['ask'] spread = 1 - ticker['bid'] / ticker['ask']
if spread > self._max_spread_ratio: if spread > self._max_spread_ratio:
self.log_on_refresh(logger.info, f"Removed {ticker['symbol']} from whitelist, " self.log_once(f"Removed {pair} from whitelist, because spread "
f"because spread {spread * 100:.3f}% >" f"{spread * 100:.3f}% > {self._max_spread_ratio * 100}%",
f"{self._max_spread_ratio * 100}%") logger.info)
return False return False
else: else:
return True return True
self.log_once(f"Removed {pair} from whitelist due to invalid ticker data: {ticker}",
logger.info)
return False return False

View File

@@ -7,7 +7,7 @@ import logging
from typing import Any, Dict, List from typing import Any, Dict, List
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -50,9 +50,12 @@ class StaticPairList(IPairList):
:return: List of pairs :return: List of pairs
""" """
if self._allow_inactive: if self._allow_inactive:
return self._config['exchange']['pair_whitelist'] return self.verify_whitelist(
self._config['exchange']['pair_whitelist'], logger.info, keep_invalid=True
)
else: else:
return self._whitelist_for_active_markets(self._config['exchange']['pair_whitelist']) return self._whitelist_for_active_markets(
self.verify_whitelist(self._config['exchange']['pair_whitelist'], logger.info))
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]: def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
""" """

View File

@@ -8,7 +8,7 @@ from datetime import datetime
from typing import Any, Dict, List from typing import Any, Dict, List
from freqtrade.exceptions import OperationalException from freqtrade.exceptions import OperationalException
from freqtrade.pairlist.IPairList import IPairList from freqtrade.plugins.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -111,6 +111,6 @@ class VolumePairList(IPairList):
# Limit pairlist to the requested number of pairs # Limit pairlist to the requested number of pairs
pairs = pairs[:self._number_pairs] pairs = pairs[:self._number_pairs]
self.log_on_refresh(logger.info, f"Searching {self._number_pairs} pairs: {pairs}") self.log_once(f"Searching {self._number_pairs} pairs: {pairs}", logger.info)
return pairs return pairs

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