Compare commits

..

691 Commits

Author SHA1 Message Date
Matthias
9abfdaaaa2 Merge pull request #3119 from freqtrade/new_release
New release 2020.3
2020-03-28 19:49:39 +01:00
Matthias
da191e4ac8 Version bump 2020.3 2020-03-28 17:45:44 +01:00
Matthias
75de96ae6d Merge branch 'master' into new_release 2020-03-28 17:45:17 +01:00
hroff-1902
39ea126698 Merge pull request #3090 from freqtrade/remove_partial_candle_docs
Remove partial candle documentation
2020-03-25 19:55:44 +03:00
Matthias
53efc56b8a Merge pull request #3096 from freqtrade/max_open_trades
Update max_open_trades documentation
2020-03-25 09:37:40 +01:00
Matthias
6f687c97ce Update docs/configuration.md
Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com>
2020-03-25 09:24:37 +01:00
hroff-1902
be5b68627c Merge pull request #3093 from freqtrade/trades_abs_profit
Add close_profit_abs column
2020-03-25 11:13:56 +03:00
Matthias
dfac7448d1 fix typo
Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com>
2020-03-25 08:33:22 +01:00
Matthias
4ea6f9d7eb Merge pull request #3110 from freqtrade/fix_random_test
[minor] Test warnings with filter always on
2020-03-25 08:32:45 +01:00
Matthias
d9a5e1cd48 Update docs/exchanges.md
Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com>
2020-03-25 08:30:18 +01:00
hroff-1902
65f19fde40 Merge pull request #3097 from freqtrade/parse_config_error
Improve config parse error handling
2020-03-25 09:18:16 +03:00
Matthias
be41981ef0 Test warnings with filter always on 2020-03-24 20:10:15 +01:00
Matthias
030c487d6b Merge pull request #3100 from freqtrade/dependabot/pip/develop/pandas-1.0.3
Bump pandas from 1.0.2 to 1.0.3
2020-03-23 10:41:06 +01:00
Matthias
51edddcaf6 Merge pull request #3101 from freqtrade/dependabot/pip/develop/ccxt-1.24.83
Bump ccxt from 1.24.31 to 1.24.83
2020-03-23 10:21:46 +01:00
dependabot-preview[bot]
cb1bc5d5ab Bump pandas from 1.0.2 to 1.0.3
Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.0.2 to 1.0.3.
- [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.0.2...v1.0.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-23 09:15:43 +00:00
Matthias
9b43d9d75e Merge pull request #3099 from freqtrade/dependabot/pip/develop/tabulate-0.8.7
Bump tabulate from 0.8.6 to 0.8.7
2020-03-23 10:15:31 +01:00
Matthias
c3787afa2e Merge pull request #3098 from freqtrade/dependabot/pip/develop/numpy-1.18.2
Bump numpy from 1.18.1 to 1.18.2
2020-03-23 10:14:25 +01:00
dependabot-preview[bot]
98dc4b4ca3 Bump ccxt from 1.24.31 to 1.24.83
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.24.31 to 1.24.83.
- [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.24.31...1.24.83)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-23 09:06:41 +00:00
dependabot-preview[bot]
de91e169bc Bump tabulate from 0.8.6 to 0.8.7
Bumps [tabulate](https://github.com/astanin/python-tabulate) from 0.8.6 to 0.8.7.
- [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.6...v0.8.7)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-23 09:05:12 +00:00
dependabot-preview[bot]
f4a69ba5a7 Bump numpy from 1.18.1 to 1.18.2
Bumps [numpy](https://github.com/numpy/numpy) from 1.18.1 to 1.18.2.
- [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.18.1...v1.18.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-23 09:04:50 +00:00
Matthias
d581b7e2d7 Add fallback if no error could be determined 2020-03-23 07:57:30 +01:00
Matthias
8f7e113d79 Add additional test 2020-03-23 07:54:27 +01:00
Matthias
6c55b40fe0 Add test verifying config printing 2020-03-22 20:15:33 +01:00
Matthias
45aaa8c09d Parse and show relevant configuration section 2020-03-22 20:09:01 +01:00
Matthias
acd402187a Update max_open_trades documentation 2020-03-22 19:39:36 +01:00
hroff-1902
e079d36f84 Merge pull request #3094 from freqtrade/gitignore
[minor] Add example notebook to gitignore again
2020-03-22 18:38:33 +03:00
Matthias
f14c496ce9 Remove calc_close_profit from RPC
This is now possible - but only for closed trades, so certain occurances
need to remain.
2020-03-22 11:28:18 +01:00
Matthias
efd94c84de Add example notebook to gitignore again 2020-03-22 11:22:49 +01:00
Matthias
2c434e9b11 Add close_proit_abs column 2020-03-22 11:16:23 +01:00
Matthias
e30faf8c8c Remove partial candle documentation
It wasn't working 100% correctly - see #2993
2020-03-21 20:04:05 +01:00
hroff-1902
fb4e9b3938 Merge pull request #3025 from yazeed/minor_create_trade_optimization
minor create_trade() optimization
2020-03-21 10:36:39 +03:00
Matthias
f320c0a410 Merge pull request #3087 from hroff-1902/edge-cosmetics-1
minor: Edge cosmetics
2020-03-20 08:12:21 +01:00
Yazeed Al Oyoun
942792f123 updated as suggested 2020-03-20 05:48:53 +01:00
hroff-1902
3e0ffdce75 Adjust tests 2020-03-20 04:21:17 +03:00
hroff-1902
5f9479b39f Edge import cosmetics 2020-03-20 02:10:44 +03:00
hroff-1902
d1bfe12bde Merge pull request #3086 from freqtrade/ftx_cancel_order
Ftx cancel order
2020-03-19 23:43:29 +03:00
Matthias
5e702f6891 Verify cancel_order returnvalue is a dictionary 2020-03-19 19:44:14 +01:00
Matthias
ecf3a3e070 Add test validating different return values 2020-03-19 19:44:10 +01:00
Matthias
ac6eef6922 Merge pull request #3062 from Fredrik81/plot-trades
Plotting: Fix if no file exists and new skip option
2020-03-18 20:00:50 +01:00
Matthias
3e1bef888a Fix flake8 error 2020-03-18 19:40:13 +01:00
Fredrik81
0920d6fce4 Update freqtrade/data/btanalysis.py
Co-Authored-By: Matthias <xmatthias@outlook.com>
2020-03-18 11:01:09 +01:00
Fredrik81
05250ba661 Update docs/plotting.md
Co-Authored-By: Matthias <xmatthias@outlook.com>
2020-03-18 11:00:33 +01:00
hroff-1902
961ad07dba Merge pull request #3079 from freqtrade/fix_pypi_install
Add template and jupyter files to release
2020-03-18 01:07:53 +03:00
Matthias
4f46fb9bf5 Add template and jupyter files to release 2020-03-17 19:33:18 +01:00
Matthias
50e348b81c Merge pull request #3069 from freqtrade/dependabot/pip/develop/sqlalchemy-1.3.15
Bump sqlalchemy from 1.3.13 to 1.3.15
2020-03-16 10:19:32 +01:00
Matthias
84b27585e6 Merge pull request #3074 from freqtrade/dependabot/pip/develop/mypy-0.770
Bump mypy from 0.761 to 0.770
2020-03-16 10:19:05 +01:00
dependabot-preview[bot]
62d449251c Bump mypy from 0.761 to 0.770
Bumps [mypy](https://github.com/python/mypy) from 0.761 to 0.770.
- [Release notes](https://github.com/python/mypy/releases)
- [Commits](https://github.com/python/mypy/compare/v0.761...v0.770)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-16 08:54:12 +00:00
dependabot-preview[bot]
7a7530d57d Bump sqlalchemy from 1.3.13 to 1.3.15
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.3.13 to 1.3.15.
- [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-preview[bot] <support@dependabot.com>
2020-03-16 08:53:20 +00:00
Matthias
7fb266671d Merge pull request #3073 from freqtrade/dependabot/pip/develop/pytest-5.4.1
Bump pytest from 5.3.5 to 5.4.1
2020-03-16 09:52:58 +01:00
Matthias
addb4b5f8f Merge pull request #3071 from freqtrade/dependabot/pip/develop/ccxt-1.24.31
Bump ccxt from 1.23.81 to 1.24.31
2020-03-16 09:52:10 +01:00
Matthias
d2ab7633ef Merge pull request #3072 from freqtrade/dependabot/pip/develop/plotly-4.5.4
Bump plotly from 4.5.3 to 4.5.4
2020-03-16 09:51:35 +01:00
Matthias
ba1bd2d5a4 Merge pull request #3070 from freqtrade/dependabot/pip/develop/pandas-1.0.2
Bump pandas from 1.0.1 to 1.0.2
2020-03-16 09:51:21 +01:00
dependabot-preview[bot]
3eee0c43a7 Bump pytest from 5.3.5 to 5.4.1
Bumps [pytest](https://github.com/pytest-dev/pytest) from 5.3.5 to 5.4.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/5.3.5...5.4.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-16 08:18:33 +00:00
dependabot-preview[bot]
8cfff40fcf Bump plotly from 4.5.3 to 4.5.4
Bumps [plotly](https://github.com/plotly/plotly.py) from 4.5.3 to 4.5.4.
- [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.5.3...v4.5.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-16 08:18:07 +00:00
dependabot-preview[bot]
0b34175752 Bump ccxt from 1.23.81 to 1.24.31
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.23.81 to 1.24.31.
- [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.23.81...1.24.31)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-16 08:17:53 +00:00
dependabot-preview[bot]
e8a92cb313 Bump pandas from 1.0.1 to 1.0.2
Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.0.1 to 1.0.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.0.1...v1.0.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-16 08:17:21 +00:00
Fredrik81
06198c0028 Missed configuration.py 2020-03-15 21:27:45 +01:00
Fredrik81
8c33e07dc6 Update based on comments 2020-03-15 21:20:32 +01:00
hroff-1902
0847652df0 Merge pull request #3066 from freqtrade/backtest_small_improvements
Backtest small improvements
2020-03-15 20:20:15 +03:00
Matthias
3d4664c2a6 Remove unnecessary import 2020-03-15 15:40:12 +01:00
Matthias
e1b08ad76c Add docstring to store_backtest_result 2020-03-15 15:38:26 +01:00
Matthias
fe50a0f3a1 Move test for store_bt_results to optimize_reports 2020-03-15 15:36:53 +01:00
Matthias
e95665ceca Make backtestresult storing independent from printing 2020-03-15 15:36:23 +01:00
Matthias
a13d581658 Move backtest-result visualization out of backtesting class 2020-03-15 15:17:53 +01:00
Matthias
6106d59e1a Move store_backtest_results to optimize_reports 2020-03-15 15:17:35 +01:00
Matthias
328dbd3930 Remove unnecessary parameter to generate_text_table_sell_reason 2020-03-15 15:04:48 +01:00
hroff-1902
57ff3ff450 Merge branch 'develop' into plot-trades 2020-03-15 13:31:00 +03:00
hroff-1902
3b89e7d393 Merge pull request #3064 from freqtrade/exportfilename_path
convert exportfilename to Path when config parsing
2020-03-15 13:12:19 +03:00
Matthias
0f1640bed4 convert exportfilename to Path when config parsing 2020-03-15 09:39:45 +01:00
Fredrik81
2c0980aa3a Tests 2020-03-15 00:09:08 +01:00
Fredrik81
cf7e80f45d Docs and logging 2020-03-14 23:55:13 +01:00
Fredrik81
27faf12fde Fix if no file exists 2020-03-14 22:15:03 +01:00
hroff-1902
5d1b1573b7 Merge pull request #3061 from freqtrade/edge_test_warning
Edge test warning
2020-03-14 13:51:05 +03:00
Matthias
308d8fe2a9 Remove deprecation warnings due to date conversion 2020-03-14 10:44:46 +01:00
Matthias
c56cbc21b1 Remove indexing warning in edge 2020-03-14 10:42:01 +01:00
hroff-1902
5bfbf92e69 Merge pull request #3032 from hroff-1902/no-ticker-2
Do not use "ticker" where it's not a ticker
2020-03-13 21:10:29 +03:00
hroff-1902
59fadabb5b Fix merging 2020-03-13 20:26:14 +03:00
hroff-1902
51f52c8609 Merge branch 'develop' into no-ticker-2 2020-03-13 16:43:52 +03:00
hroff-1902
a7ed51c642 return back the name of the hyperopt data file 2020-03-13 04:04:23 +03:00
hroff-1902
ddfe5b5f1c dataframe -> df_analyzed in plotting 2020-03-13 04:00:24 +03:00
hroff-1902
b2952cd42a remove redundant dict 2020-03-13 03:58:16 +03:00
hroff-1902
ebb0187f40 dataframe -> df_analyzed in backtesting and edge 2020-03-13 03:54:56 +03:00
hroff-1902
c6bb32d419 Merge pull request #3045 from orehunt/jsondatahandler-ohlc-respect-timerange
check again for emptiness after trimming dataframe
2020-03-12 22:46:31 +03:00
Matthias
6f67b8d9b9 iCheck after clean_dataframe, too 2020-03-12 19:50:46 +01:00
Matthias
129a88d5da Extract emptyness check to it's own method 2020-03-11 19:53:28 +01:00
hroff-1902
ed5b0ee36b Merge pull request #3053 from freqtrade/hyperopt_default_volume
Hyperopt - add default volume > 0 filter
2020-03-11 08:17:54 +03:00
Matthias
2b1c146940 Add default volume > 0 filter 2020-03-10 16:05:33 +01:00
Matthias
84f0bb9a5d Merge pull request #3051 from hroff-1902/fix-sortino
Adjust handling of zero stdev in loss functions
2020-03-10 13:10:39 +01:00
Matthias
14e7f0bb13 Merge pull request #3049 from hroff-1902/hyperopt-no-unlimited
Do not allow unlimited stake_amount for hyperopt
2020-03-10 11:46:22 +01:00
hroff-1902
73c19da4b9 Adjust handling of zero stdev in loss functions 2020-03-10 13:44:16 +03:00
hroff-1902
1b6e77649a Add test for hyperopt 2020-03-10 12:42:31 +03:00
hroff-1902
81b6a950ac Adjust test for backtesting 2020-03-10 12:42:11 +03:00
hroff-1902
f7ad6c20c7 Do not allow unlimited stake_amount for hyperopt 2020-03-10 12:41:23 +03:00
hroff-1902
c49fefc94d Merge pull request #3044 from freqtrade/default_max_order_book
order_book_max - change example setting
2020-03-10 11:17:27 +03:00
Matthias
a046c4829c Apply suggestions from code review
Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com>
2020-03-10 09:03:44 +01:00
hroff-1902
52d89eadde Merge pull request #3021 from Fredrik81/print-csv
Hyperopt: Add export CSV-file option
2020-03-10 10:46:58 +03:00
hroff-1902
f148b5f734 cosmetics in lambdas 2020-03-10 10:38:37 +03:00
hroff-1902
19a9782a40 Merge pull request #3040 from freqtrade/pairlist_message
reduce Pairlist message warning level
2020-03-10 10:26:19 +03:00
Matthias
42038da7f1 Update docs/configuration.md
Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com>
2020-03-10 07:57:25 +01:00
Fredrik81
bd158eefd2 Fixed loggin 2020-03-10 03:02:52 +01:00
hroff-1902
df2c8b5f32 Merge pull request #3042 from freqtrade/hyperopt_sample_comment
Update hyperopt samples docstring
2020-03-09 23:25:36 +03:00
Fredrik81
2f5fc731bb Removed overwrite option 2020-03-09 18:53:30 +01:00
orehunt
3eaae4661d check again for emptiness after trimming dataframe 2020-03-09 17:51:21 +01:00
Matthias
e0afbcd4af Additional warning about order_book-max 2020-03-09 17:41:44 +01:00
Matthias
74a17c7b7b Clarify warning in the documentation 2020-03-09 17:38:35 +01:00
Matthias
5da63d399b Reduce default order_book_max to 1 2020-03-09 17:38:25 +01:00
Matthias
856ba203d9 Update hyperopt samples docstring 2020-03-09 15:46:46 +01:00
Matthias
c049651784 whitelist_for_active_markets should not remove blacklisted items 2020-03-09 11:30:28 +01:00
Matthias
5cbf325fda Allow different loglevels for message 2020-03-09 11:30:13 +01:00
Matthias
bf2dc90c3c Merge pull request #3039 from freqtrade/dependabot/pip/develop/plotly-4.5.3
Bump plotly from 4.5.2 to 4.5.3
2020-03-09 09:58:46 +01:00
Matthias
964d97da17 Merge pull request #3038 from freqtrade/dependabot/pip/develop/scikit-learn-0.22.2.post1
Bump scikit-learn from 0.22.2 to 0.22.2.post1
2020-03-09 09:57:46 +01:00
Matthias
5c190f82e4 Merge pull request #3035 from freqtrade/dependabot/pip/develop/wrapt-1.12.1
Bump wrapt from 1.12.0 to 1.12.1
2020-03-09 09:54:23 +01:00
Matthias
f125709954 Merge pull request #3037 from freqtrade/dependabot/pip/develop/prompt-toolkit-3.0.4
Bump prompt-toolkit from 3.0.3 to 3.0.4
2020-03-09 09:53:53 +01:00
Matthias
a3b0b8a7c5 Merge pull request #3036 from freqtrade/dependabot/pip/develop/ccxt-1.23.81
Bump ccxt from 1.23.30 to 1.23.81
2020-03-09 09:45:49 +01:00
dependabot-preview[bot]
4cc0d3dbc4 Bump plotly from 4.5.2 to 4.5.3
Bumps [plotly](https://github.com/plotly/plotly.py) from 4.5.2 to 4.5.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.5.2...v4.5.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-09 08:35:16 +00:00
dependabot-preview[bot]
23127b8da0 Bump scikit-learn from 0.22.2 to 0.22.2.post1
Bumps [scikit-learn](https://github.com/scikit-learn/scikit-learn) from 0.22.2 to 0.22.2.post1.
- [Release notes](https://github.com/scikit-learn/scikit-learn/releases)
- [Commits](https://github.com/scikit-learn/scikit-learn/compare/0.22.2...0.22.2.post1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-09 08:34:33 +00:00
dependabot-preview[bot]
46763e148b Bump prompt-toolkit from 3.0.3 to 3.0.4
Bumps [prompt-toolkit](https://github.com/prompt-toolkit/python-prompt-toolkit) from 3.0.3 to 3.0.4.
- [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.3...3.0.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-09 08:33:59 +00:00
dependabot-preview[bot]
09c25faa51 Bump ccxt from 1.23.30 to 1.23.81
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.23.30 to 1.23.81.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/1.23.30...1.23.81)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-09 08:33:37 +00:00
dependabot-preview[bot]
c7b2f173eb Bump wrapt from 1.12.0 to 1.12.1
Bumps [wrapt](https://github.com/GrahamDumpleton/wrapt) from 1.12.0 to 1.12.1.
- [Release notes](https://github.com/GrahamDumpleton/wrapt/releases)
- [Changelog](https://github.com/GrahamDumpleton/wrapt/blob/develop/docs/changes.rst)
- [Commits](https://github.com/GrahamDumpleton/wrapt/compare/1.12.0...1.12.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-09 08:32:42 +00:00
Fredrik81
cb419614cd Spelling miss 2020-03-08 23:00:21 +01:00
Fredrik81
4ad93ed6bb Changed output for null columns 2020-03-08 22:41:05 +01:00
hroff-1902
3208faf7ed Do not use ticker where it's not a ticker 2020-03-08 20:47:02 +03:00
hroff-1902
77944175e2 Merge pull request #3030 from freqtrade/coingekko
Coingekko replacing coinmarketcap
2020-03-07 20:42:08 +03:00
Matthias
281cf577d1 Remove unsupported FIAT 2020-03-07 17:03:31 +01:00
Matthias
d51bb9acfb Update conda environment file 2020-03-07 13:11:36 +01:00
Matthias
1b3038390a Update comment 2020-03-07 13:05:46 +01:00
Matthias
acea49beaf Fix tests / test mocks 2020-03-07 13:01:26 +01:00
Matthias
df5adb6ca5 Exchange coingekko for coinmarketcap 2020-03-07 11:53:08 +01:00
Matthias
93fc14d726 Exchange dependencies to coingekko 2020-03-07 11:52:02 +01:00
Matthias
847df7b70c Merge pull request #3026 from yazeed/add_default_to_ignore_roi_if_buy_signal
default for ignore_roi_if_buy_signal in freqtradebot.py
2020-03-06 19:35:41 +01:00
Matthias
bbdbc59fd1 Merge pull request #3024 from yazeed/unifying_get_buy_sell_rate_msgs
consistency between get_sell_rate with get_buy_rate
2020-03-06 19:32:31 +01:00
hroff-1902
7b0009b38d Merge pull request #3029 from freqtrade/travis_failure
Fix travis build failure
2020-03-06 18:20:19 +03:00
Matthias
78908e2496 Fix travis build failure 2020-03-06 15:57:26 +01:00
Yazeed Al Oyoun
b8d05d8751 found instance of config get without default 2020-03-05 22:14:05 +01:00
Yazeed Al Oyoun
0587256733 minor create_trade() optimization 2020-03-05 21:57:01 +01:00
Yazeed Al Oyoun
4474482307 unifying get_sell_rate with get_buy_rate 2020-03-05 20:44:29 +01:00
Fredrik81
f0d56e23a3 PEP8 fix 2020-03-05 19:58:01 +01:00
Fredrik81
91db75a707 Added tests and updated doc 2020-03-05 19:43:43 +01:00
Matthias
459f1aa130 Merge pull request #2989 from hroff-1902/no-percent-1
No "percent" where "ratio" is to be used
2020-03-05 16:20:13 +01:00
hroff-1902
eee5727426 Adjust webhook docs 2020-03-05 17:44:38 +03:00
hroff-1902
7a3660cd6b Adjust webhook tests 2020-03-05 17:44:21 +03:00
hroff-1902
34093d1208 Merge branch 'develop' into no-percent-1 2020-03-05 14:27:12 +03:00
hroff-1902
bcec46c2aa Merge pull request #3019 from freqtrade/fix_testfailure
Fix random CI failure
2020-03-05 10:56:25 +03:00
Matthias
97b194a454 Throttle may take longer than .10s on slow machines
This made this test fluky on windows CI ...
2020-03-05 06:20:36 +01:00
Fredrik81
7606d814fa Initial work on csv-file export. Missing docs and tests 2020-03-05 01:58:33 +01:00
hroff-1902
57523d58df Merge pull request #2994 from Fredrik81/hyperopt-table
Added dynamic print table function to hyperopt
2020-03-04 23:44:53 +03:00
Fredrik81
090d1e8a70 Alignment and cleanups 2020-03-04 20:51:09 +01:00
hroff-1902
33c1c8f726 Merge pull request #3018 from freqtrade/max_drawdown
Max drawdown in plot-profit
2020-03-04 20:42:57 +03:00
hroff-1902
dea4ef957e Merge pull request #2982 from freqtrade/rate_side_optional
Rate side configurable
2020-03-04 16:07:08 +03:00
Matthias
2f54aff0ce Improve documentation wording for price sides 2020-03-04 06:44:59 +01:00
Matthias
8de35e1c83 Documentation suggestions from Review
Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com>
2020-03-04 06:40:19 +01:00
Fredrik81
7652a2bb95 Updated table layout and aligning better for hyperopt 2020-03-04 00:10:47 +01:00
Matthias
9d8970a76b Add test and formatting to drawdown 2020-03-03 20:23:44 +01:00
Matthias
53dcb5d5ed Fix logging expression 2020-03-03 19:36:03 +01:00
hroff-1902
4513edf450 Merge pull request #3017 from freqtrade/ci_windows_38
CI on windows with python 3.8
2020-03-03 17:21:55 +03:00
Matthias
9bebc9ba76 Fix powershell comparison 2020-03-03 09:40:41 +01:00
Matthias
720ed0eddd Remove flucky test assert 2020-03-03 09:36:04 +01:00
Matthias
d9e83cc4e2 Run CI on windows python 3.8 2020-03-03 09:33:08 +01:00
Matthias
33a63562cb make drawdown function less restrictive 2020-03-03 07:23:38 +01:00
Matthias
88e7cab5b9 Add max_drawdown to profit plot 2020-03-03 07:21:14 +01:00
Matthias
e050511ddc Add test for max_drawdown calculation 2020-03-03 07:20:41 +01:00
Matthias
3479f7d986 Add max_drawdown function 2020-03-03 07:15:03 +01:00
Fredrik81
4aca8d7fcc PEP8 fix 2020-03-03 01:35:18 +01:00
Fredrik81
399c419163 Changed table formating. Adding some code to align hyperopt table generation. WIP 2020-03-03 01:14:56 +01:00
hroff-1902
82bdd01843 Merge pull request #3003 from Fredrik81/cores-and-arguments
Hyperopt: fix number of CPU cores, jobs and total epochs
2020-03-03 02:12:21 +03:00
hroff-1902
52cd5f9127 Better use enumerate: more correct and more pythonic 2020-03-03 01:42:25 +03:00
hroff-1902
45c9496792 Do not run optimizer for 'jobs' epochs for the last iteration 2020-03-03 01:33:11 +03:00
hroff-1902
a7d4755859 optimize calculation of current_jobs 2020-03-03 01:20:14 +03:00
hroff-1902
92425642da Fix config_jobs 2020-03-03 01:00:24 +03:00
Fredrik81
0e4862b0c8 Added logging if argument is miss-configured 2020-03-02 22:58:54 +01:00
Fredrik81
7713cfeb79 Corrected logic for -j + and - argument 2020-03-02 21:02:32 +01:00
Matthias
6e2290c4f0 Allow last to be empty -
closes #3005
2020-03-02 20:05:54 +01:00
Matthias
35ef065d7b Merge pull request #3012 from freqtrade/dependabot/pip/develop/scikit-learn-0.22.2
Bump scikit-learn from 0.22.1 to 0.22.2
2020-03-02 10:54:20 +01:00
Matthias
dae04e2752 Merge pull request #3011 from freqtrade/dependabot/pip/develop/ccxt-1.23.30
Bump ccxt from 1.22.95 to 1.23.30
2020-03-02 10:50:25 +01:00
Matthias
4605bdad29 Merge pull request #3013 from freqtrade/dependabot/pip/develop/plotly-4.5.2
Bump plotly from 4.5.1 to 4.5.2
2020-03-02 10:49:55 +01:00
dependabot-preview[bot]
a17f3fb8be Bump plotly from 4.5.1 to 4.5.2
Bumps [plotly](https://github.com/plotly/plotly.py) from 4.5.1 to 4.5.2.
- [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.5.1...v4.5.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-02 08:39:25 +00:00
dependabot-preview[bot]
485075b8f2 Bump scikit-learn from 0.22.1 to 0.22.2
Bumps [scikit-learn](https://github.com/scikit-learn/scikit-learn) from 0.22.1 to 0.22.2.
- [Release notes](https://github.com/scikit-learn/scikit-learn/releases)
- [Commits](https://github.com/scikit-learn/scikit-learn/compare/0.22.1...0.22.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-02 08:38:36 +00:00
dependabot-preview[bot]
e204e3277b Bump ccxt from 1.22.95 to 1.23.30
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.22.95 to 1.23.30.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/1.22.95...1.23.30)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-02 08:37:58 +00:00
hroff-1902
db1227f279 Merge pull request #3007 from yazeed/hyperopt_interface_sort
hyperopt_interface.py code styling
2020-03-02 02:22:44 +03:00
Yazeed Al Oyoun
77b7f95efb simple code styling fixes 2020-03-02 00:14:01 +01:00
hroff-1902
8475baba4e Merge pull request #2995 from freqtrade/stake_curr_empty
Allow Stake currency empty when using download-data
2020-03-02 00:53:09 +03:00
hroff-1902
e20b06408c Merge pull request #3000 from freqtrade/fix/jupyter_example
[minor] Fix jupyter notebook example
2020-03-02 00:49:21 +03:00
Fredrik81
f08c7eedf1 Changed jobs to be dynamic for last loop 2020-03-01 14:35:13 +01:00
Fredrik81
75b4f1a442 Fix alignment of higher values 2020-03-01 14:12:27 +01:00
Fredrik81
379275e2d6 Updated tests 2020-03-01 03:24:04 +01:00
Fredrik81
267416eced Changed test for new table printing 2020-03-01 03:11:00 +01:00
Fredrik81
e89fd33229 Fix for more arguments 2020-02-29 23:57:15 +01:00
Fredrik81
7a4edb1cd8 Fix: When total epochs is less than cpu cores 2020-02-29 23:41:59 +01:00
hroff-1902
0a2f854302 Merge pull request #3001 from freqtrade/doc-nonce
[minor] Add note about InvalidNonce to documentation
2020-03-01 01:28:34 +03:00
Fredrik81
23ae0653bd Changed table output to match hyperopt-list command 2020-02-29 23:24:08 +01:00
Matthias
60f04cff4d Simplify expression 2020-02-29 20:41:03 +01:00
Matthias
54c07e5e62 Merge pull request #2997 from freqtrade/new_release
New release 2020.02
2020-02-29 19:33:12 +01:00
Matthias
8fce82b412 Merge pull request #3002 from freqtrade/kraken_supported
Add official support for Kraken
2020-02-29 19:32:43 +01:00
Matthias
18d724f7a1 Adjust wording of supported exchanges 2020-02-29 19:22:36 +01:00
Matthias
d7373be553 Add official support for Kraken 2020-02-29 16:58:22 +01:00
Matthias
4c39f36084 Add note about InvalidNonce to documentation 2020-02-29 16:36:33 +01:00
hroff-1902
415f1dc25b Merge pull request #2999 from freqtrade/release_docs
[minor] improve and correct release documentation
2020-02-29 18:31:11 +03:00
hroff-1902
293d1fca5c Merge pull request #2998 from freqtrade/try_fix_randomfailure
Try fix random testfailure
2020-02-29 18:29:54 +03:00
Matthias
848054d140 Fix jupyter notebook example -
generate_candlestick_graph() needs a filtered pairlist, not a list
containing all pairs
2020-02-29 15:53:54 +01:00
Matthias
f25adf3b12 improve and correct release documentation 2020-02-29 15:48:36 +01:00
Matthias
9336d8ee02 Try fix random testfailure 2020-02-29 15:44:45 +01:00
Matthias
a6b48f7366 Version bump 2020.02 2020-02-29 15:16:55 +01:00
Matthias
c6fd6a0fbf Merge branch 'master' into new_release 2020-02-29 15:16:45 +01:00
Matthias
60579485e5 fix empty stake currency problem 2020-02-29 14:56:36 +01:00
Matthias
5277d71913 Add test for empty stake-currency 2020-02-29 14:56:04 +01:00
hroff-1902
0528af1700 Merge pull request #2879 from freqtrade/sortino_hyperopt_loss
Sortino hyperopt loss
2020-02-29 11:36:27 +03:00
Fredrik81
349aa2f957 Added dynamic print table function to hyperopt 2020-02-28 21:54:04 +01:00
hroff-1902
bee8e92f02 Final changes, use sqrt i.o. statistics.pstdev 2020-02-28 23:50:25 +03:00
hroff-1902
e411717de9 No percent where ratio is to be used 2020-02-28 12:36:39 +03:00
Matthias
ac7fa8252b Merge pull request #2985 from Fredrik81/pretty-backtesting
Changed table style of backtesting and alignment of headers
2020-02-28 06:20:34 +01:00
hroff-1902
866c51acc5 Merge pull request #2988 from freqtrade/fix/dryrun
run-mode not correctly set when using --dry-run
2020-02-27 22:09:18 +03:00
Matthias
a55964a622 we Must parse --dry-run before setting run-mode 2020-02-27 19:36:54 +01:00
Matthias
5a02026f82 Add test validating behaviour 2020-02-27 19:35:58 +01:00
hroff-1902
c0001fcb8c Merge pull request #2987 from Fredrik81/table-style
Minor change to standardize table style.
2020-02-27 18:30:14 +03:00
hroff-1902
c1c0b59223 Merge pull request #2974 from freqtrade/enhance_roi_doc
Add timeframe_to_minutes to ROI documentation
2020-02-27 18:18:24 +03:00
Fredrik81
15e59654d9 Minor change to standardize table style.
This PR will target commands.
2020-02-27 16:10:45 +01:00
Matthias
e5a9c81412 Add 0 entry to enhanced ROI example 2020-02-27 14:04:12 +01:00
Fredrik81
55d471190a Changed table style of backtesting and alignment of headers 2020-02-27 13:28:28 +01:00
hroff-1902
90065843a5 Merge pull request #2983 from freqtrade/runmode
Logging should be initialized first
2020-02-27 12:22:37 +03:00
Matthias
8623300d15 Merge pull request #2984 from freqtrade/dependabot/docker/python-3.8.2-slim-buster
Bump python from 3.8.1-slim-buster to 3.8.2-slim-buster
2020-02-27 08:26:20 +01:00
dependabot-preview[bot]
bbb438bd40 Bump python from 3.8.1-slim-buster to 3.8.2-slim-buster
Bumps python from 3.8.1-slim-buster to 3.8.2-slim-buster.

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-27 06:18:09 +00:00
Matthias
e5ec97495d Logging should be initialized first 2020-02-27 07:01:00 +01:00
hroff-1902
893d9cde8d Merge pull request #2943 from Fredrik81/add-print-table
Added function to print hyperopt-list as table using tabulate
2020-02-27 05:22:41 +03:00
Matthias
b6839289ec Add price_side to sample config files 2020-02-26 20:03:13 +01:00
Matthias
0fea3a7ea7 Some final polish to configurable_side 2020-02-26 19:50:17 +01:00
Matthias
3c5e716d8f Update some documentation regarding price_side 2020-02-26 19:39:12 +01:00
Matthias
8edc3eb5fb Use generator to generate sell price scaffold testing 2020-02-26 19:39:12 +01:00
Matthias
e1cb6f4ae3 fix and improve tests in test_freqtradebot 2020-02-26 19:39:12 +01:00
Matthias
e7b9891335 Adapt rpc tests to corrected price side 2020-02-26 19:39:12 +01:00
Matthias
e4b2949188 Change buy_rate calculation to use price_side 2020-02-26 19:39:12 +01:00
Matthias
5f71232038 Refactor get_buy_rate to use rate variable 2020-02-26 19:39:12 +01:00
Matthias
de48a697b0 Use price_side for get_sell_rate 2020-02-26 19:39:12 +01:00
Matthias
f91d7beaa1 Fix constants wrong parenteses 2020-02-26 19:39:12 +01:00
hroff-1902
30b0911645 Merge pull request #2976 from gaugau3000/doc_update_config_section
[Doc update config section] - [minor]
2020-02-26 12:45:06 +03:00
hroff-1902
e6d003f8f2 Merge pull request #2973 from freqtrade/support_non_pairs
Support non pairs
2020-02-26 12:20:45 +03:00
hroff-1902
297e63de0a Merge pull request #2970 from freqtrade/install_docs
simplify installation documentation
2020-02-26 11:48:51 +03:00
Matthias
8ae0f99a96 Remove duplicate section 2020-02-26 09:05:48 +01:00
Matthias
a29653b510 Wording changes to install docs
Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com>
2020-02-26 08:59:27 +01:00
Matthias
f38accb77b Return empty string if no quote / base currency can be found 2020-02-26 07:09:54 +01:00
Matthias
4e218be51d Don't use markets[pair]['quote'] 2020-02-26 07:08:09 +01:00
Matthias
1021ffa1c3 Apply suggestions from code review
Add suggested changes to comments

Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com>
2020-02-26 07:00:08 +01:00
Matthias
1e869b86f2 Update checkout aciton to v2
https://github.com/actions/checkout/issues/23 suggests that it's fixed in v2.
2020-02-26 06:54:04 +01:00
Matthias
af4469f073 Convert to str to avoid errors 2020-02-26 06:43:15 +01:00
Matthias
df49b98c25 Implement wording-changes
Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com>
2020-02-26 06:40:13 +01:00
hroff-1902
c9b6bb1229 Merge pull request #2954 from freqtrade/rate_caching
Improve and fix buy / sell Rate caching
2020-02-26 04:27:39 +03:00
hroff-1902
5a900858d8 Merge branch 'develop' into rate_caching 2020-02-26 04:04:20 +03:00
hroff-1902
ce2e039e5f Update docs/configuration.md 2020-02-26 01:58:32 +03:00
gaugau3000
76c449c0c2 volume_pair_list_extra_doc_infos 2020-02-25 19:45:23 +00:00
gaugau3000
7d7318a3ea fix_wrong_order_type 2020-02-25 19:41:20 +00:00
Matthias
cfc22577be Add timeframe_to_minutes to ROI documentation 2020-02-25 16:54:48 +01:00
Matthias
31ac4598ba Fix last occurances of pair splitting 2020-02-25 07:16:37 +01:00
Matthias
d34515a5de Remove constraint to have pairs in base/quote format 2020-02-25 07:04:20 +01:00
Matthias
e8eaa8920e Use get_base_currency instead of splitting by / 2020-02-25 07:01:31 +01:00
Matthias
e9448dc5e2 Add tsts for quote and base currency 2020-02-25 07:01:23 +01:00
Fredrik81
cd7efde6c0 Fixed coloring so it's only targeting the values not the table borders 2020-02-24 22:06:21 +01:00
Matthias
61037ab7b8 Implement get_pair_base_curr and get_pair_quote_curr 2020-02-24 21:50:27 +01:00
Matthias
3e4f663418 Move pairlist validation to exchange (we need to use .quote) from
markets
2020-02-24 21:33:42 +01:00
Matthias
6581ba56ca Use markets.quote to validate 2020-02-24 20:41:45 +01:00
Matthias
2f349e0504 Improve install documentation by streamlining the process 2020-02-24 20:21:25 +01:00
Matthias
23b47b66ec Update install-script documentation and reorder installation steps 2020-02-24 20:11:25 +01:00
Fredrik81
23bf135b8a Alignment of table content, changed coloring, changed 'Best' column to show if it's initial_point or best 2020-02-24 11:01:14 +01:00
Matthias
6c8b5ea38c Merge pull request #2964 from freqtrade/dependabot/pip/develop/scikit-optimize-0.7.4
Bump scikit-optimize from 0.7.2 to 0.7.4
2020-02-24 10:36:45 +01:00
Matthias
4d040f3123 Merge pull request #2962 from freqtrade/dependabot/pip/develop/requests-2.23.0
Bump requests from 2.22.0 to 2.23.0
2020-02-24 10:36:19 +01:00
Matthias
88121760e4 Merge pull request #2965 from freqtrade/dependabot/pip/develop/ccxt-1.22.95
Bump ccxt from 1.22.61 to 1.22.95
2020-02-24 10:32:12 +01:00
Matthias
ae7a12200c Merge pull request #2963 from freqtrade/dependabot/pip/develop/plotly-4.5.1
Bump plotly from 4.5.0 to 4.5.1
2020-02-24 10:31:40 +01:00
dependabot-preview[bot]
d63aaf3bfd Bump ccxt from 1.22.61 to 1.22.95
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.22.61 to 1.22.95.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/1.22.61...1.22.95)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-24 08:05:15 +00:00
dependabot-preview[bot]
ff69b511e3 Bump scikit-optimize from 0.7.2 to 0.7.4
Bumps [scikit-optimize](https://github.com/scikit-optimize/scikit-optimize) from 0.7.2 to 0.7.4.
- [Release notes](https://github.com/scikit-optimize/scikit-optimize/releases)
- [Changelog](https://github.com/scikit-optimize/scikit-optimize/blob/master/CHANGELOG.md)
- [Commits](https://github.com/scikit-optimize/scikit-optimize/compare/v0.7.2...v0.7.4)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-24 08:04:44 +00:00
dependabot-preview[bot]
4054dec7a0 Bump plotly from 4.5.0 to 4.5.1
Bumps [plotly](https://github.com/plotly/plotly.py) from 4.5.0 to 4.5.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.5.0...v4.5.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-24 08:04:32 +00:00
dependabot-preview[bot]
353f722dc5 Bump requests from 2.22.0 to 2.23.0
Bumps [requests](https://github.com/psf/requests) from 2.22.0 to 2.23.0.
- [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.22.0...v2.23.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-24 08:04:07 +00:00
Matthias
83959f0e56 Merge pull request #2959 from hroff-1902/throttling
Better throttling
2020-02-24 06:54:52 +01:00
Matthias
c657a1df2b Merge pull request #2960 from yazeed/sort_indicators_full
indicators_full.j2 and sample_strategy.py ordering and added indicators
2020-02-24 06:45:20 +01:00
Fredrik81
7eb62ed32e Remove old print option for hyperopt-list and made table as default 2020-02-24 00:33:01 +01:00
hroff-1902
e545ef563c Wording adjusted in helpstring 2020-02-23 22:50:58 +03:00
Yazeed Al Oyoun
0eeafcd157 matched commenting on previous sample_strategy.py 2020-02-23 16:56:55 +01:00
Yazeed Al Oyoun
f25d6224dd modified sample_strategy 2020-02-23 16:22:19 +01:00
Yazeed Al Oyoun
e04c2dda2c fixed typo 2020-02-22 23:58:31 +01:00
hroff-1902
d2181bdd94 Adjust tests 2020-02-23 01:55:07 +03:00
Yazeed Al Oyoun
5ac6244465 added keltner channel and uo 2020-02-22 23:50:26 +01:00
Yazeed Al Oyoun
2957756275 final touches plus 2020-02-22 23:39:01 +01:00
Yazeed Al Oyoun
b49b9b515e final touches 2020-02-22 23:37:15 +01:00
Yazeed Al Oyoun
259dc75a30 some order and added weighted BB indicator to list 2020-02-22 23:10:46 +01:00
hroff-1902
ca8e52dc2c Show heartbeat message earlier after changing the state 2020-02-23 00:21:19 +03:00
Matthias
91ee48f3fb Merge pull request #2957 from hroff-1902/fix/2948-2
Fix #2948
2020-02-22 19:37:00 +01:00
hroff-1902
e2e6b940a3 copy=False does not make the changes inline anyway, so not needed 2020-02-22 19:54:19 +03:00
hroff-1902
c651e0ac82 Fix #2948 2020-02-22 19:46:40 +03:00
hroff-1902
430f53ca11 Merge pull request #2955 from freqtrade/fix/2948
Load ohlcv data as float
2020-02-22 17:16:54 +03:00
Matthias
3186add87b Use explicit column list for float parsing 2020-02-22 14:46:54 +01:00
Matthias
7ecc56fa44 Load ohlcv data as float 2020-02-22 13:10:41 +01:00
Matthias
2fe7b683cb Add tests for cached rates 2020-02-22 11:23:13 +01:00
Matthias
77ef3240cd Implement log messages 2020-02-22 11:20:19 +01:00
Matthias
97e6e5e976 Implement caching in the correct place 2020-02-22 11:12:33 +01:00
Matthias
f5b4a6d3d7 Remove fetch_ticker caching 2020-02-22 11:10:05 +01:00
hroff-1902
d9ecf3e4bf Add version and state to heartbeat message 2020-02-21 12:26:32 +03:00
hroff-1902
d2e20d86bb Align heartbeat to throttling logging 2020-02-21 05:31:21 +03:00
hroff-1902
269a669af8 Move heartbeat to worker 2020-02-21 05:07:31 +03:00
hroff-1902
881f602f91 Adjust methods params 2020-02-21 04:17:17 +03:00
hroff-1902
e0800b7c29 Make throttle start time an worker object attribute 2020-02-21 03:52:14 +03:00
hroff-1902
04aa74e5ad Better throttling 2020-02-21 03:37:38 +03:00
Fredrik81
09226fd5d5 PEP8 correction 2020-02-20 19:18:42 +01:00
Fredrik81
e7b12704de Added test for details 2020-02-20 19:12:55 +01:00
hroff-1902
43add0b159 Merge pull request #2947 from freqtrade/fix_failing_dockerbuild
Use correct strategy path for docker testing
2020-02-20 16:53:39 +03:00
Matthias
945ff09e27 Use correct strategy path for docker testing 2020-02-20 14:19:24 +01:00
hroff-1902
78ee36a8c6 Use _throttle() in stopped state instead of sleep() 2020-02-20 15:18:26 +03:00
hroff-1902
bee28a1061 Merge pull request #2944 from freqtrade/move_defaultstrategy
Move defaultstrategy
2020-02-20 08:52:24 +03:00
Matthias
10668bb249 Update tests/strategy/test_strategy.py
Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com>
2020-02-20 06:22:36 +01:00
hroff-1902
56a06cbd33 Update strings to f-strings 2020-02-20 08:19:22 +03:00
hroff-1902
bca5f804a8 Move divider log message 2020-02-20 08:17:24 +03:00
Yazeed Al Oyoun
09a1c9eed6 fixed docs description of hyperopts 2020-02-19 22:25:34 +01:00
Matthias
5adbe3c2d3 initial search path is optional ... 2020-02-19 19:50:01 +01:00
Matthias
751e2b2359 Merge pull request #2945 from freqtrade/fix/kraken_stoplosshit
Kraken stoploss bugfix
2020-02-19 19:43:29 +01:00
Matthias
a7342bd910 Fix non-existing strategy loading 2020-02-19 19:42:04 +01:00
hroff-1902
f2f2c281c0 Merge pull request #2719 from xmatthias/data_handler
Introduce Data handler
2020-02-19 21:22:08 +03:00
Matthias
d22384c7fb Full support for kraken stoploss 2020-02-19 19:21:48 +01:00
Matthias
29b369c65e Rename cli argument 2020-02-19 14:53:54 +01:00
hroff-1902
2d2fd968c8 Merge pull request #2941 from freqtrade/github_actions_tests
GitHub actions tests
2020-02-19 14:58:12 +03:00
Matthias
882d0a5933 implement documentation feedback after review 2020-02-19 12:55:08 +01:00
Matthias
09d89fbfb3 Fix last test 2020-02-19 07:15:55 +01:00
Yazeed Al Oyoun
41b4fa3b7f fixed two more typos 2020-02-19 02:59:51 +01:00
Yazeed Al Oyoun
3fb6818bd8 Merge branch 'develop' into sortino_hyperopt_loss 2020-02-19 02:37:25 +01:00
Yazeed Al Oyoun
df26c357d2 doc updates 2020-02-19 01:31:25 +01:00
Fredrik Rydin
585545405d Changed tests 2020-02-19 00:51:44 +01:00
Fredrik Rydin
2058b492eb Added function to print hyperopt-list as table using tabulate 2020-02-18 22:46:53 +01:00
Matthias
d91b9d1253 Fix some tests, don't default to freqtrade/strategy for imports 2020-02-18 20:26:20 +01:00
Matthias
1634297685 Move strategies to test subfolder 2020-02-18 20:12:10 +01:00
hroff-1902
16cbd441ce Merge pull request #2931 from freqtrade/status_badge
Add github actions badge
2020-02-18 10:55:34 +03:00
Matthias
e6dd463ca3 Revert versioning 2020-02-17 20:17:36 +01:00
Matthias
1172c95817 Use different versioning scheme 2020-02-17 20:17:08 +01:00
Matthias
0b33b798e4 Add pypi build step 2020-02-17 20:16:24 +01:00
Matthias
4c41da2ea4 Merge pull request #2934 from freqtrade/dependabot/pip/develop/python-telegram-bot-12.4.2
Bump python-telegram-bot from 12.4.1 to 12.4.2
2020-02-17 10:12:28 +01:00
Matthias
9b58b4c54d Merge pull request #2939 from freqtrade/dependabot/pip/develop/scikit-optimize-0.7.2
Bump scikit-optimize from 0.7.1 to 0.7.2
2020-02-17 09:49:45 +01:00
Matthias
a8e0526d87 Merge pull request #2937 from freqtrade/dependabot/pip/develop/wrapt-1.12.0
Bump wrapt from 1.11.2 to 1.12.0
2020-02-17 09:48:58 +01:00
Matthias
90fbf70bcc Merge pull request #2936 from freqtrade/dependabot/pip/develop/coveralls-1.11.1
Bump coveralls from 1.10.0 to 1.11.1
2020-02-17 09:47:47 +01:00
dependabot-preview[bot]
582b59044c Bump python-telegram-bot from 12.4.1 to 12.4.2
Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 12.4.1 to 12.4.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/v12.4.1...v12.4.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-17 08:47:39 +00:00
Matthias
df955b8104 Merge pull request #2935 from freqtrade/dependabot/pip/develop/ccxt-1.22.61
Bump ccxt from 1.22.39 to 1.22.61
2020-02-17 09:46:24 +01:00
Matthias
e7d687ee11 Merge pull request #2938 from freqtrade/dependabot/pip/develop/mkdocs-material-4.6.3
Bump mkdocs-material from 4.6.2 to 4.6.3
2020-02-17 09:45:51 +01:00
dependabot-preview[bot]
0fd3d74fc4 Bump scikit-optimize from 0.7.1 to 0.7.2
Bumps [scikit-optimize](https://github.com/scikit-optimize/scikit-optimize) from 0.7.1 to 0.7.2.
- [Release notes](https://github.com/scikit-optimize/scikit-optimize/releases)
- [Changelog](https://github.com/scikit-optimize/scikit-optimize/blob/master/CHANGELOG.md)
- [Commits](https://github.com/scikit-optimize/scikit-optimize/compare/v0.7.1...v0.7.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-17 08:05:00 +00:00
dependabot-preview[bot]
9435950fc9 Bump mkdocs-material from 4.6.2 to 4.6.3
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 4.6.2 to 4.6.3.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/4.6.2...4.6.3)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-17 08:04:40 +00:00
dependabot-preview[bot]
500e1c77de Bump wrapt from 1.11.2 to 1.12.0
Bumps [wrapt](https://github.com/GrahamDumpleton/wrapt) from 1.11.2 to 1.12.0.
- [Release notes](https://github.com/GrahamDumpleton/wrapt/releases)
- [Changelog](https://github.com/GrahamDumpleton/wrapt/blob/develop/docs/changes.rst)
- [Commits](https://github.com/GrahamDumpleton/wrapt/compare/1.11.2...1.12.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-17 08:04:21 +00:00
dependabot-preview[bot]
c6a3038f52 Bump coveralls from 1.10.0 to 1.11.1
Bumps [coveralls](https://github.com/coveralls-clients/coveralls-python) from 1.10.0 to 1.11.1.
- [Release notes](https://github.com/coveralls-clients/coveralls-python/releases)
- [Changelog](https://github.com/coveralls-clients/coveralls-python/blob/master/CHANGELOG.md)
- [Commits](https://github.com/coveralls-clients/coveralls-python/compare/1.10.0...1.11.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-17 08:03:57 +00:00
dependabot-preview[bot]
212d20ed08 Bump ccxt from 1.22.39 to 1.22.61
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.22.39 to 1.22.61.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/1.22.39...1.22.61)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-17 08:03:37 +00:00
Matthias
bec86b1325 Add github actions badge 2020-02-16 15:42:41 +01:00
Matthias
6335d81ceb Merge branch 'develop' into data_handler 2020-02-16 15:12:14 +01:00
hroff-1902
7b9bd70d97 Merge pull request #2930 from freqtrade/ftx_fix_ohlcv
increment limit to adjust to FTX defaults (1500 candles)
2020-02-16 15:53:27 +03:00
hroff-1902
674898bd32 Fix usage of vars in the commented out line 2020-02-16 15:26:40 +03:00
Matthias
3787ac7b98 increment limit to adjust to FTX defaults (1500 candles) 2020-02-16 13:20:11 +01:00
hroff-1902
42dfda9231 Adjust docstring 2020-02-16 13:46:07 +03:00
hroff-1902
fbe5cc44da Use statistics.pstdev 2020-02-16 13:43:23 +03:00
hroff-1902
1e84b2770c Fix values of downside_returns 2020-02-16 04:10:53 +03:00
hroff-1902
161dd1a3e6 Rename risk_free_return to minumum_accepted_return 2020-02-16 03:55:16 +03:00
hroff-1902
36670fb5d9 Merge pull request #2929 from yazeed/fix_status_table
/status table quick fix
2020-02-15 23:09:50 +03:00
Yazeed Al Oyoun
180939a962 winner, readability, with brackets as fix 2020-02-15 21:01:45 +01:00
Yazeed Al Oyoun
6e71f2f166 my fix 2020-02-15 20:55:12 +01:00
Matthias
44ac2409ff Merge pull request #2923 from hroff-1902/status-strategies
Add printing statuses for enlisted strategies and hyperopts
2020-02-15 19:43:02 +01:00
hroff-1902
6139239b86 Address points stated in comments 2020-02-15 20:43:11 +03:00
hroff-1902
9d09a67dea Merge pull request #2925 from freqtrade/fix_edgedocs
Fix edge documentation rendering
2020-02-15 19:41:59 +03:00
Matthias
87b506972f Fix edge documentation rendering 2020-02-15 13:12:29 +01:00
hroff-1902
c453969235 Merge pull request #2922 from freqtrade/config_userdir
default to loading config.json from userdir if it exists
2020-02-15 08:00:04 +03:00
hroff-1902
fdd362299f Docs adjusted 2020-02-15 07:34:39 +03:00
hroff-1902
42a5d78e60 Wording (duplicate, not duplicated) 2020-02-15 07:19:24 +03:00
hroff-1902
ddea4b9300 Fix test 2020-02-15 06:54:18 +03:00
hroff-1902
e8c0a0bcd3 Make mypy happy 2020-02-15 06:18:00 +03:00
hroff-1902
1cf19133f4 Added missing failing strategy 2020-02-15 05:41:58 +03:00
hroff-1902
29d9b6a46a Add test for enum failed 2020-02-15 04:32:10 +03:00
hroff-1902
93f9ff1b63 Fix existing test 2020-02-15 04:22:21 +03:00
hroff-1902
06b84b4086 Remove redundant code 2020-02-14 23:13:49 +03:00
hroff-1902
bba7a38144 Merge pull request #2906 from freqtrade/fix/jupyterexample
Update data-analysis documentation to properly initialize configuration
2020-02-14 22:36:35 +03:00
hroff-1902
47a91c9d8e Remove green color 2020-02-14 22:32:46 +03:00
hroff-1902
e598c769d4 Add colorization 2020-02-14 22:28:49 +03:00
Matthias
f024cc40d3 Fix windows test failure 2020-02-14 20:21:09 +01:00
Matthias
ecca7164d9 Fix small issue 2020-02-14 20:13:36 +01:00
Matthias
d5a298bbb7 Add sentence from suggestion 2020-02-14 20:12:26 +01:00
Matthias
5efbdd25a7 Properly default to user_data/config.json if it exists 2020-02-14 20:04:05 +01:00
Matthias
ab27d2c720 Merge pull request #2921 from hroff-1902/adjust-buy-notification
Move rpc send to be after db session add/flash
2020-02-14 20:02:56 +01:00
hroff-1902
c92e1d97d6 Attempt to make mypy happy 2020-02-14 21:52:02 +03:00
hroff-1902
1bc26fd07a Add printing statuses for list-hyperopts 2020-02-14 21:46:22 +03:00
Matthias
be4a9b5f4b Lowercase freqtrade 2020-02-14 19:37:20 +01:00
Matthias
9dafc2f3c8 Load config.json from user_data first 2020-02-14 19:33:10 +01:00
hroff-1902
a2d7f8a70d Split tabular printing into sep. helper function 2020-02-14 21:24:30 +03:00
hroff-1902
9cbf8c5f00 Add status for listed strategies 2020-02-14 21:15:36 +03:00
Matthias
3312fd34f3 Merge pull request #2920 from hroff-1902/remove-delete-trades
Get rid of delete_trade method in Freqtradebot
2020-02-14 07:27:52 +01:00
Matthias
ee92e8dbf4 Merge pull request #2919 from hroff-1902/adjust-main
Minor: Adjust message in main.py
2020-02-14 07:27:19 +01:00
Matthias
ec5d2d78dd Merge pull request #2918 from hroff-1902/bittrex-config
Add order_types into Bittrex config subtemplate
2020-02-14 07:26:49 +01:00
hroff-1902
20c21b42d5 Move rpc send to be after db session add/flash 2020-02-14 06:23:03 +03:00
hroff-1902
36ef5c6bdf Get rid of delete_trades method in Freqtradebot 2020-02-14 04:05:17 +03:00
hroff-1902
2dea362eda Merge pull request #2887 from yazeed/rpc_notification_fixes
Wide RPC notifications fixes
2020-02-14 03:51:23 +03:00
hroff-1902
749463e4b7 Adjust message in main.py 2020-02-14 03:05:07 +03:00
hroff-1902
a0a14a1078 freqtrade/templates/subtemplates/exchange_bittrex.j2 2020-02-14 01:08:17 +03:00
hroff-1902
4cdcf00ddc Merge branch 'develop' into rpc_notification_fixes 2020-02-14 00:10:50 +03:00
hroff-1902
0631fc937a Merge pull request #2915 from freqtrade/documentation_test
fix configuration table
2020-02-13 17:27:54 +03:00
Matthias
02148a1df2 Fix datatype styling issues 2020-02-13 15:09:09 +01:00
Matthias
a93bc74eff Update documentation ... 2020-02-13 07:04:37 +01:00
Matthias
ccc9239751 Reduce indentation of help 2020-02-13 07:02:12 +01:00
Matthias
86592c3ba1 Fix /help from telegram 2020-02-13 06:51:52 +01:00
Matthias
81f849811f Initcap Freqtrade 2020-02-13 06:30:59 +01:00
Matthias
3e6209def2 Merge pull request #2914 from freqtrade/hroff-1902-patch-1
Docs: Fix checking of runmode
2020-02-13 06:28:50 +01:00
hroff-1902
b2328cdf4f Do not subtract risk_free_ratio twice 2020-02-13 07:07:35 +03:00
hroff-1902
634bf2b15c Docs: Fix checking of runmode 2020-02-13 01:44:46 +03:00
Yazeed Al Oyoun
007cc94474 fixed tests to send refresh, since its no longer defaulted 2020-02-12 22:03:56 +01:00
Yazeed Al Oyoun
f09af888b1 modified get_buy/sell_rate refresh to true on notify_sell_cancel and notify_buy_cancel 2020-02-12 21:55:38 +01:00
Yazeed Al Oyoun
2e3b8cdba7 fixed flake8 issues on /help output 2020-02-12 21:51:58 +01:00
Yazeed Al Oyoun
f6db784a85 removed default to refresh argument in get_buy_rate and get_sell_rate 2020-02-12 21:50:33 +01:00
Yazeed Al Oyoun
47874a4527 added logic to differentiate sell orders with double asterisk 2020-02-12 21:45:55 +01:00
Matthias
2efa1c164f Revert data-location section 2020-02-12 21:43:43 +01:00
Matthias
483cba453a Fix last occurence of data_location 2020-02-12 19:58:23 +01:00
Matthias
d6b9397579 Fix typo in datadir key 2020-02-12 06:40:13 +01:00
Matthias
9a22ce69bd Merge pull request #2908 from hroff-1902/tests_load_default_strategy
Do not instantiate directly DefaultStrategy in tests
2020-02-12 06:37:19 +01:00
hroff-1902
4f3376e2a1 Do not instantiate directly DefaultStrategy in tests 2020-02-12 01:39:15 +03:00
hroff-1902
e73dac8d91 Merge pull request #2905 from Fredrik81/hyperopt-more-filters
Adding --min-trades, --max-trades, --max-avg-profit, --max-total-profit for hyperopt-list
2020-02-11 23:46:48 +03:00
Fredrik Rydin
539343b20d Adding 2 more filter options for completeness 2020-02-11 21:29:55 +01:00
Matthias
7be9f0067e Update data-analysis documentation to properly initialize configuration 2020-02-11 20:51:39 +01:00
Matthias
64fb8e28ec Merge pull request #2886 from freqtrade/docker_docs
Docker docs
2020-02-11 19:41:42 +01:00
Matthias
c35fe2c386 Add link to quick-start-guide 2020-02-11 19:29:43 +01:00
Fredrik Rydin
d1c3eabb87 Changed commands to use "check_int_positive" 2020-02-11 18:08:30 +01:00
Yazeed Al Oyoun
899de8b27c modified tests for double partial call 2020-02-11 16:50:18 +01:00
Yazeed Al Oyoun
cde1b2b56c readded rpc status message for partial buys 2020-02-11 16:28:48 +01:00
Yazeed Al Oyoun
5f4c209fca fixed one more occurence of executed buy, and test 2020-02-11 16:14:49 +01:00
Yazeed Al Oyoun
4fedf1e564 default refresh TRUE on get_buy_rate and get_sell_Rate 2020-02-11 16:05:44 +01:00
Fredrik Rydin
5b4d8d69ef Adding --min-trades and --max-trades for hyperopt-list 2020-02-11 16:02:08 +01:00
Yazeed Al Oyoun
fc29564974 Fixed messages and readability 2020-02-11 15:58:40 +01:00
Yazeed Al Oyoun
867b736b84 Fixed to Executing Buys & Sells 2020-02-11 15:50:21 +01:00
Yazeed Al Oyoun
7f4b90c68f fixed actual open_rate in notify_buy_cancel 2020-02-11 15:45:35 +01:00
Yazeed Al Oyoun
f99d1c3829 fixed open_rate instead of open_rate_requested 2020-02-11 15:44:47 +01:00
Matthias
59a576ef3e Merge pull request #2903 from hroff-1902/fix/tests_history-2
Minor: Fix tests_history.py
2020-02-11 07:14:13 +01:00
Matthias
81997cba8a Merge pull request #2902 from hroff-1902/fix/tests_hyperopt_loss
Minor: Fix tests for hyperopt_loss
2020-02-11 07:13:29 +01:00
Matthias
57fcca9696 Merge pull request #2904 from hroff-1902/fix/tests_backtesting_container
Minor: Fix usage of an item from BTContainer in tests
2020-02-11 07:02:28 +01:00
hroff-1902
29f7c5071b Fix usage of an item from BTContainer in tests 2020-02-11 04:17:10 +03:00
hroff-1902
62bcb3d766 Fix tests in test_history.py 2020-02-11 03:43:20 +03:00
hroff-1902
2bcce33f23 Merge pull request #2888 from Fredrik81/hyperopt-filters
Added filter options to "hyperopt-list" in order to easier find epochs.
2020-02-10 23:59:40 +03:00
Fredrik Rydin
f2520c11e7 Used wrong utils.md as base 2020-02-10 21:19:25 +01:00
Fredrik Rydin
c924e4d519 Updated based on feedback:
- Profit commands now use float
- Compatible with --best
- Corrected wrong information in docs
2020-02-10 20:54:31 +01:00
Matthias
d442b31f84 Merge pull request #2894 from freqtrade/hroff-1902-patch-2
Minor: Adjust mypy and flake commands
2020-02-10 19:03:09 +01:00
hroff-1902
df8a27fba6 Merge pull request #2900 from freqtrade/rem_bin
Break the old binary file so users are forced to reinstall
2020-02-10 20:52:13 +03:00
hroff-1902
05128d21a8 Suggest to run flake for scripts 2020-02-10 20:48:49 +03:00
Matthias
faf19eda86 Break the old binary file so users are forced to reinstall
Note:
This should not be relevant anymore - this binary has been deprecated
and is not being used by new installations since July 2019.
2020-02-10 17:31:49 +01:00
hroff-1902
d07c69809d Fix tests for hyperopt_loss 2020-02-10 18:32:41 +03:00
hroff-1902
da03c36875 Merge pull request #2899 from freqtrade/mypy_tests
Fix mypy type errors in tests
2020-02-10 15:43:57 +03:00
hroff-1902
0ac0ca74b5 return back hint for running mypy 2020-02-10 15:41:09 +03:00
Yazeed Al Oyoun
d69ddd2ac3 Apply suggestions from code review
Committed 1 code suggestion in code review.

Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com>
2020-02-10 11:54:12 +01:00
Matthias
1a544be828 Merge pull request #2898 from freqtrade/dependabot/pip/develop/mkdocs-material-4.6.2
Bump mkdocs-material from 4.6.0 to 4.6.2
2020-02-10 11:03:10 +01:00
Matthias
bfccb2e96a Merge pull request #2896 from freqtrade/dependabot/pip/develop/pandas-1.0.1
Bump pandas from 1.0.0 to 1.0.1
2020-02-10 11:02:54 +01:00
Matthias
7bb02d0cc6 Update docker-docs wording 2020-02-10 11:01:33 +01:00
Matthias
f220e0f6ca Merge pull request #2897 from freqtrade/dependabot/pip/develop/python-telegram-bot-12.4.1
Bump python-telegram-bot from 12.3.0 to 12.4.1
2020-02-10 10:40:37 +01:00
Matthias
83644ce5d8 Fix mypy type errors in tests 2020-02-10 10:35:48 +01:00
dependabot-preview[bot]
550f9fc891 Bump python-telegram-bot from 12.3.0 to 12.4.1
Bumps [python-telegram-bot](https://github.com/python-telegram-bot/python-telegram-bot) from 12.3.0 to 12.4.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/v12.3.0...v12.4.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-10 08:45:27 +00:00
Matthias
c7167c83cd Merge pull request #2895 from freqtrade/dependabot/pip/develop/ccxt-1.22.39
Bump ccxt from 1.22.30 to 1.22.39
2020-02-10 09:44:08 +01:00
dependabot-preview[bot]
6b4094fd92 Bump mkdocs-material from 4.6.0 to 4.6.2
Bumps [mkdocs-material](https://github.com/squidfunk/mkdocs-material) from 4.6.0 to 4.6.2.
- [Release notes](https://github.com/squidfunk/mkdocs-material/releases)
- [Changelog](https://github.com/squidfunk/mkdocs-material/blob/master/CHANGELOG)
- [Commits](https://github.com/squidfunk/mkdocs-material/compare/4.6.0...4.6.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-10 08:02:45 +00:00
dependabot-preview[bot]
88f2ad1eae Bump pandas from 1.0.0 to 1.0.1
Bumps [pandas](https://github.com/pandas-dev/pandas) from 1.0.0 to 1.0.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.0.0...v1.0.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-10 08:02:07 +00:00
dependabot-preview[bot]
90ee82ac43 Bump ccxt from 1.22.30 to 1.22.39
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.22.30 to 1.22.39.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/1.22.30...1.22.39)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-10 08:01:42 +00:00
Matthias
db0475f9c3 Merge pull request #2890 from freqtrade/hroff-1902-patch-1
Add tip on running order types for Bittrex
2020-02-10 08:33:24 +01:00
hroff-1902
4af25ec315 Adjust mypy and flake commands 2020-02-10 05:52:07 +03:00
hroff-1902
0ba8d13de9 Merge pull request #2831 from xmatthias/feat/new_config
introduce new-config subcommand
2020-02-09 22:11:37 +03:00
hroff-1902
f7c74e551f Fix wording 2020-02-09 21:56:59 +03:00
Fredrik81
5bf4c5869b Update hyperopt_commands.py
Missed a debug print
2020-02-09 19:32:09 +01:00
hroff-1902
9ec9a7b124 Fix t_index to be normalized 2020-02-09 21:20:15 +03:00
hroff-1902
cc3f65d069 Fix typo 2020-02-09 19:45:04 +03:00
hroff-1902
c83da7cadb Add section about order types into Bittrex Exchange-specific chapter 2020-02-09 19:11:06 +03:00
Matthias
7c91c77fd9 Merge pull request #2891 from freqtrade/hroff-1902-patch-2
Fix SharpeHyperOptLossDaily
2020-02-09 17:06:22 +01:00
hroff-1902
40abdd2608 Suggest changing strategy 2020-02-09 18:54:04 +03:00
hroff-1902
c89a32224c Fix SharpeHyperOptLossDaily 2020-02-09 18:40:19 +03:00
Matthias
d65a06947d Merge branch 'develop' into data_handler 2020-02-09 15:16:43 +01:00
Fredrik Rydin
eb3783dc00 Fixed a blank line issue :-( 2020-02-09 14:30:29 +01:00
Fredrik Rydin
c648ec7c0c Added test cases and fixed a minor bug 2020-02-09 14:18:56 +01:00
hroff-1902
c7ba85c2e6 Add tip on running order types for Bittrex 2020-02-09 14:19:13 +03:00
Matthias
b536d50194 Address PR Review 2020-02-09 11:41:29 +01:00
Matthias
f41de38498 Merge pull request #2884 from freqtrade/fix/stakecurr_validation
fix download-data validation problems
2020-02-09 11:07:39 +01:00
Fredrik Rydin
c96acd6ca0 Fixed to pass PEP8 2020-02-09 00:16:11 +01:00
Yazeed Al Oyoun
2796d3d8a0 added missing tests to increase coverage 2020-02-09 00:11:58 +01:00
Fredrik Rydin
636bd5acb5 Added filter options to "hyperopt-list" in order to easier find epochs.
--profitable
	Select only profitable epochs.
  --min-avg-time INT
	Select epochs on above average time.
  --max-avg-time INT
	Select epochs on under average time.
  --min-avg-profit FLOAT
	Select epochs on above average profit.
  --min-total-profit FLOAT
	Select epochs on above total profit.
2020-02-08 23:21:42 +01:00
Matthias
1a9787ac76 Add validation for data-download relevant settings 2020-02-08 21:53:34 +01:00
Yazeed Al Oyoun
879b513822 enhanced method description 2020-02-08 21:31:36 +01:00
Yazeed Al Oyoun
4fad7a462c fixes in webhook-config docs 2020-02-08 21:19:07 +01:00
Yazeed Al Oyoun
f3b1161640 wide notifications fixes 2020-02-08 21:02:52 +01:00
Matthias
c4031761fe Don't validate exchange for data-download subcommand 2020-02-08 19:29:58 +01:00
Matthias
34f04668c1 Add template for bittrex 2020-02-08 14:02:51 +01:00
Matthias
52f4187129 Allow exchange templates to configure outside-options too 2020-02-08 13:51:55 +01:00
Matthias
f508324fc8 Update docker documentation to be easier to use 2020-02-08 13:38:45 +01:00
Matthias
a1fe3850e2 Improve docker-compose file to be ready to use 2020-02-08 13:34:04 +01:00
Matthias
fff8ced3b0 Merge pull request #2843 from hroff-1902/allow-derived-strategies
Allow derived strategies
2020-02-08 09:15:35 +01:00
Matthias
67e66a6c4a Merge pull request #2882 from hroff-1902/update_advanced_hyperopt_template
Update advanced hyperopt template
2020-02-08 09:07:18 +01:00
Matthias
5bae5a6a35 Merge pull request #2881 from hroff-1902/no_nxt_in_config_example
Minor: Replace NXT with XRP in config.json.example
2020-02-08 09:05:23 +01:00
hroff-1902
61ced5e926 Fix typo 2020-02-08 02:49:06 +03:00
hroff-1902
28184201e4 Align sample_hyperopt_advanced.py to hyperopt_interface.py 2020-02-08 02:47:50 +03:00
hroff-1902
a893f70e49 Replace NXT with XRP in config.json.example 2020-02-08 02:21:39 +03:00
hroff-1902
6990f6af25 Merge pull request #2870 from freqtrade/dry_run_docs
Add considerations for dry-run
2020-02-08 00:09:58 +03:00
hroff-1902
c501fd0a70 Merge pull request #2875 from yazeed/distinguish_draws_from_wins
Add draws column to backtesting tables
2020-02-07 22:55:12 +03:00
Matthias
abf10aec98 Merge branch 'develop' into feat/new_config 2020-02-07 17:02:14 +01:00
Yazeed Al Oyoun
6b279f297c fixed test 2020-02-07 16:45:07 +03:00
Yazeed Al Oyoun
e8b9d88eb6 moved line for total_downside 2020-02-07 16:44:55 +03:00
Yazeed Al Oyoun
a46b7bcd6d more fixes... 2020-02-07 16:44:43 +03:00
Yazeed Al Oyoun
951a19fb00 added tests for both sortino methods 2020-02-07 16:44:30 +03:00
Yazeed Al Oyoun
be34dc463b fixed bad commit 2020-02-07 16:44:01 +03:00
Yazeed Al Oyoun
9bcc5d2eed fixed downside_returns to read from profit_percent_after_slippage 2020-02-07 16:36:12 +03:00
Yazeed Al Oyoun
728ab0ff21 Added both SortinoHyperOptLoss and SortinoHyperOptLossDaily 2020-02-07 16:35:28 +03:00
Yazeed Al Oyoun
b56a1f0603 initial push of sortino, work not done, still need own tests 2020-02-07 16:34:20 +03:00
Yazeed Al Oyoun
deb0b7ad67 Added both SortinoHyperOptLoss and SortinoHyperOptLossDaily 2020-02-07 16:30:37 +03:00
Yazeed Al Oyoun
44d67389d2 initial push of sortino, work not done, still need own tests 2020-02-07 16:29:27 +03:00
Yazeed Al Oyoun
aa2cb937b1 flake8 :) 2020-02-07 03:54:47 +01:00
Yazeed Al Oyoun
ff819386e1 added draws to backtesting tables, reduced len of some labels to help fit this without increasing total width 2020-02-07 03:51:50 +01:00
hroff-1902
f57bd6b616 Keep the docs clean for unexperienced users 2020-02-06 21:53:03 +03:00
hroff-1902
418e7adac1 Highlight syntax in advanced-hyperopt as well 2020-02-06 17:49:10 +03:00
hroff-1902
2034527faa Update docs/strategy-customization.md
Co-Authored-By: Matthias <xmatthias@outlook.com>
2020-02-06 17:45:15 +03:00
hroff-1902
412f5d68de Add description to hyperopt advanced doc chapter 2020-02-06 17:42:26 +03:00
hroff-1902
2846f9454f Add description in the docs 2020-02-06 17:02:11 +03:00
hroff-1902
739acaa475 Wordings improved 2020-02-06 13:54:51 +03:00
Matthias
97e48080e8 Merge pull request #2839 from hroff-1902/list-hyperopts-2
Add list-hyperopts subcommand
2020-02-06 07:06:36 +01:00
Yazeed Al Oyoun
5b00eaa42d Updated Strategy Summary table to match other backtesting tables (#2864) 2020-02-06 06:58:58 +01:00
Yazeed Al Oyoun
9639ffb140 added daily sharpe ratio hyperopt loss method, ty @djacky (#2826)
* more consistent backtesting tables and labels

* added rounding to Tot Profit % on Sell Reasosn table to be consistent with other percentiles on table.

* added daily sharpe ratio hyperopt loss method, ty @djacky

* removed commented code

* removed unused profit_abs

* added proper slippage to each trade

* replaced use of old value total_profit

* Align quotes in same area

* added daily sharpe ratio test and modified hyperopt_loss_sharpe_daily

* fixed some more line alignments

* updated docs to include SharpeHyperOptLossDaily

* Update dockerfile to 3.8.1

* Run tests against 3.8

* added daily sharpe ratio hyperopt loss method, ty @djacky

* removed commented code

* removed unused profit_abs

* added proper slippage to each trade

* replaced use of old value total_profit

* added daily sharpe ratio test and modified hyperopt_loss_sharpe_daily

* updated docs to include SharpeHyperOptLossDaily

* docs fixes

* missed one fix

* fixed standard deviation line

* fixed to bracket notation

* fixed to bracket notation

* fixed syntax error

* better readability, kept np.sqrt(365) which results in  annualized sharpe ratio

* fixed method arguments indentation

* updated commented out debug print line

* renamed after slippage profit_percent so it wont affect _calculate_results_metrics()

* Reworked to fill leading and trailing days

* No need for np; make flake happy

* Fix risk free rate

Co-authored-by: Matthias <xmatthias@outlook.com>
Co-authored-by: hroff-1902 <47309513+hroff-1902@users.noreply.github.com>
2020-02-06 06:49:08 +01:00
Matthias
586cbc750c Add considerations for dry-run 2020-02-06 06:45:11 +01:00
hroff-1902
b5ee4f17cb Merge pull request #2830 from orehunt/spreadfilter
- added spread filter
2020-02-04 23:37:09 +03:00
Matthias
6866f6389d Fix merge-error 2020-02-04 20:41:13 +01:00
hroff-1902
d2cac1d8fd Merge branch 'develop' into spreadfilter 2020-02-04 16:54:46 +03:00
untoreh
aa54fd2251 - added spread filter
- minimum value to volume pairlist
2020-02-04 14:49:59 +01:00
hroff-1902
f5fb129483 Merge pull request #2858 from freqtrade/fix/rolling_max
Fix implementation of rolling_max
2020-02-04 14:05:05 +03:00
Matthias
a707aeb3d0 Fix implementation of rolling_max 2020-02-04 07:00:53 +01:00
Matthias
f8bb6a3e06 Merge pull request #2855 from yazeed/text_mods_in_check_depth_of_market_buy
More consistency in check_depth_of_market_buy()
2020-02-04 06:24:58 +01:00
Yazeed Al Oyoun
91b4c9668c More consistency changes... 2020-02-04 01:57:24 +01:00
hroff-1902
d457d43999 Merge pull request #2833 from hroff-1902/type-hints
Add some type hints
2020-02-03 23:24:26 +03:00
hroff-1902
ffb53a6df5 get rid of typing.cast() 2020-02-03 23:08:35 +03:00
hroff-1902
82590657fb Merge pull request #2848 from freqtrade/dependabot/pip/develop/scikit-optimize-0.7.1
Bump scikit-optimize from 0.5.2 to 0.7.1
2020-02-03 22:53:01 +03:00
Matthias
54303880d3 Merge pull request #2849 from freqtrade/dependabot/pip/develop/pandas-1.0.0
Bump pandas from 0.25.3 to 1.0.0
2020-02-03 20:34:17 +01:00
Matthias
cbabc295c7 Don't convert to datetime - but convert to datetime64 instead 2020-02-03 20:25:43 +01:00
Matthias
b8aaf744e8 Merge pull request #2851 from hroff-1902/improve-logging-3
Add pair to exception messages in exchange module
2020-02-03 16:04:55 +01:00
hroff-1902
64f04845b6 Merge pull request #2850 from freqtrade/try_fix_randoM-test
make sure asyncio_loop is not initialized within ccxt code
2020-02-03 17:45:49 +03:00
hroff-1902
684cb54992 Add pair to exception msg 2020-02-03 17:17:46 +03:00
Matthias
f6c09160ab make sure asyncio_loop is not initialized within ccxt code 2020-02-03 15:17:36 +01:00
Matthias
221950cdc4 Merge pull request #2845 from freqtrade/dependabot/pip/develop/jinja2-2.11.1
Bump jinja2 from 2.10.3 to 2.11.1
2020-02-03 10:07:56 +01:00
Matthias
6e6c0757d6 Merge pull request #2847 from freqtrade/dependabot/pip/develop/ccxt-1.22.30
Bump ccxt from 1.21.91 to 1.22.30
2020-02-03 10:05:46 +01:00
Matthias
2ee99bde29 Merge pull request #2846 from freqtrade/dependabot/pip/develop/pytest-5.3.5
Bump pytest from 5.3.4 to 5.3.5
2020-02-03 09:58:04 +01:00
dependabot-preview[bot]
d5f704009f Bump pandas from 0.25.3 to 1.0.0
Bumps [pandas](https://github.com/pandas-dev/pandas) from 0.25.3 to 1.0.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/v0.25.3...v1.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-03 08:03:45 +00:00
dependabot-preview[bot]
3938418ad5 Bump scikit-optimize from 0.5.2 to 0.7.1
Bumps [scikit-optimize](https://github.com/scikit-optimize/scikit-optimize) from 0.5.2 to 0.7.1.
- [Release notes](https://github.com/scikit-optimize/scikit-optimize/releases)
- [Changelog](https://github.com/scikit-optimize/scikit-optimize/blob/master/CHANGELOG.md)
- [Commits](https://github.com/scikit-optimize/scikit-optimize/compare/v0.5.2...v0.7.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-03 08:03:21 +00:00
dependabot-preview[bot]
401748e9a7 Bump ccxt from 1.21.91 to 1.22.30
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.21.91 to 1.22.30.
- [Release notes](https://github.com/ccxt/ccxt/releases)
- [Changelog](https://github.com/ccxt/ccxt/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ccxt/ccxt/compare/1.21.91...1.22.30)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-03 08:02:54 +00:00
dependabot-preview[bot]
bc2ae3e88d Bump pytest from 5.3.4 to 5.3.5
Bumps [pytest](https://github.com/pytest-dev/pytest) from 5.3.4 to 5.3.5.
- [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/5.3.4...5.3.5)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-03 08:02:21 +00:00
dependabot-preview[bot]
7b8e665323 Bump jinja2 from 2.10.3 to 2.11.1
Bumps [jinja2](https://github.com/pallets/jinja) from 2.10.3 to 2.11.1.
- [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.10.3...2.11.1)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-02-03 08:01:28 +00:00
hroff-1902
df249c7c03 Remove unclear comment 2020-02-03 09:37:50 +03:00
hroff-1902
5c20311768 Merge pull request #2844 from freqtrade/coveralls_twice
Only run coveralls once
2020-02-03 09:18:42 +03:00
Matthias
d0506a6435 Use correct matrix variable 2020-02-03 07:01:07 +01:00
Matthias
c8960ab628 Only run coveralls once 2020-02-03 06:50:07 +01:00
hroff-1902
537596001e Allow derived strategies 2020-02-03 06:20:01 +03:00
Matthias
d8c053573a Merge pull request #2840 from freqtrade/fix/testerrror
Fix failing stoploss CI test
2020-02-02 20:49:42 +01:00
Matthias
2b69e7830d Fix failing CI test 2020-02-02 20:08:50 +01:00
Matthias
e3cb5d26c0 Merge pull request #2835 from yazeed/reduce_noise_if_use_order_book_true
reduced noise without verbose mode if use_order_book is true
2020-02-02 19:42:50 +01:00
hroff-1902
84156879f6 Fix NO_CONF_REQUIRED for list-hyperopts 2020-02-02 20:11:42 +03:00
hroff-1902
d12e03e50d Fix test inconsistency in test_freqtradebot.py 2020-02-02 20:01:25 +03:00
hroff-1902
cd0534efcc Add test 2020-02-02 19:41:22 +03:00
hroff-1902
505648fb66 Adjust docs 2020-02-02 19:41:22 +03:00
hroff-1902
857eb5ff69 Add list-hyperopts command 2020-02-02 19:41:22 +03:00
hroff-1902
3fe39a3e1b Rename constant 2020-02-02 19:41:22 +03:00
hroff-1902
a5e670b402 Add USERPATH_NOTEBOOKS 2020-02-02 19:41:22 +03:00
hroff-1902
e8c1abc509 Merge pull request #2799 from freqtrade/fix_stoploss_recreated
Fix stoploss recreated
2020-02-02 16:59:45 +03:00
hroff-1902
6594679e52 Merge pull request #2779 from freqtrade/stoploss_market
Stoploss on exchange for Kraken
2020-02-02 14:48:45 +03:00
Matthias
c1897fbc48 Merge pull request #2834 from yazeed/consistent_main_sharpe_hyperopt_loss
better readability on sharpe ratio loss method
2020-02-02 11:12:56 +01:00
Yazeed Al Oyoun
aeabe1800b modified two lines from logger.info to logger.debug cause they're too spammy 2020-02-02 10:49:00 +01:00
Matthias
d64751687b Fix link and lowercase variable 2020-02-02 10:47:44 +01:00
Yazeed Al Oyoun
3499f1b85c better readability and more consistent with daily sharpe loss method 2020-02-02 08:47:33 +01:00
hroff-1902
f3d500085c Add some type hints 2020-02-02 07:00:40 +03:00
Matthias
aa8731d0fc Merge pull request #2832 from freqtrade/python_3.8
Python 3.8
2020-02-01 19:39:45 +01:00
Matthias
cbd2b265bb Fix small error 2020-02-01 15:16:44 +01:00
Matthias
321bc336ea Run tests against 3.8 2020-02-01 15:14:55 +01:00
Matthias
4459679c64 Update dockerfile to 3.8.1 2020-02-01 15:14:44 +01:00
Matthias
628b06927c Support python3.8 virtualenvs and remove config generation via SED 2020-02-01 14:59:14 +01:00
Matthias
12317b1c53 Add some rudimentary tests for questions 2020-02-01 14:46:43 +01:00
Matthias
d1a3a2d000 Add tests for build_config 2020-02-01 14:22:40 +01:00
Matthias
cfa6a3e3d3 Don't overwrite files 2020-02-01 14:12:21 +01:00
Matthias
c224c66978 Small edits to install.md 2020-02-01 14:06:31 +01:00
Matthias
929bbe3058 Link to docker installation from index.md 2020-02-01 14:01:19 +01:00
Matthias
8796ecb2a9 Ad example for new-config with answered questions 2020-02-01 13:56:57 +01:00
Matthias
54512a66ef Update help-strings for list-utils 2020-02-01 13:52:25 +01:00
Matthias
c40a4d77f8 Use exchange_mapping to determine correct exchange-template 2020-02-01 13:46:58 +01:00
Matthias
d69ef4380b Add basic documentation for new-config option 2020-02-01 13:44:04 +01:00
Matthias
19d4e1435c Merge pull request #2828 from yazeed/line_alignment_fixes
fixed some more line alignments
2020-02-01 11:19:28 +01:00
Matthias
4a80c47fd1 Merge pull request #2825 from yazeed/better_backtesting_tables
more consistent backtesting and sell reasons tables
2020-02-01 11:18:50 +01:00
Yazeed Al Oyoun
d038bcedb0 fixed some more line alignments 2020-01-31 22:37:05 +01:00
Matthias
c396ad4daa Align quotes in same area 2020-01-31 20:41:51 +01:00
Yazeed Al Oyoun
907a61152c added rounding to Tot Profit % on Sell Reasosn table to be consistent with other percentiles on table. 2020-01-31 04:53:37 +01:00
Yazeed Al Oyoun
e2b3907df5 more consistent backtesting tables and labels 2020-01-31 04:39:18 +01:00
Matthias
4be3f053ca Exclude trading against BNB bases on binance 2020-01-30 21:42:48 +01:00
Matthias
83baa6ee2e Add test stub 2020-01-29 22:47:15 +01:00
Matthias
cebf99b5d8 Implement validation 2020-01-29 22:46:47 +01:00
Matthias
acbf13e648 Fail gracefully if user interrupted question session 2020-01-29 21:47:05 +01:00
Matthias
2f0775fa1b Extract build-config tests to new file 2020-01-29 21:31:09 +01:00
Matthias
940bfbee96 Move start_config out of build_commands file 2020-01-29 21:28:01 +01:00
Matthias
e250c56829 Add Questionaire workflow 2020-01-29 21:21:38 +01:00
Matthias
49c9258a08 enhance test 2020-01-29 20:43:10 +01:00
Matthias
dd83cb1b95 Extract selection generation to a seperate method 2020-01-29 20:27:38 +01:00
Matthias
2396f35586 Merge pull request #2819 from hroff-1902/worker-delete-state
Remove state attribute from Worker class
2020-01-29 15:57:35 +01:00
hroff-1902
68771a7861 Remove state attr from Worker 2020-01-29 17:08:36 +03:00
hroff-1902
e1356fb80e Merge pull request #2800 from yazeed/enhanced_check_depth_of_market_logging
better logging on check_depth_of_market_buy()
2020-01-29 10:56:14 +03:00
Matthias
c80d8f432a Add exchange templates 2020-01-29 07:13:38 +01:00
Matthias
122c916356 Add first version of config_deploy 2020-01-29 07:03:22 +01:00
Matthias
9f29128205 Fix small json formatting issue 2020-01-29 07:01:17 +01:00
Matthias
b384ca8fd2 Create new-config command 2020-01-29 06:47:01 +01:00
Yazeed Al Oyoun
a0b92fe0b1 removed typo 2020-01-28 19:29:47 +01:00
Yazeed Al Oyoun
328a9ffafd fixed typo in false statement 2020-01-28 19:27:49 +01:00
Matthias
d40054b9d2 Merge pull request #2815 from hroff-1902/docs-gitclone
Minor: Advise to use https method for git clone instead of ssh
2020-01-28 06:25:08 +01:00
hroff-1902
4c0e586354 Advise to use https method for git clone i.o ssh 2020-01-27 22:39:04 +03:00
Matthias
1ef148317d Merge branch 'develop' into stoploss_market 2020-01-26 20:33:41 +01:00
Matthias
1b9af9d2d8 Merge branch 'develop' into data_handler 2020-01-26 20:31:13 +01:00
Yazeed Al Oyoun
f8db7f1709 added ask price, bid price, immediate ask quantity, and immediate bid quantity to check_depth_of_market_buy. also added a line that mentions if delta condition was satisfied or not. 2020-01-25 04:17:41 +01:00
Matthias
72c273aaed Add test for closed trade case 2020-01-23 21:07:21 +01:00
Matthias
70b9bd9c0e Verify if trade is closed before acting on Stoploss_on_exchange 2020-01-23 20:36:48 +01:00
Matthias
ea5ac1efb5 Don't handle stoploss if there is an open regular order 2020-01-23 20:24:23 +01:00
Matthias
a83de241e4 Check for closed stoploss-orders first 2020-01-23 19:40:31 +01:00
Matthias
f5a44e4fc4 open_order_id should be None when handling stoploss orders 2020-01-23 19:38:35 +01:00
Matthias
1d141cd406 Merge branch 'develop' into stoploss_market 2020-01-23 19:35:05 +01:00
Matthias
bc4c469797 Merge branch 'develop' into stoploss_market 2020-01-22 20:51:52 +01:00
Matthias
fc2970f41b Merge branch 'develop' into data_handler 2020-01-21 06:58:48 +01:00
Matthias
10d9db72a8 Adjust tests slightly 2020-01-19 20:06:04 +01:00
Matthias
cf9331919f move exchange-specific order-parsing to exchange class
Related to stoploss_on_exchange in combination with trailing stoploss.

Binance contains stopPrice in the info, while kraken returns the same
value as "price".
2020-01-19 19:54:30 +01:00
Matthias
7a22aaa111 UPdate documentation to reflect that stoploss-on-exchange is also
available for kraken
2020-01-19 14:40:09 +01:00
Matthias
f1629c907a Implement stoploss for kraken 2020-01-19 14:40:09 +01:00
Matthias
e6f1912443 Use named arguments for stoploss create_order call 2020-01-19 14:40:09 +01:00
Matthias
16b34e11ca Complete rename of stoploss_limit to stoploss 2020-01-19 14:40:09 +01:00
Matthias
256fc2e78c Rename stoploss_limit to stoploss 2020-01-19 13:30:56 +01:00
Matthias
da0af489a2 Adjust tests to pass in order_types instead of rate 2020-01-19 13:25:41 +01:00
Matthias
8d2e0bfd62 Move rate-calcuation for stoploss-limit order to exchange 2020-01-19 13:13:09 +01:00
Matthias
41945138ac Converting pairs from filename to pair corrected 2020-01-05 13:35:36 +01:00
Matthias
4eaaec9d1a Implement pair_to_filename to datahandler
includes tests - taken from #2744 and modified to adapt to new structure
2020-01-05 10:36:08 +01:00
hroff-1902
bc6a10353b Introduce pair_to_filename() 2020-01-05 10:22:07 +01:00
Matthias
f82c4346b6 data conversion, not data conversation
* we're not talking to the data yet ...
2020-01-05 09:55:02 +01:00
Matthias
2409261cb7 Merge branch 'develop' into data_handler 2020-01-04 11:36:27 +01:00
Matthias
699c0d6bc3 Merge branch 'develop' into data_handler 2019-12-30 19:40:43 +01:00
Matthias
814cc20c6b Remove potential circular import 2019-12-28 19:58:41 +01:00
Matthias
f4a532ef6d Pass format to load_data 2019-12-28 14:57:39 +01:00
Matthias
6b5983339d Require dataformat entries in configuration 2019-12-28 14:47:30 +01:00
Matthias
ae1b28aab7 Remove get_datahandlerclass from package exposes 2019-12-28 14:32:11 +01:00
Matthias
e2a00c03d6 Document convert options 2019-12-28 11:24:37 +01:00
Matthias
66d18575a7 Implement abstract interface 2019-12-28 11:10:31 +01:00
Matthias
9e4fc00a0f Add test for convert_ohlcv 2019-12-28 11:03:06 +01:00
Matthias
70f3ff0461 Add test for convert_trades_Format 2019-12-28 11:03:06 +01:00
Matthias
e7054adc49 Add tests for start_convert_data 2019-12-28 11:03:06 +01:00
Matthias
28787a001c Move convert functions to convert module 2019-12-28 11:02:34 +01:00
Matthias
525550e4c7 Fix typo in parameter transition 2019-12-28 11:01:42 +01:00
Matthias
6860491189 Rename datahandler module to history module
Also move previous history.py into this module - so everything is
bundled
2019-12-28 11:01:42 +01:00
Matthias
b37b5c3d90 Remove Explicit datadir conversation 2019-12-28 11:01:42 +01:00
Matthias
9c5b94adf5 Pass data_format to methods 2019-12-28 11:01:42 +01:00
Matthias
65f539e9d8 More tests for datahandler 2019-12-28 11:01:42 +01:00
Matthias
d65c1eea7a Add some tests for datahandler 2019-12-28 11:01:42 +01:00
Matthias
8a030e7fc0 Use exists instead of is_file 2019-12-28 11:01:42 +01:00
Matthias
a3144cb2f0 remove trim_tickerlist 2019-12-28 11:01:42 +01:00
Matthias
baa942ff98 Don't use function to resolve pairname for test 2019-12-28 11:01:42 +01:00
Matthias
32c2ce146e Remove last usage of load_tickerlist 2019-12-28 11:01:42 +01:00
Matthias
4b277afc52 Remove test for load_tickerdata 2019-12-28 11:01:42 +01:00
Matthias
5479c67178 Clean up some codes which use list-based tests 2019-12-28 11:01:41 +01:00
Matthias
80dbba1280 Remove unnecessary mocks 2019-12-28 11:01:41 +01:00
Matthias
aa39f2160b Use load_data instead of a sequence of calls
in tests which don't test this
2019-12-28 11:01:41 +01:00
Matthias
a2567bea64 Remove unnecessary mock 2019-12-28 11:01:41 +01:00
Matthias
d1b52809ac Cleanup history 2019-12-28 11:01:41 +01:00
Matthias
d06777b8ce Remove old "load_cached_data" method 2019-12-28 11:01:41 +01:00
Matthias
7a6476c9ba Update tests 2019-12-28 11:01:41 +01:00
Matthias
e4f185f357 Remove 'line' from load_cached_data tests
Users are unable to use line anyway, it's only there for tests
2019-12-28 11:01:41 +01:00
Matthias
df085a6f15 Fix small bug and test 2019-12-28 11:01:41 +01:00
Matthias
c648d973c1 Implement new "load_data_for_updating" method based on dataframes 2019-12-28 11:01:41 +01:00
Matthias
ec8fb5f308 Make no-data warning optional 2019-12-28 11:01:41 +01:00
Matthias
b83487a70d Extract default dataframe columns to constant 2019-12-28 11:01:41 +01:00
Matthias
dbe8f727cb Fix typehint 2019-12-28 11:01:41 +01:00
Matthias
91c70a0e9c Change to use ohlcv_purge 2019-12-28 11:01:41 +01:00
Matthias
37c5b68987 Move dataframe validation to abstract class 2019-12-28 11:01:41 +01:00
Matthias
e861f05b75 Move dataframe trim to within jsondatahandler 2019-12-28 11:01:41 +01:00
Matthias
552c93abf0 Improve some docstrings 2019-12-28 11:01:41 +01:00
Matthias
b7c1d55491 Modify tests to point to datahandlers 2019-12-28 11:01:41 +01:00
Matthias
9876d126ca Use handler for trades 2019-12-28 11:01:41 +01:00
Matthias
9547d47ae2 Initialize datahandlers 2019-12-28 11:01:41 +01:00
Matthias
5fca17d7e1 Allow initializing handler-class just once 2019-12-28 11:01:41 +01:00
Matthias
416517b0c9 Move trim_dataframe from history to converter 2019-12-28 11:01:41 +01:00
Matthias
9d8ea2f13b Replace calls to load_tickerdata_file with DataHandler calls 2019-12-28 11:01:41 +01:00
Matthias
88fa7fc24c Simplify validate dataframe method 2019-12-28 11:01:41 +01:00
Matthias
53ee636fa0 Check if file exists before loading 2019-12-28 11:01:41 +01:00
Matthias
873f5dbe6b Revrite validate_pairdata to work with pandas 2019-12-28 11:01:41 +01:00
Matthias
db520a09ee Trim dataframe, not tickerlist 2019-12-28 11:01:41 +01:00
Matthias
866908d2ca Load and save using pandas internal function 2019-12-28 11:01:41 +01:00
Matthias
377d59abe7 Be selective how to load ohclv data for conversation 2019-12-28 11:01:41 +01:00
Matthias
d9e7d64f33 Split parse_ticker_dataframe some logic to clean_ohlcv_dataframe. 2019-12-28 11:01:41 +01:00
Matthias
1b90ec58b9 Use changed pair-handling for providers 2019-12-28 11:01:41 +01:00
Matthias
d923bab828 Remove abstract interface for now 2019-12-28 11:01:41 +01:00
Matthias
48728e2d66 Change DataProvider interface to accept pair per method 2019-12-28 11:01:41 +01:00
Matthias
e529a4c261 Fix typehint for get_datahandlerclass 2019-12-28 11:01:41 +01:00
Matthias
eff5cc0568 Add default to internals 2019-12-28 11:01:41 +01:00
Matthias
c6d6dbfdb1 Implement jsondatahandler file store 2019-12-28 11:01:41 +01:00
Matthias
8f214aec89 Fix "dumping" message to work correctly for .gz files 2019-12-28 11:01:41 +01:00
Matthias
abc6b9459a Add ohlcv_store call to convert_ohlcv 2019-12-28 11:01:41 +01:00
Matthias
d804372d74 Enhance ohlcv_convert method 2019-12-28 11:01:41 +01:00
Matthias
018e270336 Allow --pairs for convert arguments 2019-12-28 11:01:41 +01:00
Matthias
2a728ee68f fix bug in find-files 2019-12-28 11:01:41 +01:00
Matthias
3d4f62081e Allow timeframes for convert-data 2019-12-28 11:01:41 +01:00
Matthias
ef0fcb0e0f Make data-finding safe 2019-12-28 11:01:41 +01:00
Matthias
f8b8b9ac63 Convert to Path temporarily 2019-12-28 11:01:41 +01:00
Matthias
2a6b542b09 Add second subcommand to allow conversation of ohlcv and trades data
seprately
2019-12-28 11:01:41 +01:00
Matthias
c3064dfd2b Enhance validation constants 2019-12-28 11:00:45 +01:00
Matthias
cd4466a626 Add convert_* methods 2019-12-28 11:00:45 +01:00
Matthias
e5a61667dd Implement first version of jsondatahandler 2019-12-28 11:00:22 +01:00
Matthias
2496aa8e3f Add convert-data template subcommands 2019-12-28 10:59:30 +01:00
159 changed files with 6613 additions and 2578 deletions

View File

@@ -7,6 +7,8 @@ on:
- develop
- github_actions_tests
tags:
release:
types: [published]
pull_request:
schedule:
- cron: '0 5 * * 4'
@@ -18,10 +20,10 @@ jobs:
strategy:
matrix:
os: [ ubuntu-18.04, macos-latest ]
python-version: [3.7]
python-version: [3.7, 3.8]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
@@ -68,7 +70,7 @@ jobs:
pytest --random-order --cov=freqtrade --cov-config=.coveragerc
- name: Coveralls
if: startsWith(matrix.os, 'ubuntu')
if: (startsWith(matrix.os, 'ubuntu') && 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
@@ -113,10 +115,10 @@ jobs:
strategy:
matrix:
os: [ windows-latest ]
python-version: [3.7]
python-version: [3.7, 3.8]
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
@@ -128,8 +130,7 @@ jobs:
if: startsWith(runner.os, 'Windows')
with:
path: ~\AppData\Local\pip\Cache
key: ${{ runner.os }}-pip
restore-keys: ${{ runner.os }}-pip
key: ${{ matrix.os }}-${{ matrix.python-version }}-pip
- name: Installation
run: |
@@ -173,7 +174,7 @@ jobs:
docs_check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Documentation syntax
run: |
@@ -191,15 +192,40 @@ jobs:
deploy:
needs: [ build, build_windows, docs_check ]
runs-on: ubuntu-18.04
if: (github.event_name == 'push' || github.event_name == 'schedule') && github.repository == 'freqtrade/freqtrade'
if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade'
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v2
- name: Set up Python
uses: actions/setup-python@v1
with:
python-version: 3.8
- name: Extract branch name
shell: bash
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF#refs/heads/})"
id: extract_branch
- name: Build distribution
run: |
pip install -U setuptools wheel
python setup.py sdist bdist_wheel
- name: Publish to PyPI (Test)
uses: pypa/gh-action-pypi-publish@master
if: (steps.extract_branch.outputs.branch == 'master' || github.event_name == 'release')
with:
user: __token__
password: ${{ secrets.pypi_test_password }}
repository_url: https://test.pypi.org/legacy/
- name: Publish to PyPI
uses: pypa/gh-action-pypi-publish@master
if: (steps.extract_branch.outputs.branch == 'master' || github.event_name == 'release')
with:
user: __token__
password: ${{ secrets.pypi_password }}
- name: Build and test and push docker image
env:
IMAGE_NAME: freqtradeorg/freqtrade

1
.gitignore vendored
View File

@@ -6,7 +6,6 @@ user_data/*
!user_data/strategy/sample_strategy.py
!user_data/notebooks
user_data/notebooks/*
!user_data/notebooks/*example.ipynb
freqtrade-plot.html
freqtrade-profit-plot.html

View File

@@ -1,4 +1,3 @@
sudo: true
os:
- linux
dist: xenial
@@ -11,10 +10,10 @@ env:
global:
- IMAGE_NAME=freqtradeorg/freqtrade
install:
- cd build_helpers && ./install_ta-lib.sh ${HOME}/dependencies/; cd ..
- cd build_helpers && ./install_ta-lib.sh ${HOME}/dependencies; cd ..
- export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
- export TA_LIBRARY_PATH=${HOME}/dependencies/lib
- export TA_INCLUDE_PATH=${HOME}/dependencies/lib/include
- export TA_INCLUDE_PATH=${HOME}/dependencies/include
- pip install -r requirements-dev.txt
- pip install -e .
jobs:

View File

@@ -48,7 +48,7 @@ pytest tests/test_<file_name>.py::test_<method_name>
#### Run Flake8
```bash
flake8 freqtrade
flake8 freqtrade tests scripts
```
We receive a lot of code that fails the `flake8` checks.
@@ -109,11 +109,11 @@ Exceptions:
Contributors may be given commit privileges. Preference will be given to those with:
1. Past contributions to FreqTrade and other related open-source projects. Contributions to FreqTrade include both code (both accepted and pending) and friendly participation in the issue tracker and Pull request reviews. Quantity and quality are considered.
1. Past contributions to Freqtrade and other related open-source projects. Contributions to Freqtrade include both code (both accepted and pending) and friendly participation in the issue tracker and Pull request reviews. Quantity and quality are considered.
1. A coding style that the other core committers find simple, minimal, and clean.
1. Access to resources for cross-platform development and testing.
1. Time to devote to the project regularly.
Being a Committer does not grant write permission on `develop` or `master` for security reasons (Users trust FreqTrade with their Exchange API keys).
Being a Committer does not grant write permission on `develop` or `master` for security reasons (Users trust Freqtrade with their Exchange API keys).
After being Committer for some time, a Committer may be named Core Committer and given full repository access.

View File

@@ -1,4 +1,4 @@
FROM python:3.7.6-slim-stretch
FROM python:3.8.2-slim-buster
RUN apt-get update \
&& apt-get -y install curl build-essential libssl-dev \

View File

@@ -2,3 +2,4 @@ include LICENSE
include README.md
include config.json.example
recursive-include freqtrade *.py
recursive-include freqtrade/templates/ *.j2 *.ipynb

View File

@@ -1,6 +1,6 @@
# Freqtrade
[![Build Status](https://travis-ci.org/freqtrade/freqtrade.svg?branch=develop)](https://travis-ci.org/freqtrade/freqtrade)
[![Freqtrade CI](https://github.com/freqtrade/freqtrade/workflows/Freqtrade%20CI/badge.svg)](https://github.com/freqtrade/freqtrade/actions/)
[![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
[![Documentation](https://readthedocs.org/projects/freqtrade/badge/)](https://www.freqtrade.io)
[![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](https://codeclimate.com/github/freqtrade/freqtrade/maintainability)
@@ -25,7 +25,8 @@ hesitate to read the source code and understand the mechanism of this bot.
## Exchange marketplaces supported
- [X] [Bittrex](https://bittrex.com/)
- [X] [Binance](https://www.binance.com/) ([*Note for binance users](#a-note-on-binance))
- [X] [Binance](https://www.binance.com/) ([*Note for binance users](docs/exchanges.md#blacklists))
- [X] [Kraken](https://kraken.com/)
- [ ] [113 others to tests](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
## Documentation

View File

@@ -1,11 +1,11 @@
#!/usr/bin/env python3
import sys
import warnings
import logging
from freqtrade.main import main
logger = logging.getLogger(__name__)
warnings.warn(
"Deprecated - To continue to run the bot like this, please run `pip install -e .` again.",
DeprecationWarning)
main(sys.argv[1:])
logger.error("DEPRECATED installation detected, please run `pip install -e .` again.")
sys.exit(2)

Binary file not shown.

View File

@@ -3,7 +3,15 @@
# Invoke-WebRequest -Uri "https://download.lfd.uci.edu/pythonlibs/xxxxxxx/TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl" -OutFile "TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl"
python -m pip install --upgrade pip
pip install build_helpers\TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl
$pyv = python -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')"
if ($pyv -eq '3.7') {
pip install build_helpers\TA_Lib-0.4.17-cp37-cp37m-win_amd64.whl
}
if ($pyv -eq '3.8') {
pip install build_helpers\TA_Lib-0.4.17-cp38-cp38-win_amd64.whl
}
pip install -r requirements-dev.txt
pip install -e .

View File

@@ -23,7 +23,7 @@ if [ $? -ne 0 ]; then
fi
# 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 DefaultStrategy
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
if [ $? -ne 0 ]; then
echo "failed running backtest"

View File

@@ -4,7 +4,7 @@
"stake_amount": 0.05,
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"ticker_interval" : "5m",
"ticker_interval": "5m",
"dry_run": false,
"trailing_stop": false,
"unfilledtimeout": {
@@ -23,7 +23,7 @@
"ask_strategy":{
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 9,
"order_book_max": 1,
"use_sell_signal": true,
"sell_profit_only": false,
"ignore_roi_if_buy_signal": false
@@ -44,7 +44,7 @@
"DASH/BTC",
"ZEC/BTC",
"XLM/BTC",
"NXT/BTC",
"XRP/BTC",
"TRX/BTC",
"ADA/BTC",
"XMR/BTC"

View File

@@ -4,7 +4,7 @@
"stake_amount": 0.05,
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"ticker_interval" : "5m",
"ticker_interval": "5m",
"dry_run": true,
"trailing_stop": false,
"unfilledtimeout": {
@@ -23,7 +23,7 @@
"ask_strategy":{
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 9,
"order_book_max": 1,
"use_sell_signal": true,
"sell_profit_only": false,
"ignore_roi_if_buy_signal": false

View File

@@ -4,7 +4,7 @@
"stake_amount": 0.05,
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "USD",
"amount_reserve_percent" : 0.05,
"amount_reserve_percent": 0.05,
"amend_last_stake_amount": false,
"last_stake_amount_min_ratio": 0.5,
"dry_run": false,
@@ -25,6 +25,7 @@
"sell": 30
},
"bid_strategy": {
"price_side": "bid",
"use_order_book": false,
"ask_last_balance": 0.0,
"order_book_top": 1,
@@ -34,9 +35,10 @@
}
},
"ask_strategy":{
"price_side": "ask",
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 9,
"order_book_max": 1,
"use_sell_signal": true,
"sell_profit_only": false,
"ignore_roi_if_buy_signal": false
@@ -62,8 +64,8 @@
"refresh_period": 1800
},
{"method": "PrecisionFilter"},
{"method": "PriceFilter", "low_price_ratio": 0.01
}
{"method": "PriceFilter", "low_price_ratio": 0.01},
{"method": "SpreadFilter", "max_spread_ratio": 0.005}
],
"exchange": {
"name": "bittrex",
@@ -129,5 +131,7 @@
"heartbeat_interval": 60
},
"strategy": "DefaultStrategy",
"strategy_path": "user_data/strategies/"
"strategy_path": "user_data/strategies/",
"dataformat_ohlcv": "json",
"dataformat_trades": "jsongz"
}

View File

@@ -4,7 +4,7 @@
"stake_amount": 10,
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "EUR",
"ticker_interval" : "5m",
"ticker_interval": "5m",
"dry_run": true,
"trailing_stop": false,
"unfilledtimeout": {
@@ -23,7 +23,7 @@
"ask_strategy":{
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 9,
"order_book_max": 1,
"use_sell_signal": true,
"sell_profit_only": false,
"ignore_roi_if_buy_signal": false

View File

@@ -3,6 +3,18 @@ version: '3'
services:
freqtrade:
image: freqtradeorg/freqtrade:master
# Build step - only needed when additional dependencies are needed
# build:
# context: .
# dockerfile: "./Dockerfile.technical"
restart: unless-stopped
container_name: freqtrade
volumes:
- "./user_data:/freqtrade/user_data"
- "./config.json:/freqtrade/config.json"
# Default command used when running `docker compose up`
command: >
trade
--logfile /freqtrade/user_data/freqtrade.log
--db-url sqlite:////freqtrade/user_data/tradesv3.sqlite
--config /freqtrade/user_data/config.json
--strategy SampleStrategy

View File

@@ -4,6 +4,34 @@ This page explains some advanced Hyperopt topics that may require higher
coding skills and Python knowledge than creation of an ordinal hyperoptimization
class.
## Derived hyperopt classes
Custom hyperop classes can be derived in the same way [it can be done for strategies](strategy-customization.md#derived-strategies).
Applying to hyperoptimization, as an example, you may override how dimensions are defined in your optimization hyperspace:
```python
class MyAwesomeHyperOpt(IHyperOpt):
...
# Uses default stoploss dimension
class MyAwesomeHyperOpt2(MyAwesomeHyperOpt):
@staticmethod
def stoploss_space() -> List[Dimension]:
# Override boundaries for stoploss
return [
Real(-0.33, -0.01, name='stoploss'),
]
```
and then quickly switch between hyperopt classes, running optimization process with hyperopt class you need in each particular case:
```
$ freqtrade hyperopt --hyperopt MyAwesomeHyperOpt ...
or
$ freqtrade hyperopt --hyperopt MyAwesomeHyperOpt2 ...
```
## Creating and using a custom loss function
To use a custom loss function class, make sure that the function `hyperopt_loss_function` is defined in your custom hyperopt loss class.

View File

@@ -11,8 +11,8 @@ Now you have good Buy and Sell strategies and some historic data, you want to te
real data. This is what we call
[backtesting](https://en.wikipedia.org/wiki/Backtesting).
Backtesting will use the crypto-currencies (pairs) from your config file and load ticker data from `user_data/data/<exchange>` by default.
If no data is available for the exchange / pair / ticker interval combination, backtesting will ask you to download them first using `freqtrade download-data`.
Backtesting will use the crypto-currencies (pairs) from your config file and load historical candle (OHCLV) data from `user_data/data/<exchange>` by default.
If no data is available for the exchange / pair / timeframe (ticker interval) combination, backtesting will ask you to download them first using `freqtrade download-data`.
For details on downloading, please refer to the [Data Downloading](data-download.md) section in the documentation.
The result of backtesting will confirm if your bot has better odds of making a profit than a loss.
@@ -22,19 +22,19 @@ The result of backtesting will confirm if your bot has better odds of making a p
### Run a backtesting against the currencies listed in your config file
#### With 5 min tickers (Per default)
#### With 5 min candle (OHLCV) data (per default)
```bash
freqtrade backtesting
```
#### With 1 min tickers
#### With 1 min candle (OHLCV) data
```bash
freqtrade backtesting --ticker-interval 1m
```
#### Using a different on-disk ticker-data source
#### Using a different on-disk historical candle (OHLCV) data source
Assume you downloaded the history data from the Bittrex exchange and kept it in the `user_data/data/bittrex-20180101` directory.
You can then use this data for backtesting as follows:
@@ -119,40 +119,40 @@ A backtesting result will look like that:
```
========================================================= BACKTESTING REPORT ========================================================
| pair | buy count | avg profit % | cum profit % | tot profit BTC | tot profit % | avg duration | profit | loss |
|:---------|------------:|---------------:|---------------:|-----------------:|---------------:|:---------------|---------:|-------:|
| ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 | 21 |
| ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 | 8 |
| BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 | 14 |
| DASH/BTC | 13 | -0.08 | -1.07 | -0.00005343 | -0.53 | 4:39:00 | 6 | 7 |
| ENG/BTC | 18 | 1.36 | 24.54 | 0.00122807 | 12.27 | 2:50:00 | 8 | 10 |
| EOS/BTC | 36 | 0.08 | 3.06 | 0.00015304 | 1.53 | 3:34:00 | 16 | 20 |
| ETC/BTC | 26 | 0.37 | 9.51 | 0.00047576 | 4.75 | 6:14:00 | 11 | 15 |
| ETH/BTC | 33 | 0.30 | 9.96 | 0.00049856 | 4.98 | 7:31:00 | 16 | 17 |
| IOTA/BTC | 32 | 0.03 | 1.09 | 0.00005444 | 0.54 | 3:12:00 | 14 | 18 |
| LSK/BTC | 15 | 1.75 | 26.26 | 0.00131413 | 13.13 | 2:58:00 | 6 | 9 |
| LTC/BTC | 32 | -0.04 | -1.38 | -0.00006886 | -0.69 | 4:49:00 | 11 | 21 |
| NANO/BTC | 17 | 1.26 | 21.39 | 0.00107058 | 10.70 | 1:55:00 | 10 | 7 |
| NEO/BTC | 23 | 0.82 | 18.97 | 0.00094936 | 9.48 | 2:59:00 | 10 | 13 |
| REQ/BTC | 9 | 1.17 | 10.54 | 0.00052734 | 5.27 | 3:47:00 | 4 | 5 |
| XLM/BTC | 16 | 1.22 | 19.54 | 0.00097800 | 9.77 | 3:15:00 | 7 | 9 |
| XMR/BTC | 23 | -0.18 | -4.13 | -0.00020696 | -2.07 | 5:30:00 | 12 | 11 |
| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 | 23 |
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 | 15 |
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 243 |
| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |
|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|--------:|
| ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 | 0 | 21 |
| ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 | 0 | 8 |
| BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 | 0 | 14 |
| DASH/BTC | 13 | -0.08 | -1.07 | -0.00005343 | -0.53 | 4:39:00 | 6 | 0 | 7 |
| ENG/BTC | 18 | 1.36 | 24.54 | 0.00122807 | 12.27 | 2:50:00 | 8 | 0 | 10 |
| EOS/BTC | 36 | 0.08 | 3.06 | 0.00015304 | 1.53 | 3:34:00 | 16 | 0 | 20 |
| ETC/BTC | 26 | 0.37 | 9.51 | 0.00047576 | 4.75 | 6:14:00 | 11 | 0 | 15 |
| ETH/BTC | 33 | 0.30 | 9.96 | 0.00049856 | 4.98 | 7:31:00 | 16 | 0 | 17 |
| IOTA/BTC | 32 | 0.03 | 1.09 | 0.00005444 | 0.54 | 3:12:00 | 14 | 0 | 18 |
| LSK/BTC | 15 | 1.75 | 26.26 | 0.00131413 | 13.13 | 2:58:00 | 6 | 0 | 9 |
| LTC/BTC | 32 | -0.04 | -1.38 | -0.00006886 | -0.69 | 4:49:00 | 11 | 0 | 21 |
| NANO/BTC | 17 | 1.26 | 21.39 | 0.00107058 | 10.70 | 1:55:00 | 10 | 0 | 7 |
| NEO/BTC | 23 | 0.82 | 18.97 | 0.00094936 | 9.48 | 2:59:00 | 10 | 0 | 13 |
| REQ/BTC | 9 | 1.17 | 10.54 | 0.00052734 | 5.27 | 3:47:00 | 4 | 0 | 5 |
| XLM/BTC | 16 | 1.22 | 19.54 | 0.00097800 | 9.77 | 3:15:00 | 7 | 0 | 9 |
| XMR/BTC | 23 | -0.18 | -4.13 | -0.00020696 | -2.07 | 5:30:00 | 12 | 0 | 11 |
| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 | 0 | 23 |
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 | 0 | 15 |
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 |
========================================================= SELL REASON STATS =========================================================
| Sell Reason | Count | Profit | Loss |
|:-------------------|--------:|---------:|-------:|
| trailing_stop_loss | 205 | 150 | 55 |
| stop_loss | 166 | 0 | 166 |
| sell_signal | 56 | 36 | 20 |
| force_sell | 2 | 0 | 2 |
| Sell Reason | Sells | Wins | Draws | Losses |
|:-------------------|--------:|------:|-------:|--------:|
| trailing_stop_loss | 205 | 150 | 0 | 55 |
| stop_loss | 166 | 0 | 0 | 166 |
| sell_signal | 56 | 36 | 0 | 20 |
| force_sell | 2 | 0 | 0 | 2 |
====================================================== LEFT OPEN TRADES REPORT ======================================================
| pair | buy count | avg profit % | cum profit % | tot profit BTC | tot profit % | avg duration | profit | loss |
|:---------|------------:|---------------:|---------------:|-----------------:|---------------:|:---------------|---------:|-------:|
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 | 0 |
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 | 0 |
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 | 0 |
| Pair | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |
|:---------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|--------:|
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 | 0 | 0 |
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 | 0 | 0 |
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 | 0 | 0 |
```
The 1st table contains all trades the bot made, including "left open trades".
@@ -223,7 +223,7 @@ You can then load the trades to perform further analysis as shown in our [data a
To compare multiple strategies, a list of Strategies can be provided to backtesting.
This is limited to 1 ticker-interval per run, however, data is only loaded once from disk so if you have multiple
This is limited to 1 timeframe (ticker interval) value per run. However, data is only loaded once from disk so if you have multiple
strategies you'd like to compare, this will give a nice runtime boost.
All listed Strategies need to be in the same directory.
@@ -237,11 +237,11 @@ There will be an additional table comparing win/losses of the different strategi
Detailed output for all strategies one after the other will be available, so make sure to scroll up to see the details per strategy.
```
=========================================================== Strategy Summary ===========================================================
| Strategy | buy count | avg profit % | cum profit % | tot profit BTC | tot profit % | avg duration | profit | loss |
|:------------|------------:|---------------:|---------------:|-----------------:|---------------:|:---------------|---------:|-------:|
| Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 243 |
| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 825 |
=========================================================== STRATEGY SUMMARY ===========================================================
| Strategy | Buys | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses |
|:------------|-------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|-------:|
| Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 |
| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 |
```
## Next step

View File

@@ -58,9 +58,10 @@ Common arguments:
details.
-V, --version show program's version number and exit
-c PATH, --config PATH
Specify configuration file (default: `config.json`).
Multiple --config options may be used. Can be set to
`-` to read config from stdin.
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
@@ -71,6 +72,7 @@ Strategy arguments:
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
.
```
@@ -242,12 +244,15 @@ optional arguments:
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified.
--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: `config.json`).
Multiple --config options may be used. Can be set to
`-` to read config from stdin.
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
@@ -270,7 +275,7 @@ Check the corresponding [Data Downloading](data-download.md) section for more de
## Hyperopt commands
To optimize your strategy, you can use hyperopt parameter hyperoptimization
to find optimal parameter values for your stategy.
to find optimal parameter values for your strategy.
```
usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
@@ -280,7 +285,7 @@ usage: freqtrade hyperopt [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--stake-amount STAKE_AMOUNT] [--fee FLOAT]
[--hyperopt NAME] [--hyperopt-path PATH] [--eps]
[-e INT]
[--spaces {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]]
[--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]
[--continue] [--hyperopt-loss NAME]
@@ -308,9 +313,9 @@ optional arguments:
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} [{all,buy,sell,roi,stoploss} ...]
--spaces {all,buy,sell,roi,stoploss,trailing,default} [{all,buy,sell,roi,stoploss,trailing,default} ...]
Specify which parameters to hyperopt. Space-separated
list. Default: `all`.
list.
--dmmp, --disable-max-market-positions
Disable applying `max_open_trades` during backtest
(same as setting `max_open_trades` to a very high
@@ -318,7 +323,7 @@ optional arguments:
--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 best result detailization in JSON format.
--print-json Print best results in JSON format.
-j JOBS, --job-workers JOBS
The number of concurrently running jobs for
hyperoptimization (hyperopt worker processes). If -1
@@ -336,18 +341,23 @@ optional arguments:
class (IHyperOptLoss). Different functions can
generate completely different results, since the
target for optimization is different. Built-in
Hyperopt-loss-functions are: DefaultHyperOptLoss,
OnlyProfitHyperOptLoss, SharpeHyperOptLoss (default:
`DefaultHyperOptLoss`).
Hyperopt-loss-functions are:
DefaultHyperOptLoss, OnlyProfitHyperOptLoss,
SharpeHyperOptLoss, SharpeHyperOptLossDaily,
SortinoHyperOptLoss, SortinoHyperOptLossDaily.
(default: `DefaultHyperOptLoss`).
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified.
--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: `config.json`).
Multiple --config options may be used. Can be set to
`-` to read config from stdin.
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
@@ -358,6 +368,7 @@ Strategy arguments:
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
```
## Edge commands
@@ -394,12 +405,15 @@ optional arguments:
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified.
--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: `config.json`).
Multiple --config options may be used. Can be set to
`-` to read config from stdin.
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
@@ -410,6 +424,7 @@ Strategy arguments:
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).

View File

@@ -40,75 +40,81 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| Parameter | Description |
|------------|-------------|
| `max_open_trades` | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades). [More information below](#configuring-amount-per-trade).<br> ***Datatype:*** *Positive integer or -1.*
| `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *String*
| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#configuring-amount-per-trade). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Positive float or `"unlimited"`.*
| `tradable_balance_ratio` | Ratio of the total account balance the bot is allowed to trade. [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.99` 99%).*<br> ***Datatype:*** *Positive float between `0.1` and `1.0`.*
| `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade). <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
| `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.5`.* <br> ***Datatype:*** *Float (as ratio)*
| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals. <br>*Defaults to `0.05` (5%).* <br> ***Datatype:*** *Positive Float as ratio.*
| `ticker_interval` | The ticker interval to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *String*
| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). <br> ***Datatype:*** *String*
| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode. <br>*Defaults to `true`.* <br> ***Datatype:*** *Boolean*
| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in the Dry Run mode.<br>*Defaults to `1000`.* <br> ***Datatype:*** *Float*
| `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
| `minimal_roi` | **Required.** Set the threshold in percent the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Dict*
| `stoploss` | **Required.** Value of the stoploss in percent used by the bot. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Float (as ratio)*
| `trailing_stop` | Enables trailing stoploss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Boolean*
| `trailing_stop_positive` | Changes stoploss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> ***Datatype:*** *Float*
| `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0` (no offset).* <br> ***Datatype:*** *Float*
| `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
| `unfilledtimeout.buy` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).<br> ***Datatype:*** *Integer*
| `unfilledtimeout.sell` | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).<br> ***Datatype:*** *Integer*
| `bid_strategy.ask_last_balance` | **Required.** Set the bidding price. More information [below](#buy-price-without-orderbook).
| `bid_strategy.use_order_book` | Enable buying using the rates in [Order Book Bids](#buy-price-with-orderbook-enabled). <br> ***Datatype:*** *Boolean*
| `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book Bids to buy. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in [Order Book Bids](#buy-price-with-orderbook-enabled). <br>*Defaults to `1`.* <br> ***Datatype:*** *Positive Integer*
| `bid_strategy. check_depth_of_market.enabled` | Do not buy if the difference of buy orders and sell orders is met in Order Book. [Check market depth](#check-depth-of-market). <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | The difference ratio of buy orders and sell orders found in Order Book. A value below 1 means sell order size is greater, while value greater than 1 means buy order size is higher. [Check market depth](#check-depth-of-market) <br> *Defaults to `0`.* <br> ***Datatype:*** *Float (as ratio)*
| `ask_strategy.use_order_book` | Enable selling of open trades using [Order Book Asks](#sell-price-with-orderbook-enabled). <br> ***Datatype:*** *Boolean*
| `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.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.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*
| `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*
| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> ***Datatype:*** *String*
| `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details.<br> ***Datatype:*** *Boolean*
| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.<br>**Keep it in secret, do not disclose publicly.** <br> ***Datatype:*** *String*
| `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](#dynamic-pairlists)). <br> ***Datatype:*** *List*
| `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#dynamic-pairlists)). <br> ***Datatype:*** *List*
| `exchange.ccxt_config` | Additional CCXT parameters passed to the regular 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*
| `max_open_trades` | **Required.** Number of open trades your bot is allowed to have. Only one open trade per pair is possible, so the length of your pairlist is another limitation which can apply. If -1 then it is ignored (i.e. potentially unlimited open trades, limited by the pairlist). [More information below](#configuring-amount-per-trade).<br> **Datatype:** Positive integer or -1.
| `stake_currency` | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** String
| `stake_amount` | **Required.** Amount of crypto-currency your bot will use for each trade. Set it to `"unlimited"` to allow the bot to use all available balance. [More information below](#configuring-amount-per-trade). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Positive float or `"unlimited"`.
| `tradable_balance_ratio` | Ratio of the total account balance the bot is allowed to trade. [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.99` 99%).*<br> **Datatype:** Positive float between `0.1` and `1.0`.
| `amend_last_stake_amount` | Use reduced last stake amount if necessary. [More information below](#configuring-amount-per-trade). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
| `last_stake_amount_min_ratio` | Defines minimum stake amount that has to be left and executed. Applies only to the last stake amount when it's amended to a reduced value (i.e. if `amend_last_stake_amount` is set to `true`). [More information below](#configuring-amount-per-trade). <br>*Defaults to `0.5`.* <br> **Datatype:** Float (as ratio)
| `amount_reserve_percent` | Reserve some amount in min pair stake amount. The bot will reserve `amount_reserve_percent` + stoploss value when calculating min pair stake amount in order to avoid possible trade refusals. <br>*Defaults to `0.05` (5%).* <br> **Datatype:** Positive Float as ratio.
| `ticker_interval` | The timeframe (ticker interval) to use (e.g `1m`, `5m`, `15m`, `30m`, `1h` ...). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** String
| `fiat_display_currency` | Fiat currency used to show your profits. [More information below](#what-values-can-be-used-for-fiat_display_currency). <br> **Datatype:** String
| `dry_run` | **Required.** Define if the bot must be in Dry Run or production mode. <br>*Defaults to `true`.* <br> **Datatype:** Boolean
| `dry_run_wallet` | Define the starting amount in stake currency for the simulated wallet used by the bot running in the Dry Run mode.<br>*Defaults to `1000`.* <br> **Datatype:** Float
| `process_only_new_candles` | Enable processing of indicators only when new candles arrive. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
| `minimal_roi` | **Required.** Set the threshold in percent the bot will use to sell a trade. [More information below](#understand-minimal_roi). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Dict
| `stoploss` | **Required.** Value of the stoploss in percent used by the bot. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Float (as ratio)
| `trailing_stop` | Enables trailing stoploss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Boolean
| `trailing_stop_positive` | Changes stoploss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br> **Datatype:** Float
| `trailing_stop_positive_offset` | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `0.0` (no offset).* <br> **Datatype:** Float
| `trailing_only_offset_is_reached` | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
| `unfilledtimeout.buy` | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
| `unfilledtimeout.sell` | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled. [Strategy Override](#parameters-in-the-strategy).<br> **Datatype:** Integer
| `bid_strategy.price_side` | Select the side of the spread the bot should look at to get the buy rate. [More information below](#buy-price-side).<br> *Defaults to `bid`.* <br> **Datatype:** String (either `ask` or `bid`).
| `bid_strategy.ask_last_balance` | **Required.** Set the bidding price. More information [below](#buy-price-without-orderbook-enabled).
| `bid_strategy.use_order_book` | Enable buying using the rates in [Order Book Bids](#buy-price-with-orderbook-enabled). <br> **Datatype:** Boolean
| `bid_strategy.order_book_top` | Bot will use the top N rate in Order Book Bids to buy. I.e. a value of 2 will allow the bot to pick the 2nd bid rate in [Order Book Bids](#buy-price-with-orderbook-enabled). <br>*Defaults to `1`.* <br> **Datatype:** Positive Integer
| `bid_strategy. check_depth_of_market.enabled` | Do not buy if the difference of buy orders and sell orders is met in Order Book. [Check market depth](#check-depth-of-market). <br>*Defaults to `false`.* <br> **Datatype:** Boolean
| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | The difference ratio of buy orders and sell orders found in Order Book. A value below 1 means sell order size is greater, while value greater than 1 means buy order size is higher. [Check market depth](#check-depth-of-market) <br> *Defaults to `0`.* <br> **Datatype:** Float (as ratio)
| `ask_strategy.price_side` | Select the side of the spread the bot should look at to get the sell rate. [More information below](#sell-price-side).<br> *Defaults to `ask`.* <br> **Datatype:** String (either `ask` or `bid`).
| `ask_strategy.use_order_book` | Enable selling of open trades using [Order Book Asks](#sell-price-with-orderbook-enabled). <br> **Datatype:** Boolean
| `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.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.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
| `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
| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> **Datatype:** String
| `exchange.sandbox` | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details.<br> **Datatype:** Boolean
| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
| `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](#dynamic-pairlists)). <br> **Datatype:** List
| `exchange.pair_blacklist` | List of pairs the bot must absolutely avoid for trading and backtesting (see [below](#dynamic-pairlists)). <br> **Datatype:** List
| `exchange.ccxt_config` | Additional CCXT parameters passed to the regular 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
| `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*
| `pairlists` | Define one or more pairlists to be used. [More information below](#dynamic-pairlists). <br>*Defaults to `StaticPairList`.* <br> ***Datatype:*** *List of Dicts*
| `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.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*
| `webhook.enabled` | Enable usage of Webhook notifications <br> ***Datatype:*** *Boolean*
| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> ***Datatype:*** *String*
| `webhook.webhookbuy` | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> ***Datatype:*** *String*
| `webhook.webhooksell` | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> ***Datatype:*** *String*
| `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> ***Datatype:*** *String*
| `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details. <br> ***Datatype:*** *Boolean*
| `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details. <br> ***Datatype:*** *IPv4*
| `api_server.listen_port` | Bind Port. See the [API Server documentation](rest-api.md) for more details. <br>***Datatype:*** *Integer between 1024 and 65535*
| `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*
| `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`*
| `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below. <br> ***Datatype:*** *Boolean*
| `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`. <br> ***Datatype:*** *ClassName*
| `strategy_path` | Adds an additional strategy lookup path (must be a directory). <br> ***Datatype:*** *String*
| `internals.process_throttle_secs` | Set the process throttle. Value in second. <br>*Defaults to `5` seconds.* <br> ***Datatype:*** *Positive Integer*
| `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages. <br>*Defaults to `60` seconds.* <br> ***Datatype:*** *Positive Integer or 0*
| `internals.sd_notify` | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details. <br> ***Datatype:*** *Boolean*
| `logfile` | Specifies logfile name. Uses a rolling strategy for log file rotation for 10 files with the 1MB limit per file. <br> ***Datatype:*** *String*
| `user_data_dir` | Directory containing user data. <br> *Defaults to `./user_data/`*. <br> ***Datatype:*** *String*
| `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](#dynamic-pairlists). <br>*Defaults to `StaticPairList`.* <br> **Datatype:** List of Dicts
| `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.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
| `webhook.enabled` | Enable usage of Webhook notifications <br> **Datatype:** Boolean
| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
| `webhook.webhookbuy` | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
| `webhook.webhookbuycancel` | Payload to send on buy order cancel. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
| `webhook.webhooksell` | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
| `webhook.webhooksellcancel` | Payload to send on sell order cancel. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
| `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details. <br> **Datatype:** String
| `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** Boolean
| `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details. <br> **Datatype:** IPv4
| `api_server.listen_port` | Bind Port. See the [API Server documentation](rest-api.md) for more details. <br>**Datatype:** Integer between 1024 and 65535
| `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
| `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`
| `forcebuy_enable` | Enables the RPC Commands to force a buy. More information below. <br> **Datatype:** Boolean
| `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`. <br> **Datatype:** ClassName
| `strategy_path` | Adds an additional strategy lookup path (must be a directory). <br> **Datatype:** String
| `internals.process_throttle_secs` | Set the process throttle. Value in second. <br>*Defaults to `5` seconds.* <br> **Datatype:** Positive Intege
| `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
| `logfile` | Specifies logfile name. Uses a rolling strategy for log file rotation for 10 files with the 1MB limit per file. <br> **Datatype:** String
| `user_data_dir` | Directory containing user data. <br> *Defaults to `./user_data/`*. <br> **Datatype:** String
| `dataformat_ohlcv` | Data format to use to store historical candle (OHLCV) data. <br> *Defaults to `json`*. <br> **Datatype:** String
| `dataformat_trades` | Data format to use to store historical trades data. <br> *Defaults to `jsongz`*. <br> **Datatype:** String
### Parameters in the strategy
@@ -278,7 +284,7 @@ If this is configured, the following 4 values (`buy`, `sell`, `stoploss` and
The below is the default which is used if this is not configured in either strategy or configuration file.
Since `stoploss_on_exchange` uses limit orders, the exchange needs 2 prices, the stoploss_price and the Limit price.
`stoploss` defines the stop-price - and limit should be slightly below this. This defaults to 0.99 / 1%.
`stoploss` defines the stop-price - and limit should be slightly below this. This defaults to 0.99 / 1% (configurable via `stoploss_on_exchange_limit_ratio`).
Calculation example: we bought the asset at 100$.
Stop-price is 95$, then limit would be `95 * 0.99 = 94.05$` - so the stoploss will happen between 95$ and 94.05$.
@@ -336,7 +342,7 @@ This is most of the time the default time in force. It means the order will rema
on exchange till it is canceled by user. It can be fully or partially fulfilled.
If partially fulfilled, the remaining will stay on the exchange till cancelled.
**FOK (Full Or Kill):**
**FOK (Fill Or Kill):**
It means if the order is not executed immediately AND fully then it is canceled by the exchange.
@@ -366,16 +372,18 @@ The possible values are: `gtc` (default), `fok` or `ioc`.
Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports over 100 cryptocurrency
exchange markets and trading APIs. The complete up-to-date list can be found in the
[CCXT repo homepage](https://github.com/ccxt/ccxt/tree/master/python). However, the bot was tested
with only Bittrex and Binance.
The bot was tested with the following exchanges:
[CCXT repo homepage](https://github.com/ccxt/ccxt/tree/master/python).
However, the bot was tested by the development team with only Bittrex, Binance and Kraken,
so the these are the only officially supported exhanges:
- [Bittrex](https://bittrex.com/): "bittrex"
- [Binance](https://www.binance.com/): "binance"
- [Kraken](https://kraken.com/): "kraken"
Feel free to test other exchanges and submit your PR to improve the bot.
Some exchanges require special configuration, which can be found on the [Exchange-specific Notes](exchanges.md) documentation page.
#### Sample exchange configuration
A exchange configuration for "binance" would look as follows:
@@ -405,7 +413,7 @@ Advanced options can be configured using the `_ft_has_params` setting, which wil
Available options are listed in the exchange-class as `_ft_has_default`.
For example, to test the order type `FOK` with Kraken, and modify candle_limit to 200 (so you only get 200 candles per call):
For example, to test the order type `FOK` with Kraken, and modify candle limit to 200 (so you only get 200 candles per API call):
```json
"exchange": {
@@ -457,34 +465,89 @@ Orderbook `bid` (buy) side depth is then divided by the orderbook `ask` (sell) s
!!! 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 `bid` (buy) 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.
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
When not using orderbook (`bid_strategy.use_order_book=False`), Freqtrade uses the best `ask` (sell) price from the ticker if it's below the `last` traded price from the ticker. Otherwise (when the `ask` price is not below the `last` price), it calculates a rate between `ask` and `last` price.
The following section uses `side` as the configured `bid_strategy.price_side`.
The `bid_strategy.ask_last_balance` configuration parameter controls this. A value of `0.0` will use `ask` price, while `1.0` will use the `last` price and values between those interpolate between ask and last price.
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.
Using `ask` price often guarantees quicker success in the bid, but the bot can also end up paying more than what would have been necessary.
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 `ask` orderbook side are validated for a profitable sell-possibility based on the strategy configuration and the sell order is placed at the first profitable spot.
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 "Orderbook and stoploss_on_exchange"
Using `ask_strategy.order_book_max` higher than 1 may increase the risk, since an eventual [stoploss on exchange](#understand-order_types) will be needed to be cancelled as soon as the order is placed.
!!! 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 `bid` price from the ticker will be used as the sell price.
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.
## Pairlists
@@ -503,6 +566,7 @@ Inactive markets and blacklisted pairs are always removed from the resulting `pa
* [`VolumePairList`](#volume-pair-list)
* [`PrecisionFilter`](#precision-filter)
* [`PriceFilter`](#price-pair-filter)
* [`SpreadFilter`](#spread-filter)
!!! Tip "Testing pairlists"
Pairlist configurations can be quite tricky to get right. Best use the [`test-pairlist`](utils.md#test-pairlist) subcommand to test your configuration quickly.
@@ -527,6 +591,12 @@ It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklis
`refresh_period` allows setting the period (in seconds), at which the pairlist will be refreshed. Defaults to 1800s (30 minutes).
`VolumePairList` is based on the ticker data, as reported by the ccxt library:
* The `bidVolume` is the volume (amount) of current best bid in the orderbook.
* The `askVolume` is the volume (amount) of current best ask in the orderbook.
* The `quoteVolume` is the amount of quote (stake) currency traded (bought or sold) in last 24 hours.
```json
"pairlists": [{
"method": "VolumePairList",
@@ -551,6 +621,11 @@ Min price precision is 8 decimals. If price is 0.00000011 - one step would be 0.
These pairs are dangerous since it may be impossible to place the desired stoploss - and often result in high losses.
#### Spread Filter
Removes pairs that have a difference between asks and bids above the specified ratio (default `0.005`).
Example:
If `DOGE/BTC` maximum bid is 0.00000026 and minimum ask is 0.00000027 the ratio is calculated as: `1 - bid/ask ~= 0.037` which is `> 0.005`
### Full Pairlist example
The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, sorting by `quoteVolume` and applies both [`PrecisionFilter`](#precision-filter) and [`PriceFilter`](#price-pair-filter), filtering all assets where 1 priceunit is > 1%.
@@ -602,12 +677,25 @@ Once you will be happy with your bot performance running in the Dry-run mode, yo
!!! Note
A simulated wallet is available during dry-run mode, and will assume a starting capital of `dry_run_wallet` (defaults to 1000).
### 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.
* Wallets (`/balance`) are simulated.
* Orders are simulated, and will not be posted to the exchange.
* 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.
## Switch to production mode
In production mode, the bot will engage your money. Be careful, since a wrong
strategy can lose all your money. Be aware of what you are doing when
you run it in production mode.
### Setup your exchange account
You will need to create API Keys (usually you get `key` and `secret`, some exchanges require an additional `password`) from the Exchange website and you'll need to insert this into the appropriate fields in the configuration or when asked by the `freqtrade new-config` command.
API Keys are usually only required for live trading (trading for real money, bot running in "production mode", executing real orders on the exchange) and are not required for the bot running in dry-run (trade simulation) mode. When you setup the bot in dry-run mode, you may fill these fields with empty values.
### To switch your bot in production mode
**Edit your `config.json` file.**
@@ -629,9 +717,6 @@ you run it in production mode.
}
```
!!! Note
If you have an exchange API key yet, [see our tutorial](installation.md#setup-your-exchange-account).
You should also make sure to read the [Exchanges](exchanges.md) section of the documentation to be aware of potential configuration details specific to your exchange.
### Using proxy with Freqtrade
@@ -656,7 +741,7 @@ freqtrade
## Embedding Strategies
FreqTrade provides you with with an easy way to embed the strategy into your configuration file.
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.

View File

@@ -12,6 +12,152 @@ Otherwise `--exchange` becomes mandatory.
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.
Be carefull though: If the number is too small (which would result in a few missing days), the whole dataset will be removed and only xx days will be downloaded.
### Usage
```
usage: freqtrade download-data [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [-p PAIRS [PAIRS ...]]
[--pairs-file FILE] [--days INT] [--dl-trades] [--exchange EXCHANGE]
[-t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w} ...]]
[--erase] [--data-format-ohlcv {json,jsongz}] [--data-format-trades {json,jsongz}]
optional arguments:
-h, --help show this help message and exit
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Show profits for only these pairs. Pairs are space-separated.
--pairs-file FILE File containing a list of pairs to download.
--days INT Download data for given number of days.
--dl-trades Download trades instead of OHLCV data. The bot will resample trades to the desired timeframe as specified as
--timeframes/-t.
--exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no config is provided.
-t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w} ...], --timeframes {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w} ...]
Specify which tickers to download. Space-separated list. Default: `1m 5m`.
--erase Clean all existing data for the selected exchange/pairs/timeframes.
--data-format-ohlcv {json,jsongz}
Storage format for downloaded candle (OHLCV) data. (default: `json`).
--data-format-trades {json,jsongz}
Storage format for downloaded trades data. (default: `jsongz`).
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: `config.json`). 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.
```
### Data format
Freqtrade currently supports 2 dataformats, `json` (plain "text" json files) and `jsongz` (a gzipped version of json files).
By default, OHLCV data is stored as `json` data, while trades data is stored as `jsongz` data.
This can be changed via the `--data-format-ohlcv` and `--data-format-trades` parameters respectivly.
If the default dataformat has been changed during download, then the keys `dataformat_ohlcv` and `dataformat_trades` in the configuration file need to be adjusted to the selected dataformat as well.
!!! Note
You can convert between data-formats using the [convert-data](#subcommand-convert-data) and [convert-trade-data](#subcommand-convert-trade-data) methods.
#### Subcommand convert data
```
usage: freqtrade convert-data [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH]
[-p PAIRS [PAIRS ...]] --format-from
{json,jsongz} --format-to {json,jsongz}
[--erase]
[-t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w} ...]]
optional arguments:
-h, --help show this help message and exit
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Show profits for only these pairs. Pairs are space-
separated.
--format-from {json,jsongz}
Source format for data conversion.
--format-to {json,jsongz}
Destination format for data conversion.
--erase Clean all existing data for the selected
exchange/pairs/timeframes.
-t {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w} ...], --timeframes {1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w} [{1m,3m,5m,15m,30m,1h,2h,4h,6h,8h,12h,1d,3d,1w} ...]
Specify which tickers to download. Space-separated
list. Default: `1m 5m`.
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: `config.json`).
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.
```
##### Example converting data
The following command will convert all candle (OHLCV) data available in `~/.freqtrade/data/binance` from json to jsongz, saving diskspace in the process.
It'll also remove original json data files (`--erase` parameter).
``` bash
freqtrade convert-data --format-from json --format-to jsongz --data-dir ~/.freqtrade/data/binance -t 5m 15m --erase
```
#### Subcommand convert-trade data
```
usage: freqtrade convert-trade-data [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH]
[-p PAIRS [PAIRS ...]] --format-from
{json,jsongz} --format-to {json,jsongz}
[--erase]
optional arguments:
-h, --help show this help message and exit
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Show profits for only these pairs. Pairs are space-
separated.
--format-from {json,jsongz}
Source format for data conversion.
--format-to {json,jsongz}
Destination format for data conversion.
--erase Clean all existing data for the selected
exchange/pairs/timeframes.
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: `config.json`).
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.
```
##### Example converting trades
The following command will convert all available trade-data in `~/.freqtrade/data/kraken` from jsongz to json.
It'll also remove original jsongz data files (`--erase` parameter).
``` bash
freqtrade convert-trade-data --format-from jsongz --format-to json --data-dir ~/.freqtrade/data/kraken --erase
```
### Pairs file
In alternative to the whitelist from `config.json`, a `pairs.json` file can be used.
@@ -46,15 +192,15 @@ Then run:
freqtrade download-data --exchange binance
```
This will download ticker data for all the currency pairs you defined in `pairs.json`.
This will download historical candle (OHLCV) data for all the currency pairs you defined in `pairs.json`.
### Other Notes
- To use a different directory than the exchange specific default, use `--datadir user_data/data/some_directory`.
- To change the exchange used to download the tickers, please use a different configuration file (you'll probably need to adjust ratelimits etc.)
- To change the exchange used to download the historical data from, please use a different configuration file (you'll probably need to adjust ratelimits etc.)
- To use `pairs.json` from some other directory, use `--pairs-file some_other_dir/pairs.json`.
- To download ticker data for only 10 days, use `--days 10` (defaults to 30 days).
- Use `--timeframes` to specify which tickers to download. Default is `--timeframes 1m 5m` which will download 1-minute and 5-minute tickers.
- To download historical candle (OHLCV) data for only 10 days, use `--days 10` (defaults to 30 days).
- Use `--timeframes` to specify what timeframe download the historical candle (OHLCV) data for. Default is `--timeframes 1m 5m` which will download 1-minute and 5-minute data.
- To use exchange, timeframe and list of pairs as defined in your configuration file, use the `-c/--config` option. With this, the script uses the whitelist defined in the config as the list of currency pairs to download data for and does not require the pairs.json file. You can combine `-c/--config` with most other options.
### Trades (tick) data

View File

@@ -1,6 +1,6 @@
# Development Help
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 in [slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LTU1MTgxMjkzNmYxNWE1MDEzYzQ3YmU4N2MwZjUyNjJjODRkMDVkNjg4YTAyZGYzYzlhOTZiMTE4ZjQ4YzM0OGE) where you can ask questions.
@@ -153,7 +153,7 @@ In VolumePairList, this implements different methods of sorting, does early vali
## Implement a new Exchange (WIP)
!!! Note
This section is a Work in Progress and is not a complete guide on how to test a new exchange with FreqTrade.
This section is a Work in Progress and is not a complete guide on how to test a new exchange with Freqtrade.
Most exchanges supported by CCXT should work out of the box.
@@ -165,7 +165,7 @@ Since CCXT does not provide unification for Stoploss On Exchange yet, we'll need
### Incomplete candles
While fetching OHLCV data, we're may end up getting incomplete candles (Depending on the exchange).
While fetching candle (OHLCV) data, we may end up getting incomplete candles (depending on the exchange).
To demonstrate this, we'll use daily candles (`"1d"`) to keep things simple.
We query the api (`ct.fetch_ohlcv()`) for the timeframe and look at the date of the last entry. If this entry changes or shows the date of a "incomplete" candle, then we should drop this since having incomplete candles is problematic because indicators assume that only complete candles are passed to them, and will generate a lot of false buy signals. By default, we're therefore removing the last candle assuming it's incomplete.
@@ -174,14 +174,14 @@ To check how the new exchange behaves, you can use the following snippet:
``` python
import ccxt
from datetime import datetime
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.data.converter import ohlcv_to_dataframe
ct = ccxt.binance()
timeframe = "1d"
pair = "XLM/BTC" # Make sure to use a pair that exists on that exchange!
raw = ct.fetch_ohlcv(pair, timeframe=timeframe)
# convert to dataframe
df1 = parse_ticker_dataframe(raw, timeframe, pair=pair, drop_incomplete=False)
df1 = ohlcv_to_dataframe(raw, timeframe, pair=pair, drop_incomplete=False)
print(df1.tail(1))
print(datetime.utcnow())
@@ -234,7 +234,7 @@ git checkout -b new_release <commitid>
Determine if crucial bugfixes have been made between this commit and the current state, and eventually cherry-pick these.
* Edit `freqtrade/__init__.py` and add the version matching the current date (for example `2019.7` for July 2019). Minor versions can be `2019.7-1` should we need to do a second release that month.
* Edit `freqtrade/__init__.py` and add the version matching the current date (for example `2019.7` for July 2019). Minor versions can be `2019.7.1` should we need to do a second release that month. Version numbers must follow allowed versions from PEP0440 to avoid failures pushing to pypi.
* Commit this part
* push that branch to the remote and create a PR against the master branch
@@ -268,11 +268,6 @@ Once the PR against master is merged (best right after merging):
* Use "master" as reference (this step comes after the above PR is merged).
* Use the above changelog as release comment (as codeblock)
### After-release
* Update version in develop by postfixing that with `-dev` (`2019.6 -> 2019.6-dev`).
* Create a PR against develop to update that branch.
## Releases
### pypi

View File

@@ -1,4 +1,4 @@
# Using FreqTrade with Docker
# Using Freqtrade with Docker
## Install Docker
@@ -8,13 +8,141 @@ Start by downloading and installing Docker CE for your platform:
* [Windows](https://docs.docker.com/docker-for-windows/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).
Once you have Docker installed, simply prepare the config file (e.g. `config.json`) and run the image for `freqtrade` as explained below.
## Download the official FreqTrade docker image
## Freqtrade with docker-compose
Freqtrade provides an official Docker image on [Dockerhub](https://hub.docker.com/r/freqtradeorg/freqtrade/), as well as a [docker-compose file](https://github.com/freqtrade/freqtrade/blob/develop/docker-compose.yml) ready for usage.
!!! Note
The following section assumes that docker and docker-compose is installed and available to the logged in user.
!!! Note
All below comands use relative directories and will have to be executed from the directory containing the `docker-compose.yml` file.
### Docker quick start
Create a new directory and place the [docker-compose file](https://github.com/freqtrade/freqtrade/blob/develop/docker-compose.yml) in this directory.
``` bash
mkdir ft_userdata
cd ft_userdata/
# Download the docker-compose file from the repository
curl https://raw.githubusercontent.com/freqtrade/freqtrade/develop/docker-compose.yml -o docker-compose.yml
# Pull the freqtrade image
docker-compose pull
# Create user directory structure
docker-compose run --rm freqtrade create-userdir --userdir user_data
# Create configuration - Requires answering interactive questions
docker-compose run --rm freqtrade new-config --config user_data/config.json
```
The above snippet creates a new directory called "ft_userdata", downloads the latest compose file and pulls the freqtrade image.
The last 2 steps in the snippet create the directory with user-data, as well as (interactively) the default configuration based on your selections.
!!! Note
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.
#### Adding your strategy
The configuration is now available as `user_data/config.json`.
You should now copy your strategy to `user_data/strategies/` - and add the Strategy class name to the `docker-compose.yml` file, replacing `SampleStrategy`. If you wish to run the bot with the SampleStrategy, just leave it as it is.
!!! Warning
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!
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).
``` bash
docker-compose up -d
```
#### Docker-compose logs
Logs will be written to `user_data/freqtrade.log`.
Alternatively, you can check the latest logs using `docker-compose logs -f`.
#### Database
The database will be in the user_data directory as well, and will be called `user_data/tradesv3.sqlite`.
#### Updating freqtrade with docker-compose
To update freqtrade when using docker-compose is as simple as running the following 2 commands:
``` bash
# Download the latest image
docker-compose pull
# Restart the image
docker-compose up -d
```
This will first pull the latest image, and will then restart the container with the just pulled version.
!!! Note
You should always check the changelog for breaking changes / manual interventions required and make sure the bot starts correctly after the update.
#### Going from here
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>`.
!!! 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).
##### Example: Download data with docker-compose
Download backtesting data for 5 days for the pair ETH/BTC and 1h timeframe from Binance. The data will be stored in the directory `user_data/data/` on the host.
``` bash
docker-compose run --rm freqtrade download-data --pairs ETH/BTC --exchange binance --days 5 -t 1h
```
Head over to the [Data Downloading Documentation](data-download.md) for more details on downloading data.
##### Example: Backtest with docker-compose
Run backtesting in docker-containers for SampleStrategy and specified timerange of historical data, on 5m timeframe:
``` bash
docker-compose run --rm freqtrade backtesting --config user_data/config.json --strategy SampleStrategy --timerange 20190801-20191001 -i 5m
```
Head over to the [Backtesting Documentation](backtesting.md) to learn more.
#### Additional dependencies with docker-compose
If your strategy requires dependencies not included in the default image (like [technical](https://github.com/freqtrade/technical)) - it will be necessary to build the image on your host.
For this, please create a Dockerfile containing installation steps for the additional dependencies (have a look at [Dockerfile.technical](https://github.com/freqtrade/freqtrade/blob/develop/Dockerfile.technical) for an example).
You'll then also need to modify the `docker-compose.yml` file and uncomment the build step, as well as rename the image to avoid naming collisions.
``` yaml
image: freqtrade_custom
build:
context: .
dockerfile: "./Dockerfile.<yourextension>"
```
You can then run `docker-compose build` to build the docker image, and run it using the commands described above.
## Freqtrade with docker without docker-compose
!!! Warning
The below documentation is provided for completeness and assumes that you are somewhat familiar with running docker containers. If you're just starting out with docker, we recommend to follow the [Freqtrade with docker-compose](#freqtrade-with-docker-compose) instructions.
### Download the official Freqtrade docker image
Pull the image from docker hub.
Branches / tags available can be checked out on [Dockerhub](https://hub.docker.com/r/freqtradeorg/freqtrade/tags/).
Branches / tags available can be checked out on [Dockerhub tags page](https://hub.docker.com/r/freqtradeorg/freqtrade/tags/).
```bash
docker pull freqtradeorg/freqtrade:develop

View File

@@ -145,19 +145,19 @@ Edge module has following configuration options:
| Parameter | Description |
|------------|-------------|
| `enabled` | If true, then Edge will run periodically. <br>*Defaults to `false`.* <br> ***Datatype:*** *Boolean*
| `process_throttle_secs` | How often should Edge run in seconds. <br>*Defaults to `3600` (once per hour).* <br> ***Datatype:*** *Integer*
| `calculate_since_number_of_days` | Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy. <br> **Note** that it downloads historical data so increasing this number would lead to slowing down the bot. <br>*Defaults to `7`.* <br> ***Datatype:*** *Integer*
| `capital_available_percentage` | **DEPRECATED - [replaced with `tradable_balance_ratio`](configuration.md#Available balance)** This is the percentage of the total capital on exchange in stake currency. <br>As an example if you have 10 ETH available in your wallet on the exchange and this value is 0.5 (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers it as available capital. <br>*Defaults to `0.5`.* <br> ***Datatype:*** *Float*
| `allowed_risk` | Ratio of allowed risk per trade. <br>*Defaults to `0.01` (1%)).* <br> ***Datatype:*** *Float*
| `stoploss_range_min` | Minimum stoploss. <br>*Defaults to `-0.01`.* <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*
| `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*
| `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 ticker interval. 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*
| `enabled` | If true, then Edge will run periodically. <br>*Defaults to `false`.* <br> **Datatype:** Boolean
| `process_throttle_secs` | How often should Edge run in seconds. <br>*Defaults to `3600` (once per hour).* <br> **Datatype:** Integer
| `calculate_since_number_of_days` | Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy. <br> **Note** that it downloads historical data so increasing this number would lead to slowing down the bot. <br>*Defaults to `7`.* <br> **Datatype:** Integer
| `capital_available_percentage` | **DEPRECATED - [replaced with `tradable_balance_ratio`](configuration.md#Available balance)** This is the percentage of the total capital on exchange in stake currency. <br>As an example if you have 10 ETH available in your wallet on the exchange and this value is 0.5 (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers it as available capital. <br>*Defaults to `0.5`.* <br> **Datatype:** Float
| `allowed_risk` | Ratio of allowed risk per trade. <br>*Defaults to `0.01` (1%)).* <br> **Datatype:** Float
| `stoploss_range_min` | Minimum stoploss. <br>*Defaults to `-0.01`.* <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
| `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
| `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 (ticker interval). 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
## Running Edge independently

View File

@@ -5,7 +5,7 @@ This page combines common gotchas and informations which are exchange-specific a
## Binance
!!! Tip "Stoploss on Exchange"
Binance is currently the only exchange supporting `stoploss_on_exchange`. It provides great advantages, so we recommend to benefit from it.
Binance supports `stoploss_on_exchange` and uses stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it.
### Blacklists
@@ -22,6 +22,9 @@ Binance has been split into 3, and users must use the correct ccxt exchange ID f
## Kraken
!!! Tip "Stoploss on Exchange"
Kraken supports `stoploss_on_exchange` and uses stop-loss-market orders. It provides great advantages, so we recommend to benefit from it, however since the resulting order is a stoploss-market order, sell-rates are not guaranteed, which makes this feature less secure than on other exchanges. This limitation is based on kraken's policy [source](https://blog.kraken.com/post/1234/announcement-delisting-pairs-and-temporary-suspension-of-advanced-order-types/) and [source2](https://blog.kraken.com/post/1494/kraken-enables-advanced-orders-and-adds-10-currency-pairs/) - which has stoploss-limit orders disabled.
### Historic Kraken data
The Kraken API does only provide 720 historic candles, which is sufficient for Freqtrade dry-run and live trade modes, but is a problem for backtesting.
@@ -29,6 +32,10 @@ To download data for the Kraken exchange, using `--dl-trades` is mandatory, othe
## Bittrex
### Order types
Bittrex does not support market orders. If you have a message at the bot startup about this, you should change order type values set in your configuration and/or in the strategy from `"market"` to `"limit"`. See some more details on this [here in the FAQ](faq.md#im-getting-the-exchange-bittrex-does-not-support-market-orders-message-and-cannot-run-my-strategy).
### Restricted markets
Bittrex split its exchange into US and International versions.
@@ -55,6 +62,11 @@ res = [ f"{x['MarketCurrency']}/{x['BaseCurrency']}" for x in ct.publicGetMarket
print(res)
```
## All exchanges
Should you experience constant errors with Nonce (like `InvalidNonce`), it is best to regenerate the API keys. Resetting Nonce is difficult and it's usually easier to regenerate the API keys.
## Random notes for other exchanges
* The Ocean (exchange id: `theocean`) exchange uses Web3 functionality and requires `web3` python package to be installed:
@@ -62,23 +74,13 @@ print(res)
$ pip3 install web3
```
### Send incomplete candles to the strategy
### Getting latest price / Incomplete candles
Most exchanges return incomplete candles via their ohlcv / klines interface.
By default, Freqtrade assumes that incomplete candles are returned and removes the last candle assuming it's an incomplete candle.
Most exchanges return current incomplete candle via their OHLCV/klines API interface.
By default, Freqtrade assumes that incomplete candle is fetched from the exchange and removes the last candle assuming it's the incomplete candle.
Whether your exchange returns incomplete candles or not can be checked using [the helper script](developer.md#Incomplete-candles) from the Contributor documentation.
If the exchange does return incomplete candles and you would like to have incomplete candles in your strategy, you can set the following parameter in the configuration file.
Due to the danger of repainting, Freqtrade does not allow you to use this incomplete candle.
``` json
{
"exchange": {
"_ft_has_params": {"ohlcv_partial_candle": false}
}
}
```
!!! Warning "Danger of repainting"
Changing this parameter makes the strategy responsible to avoid repainting and handle this accordingly. Doing this is therefore not recommended, and should only be performed by experienced users who are fully aware of the impact this setting has.
However, if it is based on the need for the latest price for your strategy - then this requirement can be acquired using the [data provider](strategy-customization.md#possible-options-for-dataprovider) from within the strategy.

View File

@@ -45,12 +45,28 @@ the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-c
You can use the `/forcesell all` command from Telegram.
### I get the message "RESTRICTED_MARKET"
### I'm getting the "RESTRICTED_MARKET" message in the log
Currently known to happen for US Bittrex users.
Read [the Bittrex section about restricted markets](exchanges.md#restricted-markets) for more information.
### 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).
To fix it for Bittrex, redefine order types in the strategy to use "limit" instead of "market":
```
order_types = {
...
'stoploss': 'limit',
...
}
```
Same fix should be done 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?
By default, the bot writes its log into stderr stream. This is implemented this way so that you can easily separate the bot's diagnostics messages from Backtesting, Edge and Hyperopt results, output from other various Freqtrade utility subcommands, as well as from the output of your custom `print()`'s you may have inserted into your strategy. So if you need to search the log messages with the grep utility, you need to redirect stderr to stdout and disregard stdout.

View File

@@ -31,9 +31,9 @@ This will create a new hyperopt file from a template, which will be located unde
Depending on the space you want to optimize, only some of the below are required:
* fill `buy_strategy_generator` - for buy signal optimization
* fill `indicator_space` - for buy signal optimzation
* fill `indicator_space` - for buy signal optimization
* fill `sell_strategy_generator` - for sell signal optimization
* fill `sell_indicator_space` - for sell signal optimzation
* fill `sell_indicator_space` - for sell signal optimization
!!! Note
`populate_indicators` needs to create all indicators any of thee spaces may use, otherwise hyperopt will not work.
@@ -57,7 +57,7 @@ Rarely you may also need to override:
!!! Tip "Quickly optimize ROI, stoploss and trailing stoploss"
You can quickly optimize the spaces `roi`, `stoploss` and `trailing` without changing anything (i.e. without creation of a "complete" Hyperopt class with dimensions, parameters, triggers and guards, as described in this document) from the default hyperopt template by relying on your strategy to do most of the calculations.
``` python
```python
# Have a working strategy at hand.
freqtrade new-hyperopt --hyperopt EmptyHyperopt
@@ -75,17 +75,17 @@ Copy the file `user_data/hyperopts/sample_hyperopt.py` into `user_data/hyperopts
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 `populate_buy_trend()` - applying the parameters.
* Inside `indicator_space()` - the parameters hyperopt shall be optimizing.
* Inside `populate_buy_trend()` - applying the parameters.
There you have two different types of indicators: 1. `guards` and 2. `triggers`.
1. Guards are conditions like "never buy if ADX < 10", or never buy if current price is over EMA10.
2. Triggers are ones that actually trigger buy in specific moment, like "buy when EMA5 crosses over EMA10" or "buy when close price touches lower bollinger band".
2. Triggers are ones that actually trigger buy in specific moment, like "buy when EMA5 crosses over EMA10" or "buy when close price touches lower Bollinger band".
Hyperoptimization will, for each eval round, pick one trigger and possibly
multiple guards. The constructed strategy will be something like
"*buy exactly when close price touches lower bollinger band, BUT only if
"*buy exactly when close price touches lower Bollinger band, BUT only if
ADX > 10*".
If you have updated the buy strategy, i.e. changed the contents of
@@ -103,9 +103,10 @@ Place the corresponding settings into the following methods
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-`.
#### Using ticker-interval as part of the Strategy
#### Using timeframe as a part of the Strategy
The Strategy exposes the ticker-interval as `self.ticker_interval`. The same value is available as class-attribute `HyperoptName.ticker_interval`.
The Strategy class exposes the timeframe (ticker interval) value as the `self.ticker_interval` attribute.
The same value is available as class-attribute `HyperoptName.ticker_interval`.
In the case of the linked sample-value this would be `SampleHyperOpt.ticker_interval`.
## Solving a Mystery
@@ -141,7 +142,7 @@ 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:
``` python
```python
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
conditions = []
# GUARDS AND TRENDS
@@ -159,6 +160,9 @@ So let's write the buy strategy using these values:
dataframe['macd'], dataframe['macdsignal']
))
# Check that volume is not 0
conditions.append(dataframe['volume'] > 0)
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
@@ -172,7 +176,7 @@ So let's write the buy strategy using these values:
Hyperopting will now call this `populate_buy_trend` as many times you ask it (`epochs`)
with different value combinations. It will then use the given historical data and make
buys based on the buy signals generated with the above function and based on the results
it will end with telling you which paramter combination produced the best profits.
it will end with telling you which parameter combination produced the best profits.
The above setup expects to find ADX, RSI and Bollinger Bands in the populated indicators.
When you want to test an indicator that isn't used by the bot currently, remember to
@@ -182,7 +186,7 @@ add it to the `populate_indicators()` method in your custom hyperopt file.
Each hyperparameter tuning requires a target. This is usually defined as a loss function (sometimes also called objective function), which should decrease for more desirable results, and increase for bad results.
By default, FreqTrade uses a loss function, which has been with freqtrade since the beginning and optimizes mostly for short trade duration and avoiding losses.
By default, Freqtrade uses a loss function, which has been with freqtrade since the beginning and optimizes mostly for short trade duration and avoiding losses.
A different loss function can be specified by using the `--hyperopt-loss <Class-name>` argument.
This class should be in its own file within the `user_data/hyperopts/` directory.
@@ -191,7 +195,10 @@ Currently, the following loss functions are builtin:
* `DefaultHyperOptLoss` (default legacy Freqtrade hyperoptimization loss function)
* `OnlyProfitHyperOptLoss` (which takes only amount of profit into consideration)
* `SharpeHyperOptLoss` (optimizes Sharpe Ratio calculated on the trade returns)
* `SharpeHyperOptLoss` (optimizes Sharpe Ratio calculated on trade returns relative to standard deviation)
* `SharpeHyperOptLossDaily` (optimizes Sharpe Ratio calculated on **daily** trade returns relative to standard deviation)
* `SortinoHyperOptLoss` (optimizes Sortino Ratio calculated on trade returns relative to **downside** standard deviation)
* `SortinoHyperOptLossDaily` (optimizes Sortino Ratio calculated on **daily** trade returns relative to **downside** standard deviation)
Creation of a custom loss function is covered in the [Advanced Hyperopt](advanced-hyperopt.md) part of the documentation.
@@ -206,7 +213,7 @@ We strongly recommend to use `screen` or `tmux` to prevent any connection loss.
freqtrade hyperopt --config config.json --hyperopt <hyperoptname> -e 5000 --spaces all
```
Use `<hyperoptname>` as the name of the custom hyperopt used.
Use `<hyperoptname>` as the name of the custom hyperopt used.
The `-e` option will set how many evaluations hyperopt will do. We recommend
running at least several thousand evaluations.
@@ -219,11 +226,11 @@ The `--spaces all` option determines that all possible parameters should be opti
!!! Warning
When switching parameters or changing configuration options, make sure to not use the argument `--continue` so temporary results can be removed.
### Execute Hyperopt with Different Ticker-Data Source
### Execute Hyperopt with different historical data source
If you would like to hyperopt parameters using an alternate ticker data that
you have on-disk, use the `--datadir PATH` option. Default hyperopt will
use data from directory `user_data/data`.
If you would like to hyperopt parameters using an alternate historical data set that
you have on-disk, use the `--datadir PATH` option. By default, hyperopt
uses data from directory `user_data/data`.
### Running Hyperopt with Smaller Testset
@@ -271,7 +278,7 @@ In some situations, you may need to run Hyperopt (and Backtesting) with the
By default, hyperopt emulates the behavior of the Freqtrade Live Run/Dry Run, where only one
open trade is allowed for every traded pair. The total number of trades open for all pairs
is also limited by the `max_open_trades` setting. During Hyperopt/Backtesting this may lead to
some potential trades to be hidden (or masked) by previosly open trades.
some potential trades to be hidden (or masked) by previously open trades.
The `--eps`/`--enable-position-stacking` argument allows emulation of buying the same pair multiple times,
while `--dmmp`/`--disable-max-market-positions` disables applying `max_open_trades`
@@ -323,7 +330,7 @@ method, what those values match to.
So for example you had `rsi-value: 29.0` so we would look at `rsi`-block, that translates to the following code block:
``` python
```python
(dataframe['rsi'] < 29.0)
```
@@ -372,20 +379,21 @@ In order to use this best ROI table found by Hyperopt in backtesting and for liv
118: 0
}
```
As stated in the comment, you can also use it as the value of the `minimal_roi` setting in the configuration file.
#### Default ROI Search Space
If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace for you -- it's the hyperspace of components for the ROI tables. By default, each ROI table generated by the Freqtrade consists of 4 rows (steps). Hyperopt implements adaptive ranges for ROI tables with ranges for values in the ROI steps that depend on the ticker_interval used. By default the values vary in the following ranges (for some of the most used ticker intervals, values are rounded to 5 digits after the decimal point):
If you are optimizing ROI, Freqtrade creates the 'roi' optimization hyperspace for you -- it's the hyperspace of components for the ROI tables. By default, each ROI table generated by the Freqtrade consists of 4 rows (steps). Hyperopt implements adaptive ranges for ROI tables with ranges for values in the ROI steps that depend on the ticker_interval used. By default the values vary in the following ranges (for some of the most used timeframes, values are rounded to 5 digits after the decimal point):
| # step | 1m | | 5m | | 1h | | 1d | |
|---|---|---|---|---|---|---|---|---|
| 1 | 0 | 0.01161...0.11992 | 0 | 0.03...0.31 | 0 | 0.06883...0.71124 | 0 | 0.12178...1.25835 |
| 2 | 2...8 | 0.00774...0.04255 | 10...40 | 0.02...0.11 | 120...480 | 0.04589...0.25238 | 2880...11520 | 0.08118...0.44651 |
| 3 | 4...20 | 0.00387...0.01547 | 20...100 | 0.01...0.04 | 240...1200 | 0.02294...0.09177 | 5760...28800 | 0.04059...0.16237 |
| 4 | 6...44 | 0.0 | 30...220 | 0.0 | 360...2640 | 0.0 | 8640...63360 | 0.0 |
| # step | 1m | | 5m | | 1h | | 1d | |
| ------ | ------ | ----------------- | -------- | ----------- | ---------- | ----------------- | ------------ | ----------------- |
| 1 | 0 | 0.01161...0.11992 | 0 | 0.03...0.31 | 0 | 0.06883...0.71124 | 0 | 0.12178...1.25835 |
| 2 | 2...8 | 0.00774...0.04255 | 10...40 | 0.02...0.11 | 120...480 | 0.04589...0.25238 | 2880...11520 | 0.08118...0.44651 |
| 3 | 4...20 | 0.00387...0.01547 | 20...100 | 0.01...0.04 | 240...1200 | 0.02294...0.09177 | 5760...28800 | 0.04059...0.16237 |
| 4 | 6...44 | 0.0 | 30...220 | 0.0 | 360...2640 | 0.0 | 8640...63360 | 0.0 |
These ranges should be sufficient in most cases. The minutes in the steps (ROI dict keys) are scaled linearly depending on the ticker interval used. The ROI values in the steps (ROI dict values) are scaled logarithmically depending on the ticker interval used.
These ranges should be sufficient in most cases. The minutes in the steps (ROI dict keys) are scaled linearly depending on the timeframe (ticker interval) used. The ROI values in the steps (ROI dict values) are scaled logarithmically depending on the timeframe used.
If you have the `generate_roi_table()` and `roi_space()` methods in your custom hyperopt file, remove them in order to utilize these adaptive ROI tables and the ROI hyperoptimization space generated by Freqtrade by default.
@@ -416,6 +424,7 @@ In order to use this best stoploss value found by Hyperopt in backtesting and fo
# This attribute will be overridden if the config file contains "stoploss"
stoploss = -0.27996
```
As stated in the comment, you can also use it as the value of the `stoploss` setting in the configuration file.
#### Default Stoploss Search Space
@@ -452,6 +461,7 @@ In order to use these best trailing stop parameters found by Hyperopt in backtes
trailing_stop_positive_offset = 0.06038
trailing_only_offset_is_reached = True
```
As stated in the comment, you can also use it as the values of the corresponding settings in the configuration file.
#### Default Trailing Stop Search Space

View File

@@ -1,5 +1,5 @@
# Freqtrade
[![Build Status](https://travis-ci.org/freqtrade/freqtrade.svg?branch=develop)](https://travis-ci.org/freqtrade/freqtrade)
[![Freqtrade CI](https://github.com/freqtrade/freqtrade/workflows/Freqtrade%20CI/badge.svg)](https://github.com/freqtrade/freqtrade/actions/)
[![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
[![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](https://codeclimate.com/github/freqtrade/freqtrade/maintainability)
@@ -51,12 +51,15 @@ To run this bot we recommend you a cloud instance with a minimum of:
### Software requirements
- Docker (Recommended)
Alternatively
- Python 3.6.x
- pip (pip3)
- git
- TA-Lib
- virtualenv (Recommended)
- Docker (Recommended)
## Support
@@ -67,4 +70,4 @@ Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODc
## Ready to try?
Begin by reading our installation guide [here](installation).
Begin by reading our installation guide [for docker](docker.md), or for [installation without docker](installation.md).

View File

@@ -2,6 +2,8 @@
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.
## Prerequisite
### Requirements
@@ -14,15 +16,7 @@ Click each one for install guide:
* [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) (Recommended)
* [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html) (install instructions below)
### API keys
Before running your bot in production you will need to setup few
external API. In production mode, the bot will require valid Exchange API
credentials. We also recommend a [Telegram bot](telegram-usage.md#setup-your-telegram-bot) (optional but recommended).
### Setup your exchange account
You will need to create API Keys (Usually you get `key` and `secret`) from the Exchange website and insert this into the appropriate fields in the configuration or when asked by the installation script.
We also recommend a [Telegram bot](telegram-usage.md#setup-your-telegram-bot), which is optional but recommended.
## Quick start
@@ -31,7 +25,7 @@ Freqtrade provides the Linux/MacOS Easy Installation script to install all depen
!!! 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.
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 `master` 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).
@@ -42,11 +36,12 @@ The easiest way to install and run Freqtrade is to clone the bot GitHub reposito
This can be achieved with the following commands:
```bash
git clone git@github.com:freqtrade/freqtrade.git
git clone https://github.com/freqtrade/freqtrade.git
cd freqtrade
git checkout master # Optional, see (1)
./setup.sh --install
```
(1) This command switches the cloned repository to the use of the `master` 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 master`/`git checkout develop` commands.
## Easy Installation Script (Linux/MacOS)
@@ -64,11 +59,11 @@ usage:
** --install **
With this option, the script will install everything you need to run the bot:
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
* Configure your `config.json` file
* Setup your virtualenv under `.env/`
This option is a combination of installation tasks, `--reset` and `--config`.
@@ -82,7 +77,7 @@ This option will hard reset your branch (only if you are on either `master` or `
** --config **
Use this option to configure the `config.json` configuration file. The script will interactively ask you questions to setup your bot and create your `config.json`.
DEPRECATED - use `freqtrade new-config -c config.json` instead.
------
@@ -129,6 +124,17 @@ bash setup.sh -i
#### 1. Install TA-Lib
Use the provided ta-lib installation script
```bash
sudo ./build_helpers/install_ta-lib.sh
```
!!! Note
This will use the ta-lib tar.gz included in this repository.
##### TA-Lib manual installation
Official webpage: https://mrjbq7.github.io/ta-lib/install.html
```bash
@@ -184,7 +190,8 @@ python3 -m pip install -e .
# Initialize the user_directory
freqtrade create-userdir --userdir user_data/
cp config.json.example config.json
# Create a new configuration file
freqtrade new-config --config config.json
```
> *To edit the config please refer to [Bot Configuration](configuration.md).*

View File

@@ -23,44 +23,64 @@ The `freqtrade plot-dataframe` subcommand shows an interactive graph with three
Possible arguments:
```
usage: freqtrade plot-dataframe [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [-s NAME]
[--strategy-path PATH] [-p PAIRS [PAIRS ...]] [--indicators1 INDICATORS1 [INDICATORS1 ...]]
[--indicators2 INDICATORS2 [INDICATORS2 ...]] [--plot-limit INT] [--db-url PATH]
[--trade-source {DB,file}] [--export EXPORT] [--export-filename PATH] [--timerange TIMERANGE]
[-i TICKER_INTERVAL]
usage: freqtrade plot-dataframe [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [-s NAME]
[--strategy-path PATH] [-p PAIRS [PAIRS ...]]
[--indicators1 INDICATORS1 [INDICATORS1 ...]]
[--indicators2 INDICATORS2 [INDICATORS2 ...]]
[--plot-limit INT] [--db-url PATH]
[--trade-source {DB,file}] [--export EXPORT]
[--export-filename PATH]
[--timerange TIMERANGE] [-i TICKER_INTERVAL]
[--no-trades]
optional arguments:
-h, --help show this help message and exit
-p PAIRS [PAIRS ...], --pairs PAIRS [PAIRS ...]
Show profits for only these pairs. Pairs are space-separated.
Show profits for only these pairs. Pairs are space-
separated.
--indicators1 INDICATORS1 [INDICATORS1 ...]
Set indicators from your strategy you want in the first row of the graph. Space-separated list. Example:
Set indicators from your strategy you want in the
first row of the graph. Space-separated list. Example:
`ema3 ema5`. Default: `['sma', 'ema3', 'ema5']`.
--indicators2 INDICATORS2 [INDICATORS2 ...]
Set indicators from your strategy you want in the third row of the graph. Space-separated list. Example:
Set indicators from your strategy you want in the
third row of the graph. Space-separated list. Example:
`fastd fastk`. Default: `['macd', 'macdsignal']`.
--plot-limit INT Specify tick limit for plotting. Notice: too high values cause huge files. Default: 750.
--db-url PATH Override trades database URL, this is useful in custom deployments (default: `sqlite:///tradesv3.sqlite`
for Live Run mode, `sqlite:///tradesv3.dryrun.sqlite` for Dry Run).
--plot-limit INT Specify tick limit for plotting. Notice: too high
values cause huge files. Default: 750.
--db-url PATH Override trades database URL, this is useful in custom
deployments (default: `sqlite:///tradesv3.sqlite` for
Live Run mode, `sqlite:///tradesv3.dryrun.sqlite` for
Dry Run).
--trade-source {DB,file}
Specify the source for trades (Can be DB or file (backtest file)) Default: file
--export EXPORT Export backtest results, argument are: trades. Example: `--export=trades`
Specify the source for trades (Can be DB or file
(backtest file)) Default: file
--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`
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`
--timerange TIMERANGE
Specify what timerange of data to use.
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL
Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`).
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
--no-trades Skip using trades from backtesting file and DB.
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
--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: `config.json`). Multiple --config options may be used. Can be set to
`-` to read config from stdin.
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
@@ -68,9 +88,9 @@ Common arguments:
Strategy arguments:
-s NAME, --strategy NAME
Specify strategy class name which will be used by the bot.
Specify strategy class name which will be used by the
bot.
--strategy-path PATH Specify additional strategy lookup path.
```
Example:
@@ -196,6 +216,7 @@ The first graph is good to get a grip of how the overall market progresses.
The second graph will show if your algorithm works or doesn't.
Perhaps you want an algorithm that steadily makes small profits, or one that acts less often, but makes big swings.
This graph will also highlight the start (and end) of the Max drawdown period.
The third graph can be useful to spot outliers, events in pairs that cause profit spikes.

View File

@@ -1,2 +1,2 @@
mkdocs-material==4.6.0
mkdocs-material==4.6.3
mdx_truly_sane_lists==1.2

View File

@@ -74,7 +74,7 @@ docker run -d \
## Consuming the API
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.
``` bash
python3 scripts/rest_client.py <command> [optional parameters]

View File

@@ -27,7 +27,7 @@ So this parameter will tell the bot how often it should update the stoploss orde
This same logic will reapply a stoploss order on the exchange should you cancel it accidentally.
!!! Note
Stoploss on exchange is only supported for Binance as of now.
Stoploss on exchange is only supported for Binance (stop-loss-limit) and Kraken (stop-loss-market) as of now.
## Static Stop Loss

View File

@@ -84,7 +84,7 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame
Performance Note: For the best performance be frugal on the number of indicators
you are using. Let uncomment only the indicator you are using in your strategies
or your hyperopt configuration, otherwise you will waste your memory and CPU usage.
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
:param dataframe: Dataframe with data from the exchange
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
"""
@@ -249,6 +249,23 @@ minimal_roi = {
While technically not completely disabled, this would sell once the trade reaches 10000% Profit.
To use times based on candle duration (ticker_interval or timeframe), the following snippet can be handy.
This will allow you to change the ticket_interval for the strategy, and ROI times will still be set as candles (e.g. after 3 candles ...)
``` python
from freqtrade.exchange import timeframe_to_minutes
class AwesomeStrategy(IStrategy):
ticker_interval = "1d"
ticker_interval_mins = timeframe_to_minutes(ticker_interval)
minimal_roi = {
"0": 0.05, # 5% for the first 3 candles
str(ticker_interval_mins * 3)): 0.02, # 2% after 3 candles
str(ticker_interval_mins * 6)): 0.01, # 1% After 6 candles
}
```
### Stoploss
Setting a stoploss is highly recommended to protect your capital from strong moves against you.
@@ -267,13 +284,14 @@ If your exchange supports it, it's recommended to also set `"stoploss_on_exchang
For more information on order_types please look [here](configuration.md#understand-order_types).
### Ticker interval
### Timeframe (ticker interval)
This is the set of candles the bot should download and use for the analysis.
Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported by your exchange should work.
Please note that the same buy/sell signals may work with one interval, but not the other.
This setting is accessible within the strategy by using `self.ticker_interval`.
Please note that the same buy/sell signals may work well with one timeframe, but not with the others.
This setting is accessible within the strategy methods as the `self.ticker_interval` attribute.
### Metadata dict
@@ -318,14 +336,14 @@ Please always check the mode of operation to select the correct method to get da
#### Possible options for DataProvider
- `available_pairs` - Property with tuples listing cached pairs with their intervals (pair, interval).
- `ohlcv(pair, timeframe)` - Currently cached ticker data for the pair, returns DataFrame or empty DataFrame.
- `ohlcv(pair, timeframe)` - Currently cached candle (OHLCV) data for the pair, returns DataFrame or empty DataFrame.
- `historic_ohlcv(pair, timeframe)` - Returns historical data stored on disk.
- `get_pair_dataframe(pair, timeframe)` - This is a universal method, which returns either historical data (for backtesting) or cached live data (for the Dry-Run and Live-Run modes).
- `orderbook(pair, maximum)` - Returns latest orderbook data for the pair, a dict with bids/asks with a total of `maximum` entries.
- `market(pair)` - Returns market data for the pair: fees, limits, precisions, activity flag, etc. See [ccxt documentation](https://github.com/ccxt/ccxt/wiki/Manual#markets) for more details on Market data structure.
- `runmode` - Property containing the current runmode.
#### Example: fetch live ohlcv / historic data for the first informative pair
#### Example: fetch live / historical candle (OHLCV) data for the first informative pair
``` python
if self.dp:
@@ -346,7 +364,7 @@ if self.dp:
``` python
if self.dp:
if self.dp.runmode in ('live', 'dry_run'):
if self.dp.runmode.value in ('live', 'dry_run'):
ob = self.dp.orderbook(metadata['pair'], 1)
dataframe['best_bid'] = ob['bids'][0][0]
dataframe['best_ask'] = ob['asks'][0][0]
@@ -360,8 +378,8 @@ if self.dp:
``` python
if self.dp:
for pair, ticker in self.dp.available_pairs:
print(f"available {pair}, {ticker}")
for pair, timeframe in self.dp.available_pairs:
print(f"available {pair}, {timeframe}")
```
#### Get data for non-tradeable pairs
@@ -422,7 +440,7 @@ from freqtrade.persistence import Trade
The following example queries for the current pair and trades from today, however other filters can easily be added.
``` python
if self.config['runmode'] in ('live', 'dry_run'):
if self.config['runmode'].value in ('live', 'dry_run'):
trades = Trade.get_trades([Trade.pair == metadata['pair'],
Trade.open_date > datetime.utcnow() - timedelta(days=1),
Trade.is_open == False,
@@ -434,7 +452,7 @@ if self.config['runmode'] in ('live', 'dry_run'):
Get amount of stake_currency currently invested in Trades:
``` python
if self.config['runmode'] in ('live', 'dry_run'):
if self.config['runmode'].value in ('live', 'dry_run'):
total_stakes = Trade.total_open_trades_stakes()
```
@@ -442,7 +460,7 @@ Retrieve performance per pair.
Returns a List of dicts per pair.
``` python
if self.config['runmode'] in ('live', 'dry_run'):
if self.config['runmode'].value in ('live', 'dry_run'):
performance = Trade.get_overall_performance()
```
@@ -487,7 +505,7 @@ from datetime import timedelta, datetime, timezone
# --------
# Within populate indicators (or populate_buy):
if self.config['runmode'] in ('live', 'dry_run'):
if self.config['runmode'].value in ('live', 'dry_run'):
# fetch closed trades for the last 2 days
trades = Trade.get_trades([Trade.pair == metadata['pair'],
Trade.open_date > datetime.utcnow() - timedelta(days=2),
@@ -532,6 +550,27 @@ If you want to use a strategy from a different directory you can pass `--strateg
freqtrade trade --strategy AwesomeStrategy --strategy-path /some/directory
```
### 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:
``` python
class MyAwesomeStrategy(IStrategy):
...
stoploss = 0.13
trailing_stop = False
# All other attributes and methods are here as they
# should be in any custom strategy...
...
class MyAwesomeStrategy2(MyAwesomeStrategy):
# Override something
stoploss = 0.08
trailing_stop = True
```
Both attributes and methods may be overriden, altering behavior of the original strategy in a way you need.
### Common mistakes when developing strategies
Backtesting analyzes the whole time-range at once for performance reasons. Because of this, strategy authors need to make sure that strategies do not look-ahead into the future.

View File

@@ -1,24 +1,28 @@
# Strategy analysis example
Debugging a strategy can be time-consuming. FreqTrade offers helper functions to visualize raw data.
Debugging a strategy can be time-consuming. Freqtrade offers helper functions to visualize raw data.
The following assumes you work with SampleStrategy, data for 5m timeframe from Binance and have downloaded them into the data directory in the default location.
## Setup
```python
from pathlib import Path
from freqtrade.configuration import Configuration
# Customize these according to your needs.
# Initialize empty configuration object
config = Configuration.from_files([])
# Optionally, use existing configuration file
# config = Configuration.from_files(["config.json"])
# Define some constants
timeframe = "5m"
config["ticker_interval"] = "5m"
# Name of the strategy class
strategy_name = 'SampleStrategy'
# Path to user data
user_data_dir = Path('user_data')
# Location of the strategy
strategy_location = user_data_dir / 'strategies'
config["strategy"] = "SampleStrategy"
# Location of the data
data_location = Path(user_data_dir, 'data', 'binance')
data_location = Path(config['user_data_dir'], 'data', 'binance')
# Pair to analyze - Only use one pair here
pair = "BTC_USDT"
```
@@ -29,7 +33,7 @@ pair = "BTC_USDT"
from freqtrade.data.history import load_pair_history
candles = load_pair_history(datadir=data_location,
timeframe=timeframe,
timeframe=config["ticker_interval"],
pair=pair)
# Confirm success
@@ -44,9 +48,7 @@ candles.head()
```python
# Load strategy using values set above
from freqtrade.resolvers import StrategyResolver
strategy = StrategyResolver.load_strategy({'strategy': strategy_name,
'user_data_dir': user_data_dir,
'strategy_path': strategy_location})
strategy = StrategyResolver.load_strategy(config)
# Generate buy/sell signals using strategy
df = strategy.analyze_ticker(candles, {'pair': pair})
@@ -86,7 +88,7 @@ Analyze a trades dataframe (also used below for plotting)
from freqtrade.data.btanalysis import load_backtest_data
# Load backtest results
trades = load_backtest_data(user_data_dir / "backtest_results/backtest-result.json")
trades = load_backtest_data(config["user_data_dir"] / "backtest_results/backtest-result.json")
# Show value-counts per pair
trades.groupby("pair")["sell_reason"].value_counts()
@@ -119,7 +121,6 @@ from freqtrade.data.btanalysis import analyze_trade_parallelism
# Analyze the above
parallel_trades = analyze_trade_parallelism(trades, '5m')
parallel_trades.plot()
```
@@ -132,11 +133,14 @@ Freqtrade offers interactive plotting capabilities based on plotly.
from freqtrade.plot.plotting import generate_candlestick_graph
# Limit graph period to keep plotly quick and reactive
# Filter trades to one pair
trades_red = trades.loc[trades['pair'] == pair]
data_red = data['2019-06-01':'2019-06-10']
# Generate candlestick graph
graph = generate_candlestick_graph(pair=pair,
data=data_red,
trades=trades,
trades=trades_red,
indicators1=['sma20', 'ema50', 'ema55'],
indicators2=['rsi', 'macd', 'macdsignal', 'macdhist']
)

View File

@@ -55,7 +55,7 @@ official commands. You can ask at any moment for help with `/help`.
| `/reload_conf` | | Reloads the configuration file
| `/show_config` | | Shows part of the current configuration with relevant settings to operation
| `/status` | | Lists all open trades
| `/status table` | | List all open trades in a table format
| `/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 (**)
| `/count` | | Displays number of trades used and available
| `/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`).

View File

@@ -36,6 +36,38 @@ optional arguments:
└── sample_strategy.py
```
## Create new config
Creates a new configuration file, asking some questions which are important selections for a configuration.
```
usage: freqtrade new-config [-h] [-c PATH]
optional arguments:
-h, --help show this help message and exit
-c PATH, --config PATH
Specify configuration file (default: `config.json`). Multiple --config options may be used. Can be set to `-`
to read config from stdin.
```
!!! Warning
Only vital questions are asked. Freqtrade offers a lot more configuration possibilities, which are listed in the [Configuration documentation](configuration.md#configuration-parameters)
### Create config examples
```
$ freqtrade new-config --config config_binance.json
? Do you want to enable Dry-run (simulated trades)? Yes
? Please insert your stake currency: BTC
? Please insert your stake amount: 0.05
? Please insert max_open_trades (Integer or 'unlimited'): 3
? Please insert your timeframe (ticker interval): 5m
? Please insert your display Currency (for reporting): USD
? Select exchange binance
? Do you want to enable Telegram? No
```
## Create new strategy
Creates a new strategy from a template similar to SampleStrategy.
@@ -108,26 +140,62 @@ With custom user directory
freqtrade new-hyperopt --userdir ~/.freqtrade/ --hyperopt AwesomeHyperopt
```
## List Strategies
## List Strategies and List Hyperopts
Use the `list-strategies` subcommand to see all strategies in one particular directory.
Use the `list-strategies` subcommand to see all strategies in one particular directory and the `list-hyperopts` subcommand to list custom Hyperopts.
These subcommands are useful for finding problems in your environment with loading strategies or hyperopt classes: modules with strategies or hyperopt classes that contain errors and failed to load are printed in red (LOAD FAILED), while strategies or hyperopt classes with duplicate names are printed in yellow (DUPLICATE NAME).
```
freqtrade list-strategies --help
usage: freqtrade list-strategies [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--strategy-path PATH] [-1]
usage: freqtrade list-strategies [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH]
[--strategy-path PATH] [-1] [--no-color]
optional arguments:
-h, --help show this help message and exit
--strategy-path PATH Specify additional strategy lookup path.
-1, --one-column Print output in one column.
--no-color Disable colorization of hyperopt results. May be
useful if you are redirecting output to a file.
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified. Special values are: 'syslog', 'journald'. See the documentation for more details.
--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: `config.json`). Multiple --config options may be used. Can be set to `-`
to read config from stdin.
Specify configuration file (default: `config.json`).
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.
```
```
usage: freqtrade list-hyperopts [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH]
[--hyperopt-path PATH] [-1] [--no-color]
optional arguments:
-h, --help show this help message and exit
--hyperopt-path PATH Specify additional lookup path for Hyperopt and
Hyperopt Loss functions.
-1, --one-column Print output in one column.
--no-color Disable colorization of hyperopt results. May be
useful if you are redirecting output to a file.
Common arguments:
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log 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: `config.json`).
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
@@ -135,20 +203,34 @@ Common arguments:
```
!!! Warning
Using this command will try to load all python files from a directory. This can be a security risk if untrusted files reside in this directory, since all module-level code is executed.
Using these commands will try to load all python files from a directory. This can be a security risk if untrusted files reside in this directory, since all module-level code is executed.
Example: search default strategy directory within userdir
Example: Search default strategies and hyperopts directories (within the default userdir).
``` bash
freqtrade list-strategies
freqtrade list-hyperopts
```
Example: Search strategies and hyperopts directory within the userdir.
``` bash
freqtrade list-strategies --userdir ~/.freqtrade/
freqtrade list-hyperopts --userdir ~/.freqtrade/
```
Example: search dedicated strategy path
Example: Search dedicated strategy path.
``` bash
freqtrade list-strategies --strategy-path ~/.freqtrade/strategies/
```
Example: Search dedicated hyperopt path.
``` bash
freqtrade list-hyperopt --hyperopt-path ~/.freqtrade/hyperopts/
```
## List Exchanges
Use the `list-exchanges` subcommand to see the exchanges available for the bot.
@@ -176,23 +258,34 @@ All exchanges supported by the ccxt library: _1btcxe, acx, adara, allcoin, anxpr
## List Timeframes
Use the `list-timeframes` subcommand to see the list of ticker intervals (timeframes) available for the exchange.
Use the `list-timeframes` subcommand to see the list of timeframes (ticker intervals) available for the exchange.
```
usage: freqtrade list-timeframes [-h] [--exchange EXCHANGE] [-1]
usage: freqtrade list-timeframes [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH] [--userdir PATH] [--exchange EXCHANGE] [-1]
optional arguments:
-h, --help show this help message and exit
--exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no
config is provided.
-1, --one-column Print output in one column.
-h, --help show this help message and exit
--exchange EXCHANGE Exchange name (default: `bittrex`). Only valid if no config is provided.
-1, --one-column Print output in one column.
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: `config.json`). 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.
```
* Example: see the timeframes for the 'binance' exchange, set in the configuration file:
```
$ freqtrade -c config_binance.json list-timeframes
$ freqtrade list-timeframes -c config_binance.json
...
Timeframes available for the exchange `binance`: 1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h, 1d, 3d, 1w, 1M
```
@@ -216,14 +309,16 @@ You can print info about any pair/market with these subcommands - and you can fi
These subcommands have same usage and same set of available options:
```
usage: freqtrade list-markets [-h] [--exchange EXCHANGE] [--print-list]
[--print-json] [-1] [--print-csv]
usage: freqtrade list-markets [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [--exchange EXCHANGE]
[--print-list] [--print-json] [-1] [--print-csv]
[--base BASE_CURRENCY [BASE_CURRENCY ...]]
[--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]]
[-a]
usage: freqtrade list-pairs [-h] [--exchange EXCHANGE] [--print-list]
[--print-json] [-1] [--print-csv]
usage: freqtrade list-pairs [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [--exchange EXCHANGE]
[--print-list] [--print-json] [-1] [--print-csv]
[--base BASE_CURRENCY [BASE_CURRENCY ...]]
[--quote QUOTE_CURRENCY [QUOTE_CURRENCY ...]] [-a]
@@ -242,6 +337,22 @@ optional arguments:
Specify quote currency(-ies). Space-separated list.
-a, --all Print all pairs or market symbols. By default only
active ones are shown.
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: `config.json`).
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.
```
By default, only active pairs/markets are shown. Active pairs/markets are those that can currently be traded
@@ -263,7 +374,7 @@ $ freqtrade list-pairs --quote USD --print-json
human-readable list with summary:
```
$ freqtrade -c config_binance.json list-pairs --all --base BTC ETH --quote USDT USD --print-list
$ freqtrade list-pairs -c config_binance.json --all --base BTC ETH --quote USDT USD --print-list
```
* Print all markets on exchange "Kraken", in the tabular format:
@@ -311,17 +422,53 @@ You can list the hyperoptimization epochs the Hyperopt module evaluated previous
```
usage: freqtrade hyperopt-list [-h] [-v] [--logfile FILE] [-V] [-c PATH]
[-d PATH] [--userdir PATH] [--best]
[--profitable] [--no-color] [--print-json]
[--no-details]
[--profitable] [--min-trades INT]
[--max-trades INT] [--min-avg-time FLOAT]
[--max-avg-time FLOAT] [--min-avg-profit FLOAT]
[--max-avg-profit FLOAT]
[--min-total-profit FLOAT]
[--max-total-profit FLOAT] [--no-color]
[--print-json] [--no-details]
[--export-csv FILE]
optional arguments:
-h, --help show this help message and exit
--best Select only best epochs.
--profitable Select only profitable epochs.
--min-trades INT Select epochs with more than INT trades.
--max-trades INT Select epochs with less than INT trades.
--min-avg-time FLOAT Select epochs on above average time.
--max-avg-time FLOAT Select epochs on under average time.
--min-avg-profit FLOAT
Select epochs on above average profit.
--max-avg-profit FLOAT
Select epochs on below average profit.
--min-total-profit FLOAT
Select epochs on above total profit.
--max-total-profit FLOAT
Select epochs on below total profit.
--no-color Disable colorization of hyperopt results. May be
useful if you are redirecting output to a file.
--print-json Print best result detailization in JSON format.
--no-details Do not print best epoch details.
--export-csv FILE Export to CSV-File. This will disable table print.
Example: --export-csv hyperopt.csv
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.
```
### Examples

View File

@@ -15,10 +15,20 @@ Sample configuration (tested using IFTTT).
"value2": "limit {limit:8f}",
"value3": "{stake_amount:8f} {stake_currency}"
},
"webhookbuycancel": {
"value1": "Cancelling Open Buy Order for {pair}",
"value2": "limit {limit:8f}",
"value3": "{stake_amount:8f} {stake_currency}"
},
"webhooksell": {
"value1": "Selling {pair}",
"value2": "limit {limit:8f}",
"value3": "profit: {profit_amount:8f} {stake_currency}"
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
},
"webhooksellcancel": {
"value1": "Cancelling Open Sell Order for {pair}",
"value2": "limit {limit:8f}",
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
},
"webhookstatus": {
"value1": "Status: {status}",
@@ -40,10 +50,29 @@ Possible parameters are:
* `exchange`
* `pair`
* `limit`
* `amount`
* `open_date`
* `stake_amount`
* `stake_currency`
* `fiat_currency`
* `order_type`
* `current_rate`
### Webhookbuycancel
The fields in `webhook.webhookbuycancel` are filled when the bot cancels a buy order. Parameters are filled using string.format.
Possible parameters are:
* `exchange`
* `pair`
* `limit`
* `amount`
* `open_date`
* `stake_amount`
* `stake_currency`
* `fiat_currency`
* `order_type`
* `current_rate`
### Webhooksell
@@ -58,7 +87,28 @@ Possible parameters are:
* `open_rate`
* `current_rate`
* `profit_amount`
* `profit_percent`
* `profit_ratio`
* `stake_currency`
* `fiat_currency`
* `sell_reason`
* `order_type`
* `open_date`
* `close_date`
### Webhooksellcancel
The fields in `webhook.webhooksellcancel` are filled when the bot cancels a sell order. Parameters are filled using string.format.
Possible parameters are:
* `exchange`
* `pair`
* `gain`
* `limit`
* `amount`
* `open_rate`
* `current_rate`
* `profit_amount`
* `profit_ratio`
* `stake_currency`
* `fiat_currency`
* `sell_reason`

View File

@@ -45,7 +45,7 @@ dependencies:
- pip:
# Required for app
- cython
- coinmarketcap
- pycoingecko
- ccxt
- TA-Lib
- py_find_1st

View File

@@ -1,13 +1,27 @@
""" FreqTrade bot """
__version__ = '2020.01'
""" Freqtrade bot """
__version__ = '2020.3'
if __version__ == 'develop':
try:
import subprocess
__version__ = 'develop-' + subprocess.check_output(
['git', 'log', '--format="%h"', '-n 1'],
stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"')
# from datetime import datetime
# last_release = subprocess.check_output(
# ['git', 'tag']
# ).decode('utf-8').split()[-1].split(".")
# # Releases are in the format "2020.1" - we increment the latest version for dev.
# prefix = f"{last_release[0]}.{int(last_release[1]) + 1}"
# dev_version = int(datetime.now().timestamp() // 1000)
# __version__ = f"{prefix}.dev{dev_version}"
# subprocess.check_output(
# ['git', 'log', '--format="%h"', '-n 1'],
# stderr=subprocess.DEVNULL).decode("utf-8").rstrip().strip('"')
except Exception:
# git not available, ignore
pass

View File

@@ -7,13 +7,16 @@ Note: Be careful with file-scoped imports in these subfiles.
as they are parsed on startup, nothing containing optional modules should be loaded.
"""
from freqtrade.commands.arguments import Arguments
from freqtrade.commands.data_commands import start_download_data
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.deploy_commands import (start_create_userdir,
start_new_hyperopt,
start_new_strategy)
from freqtrade.commands.hyperopt_commands import (start_hyperopt_list,
start_hyperopt_show)
from freqtrade.commands.list_commands import (start_list_exchanges,
start_list_hyperopts,
start_list_markets,
start_list_strategies,
start_list_timeframes)

View File

@@ -6,8 +6,8 @@ from functools import partial
from pathlib import Path
from typing import Any, Dict, List, Optional
from freqtrade import constants
from freqtrade.commands.cli_options import AVAILABLE_CLI_OPTIONS
from freqtrade.constants import DEFAULT_CONFIG
ARGS_COMMON = ["verbosity", "logfile", "version", "config", "datadir", "user_data_dir"]
@@ -30,7 +30,9 @@ ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"]
ARGS_LIST_STRATEGIES = ["strategy_path", "print_one_column"]
ARGS_LIST_STRATEGIES = ["strategy_path", "print_one_column", "print_colorized"]
ARGS_LIST_HYPEROPTS = ["hyperopt_path", "print_one_column", "print_colorized"]
ARGS_LIST_EXCHANGES = ["print_one_column", "list_exchanges_all"]
@@ -43,29 +45,40 @@ ARGS_TEST_PAIRLIST = ["config", "quote_currencies", "print_one_column", "list_pa
ARGS_CREATE_USERDIR = ["user_data_dir", "reset"]
ARGS_BUILD_CONFIG = ["config"]
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"]
ARGS_BUILD_HYPEROPT = ["user_data_dir", "hyperopt", "template"]
ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase"]
ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes"]
ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "download_trades", "exchange",
"timeframes", "erase"]
"timeframes", "erase", "dataformat_ohlcv", "dataformat_trades"]
ARGS_PLOT_DATAFRAME = ["pairs", "indicators1", "indicators2", "plot_limit",
"db_url", "trade_source", "export", "exportfilename",
"timerange", "ticker_interval"]
"timerange", "ticker_interval", "no_trades"]
ARGS_PLOT_PROFIT = ["pairs", "timerange", "export", "exportfilename", "db_url",
"trade_source", "ticker_interval"]
ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable", "print_colorized",
"print_json", "hyperopt_list_no_details"]
ARGS_HYPEROPT_LIST = ["hyperopt_list_best", "hyperopt_list_profitable",
"hyperopt_list_min_trades", "hyperopt_list_max_trades",
"hyperopt_list_min_avg_time", "hyperopt_list_max_avg_time",
"hyperopt_list_min_avg_profit", "hyperopt_list_max_avg_profit",
"hyperopt_list_min_total_profit", "hyperopt_list_max_total_profit",
"print_colorized", "print_json", "hyperopt_list_no_details",
"export_csv"]
ARGS_HYPEROPT_SHOW = ["hyperopt_list_best", "hyperopt_list_profitable", "hyperopt_show_index",
"print_json", "hyperopt_show_no_header"]
NO_CONF_REQURIED = ["download-data", "list-timeframes", "list-markets", "list-pairs",
"list-strategies", "hyperopt-list", "hyperopt-show", "plot-dataframe",
"plot-profit"]
NO_CONF_REQURIED = ["convert-data", "convert-trade-data", "download-data", "list-timeframes",
"list-markets", "list-pairs", "list-strategies",
"list-hyperopts", "hyperopt-list", "hyperopt-show",
"plot-dataframe", "plot-profit"]
NO_CONF_ALLOWED = ["create-userdir", "list-exchanges", "new-hyperopt", "new-strategy"]
@@ -99,10 +112,23 @@ class Arguments:
# Workaround issue in argparse with action='append' and default value
# (see https://bugs.python.org/issue16399)
# Allow no-config for certain commands (like downloading / plotting)
if ('config' in parsed_arg and parsed_arg.config is None and
((Path.cwd() / constants.DEFAULT_CONFIG).is_file() or
not ('command' in parsed_arg and parsed_arg.command in NO_CONF_REQURIED))):
parsed_arg.config = [constants.DEFAULT_CONFIG]
if ('config' in parsed_arg and parsed_arg.config is None):
conf_required = ('command' in parsed_arg and parsed_arg.command in NO_CONF_REQURIED)
if 'user_data_dir' in parsed_arg and parsed_arg.user_data_dir is not None:
user_dir = parsed_arg.user_data_dir
else:
# Default case
user_dir = 'user_data'
# Try loading from "user_data/config.json"
cfgfile = Path(user_dir) / DEFAULT_CONFIG
if cfgfile.is_file():
parsed_arg.config = [str(cfgfile)]
else:
# Else use "config.json".
cfgfile = Path.cwd() / DEFAULT_CONFIG
if cfgfile.is_file() or not conf_required:
parsed_arg.config = [DEFAULT_CONFIG]
return parsed_arg
@@ -130,11 +156,13 @@ class Arguments:
self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot')
self._build_args(optionlist=['version'], parser=self.parser)
from freqtrade.commands import (start_create_userdir, start_download_data,
from freqtrade.commands import (start_create_userdir, start_convert_data,
start_download_data,
start_hyperopt_list, start_hyperopt_show,
start_list_exchanges, start_list_markets,
start_list_strategies, start_new_hyperopt,
start_new_strategy, start_list_timeframes,
start_list_exchanges, start_list_hyperopts,
start_list_markets, start_list_strategies,
start_list_timeframes, start_new_config,
start_new_hyperopt, start_new_strategy,
start_plot_dataframe, start_plot_profit,
start_backtesting, start_hyperopt, start_edge,
start_test_pairlist, start_trading)
@@ -177,6 +205,12 @@ class Arguments:
create_userdir_cmd.set_defaults(func=start_create_userdir)
self._build_args(optionlist=ARGS_CREATE_USERDIR, parser=create_userdir_cmd)
# add new-config subcommand
build_config_cmd = subparsers.add_parser('new-config',
help="Create new config")
build_config_cmd.set_defaults(func=start_new_config)
self._build_args(optionlist=ARGS_BUILD_CONFIG, parser=build_config_cmd)
# add new-strategy subcommand
build_strategy_cmd = subparsers.add_parser('new-strategy',
help="Create new strategy")
@@ -198,6 +232,15 @@ class Arguments:
list_strategies_cmd.set_defaults(func=start_list_strategies)
self._build_args(optionlist=ARGS_LIST_STRATEGIES, parser=list_strategies_cmd)
# Add list-hyperopts subcommand
list_hyperopts_cmd = subparsers.add_parser(
'list-hyperopts',
help='Print available hyperopt classes.',
parents=[_common_parser],
)
list_hyperopts_cmd.set_defaults(func=start_list_hyperopts)
self._build_args(optionlist=ARGS_LIST_HYPEROPTS, parser=list_hyperopts_cmd)
# Add list-exchanges subcommand
list_exchanges_cmd = subparsers.add_parser(
'list-exchanges',
@@ -251,6 +294,24 @@ class Arguments:
download_data_cmd.set_defaults(func=start_download_data)
self._build_args(optionlist=ARGS_DOWNLOAD_DATA, parser=download_data_cmd)
# Add convert-data subcommand
convert_data_cmd = subparsers.add_parser(
'convert-data',
help='Convert candle (OHLCV) data from one format to another.',
parents=[_common_parser],
)
convert_data_cmd.set_defaults(func=partial(start_convert_data, ohlcv=True))
self._build_args(optionlist=ARGS_CONVERT_DATA_OHLCV, parser=convert_data_cmd)
# Add convert-trade-data subcommand
convert_trade_data_cmd = subparsers.add_parser(
'convert-trade-data',
help='Convert trade data from one format to another.',
parents=[_common_parser],
)
convert_trade_data_cmd.set_defaults(func=partial(start_convert_data, ohlcv=False))
self._build_args(optionlist=ARGS_CONVERT_DATA, parser=convert_trade_data_cmd)
# Add Plotting subcommand
plot_dataframe_cmd = subparsers.add_parser(
'plot-dataframe',

View File

@@ -0,0 +1,193 @@
import logging
from pathlib import Path
from typing import Any, Dict
from questionary import Separator, prompt
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
from freqtrade.exchange import available_exchanges, MAP_EXCHANGE_CHILDCLASS
from freqtrade.misc import render_template
from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__)
def validate_is_int(val):
try:
_ = int(val)
return True
except Exception:
return False
def validate_is_float(val):
try:
_ = float(val)
return True
except Exception:
return False
def ask_user_overwrite(config_path: Path) -> bool:
questions = [
{
"type": "confirm",
"name": "overwrite",
"message": f"File {config_path} already exists. Overwrite?",
"default": False,
},
]
answers = prompt(questions)
return answers['overwrite']
def ask_user_config() -> Dict[str, Any]:
"""
Ask user a few questions to build the configuration.
Interactive questions built using https://github.com/tmbo/questionary
:returns: Dict with keys to put into template
"""
questions = [
{
"type": "confirm",
"name": "dry_run",
"message": "Do you want to enable Dry-run (simulated trades)?",
"default": True,
},
{
"type": "text",
"name": "stake_currency",
"message": "Please insert your stake currency:",
"default": 'BTC',
},
{
"type": "text",
"name": "stake_amount",
"message": "Please insert your stake amount:",
"default": "0.01",
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_float(val),
},
{
"type": "text",
"name": "max_open_trades",
"message": f"Please insert max_open_trades (Integer or '{UNLIMITED_STAKE_AMOUNT}'):",
"default": "3",
"validate": lambda val: val == UNLIMITED_STAKE_AMOUNT or validate_is_int(val)
},
{
"type": "text",
"name": "ticker_interval",
"message": "Please insert your timeframe (ticker interval):",
"default": "5m",
},
{
"type": "text",
"name": "fiat_display_currency",
"message": "Please insert your display Currency (for reporting):",
"default": 'USD',
},
{
"type": "select",
"name": "exchange_name",
"message": "Select exchange",
"choices": [
"binance",
"binanceje",
"binanceus",
"bittrex",
"kraken",
Separator(),
"other",
],
},
{
"type": "autocomplete",
"name": "exchange_name",
"message": "Type your exchange name (Must be supported by ccxt)",
"choices": available_exchanges(),
"when": lambda x: x["exchange_name"] == 'other'
},
{
"type": "password",
"name": "exchange_key",
"message": "Insert Exchange Key",
"when": lambda x: not x['dry_run']
},
{
"type": "password",
"name": "exchange_secret",
"message": "Insert Exchange Secret",
"when": lambda x: not x['dry_run']
},
{
"type": "confirm",
"name": "telegram",
"message": "Do you want to enable Telegram?",
"default": False,
},
{
"type": "password",
"name": "telegram_token",
"message": "Insert Telegram token",
"when": lambda x: x['telegram']
},
{
"type": "text",
"name": "telegram_chat_id",
"message": "Insert Telegram chat id",
"when": lambda x: x['telegram']
},
]
answers = prompt(questions)
if not answers:
# Interrupted questionary sessions return an empty dict.
raise OperationalException("User interrupted interactive questions.")
return answers
def deploy_new_config(config_path: Path, selections: Dict[str, Any]) -> None:
"""
Applies selections to the template and writes the result to config_path
:param config_path: Path object for new config file. Should not exist yet
:param selecions: Dict containing selections taken by the user.
"""
from jinja2.exceptions import TemplateNotFound
try:
exchange_template = MAP_EXCHANGE_CHILDCLASS.get(
selections['exchange_name'], selections['exchange_name'])
selections['exchange'] = render_template(
templatefile=f"subtemplates/exchange_{exchange_template}.j2",
arguments=selections
)
except TemplateNotFound:
selections['exchange'] = render_template(
templatefile=f"subtemplates/exchange_generic.j2",
arguments=selections
)
config_text = render_template(templatefile='base_config.json.j2',
arguments=selections)
logger.info(f"Writing config to `{config_path}`.")
config_path.write_text(config_text)
def start_new_config(args: Dict[str, Any]) -> None:
"""
Create a new strategy from a template
Asking the user questions to fill out the templateaccordingly.
"""
config_path = Path(args['config'][0])
if config_path.exists():
overwrite = ask_user_overwrite(config_path)
if overwrite:
config_path.unlink()
else:
raise OperationalException(
f"Configuration file `{config_path}` already exists. "
"Please delete it or use a different configuration file name.")
selections = ask_user_config()
deploy_new_config(config_path, selections)

View File

@@ -59,7 +59,8 @@ AVAILABLE_CLI_OPTIONS = {
),
"config": Arg(
'-c', '--config',
help=f'Specify configuration file (default: `{constants.DEFAULT_CONFIG}`). '
help=f'Specify configuration file (default: `userdir/{constants.DEFAULT_CONFIG}` '
f'or `config.json` whichever exists). '
f'Multiple --config options may be used. '
f'Can be set to `-` to read config from stdin.',
action='append',
@@ -220,6 +221,13 @@ AVAILABLE_CLI_OPTIONS = {
action='store_true',
default=False,
),
"export_csv": Arg(
'--export-csv',
help='Export to CSV-File.'
' This will disable table print.'
' Example: --export-csv hyperopt.csv',
metavar='FILE',
),
"hyperopt_jobs": Arg(
'-j', '--job-workers',
help='The number of concurrently running jobs for hyperoptimization '
@@ -256,7 +264,8 @@ AVAILABLE_CLI_OPTIONS = {
help='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: '
'DefaultHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss.'
'DefaultHyperOptLoss, OnlyProfitHyperOptLoss, SharpeHyperOptLoss, SharpeHyperOptLossDaily, '
'SortinoHyperOptLoss, SortinoHyperOptLossDaily.'
'(default: `%(default)s`).',
metavar='NAME',
default=constants.DEFAULT_HYPEROPT_LOSS,
@@ -332,6 +341,30 @@ AVAILABLE_CLI_OPTIONS = {
'desired timeframe as specified as --timeframes/-t.',
action='store_true',
),
"format_from": Arg(
'--format-from',
help='Source format for data conversion.',
choices=constants.AVAILABLE_DATAHANDLERS,
required=True,
),
"format_to": Arg(
'--format-to',
help='Destination format for data conversion.',
choices=constants.AVAILABLE_DATAHANDLERS,
required=True,
),
"dataformat_ohlcv": Arg(
'--data-format-ohlcv',
help='Storage format for downloaded candle (OHLCV) data. (default: `%(default)s`).',
choices=constants.AVAILABLE_DATAHANDLERS,
default='json'
),
"dataformat_trades": Arg(
'--data-format-trades',
help='Storage format for downloaded trades data. (default: `%(default)s`).',
choices=constants.AVAILABLE_DATAHANDLERS,
default='jsongz'
),
"exchange": Arg(
'--exchange',
help=f'Exchange name (default: `{constants.DEFAULT_EXCHANGE}`). '
@@ -380,6 +413,11 @@ AVAILABLE_CLI_OPTIONS = {
metavar='INT',
default=750,
),
"no_trades": Arg(
'--no-trades',
help='Skip using trades from backtesting file and DB.',
action='store_true',
),
"trade_source": Arg(
'--trade-source',
help='Specify the source for trades (Can be DB or file (backtest file)) '
@@ -398,6 +436,54 @@ AVAILABLE_CLI_OPTIONS = {
help='Select only best epochs.',
action='store_true',
),
"hyperopt_list_min_trades": Arg(
'--min-trades',
help='Select epochs with more than INT trades.',
type=check_int_positive,
metavar='INT',
),
"hyperopt_list_max_trades": Arg(
'--max-trades',
help='Select epochs with less than INT trades.',
type=check_int_positive,
metavar='INT',
),
"hyperopt_list_min_avg_time": Arg(
'--min-avg-time',
help='Select epochs on above average time.',
type=float,
metavar='FLOAT',
),
"hyperopt_list_max_avg_time": Arg(
'--max-avg-time',
help='Select epochs on under average time.',
type=float,
metavar='FLOAT',
),
"hyperopt_list_min_avg_profit": Arg(
'--min-avg-profit',
help='Select epochs on above average profit.',
type=float,
metavar='FLOAT',
),
"hyperopt_list_max_avg_profit": Arg(
'--max-avg-profit',
help='Select epochs on below average profit.',
type=float,
metavar='FLOAT',
),
"hyperopt_list_min_total_profit": Arg(
'--min-total-profit',
help='Select epochs on above total profit.',
type=float,
metavar='FLOAT',
),
"hyperopt_list_max_total_profit": Arg(
'--max-total-profit',
help='Select epochs on below total profit.',
type=float,
metavar='FLOAT',
),
"hyperopt_list_no_details": Arg(
'--no-details',
help='Do not print best epoch details.',

View File

@@ -5,6 +5,8 @@ from typing import Any, Dict, List
import arrow
from freqtrade.configuration import TimeRange, setup_utils_configuration
from freqtrade.data.converter import (convert_ohlcv_format,
convert_trades_format)
from freqtrade.data.history import (convert_trades_to_ohlcv,
refresh_backtest_ohlcv_data,
refresh_backtest_trades_data)
@@ -37,22 +39,32 @@ def start_download_data(args: Dict[str, Any]) -> None:
pairs_not_available: List[str] = []
# Init exchange
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config)
exchange = ExchangeResolver.load_exchange(config['exchange']['name'], config, validate=False)
# Manual validations of relevant settings
exchange.validate_pairs(config['pairs'])
for timeframe in config['timeframes']:
exchange.validate_timeframes(timeframe)
try:
if config.get('download_trades'):
pairs_not_available = refresh_backtest_trades_data(
exchange, pairs=config["pairs"], datadir=config['datadir'],
timerange=timerange, erase=config.get("erase"))
timerange=timerange, erase=bool(config.get("erase")),
data_format=config['dataformat_trades'])
# Convert downloaded trade data to different timeframes
convert_trades_to_ohlcv(
pairs=config["pairs"], timeframes=config["timeframes"],
datadir=config['datadir'], timerange=timerange, erase=config.get("erase"))
datadir=config['datadir'], timerange=timerange, erase=bool(config.get("erase")),
data_format_ohlcv=config['dataformat_ohlcv'],
data_format_trades=config['dataformat_trades'],
)
else:
pairs_not_available = refresh_backtest_ohlcv_data(
exchange, pairs=config["pairs"], timeframes=config["timeframes"],
datadir=config['datadir'], timerange=timerange, erase=config.get("erase"))
datadir=config['datadir'], timerange=timerange, erase=bool(config.get("erase")),
data_format=config['dataformat_ohlcv'])
except KeyboardInterrupt:
sys.exit("SIGINT received, aborting ...")
@@ -61,3 +73,18 @@ def start_download_data(args: Dict[str, Any]) -> None:
if pairs_not_available:
logger.info(f"Pairs [{','.join(pairs_not_available)}] not available "
f"on exchange {exchange.name}.")
def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None:
"""
Convert data from one format to another
"""
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
if ohlcv:
convert_ohlcv_format(config,
convert_from=args['format_from'], convert_to=args['format_to'],
erase=args['erase'])
else:
convert_trades_format(config,
convert_from=args['format_from'], convert_to=args['format_to'],
erase=args['erase'])

View File

@@ -6,7 +6,7 @@ from typing import Any, Dict
from freqtrade.configuration import setup_utils_configuration
from freqtrade.configuration.directory_operations import (copy_sample_files,
create_userdata_dir)
from freqtrade.constants import USERPATH_HYPEROPTS, USERPATH_STRATEGY
from freqtrade.constants import USERPATH_HYPEROPTS, USERPATH_STRATEGIES
from freqtrade.exceptions import OperationalException
from freqtrade.misc import render_template
from freqtrade.state import RunMode
@@ -28,7 +28,7 @@ def start_create_userdir(args: Dict[str, Any]) -> None:
sys.exit(1)
def deploy_new_strategy(strategy_name, strategy_path: Path, subtemplate: str):
def deploy_new_strategy(strategy_name: str, strategy_path: Path, subtemplate: str) -> None:
"""
Deploy new strategy from template to strategy_path
"""
@@ -57,7 +57,7 @@ def start_new_strategy(args: Dict[str, Any]) -> None:
if args["strategy"] == "DefaultStrategy":
raise OperationalException("DefaultStrategy is not allowed as name.")
new_path = config['user_data_dir'] / USERPATH_STRATEGY / (args["strategy"] + ".py")
new_path = config['user_data_dir'] / USERPATH_STRATEGIES / (args["strategy"] + ".py")
if new_path.exists():
raise OperationalException(f"`{new_path}` already exists. "
@@ -69,7 +69,7 @@ def start_new_strategy(args: Dict[str, Any]) -> None:
raise OperationalException("`new-strategy` requires --strategy to be set.")
def deploy_new_hyperopt(hyperopt_name, hyperopt_path: Path, subtemplate: str):
def deploy_new_hyperopt(hyperopt_name: str, hyperopt_path: Path, subtemplate: str) -> None:
"""
Deploys a new hyperopt template to hyperopt_path
"""

124
freqtrade/commands/hyperopt_commands.py Normal file → Executable file
View File

@@ -19,13 +19,25 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
only_best = config.get('hyperopt_list_best', False)
only_profitable = config.get('hyperopt_list_profitable', False)
print_colorized = config.get('print_colorized', False)
print_json = config.get('print_json', False)
export_csv = config.get('export_csv', None)
no_details = config.get('hyperopt_list_no_details', False)
no_header = False
filteroptions = {
'only_best': config.get('hyperopt_list_best', False),
'only_profitable': config.get('hyperopt_list_profitable', False),
'filter_min_trades': config.get('hyperopt_list_min_trades', 0),
'filter_max_trades': config.get('hyperopt_list_max_trades', 0),
'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None),
'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None),
'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None),
'filter_max_avg_profit': config.get('hyperopt_list_max_avg_profit', None),
'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None),
'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None)
}
trials_file = (config['user_data_dir'] /
'hyperopt_results' / 'hyperopt_results.pickle')
@@ -33,27 +45,28 @@ def start_hyperopt_list(args: Dict[str, Any]) -> None:
trials = Hyperopt.load_previous_results(trials_file)
total_epochs = len(trials)
trials = _hyperopt_filter_trials(trials, only_best, only_profitable)
# TODO: fetch the interval for epochs to print from the cli option
epoch_start, epoch_stop = 0, None
trials = _hyperopt_filter_trials(trials, filteroptions)
if print_colorized:
colorama_init(autoreset=True)
try:
# Human-friendly indexes used here (starting from 1)
for val in trials[epoch_start:epoch_stop]:
Hyperopt.print_results_explanation(val, total_epochs, not only_best, print_colorized)
except KeyboardInterrupt:
print('User interrupted..')
if not export_csv:
try:
Hyperopt.print_result_table(config, trials, total_epochs,
not filteroptions['only_best'], print_colorized, 0)
except KeyboardInterrupt:
print('User interrupted..')
if trials and not no_details:
sorted_trials = sorted(trials, key=itemgetter('loss'))
results = sorted_trials[0]
Hyperopt.print_epoch_details(results, total_epochs, print_json, no_header)
if trials and export_csv:
Hyperopt.export_csv_file(
config, trials, total_epochs, not filteroptions['only_best'], export_csv
)
def start_hyperopt_show(args: Dict[str, Any]) -> None:
"""
@@ -63,52 +76,109 @@ def start_hyperopt_show(args: Dict[str, Any]) -> None:
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
only_best = config.get('hyperopt_list_best', False)
only_profitable = config.get('hyperopt_list_profitable', False)
print_json = config.get('print_json', False)
no_header = config.get('hyperopt_show_no_header', False)
trials_file = (config['user_data_dir'] /
'hyperopt_results' / 'hyperopt_results.pickle')
n = config.get('hyperopt_show_index', -1)
filteroptions = {
'only_best': config.get('hyperopt_list_best', False),
'only_profitable': config.get('hyperopt_list_profitable', False),
'filter_min_trades': config.get('hyperopt_list_min_trades', 0),
'filter_max_trades': config.get('hyperopt_list_max_trades', 0),
'filter_min_avg_time': config.get('hyperopt_list_min_avg_time', None),
'filter_max_avg_time': config.get('hyperopt_list_max_avg_time', None),
'filter_min_avg_profit': config.get('hyperopt_list_min_avg_profit', None),
'filter_max_avg_profit': config.get('hyperopt_list_max_avg_profit', None),
'filter_min_total_profit': config.get('hyperopt_list_min_total_profit', None),
'filter_max_total_profit': config.get('hyperopt_list_max_total_profit', None)
}
# Previous evaluations
trials = Hyperopt.load_previous_results(trials_file)
total_epochs = len(trials)
trials = _hyperopt_filter_trials(trials, only_best, only_profitable)
trials = _hyperopt_filter_trials(trials, filteroptions)
trials_epochs = len(trials)
n = config.get('hyperopt_show_index', -1)
if n > trials_epochs:
raise OperationalException(
f"The index of the epoch to show should be less than {trials_epochs + 1}.")
f"The index of the epoch to show should be less than {trials_epochs + 1}.")
if n < -trials_epochs:
raise OperationalException(
f"The index of the epoch to show should be greater than {-trials_epochs - 1}.")
f"The index of the epoch to show should be greater than {-trials_epochs - 1}.")
# Translate epoch index from human-readable format to pythonic
if n > 0:
n -= 1
print_json = config.get('print_json', False)
if trials:
val = trials[n]
Hyperopt.print_epoch_details(val, total_epochs, print_json, no_header,
header_str="Epoch details")
def _hyperopt_filter_trials(trials: List, only_best: bool, only_profitable: bool) -> List:
def _hyperopt_filter_trials(trials: List, filteroptions: dict) -> List:
"""
Filter our items from the list of hyperopt results
"""
if only_best:
if filteroptions['only_best']:
trials = [x for x in trials if x['is_best']]
if only_profitable:
if filteroptions['only_profitable']:
trials = [x for x in trials if x['results_metrics']['profit'] > 0]
if filteroptions['filter_min_trades'] > 0:
trials = [
x for x in trials
if x['results_metrics']['trade_count'] > filteroptions['filter_min_trades']
]
if filteroptions['filter_max_trades'] > 0:
trials = [
x for x in trials
if x['results_metrics']['trade_count'] < filteroptions['filter_max_trades']
]
if filteroptions['filter_min_avg_time'] is not None:
trials = [x for x in trials if x['results_metrics']['trade_count'] > 0]
trials = [
x for x in trials
if x['results_metrics']['duration'] > filteroptions['filter_min_avg_time']
]
if filteroptions['filter_max_avg_time'] is not None:
trials = [x for x in trials if x['results_metrics']['trade_count'] > 0]
trials = [
x for x in trials
if x['results_metrics']['duration'] < filteroptions['filter_max_avg_time']
]
if filteroptions['filter_min_avg_profit'] is not None:
trials = [x for x in trials if x['results_metrics']['trade_count'] > 0]
trials = [
x for x in trials
if x['results_metrics']['avg_profit']
> filteroptions['filter_min_avg_profit']
]
if filteroptions['filter_max_avg_profit'] is not None:
trials = [x for x in trials if x['results_metrics']['trade_count'] > 0]
trials = [
x for x in trials
if x['results_metrics']['avg_profit']
< filteroptions['filter_max_avg_profit']
]
if filteroptions['filter_min_total_profit'] is not None:
trials = [x for x in trials if x['results_metrics']['trade_count'] > 0]
trials = [
x for x in trials
if x['results_metrics']['profit'] > filteroptions['filter_min_total_profit']
]
if filteroptions['filter_max_total_profit'] is not None:
trials = [x for x in trials if x['results_metrics']['trade_count'] > 0]
trials = [
x for x in trials
if x['results_metrics']['profit'] < filteroptions['filter_max_total_profit']
]
logger.info(f"{len(trials)} " +
("best " if only_best else "") +
("profitable " if only_profitable else "") +
("best " if filteroptions['only_best'] else "") +
("profitable " if filteroptions['only_profitable'] else "") +
"epochs found.")
return trials

View File

@@ -3,13 +3,15 @@ import logging
import sys
from collections import OrderedDict
from pathlib import Path
from typing import Any, Dict
from typing import Any, Dict, List
from colorama import init as colorama_init
from colorama import Fore, Style
import rapidjson
from tabulate import tabulate
from freqtrade.configuration import setup_utils_configuration
from freqtrade.constants import USERPATH_STRATEGY
from freqtrade.constants import USERPATH_HYPEROPTS, USERPATH_STRATEGIES
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import (available_exchanges, ccxt_exchanges,
market_is_active, symbol_is_pair)
@@ -36,22 +38,63 @@ def start_list_exchanges(args: Dict[str, Any]) -> None:
print(f"Exchanges available for Freqtrade: {', '.join(exchanges)}")
def _print_objs_tabular(objs: List, print_colorized: bool) -> None:
if print_colorized:
colorama_init(autoreset=True)
red = Fore.RED
yellow = Fore.YELLOW
reset = Style.RESET_ALL
else:
red = ''
yellow = ''
reset = ''
names = [s['name'] for s in objs]
objss_to_print = [{
'name': s['name'] if s['name'] else "--",
'location': s['location'].name,
'status': (red + "LOAD FAILED" + reset if s['class'] is None
else "OK" if names.count(s['name']) == 1
else yellow + "DUPLICATE NAME" + reset)
} for s in objs]
print(tabulate(objss_to_print, headers='keys', tablefmt='psql', stralign='right'))
def start_list_strategies(args: Dict[str, Any]) -> None:
"""
Print Strategies available in a directory
Print files with Strategy custom classes available in the directory
"""
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
directory = Path(config.get('strategy_path', config['user_data_dir'] / USERPATH_STRATEGY))
strategies = StrategyResolver.search_all_objects(directory)
directory = Path(config.get('strategy_path', config['user_data_dir'] / USERPATH_STRATEGIES))
strategy_objs = StrategyResolver.search_all_objects(directory, not args['print_one_column'])
# Sort alphabetically
strategies = sorted(strategies, key=lambda x: x['name'])
strats_to_print = [{'name': s['name'], 'location': s['location'].name} for s in strategies]
strategy_objs = sorted(strategy_objs, key=lambda x: x['name'])
if args['print_one_column']:
print('\n'.join([s['name'] for s in strategies]))
print('\n'.join([s['name'] for s in strategy_objs]))
else:
print(tabulate(strats_to_print, headers='keys', tablefmt='pipe'))
_print_objs_tabular(strategy_objs, config.get('print_colorized', False))
def start_list_hyperopts(args: Dict[str, Any]) -> None:
"""
Print files with HyperOpt custom classes available in the directory
"""
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
directory = Path(config.get('hyperopt_path', config['user_data_dir'] / USERPATH_HYPEROPTS))
hyperopt_objs = HyperOptResolver.search_all_objects(directory, not args['print_one_column'])
# Sort alphabetically
hyperopt_objs = sorted(hyperopt_objs, key=lambda x: x['name'])
if args['print_one_column']:
print('\n'.join([s['name'] for s in hyperopt_objs]))
else:
_print_objs_tabular(hyperopt_objs, config.get('print_colorized', False))
def start_list_timeframes(args: Dict[str, Any]) -> None:
@@ -149,7 +192,7 @@ def start_list_markets(args: Dict[str, Any], pairs_only: bool = False) -> None:
else:
# print data as a table, with the human-readable summary
print(f"{summary_str}:")
print(tabulate(tabular_data, headers='keys', tablefmt='pipe'))
print(tabulate(tabular_data, headers='keys', tablefmt='psql', stralign='right'))
elif not (args.get('print_one_column', False) or
args.get('list_pairs_print_json', False) or
args.get('print_csv', False)):

View File

@@ -17,10 +17,15 @@ def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[
"""
config = setup_utils_configuration(args, method)
if method == RunMode.BACKTEST:
if config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT:
raise DependencyException('stake amount could not be "%s" for backtesting' %
constants.UNLIMITED_STAKE_AMOUNT)
no_unlimited_runmodes = {
RunMode.BACKTEST: 'backtesting',
RunMode.HYPEROPT: 'hyperoptimization',
}
if (method in no_unlimited_runmodes.keys() and
config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT):
raise DependencyException(
f'The value of `stake_amount` cannot be set as "{constants.UNLIMITED_STAKE_AMOUNT}" '
f'for {no_unlimited_runmodes[method]}')
return config

View File

@@ -5,7 +5,7 @@ from freqtrade.exceptions import OperationalException
from freqtrade.state import RunMode
def validate_plot_args(args: Dict[str, Any]):
def validate_plot_args(args: Dict[str, Any]) -> None:
if not args.get('datadir') and not args.get('config'):
raise OperationalException(
"You need to specify either `--datadir` or `--config` "

View File

@@ -10,7 +10,7 @@ from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
def remove_credentials(config: Dict[str, Any]):
def remove_credentials(config: Dict[str, Any]) -> None:
"""
Removes exchange keys from the configuration and specifies dry-run
Used for backtesting / hyperopt / edge and utils.

View File

@@ -150,15 +150,3 @@ def _validate_whitelist(conf: Dict[str, Any]) -> None:
if (pl.get('method') == 'StaticPairList'
and not conf.get('exchange', {}).get('pair_whitelist')):
raise OperationalException("StaticPairList requires pair_whitelist to be set.")
if pl.get('method') == 'StaticPairList':
stake = conf['stake_currency']
invalid_pairs = []
for pair in conf['exchange'].get('pair_whitelist'):
if not pair.endswith(f'/{stake}'):
invalid_pairs.append(pair)
if invalid_pairs:
raise OperationalException(
f"Stake-currency '{stake}' not compatible with pair-whitelist. "
f"Please remove the following pairs: {invalid_pairs}")

View File

@@ -96,6 +96,8 @@ class Configuration:
# Keep a copy of the original configuration file
config['original_config'] = deepcopy(config)
self._process_logging_options(config)
self._process_runmode(config)
self._process_common_options(config)
@@ -146,8 +148,6 @@ class Configuration:
def _process_common_options(self, config: Dict[str, Any]) -> None:
self._process_logging_options(config)
# Set strategy if not specified in config and or if it's non default
if self.args.get("strategy") or not config.get('strategy'):
config.update({'strategy': self.args.get("strategy")})
@@ -167,10 +167,6 @@ class Configuration:
if 'sd_notify' in self.args and self.args["sd_notify"]:
config['internals'].update({'sd_notify': True})
self._args_to_config(config, argname='dry_run',
logstring='Parameter --dry-run detected, '
'overriding dry_run to: {} ...')
def _process_datadir_options(self, config: Dict[str, Any]) -> None:
"""
Extract information for sys.argv and load directory configurations
@@ -200,6 +196,7 @@ class Configuration:
if self.args.get('exportfilename'):
self._args_to_config(config, argname='exportfilename',
logstring='Storing backtest results to {} ...')
config['exportfilename'] = Path(config['exportfilename'])
else:
config['exportfilename'] = (config['user_data_dir']
/ 'backtest_results/backtest-result.json')
@@ -286,6 +283,9 @@ class Configuration:
self._args_to_config(config, argname='print_json',
logstring='Parameter --print-json detected ...')
self._args_to_config(config, argname='export_csv',
logstring='Parameter --export-csv detected: {}')
self._args_to_config(config, argname='hyperopt_jobs',
logstring='Parameter -j/--job-workers detected: {}')
@@ -310,6 +310,30 @@ class Configuration:
self._args_to_config(config, argname='hyperopt_list_profitable',
logstring='Parameter --profitable detected: {}')
self._args_to_config(config, argname='hyperopt_list_min_trades',
logstring='Parameter --min-trades detected: {}')
self._args_to_config(config, argname='hyperopt_list_max_trades',
logstring='Parameter --max-trades detected: {}')
self._args_to_config(config, argname='hyperopt_list_min_avg_time',
logstring='Parameter --min-avg-time detected: {}')
self._args_to_config(config, argname='hyperopt_list_max_avg_time',
logstring='Parameter --max-avg-time detected: {}')
self._args_to_config(config, argname='hyperopt_list_min_avg_profit',
logstring='Parameter --min-avg-profit detected: {}')
self._args_to_config(config, argname='hyperopt_list_max_avg_profit',
logstring='Parameter --max-avg-profit detected: {}')
self._args_to_config(config, argname='hyperopt_list_min_total_profit',
logstring='Parameter --min-total-profit detected: {}')
self._args_to_config(config, argname='hyperopt_list_max_total_profit',
logstring='Parameter --max-total-profit detected: {}')
self._args_to_config(config, argname='hyperopt_list_no_details',
logstring='Parameter --no-details detected: {}')
@@ -335,20 +359,34 @@ class Configuration:
self._args_to_config(config, argname='erase',
logstring='Erase detected. Deleting existing data.')
self._args_to_config(config, argname='no_trades',
logstring='Parameter --no-trades detected.')
self._args_to_config(config, argname='timeframes',
logstring='timeframes --timeframes: {}')
self._args_to_config(config, argname='days',
logstring='Detected --days: {}')
self._args_to_config(config, argname='download_trades',
logstring='Detected --dl-trades: {}')
self._args_to_config(config, argname='dataformat_ohlcv',
logstring='Using "{}" to store OHLCV data.')
self._args_to_config(config, argname='dataformat_trades',
logstring='Using "{}" to store trades data.')
def _process_runmode(self, config: Dict[str, Any]) -> None:
self._args_to_config(config, argname='dry_run',
logstring='Parameter --dry-run detected, '
'overriding dry_run to: {} ...')
if not self.runmode:
# Handle real mode, infer dry/live from config
self.runmode = RunMode.DRY_RUN if config.get('dry_run', True) else RunMode.LIVE
logger.info(f"Runmode set to {self.runmode}.")
logger.info(f"Runmode set to {self.runmode.value}.")
config.update({'runmode': self.runmode})

View File

@@ -13,7 +13,7 @@ logger = logging.getLogger(__name__)
def check_conflicting_settings(config: Dict[str, Any],
section1: str, name1: str,
section2: str, name2: str):
section2: str, name2: str) -> None:
section1_config = config.get(section1, {})
section2_config = config.get(section2, {})
if name1 in section1_config and name2 in section2_config:
@@ -28,7 +28,7 @@ def check_conflicting_settings(config: Dict[str, Any],
def process_deprecated_setting(config: Dict[str, Any],
section1: str, name1: str,
section2: str, name2: str):
section2: str, name2: str) -> None:
section2_config = config.get(section2, {})
if name2 in section2_config:

View File

@@ -23,7 +23,7 @@ def create_datadir(config: Dict[str, Any], datadir: Optional[str] = None) -> Pat
return folder
def create_userdata_dir(directory: str, create_dir=False) -> Path:
def create_userdata_dir(directory: str, create_dir: bool = False) -> Path:
"""
Create userdata directory structure.
if create_dir is True, then the parent-directory will be created if it does not exist.

View File

@@ -1,13 +1,15 @@
"""
This module contain functions to load the configuration file
"""
import rapidjson
import logging
import re
import sys
from pathlib import Path
from typing import Any, Dict
from freqtrade.exceptions import OperationalException
import rapidjson
from freqtrade.exceptions import OperationalException
logger = logging.getLogger(__name__)
@@ -15,6 +17,26 @@ logger = logging.getLogger(__name__)
CONFIG_PARSE_MODE = rapidjson.PM_COMMENTS | rapidjson.PM_TRAILING_COMMAS
def log_config_error_range(path: str, errmsg: str) -> str:
"""
Parses configuration file and prints range around error
"""
if path != '-':
offsetlist = re.findall(r'(?<=Parse\serror\sat\soffset\s)\d+', errmsg)
if offsetlist:
offset = int(offsetlist[0])
text = Path(path).read_text()
# Fetch an offset of 80 characters around the error line
subtext = text[offset-min(80, offset):offset+80]
segments = subtext.split('\n')
if len(segments) > 3:
# Remove first and last lines, to avoid odd truncations
return '\n'.join(segments[1:-1])
else:
return subtext
return ''
def load_config_file(path: str) -> Dict[str, Any]:
"""
Loads a config file from the given path
@@ -29,5 +51,12 @@ def load_config_file(path: str) -> Dict[str, Any]:
raise OperationalException(
f'Config file "{path}" not found!'
' Please create a config file or check whether it exists.')
except rapidjson.JSONDecodeError as e:
err_range = log_config_error_range(path, str(e))
raise OperationalException(
f'{e}\n'
f'Please verify the following segment of your configuration:\n{err_range}'
if err_range else 'Please verify your configuration file for syntax errors.'
)
return config

View File

@@ -7,6 +7,7 @@ from typing import Optional
import arrow
logger = logging.getLogger(__name__)
@@ -30,7 +31,7 @@ class TimeRange:
return (self.starttype == other.starttype and self.stoptype == other.stoptype
and self.startts == other.startts and self.stopts == other.stopts)
def subtract_start(self, seconds) -> None:
def subtract_start(self, seconds: int) -> None:
"""
Subtracts <seconds> from startts if startts is set.
:param seconds: Seconds to subtract from starttime
@@ -44,7 +45,7 @@ class TimeRange:
"""
Adjust startts by <startup_candles> candles.
Applies only if no startup-candles have been available.
:param timeframe_secs: Ticker timeframe in seconds e.g. `timeframe_to_seconds('5m')`
:param timeframe_secs: Timeframe in seconds e.g. `timeframe_to_seconds('5m')`
:param startup_candles: Number of candles to move start-date forward
:param min_date: Minimum data date loaded. Key kriterium to decide if start-time
has to be moved
@@ -59,7 +60,7 @@ class TimeRange:
self.starttype = 'date'
@staticmethod
def parse_timerange(text: Optional[str]):
def parse_timerange(text: Optional[str]) -> 'TimeRange':
"""
Parse the value of the argument --timerange to determine what is the range desired
:param text: value from --timerange

View File

@@ -15,22 +15,27 @@ UNLIMITED_STAKE_AMOUNT = 'unlimited'
DEFAULT_AMOUNT_RESERVE_PERCENT = 0.05
REQUIRED_ORDERTIF = ['buy', 'sell']
REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']
ORDERBOOK_SIDES = ['ask', 'bid']
ORDERTYPE_POSSIBILITIES = ['limit', 'market']
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'PrecisionFilter', 'PriceFilter']
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList',
'PrecisionFilter', 'PriceFilter', 'SpreadFilter']
AVAILABLE_DATAHANDLERS = ['json', 'jsongz']
DRY_RUN_WALLET = 1000
MATH_CLOSE_PREC = 1e-14 # Precision used for float comparisons
DEFAULT_DATAFRAME_COLUMNS = ['date', 'open', 'high', 'low', 'close', 'volume']
USERPATH_HYPEROPTS = 'hyperopts'
USERPATH_STRATEGY = 'strategies'
USERPATH_STRATEGIES = 'strategies'
USERPATH_NOTEBOOKS = 'notebooks'
# Soure files with destination directories within user-directory
USER_DATA_FILES = {
'sample_strategy.py': USERPATH_STRATEGY,
'sample_strategy.py': USERPATH_STRATEGIES,
'sample_hyperopt_advanced.py': USERPATH_HYPEROPTS,
'sample_hyperopt_loss.py': USERPATH_HYPEROPTS,
'sample_hyperopt.py': USERPATH_HYPEROPTS,
'strategy_analysis_example.ipynb': 'notebooks',
'strategy_analysis_example.ipynb': USERPATH_NOTEBOOKS,
}
SUPPORTED_FIAT = [
@@ -38,7 +43,7 @@ SUPPORTED_FIAT = [
"EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY",
"KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN",
"RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD",
"BTC", "XBT", "ETH", "XRP", "LTC", "BCH", "USDT"
"BTC", "ETH", "XRP", "LTC", "BCH"
]
MINIMAL_CONFIG = {
@@ -76,7 +81,7 @@ CONF_SCHEMA = {
'amend_last_stake_amount': {'type': 'boolean', 'default': False},
'last_stake_amount_min_ratio': {
'type': 'number', 'minimum': 0.0, 'maximum': 1.0, 'default': 0.5
},
},
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
'dry_run': {'type': 'boolean'},
'dry_run_wallet': {'type': 'number', 'default': DRY_RUN_WALLET},
@@ -109,15 +114,16 @@ CONF_SCHEMA = {
'minimum': 0,
'maximum': 1,
'exclusiveMaximum': False,
'use_order_book': {'type': 'boolean'},
'order_book_top': {'type': 'integer', 'maximum': 20, 'minimum': 1},
'check_depth_of_market': {
'type': 'object',
'properties': {
'enabled': {'type': 'boolean'},
'bids_to_ask_delta': {'type': 'number', 'minimum': 0},
}
},
},
'price_side': {'type': 'string', 'enum': ORDERBOOK_SIDES, 'default': 'bid'},
'use_order_book': {'type': 'boolean'},
'order_book_top': {'type': 'integer', 'maximum': 20, 'minimum': 1},
'check_depth_of_market': {
'type': 'object',
'properties': {
'enabled': {'type': 'boolean'},
'bids_to_ask_delta': {'type': 'number', 'minimum': 0},
}
},
},
'required': ['ask_last_balance']
@@ -125,6 +131,7 @@ CONF_SCHEMA = {
'ask_strategy': {
'type': 'object',
'properties': {
'price_side': {'type': 'string', 'enum': ORDERBOOK_SIDES, 'default': 'ask'},
'use_order_book': {'type': 'boolean'},
'order_book_min': {'type': 'integer', 'minimum': 1},
'order_book_max': {'type': 'integer', 'minimum': 1, 'maximum': 50},
@@ -189,7 +196,9 @@ CONF_SCHEMA = {
'properties': {
'enabled': {'type': 'boolean'},
'webhookbuy': {'type': 'object'},
'webhookbuycancel': {'type': 'object'},
'webhooksell': {'type': 'object'},
'webhooksellcancel': {'type': 'object'},
'webhookstatus': {'type': 'object'},
},
},
@@ -213,11 +222,22 @@ CONF_SCHEMA = {
'forcebuy_enable': {'type': 'boolean'},
'internals': {
'type': 'object',
'default': {},
'properties': {
'process_throttle_secs': {'type': 'integer'},
'interval': {'type': 'integer'},
'sd_notify': {'type': 'boolean'},
}
},
'dataformat_ohlcv': {
'type': 'string',
'enum': AVAILABLE_DATAHANDLERS,
'default': 'json'
},
'dataformat_trades': {
'type': 'string',
'enum': AVAILABLE_DATAHANDLERS,
'default': 'jsongz'
}
},
'definitions': {
@@ -234,7 +254,6 @@ CONF_SCHEMA = {
'type': 'array',
'items': {
'type': 'string',
'pattern': '^[0-9A-Z]+/[0-9A-Z]+$'
},
'uniqueItems': True
},
@@ -242,7 +261,6 @@ CONF_SCHEMA = {
'type': 'array',
'items': {
'type': 'string',
'pattern': '^[0-9A-Z]+/[0-9A-Z]+$'
},
'uniqueItems': True
},
@@ -284,13 +302,19 @@ SCHEMA_TRADE_REQUIRED = [
'last_stake_amount_min_ratio',
'dry_run',
'dry_run_wallet',
'ask_strategy',
'bid_strategy',
'unfilledtimeout',
'stoploss',
'minimal_roi',
'internals',
'dataformat_ohlcv',
'dataformat_trades',
]
SCHEMA_MINIMAL_REQUIRED = [
'exchange',
'dry_run',
'dataformat_ohlcv',
'dataformat_trades',
]

View File

@@ -3,7 +3,7 @@ Helpers when analyzing backtest data
"""
import logging
from pathlib import Path
from typing import Dict
from typing import Dict, Union, Tuple
import numpy as np
import pandas as pd
@@ -20,7 +20,7 @@ BT_DATA_COLUMNS = ["pair", "profitperc", "open_time", "close_time", "index", "du
"open_rate", "close_rate", "open_at_end", "sell_reason"]
def load_backtest_data(filename) -> pd.DataFrame:
def load_backtest_data(filename: Union[Path, str]) -> pd.DataFrame:
"""
Load backtest data file.
:param filename: pathlib.Path object, or string pointing to the file.
@@ -111,7 +111,7 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame:
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),
if t.close_date else None),
t.sell_reason,
t.fee_open, t.fee_close,
t.open_rate_requested,
@@ -129,16 +129,26 @@ def load_trades_from_db(db_url: str) -> pd.DataFrame:
return trades
def load_trades(source: str, db_url: str, exportfilename: str) -> pd.DataFrame:
def load_trades(source: str, db_url: str, exportfilename: Path,
no_trades: bool = False) -> pd.DataFrame:
"""
Based on configuration option "trade_source":
* loads data from DB (using `db_url`)
* loads data from backtestfile (using `exportfilename`)
:param source: "DB" or "file" - specify source to load from
:param db_url: sqlalchemy formatted url to a database
:param exportfilename: Json file generated by backtesting
:param no_trades: Skip using trades, only return backtesting data columns
:return: DataFrame containing trades
"""
if no_trades:
df = pd.DataFrame(columns=BT_DATA_COLUMNS)
return df
if source == "DB":
return load_trades_from_db(db_url)
elif source == "file":
return load_backtest_data(Path(exportfilename))
return load_backtest_data(exportfilename)
def extract_trades_of_period(dataframe: pd.DataFrame, trades: pd.DataFrame) -> pd.DataFrame:
@@ -151,16 +161,17 @@ def extract_trades_of_period(dataframe: pd.DataFrame, trades: pd.DataFrame) -> p
return trades
def combine_tickers_with_mean(tickers: Dict[str, pd.DataFrame], column: str = "close"):
def combine_dataframes_with_mean(data: Dict[str, pd.DataFrame],
column: str = "close") -> pd.DataFrame:
"""
Combine multiple dataframes "column"
:param tickers: Dict of Dataframes, dict key should be pair.
:param data: Dict of Dataframes, dict key should be pair.
:param column: Column in the original dataframes to use
:return: DataFrame with the column renamed to the dict key, and a column
named mean, containing the mean of all pairs.
"""
df_comb = pd.concat([tickers[pair].set_index('date').rename(
{column: pair}, axis=1)[pair] for pair in tickers], axis=1)
df_comb = pd.concat([data[pair].set_index('date').rename(
{column: pair}, axis=1)[pair] for pair in data], axis=1)
df_comb['mean'] = df_comb.mean(axis=1)
@@ -187,3 +198,28 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str,
# FFill to get continuous
df[col_name] = df[col_name].ffill()
return df
def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_time',
value_col: str = 'profitperc'
) -> Tuple[float, pd.Timestamp, pd.Timestamp]:
"""
Calculate max drawdown and the corresponding close dates
:param trades: DataFrame containing trades (requires columns close_time and profitperc)
:param date_col: Column in DataFrame to use for dates (defaults to 'close_time')
:param value_col: Column in DataFrame to use for values (defaults to 'profitperc')
:return: Tuple (float, highdate, lowdate) with absolute max drawdown, high and low time
:raise: ValueError if trade-dataframe was found empty.
"""
if len(trades) == 0:
raise ValueError("Trade dataframe empty.")
profit_results = trades.sort_values(date_col)
max_drawdown_df = pd.DataFrame()
max_drawdown_df['cumulative'] = profit_results[value_col].cumsum()
max_drawdown_df['high_value'] = max_drawdown_df['cumulative'].cummax()
max_drawdown_df['drawdown'] = max_drawdown_df['cumulative'] - max_drawdown_df['high_value']
high_date = profit_results.loc[max_drawdown_df['high_value'].idxmax(), date_col]
low_date = profit_results.loc[max_drawdown_df['drawdown'].idxmin(), date_col]
return abs(min(max_drawdown_df['drawdown'])), high_date, low_date

View File

@@ -2,20 +2,23 @@
Functions to convert data from one format to another
"""
import logging
from datetime import datetime, timezone
from typing import Any, Dict
import pandas as pd
from pandas import DataFrame, to_datetime
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS
logger = logging.getLogger(__name__)
def parse_ticker_dataframe(ticker: list, timeframe: str, pair: str, *,
fill_missing: bool = True,
drop_incomplete: bool = True) -> DataFrame:
def ohlcv_to_dataframe(ohlcv: list, timeframe: str, pair: str, *,
fill_missing: bool = True, drop_incomplete: bool = True) -> DataFrame:
"""
Converts a ticker-list (format ccxt.fetch_ohlcv) to a Dataframe
:param ticker: ticker list, as returned by exchange.async_get_candle_history
Converts a list with candle (OHLCV) data (in format returned by ccxt.fetch_ohlcv)
to a Dataframe
:param ohlcv: list with candle (OHLCV) data, as returned by exchange.async_get_candle_history
:param timeframe: timeframe (e.g. 5m). Used to fill up eventual missing data
:param pair: Pair this data is for (used to warn if fillup was necessary)
:param fill_missing: fill up missing candles with 0 candles
@@ -23,23 +26,40 @@ def parse_ticker_dataframe(ticker: list, timeframe: str, pair: str, *,
:param drop_incomplete: Drop the last candle of the dataframe, assuming it's incomplete
:return: DataFrame
"""
logger.debug("Parsing tickerlist to dataframe")
cols = ['date', 'open', 'high', 'low', 'close', 'volume']
frame = DataFrame(ticker, columns=cols)
logger.debug(f"Converting candle (OHLCV) data to dataframe for pair {pair}.")
cols = DEFAULT_DATAFRAME_COLUMNS
df = DataFrame(ohlcv, columns=cols)
frame['date'] = to_datetime(frame['date'],
unit='ms',
utc=True,
infer_datetime_format=True)
df['date'] = to_datetime(df['date'], unit='ms', utc=True, infer_datetime_format=True)
# Some exchanges return int values for volume and even for ohlc.
# Some exchanges return int values for Volume and even for OHLC.
# Convert them since TA-LIB indicators used in the strategy assume floats
# and fail with exception...
frame = frame.astype(dtype={'open': 'float', 'high': 'float', 'low': 'float', 'close': 'float',
'volume': 'float'})
df = df.astype(dtype={'open': 'float', 'high': 'float', 'low': 'float', 'close': 'float',
'volume': 'float'})
return clean_ohlcv_dataframe(df, timeframe, pair,
fill_missing=fill_missing,
drop_incomplete=drop_incomplete)
def clean_ohlcv_dataframe(data: DataFrame, timeframe: str, pair: str, *,
fill_missing: bool = True,
drop_incomplete: bool = True) -> DataFrame:
"""
Clense a OHLCV dataframe by
* Grouping it by date (removes duplicate tics)
* dropping last candles if requested
* Filling up missing data (if requested)
:param data: DataFrame containing candle (OHLCV) data.
:param timeframe: timeframe (e.g. 5m). Used to fill up eventual missing data
:param pair: Pair this data is for (used to warn if fillup was necessary)
:param fill_missing: fill up missing candles with 0 candles
(see ohlcv_fill_up_missing_data for details)
:param drop_incomplete: Drop the last candle of the dataframe, assuming it's incomplete
:return: DataFrame
"""
# group by index and aggregate results to eliminate duplicate ticks
frame = frame.groupby(by='date', as_index=False, sort=True).agg({
data = data.groupby(by='date', as_index=False, sort=True).agg({
'open': 'first',
'high': 'max',
'low': 'min',
@@ -48,13 +68,13 @@ def parse_ticker_dataframe(ticker: list, timeframe: str, pair: str, *,
})
# eliminate partial candle
if drop_incomplete:
frame.drop(frame.tail(1).index, inplace=True)
data.drop(data.tail(1).index, inplace=True)
logger.debug('Dropping last candle')
if fill_missing:
return ohlcv_fill_up_missing_data(frame, timeframe, pair)
return ohlcv_fill_up_missing_data(data, timeframe, pair)
else:
return frame
return data
def ohlcv_fill_up_missing_data(dataframe: DataFrame, timeframe: str, pair: str) -> DataFrame:
@@ -65,16 +85,16 @@ def ohlcv_fill_up_missing_data(dataframe: DataFrame, timeframe: str, pair: str)
"""
from freqtrade.exchange import timeframe_to_minutes
ohlc_dict = {
ohlcv_dict = {
'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last',
'volume': 'sum'
}
ticker_minutes = timeframe_to_minutes(timeframe)
timeframe_minutes = timeframe_to_minutes(timeframe)
# Resample to create "NAN" values
df = dataframe.resample(f'{ticker_minutes}min', on='date').agg(ohlc_dict)
df = dataframe.resample(f'{timeframe_minutes}min', on='date').agg(ohlcv_dict)
# Forwardfill close for missing columns
df['close'] = df['close'].fillna(method='ffill')
@@ -92,8 +112,26 @@ def ohlcv_fill_up_missing_data(dataframe: DataFrame, timeframe: str, pair: str)
return df
def trim_dataframe(df: DataFrame, timerange, df_date_col: str = 'date') -> DataFrame:
"""
Trim dataframe based on given timerange
:param df: Dataframe to trim
:param timerange: timerange (use start and end date if available)
:param: df_date_col: Column in the dataframe to use as Date column
:return: trimmed dataframe
"""
if timerange.starttype == 'date':
start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc)
df = df.loc[df[df_date_col] >= start, :]
if timerange.stoptype == 'date':
stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc)
df = df.loc[df[df_date_col] <= stop, :]
return df
def order_book_to_dataframe(bids: list, asks: list) -> DataFrame:
"""
TODO: This should get a dedicated test
Gets order book list, returns dataframe with below format per suggested by creslin
-------------------------------------------------------------------
b_sum b_size bids asks a_size a_sum
@@ -116,23 +154,84 @@ def order_book_to_dataframe(bids: list, asks: list) -> DataFrame:
return frame
def trades_to_ohlcv(trades: list, timeframe: str) -> list:
def trades_to_ohlcv(trades: list, timeframe: str) -> DataFrame:
"""
Converts trades list to ohlcv list
Converts trades list to OHLCV list
TODO: This should get a dedicated test
:param trades: List of trades, as returned by ccxt.fetch_trades.
:param timeframe: Ticker timeframe to resample data to
:return: ohlcv timeframe as list (as returned by ccxt.fetch_ohlcv)
:param timeframe: Timeframe to resample data to
:return: OHLCV Dataframe.
"""
from freqtrade.exchange import timeframe_to_minutes
ticker_minutes = timeframe_to_minutes(timeframe)
timeframe_minutes = timeframe_to_minutes(timeframe)
df = pd.DataFrame(trades)
df['datetime'] = pd.to_datetime(df['datetime'])
df = df.set_index('datetime')
df_new = df['price'].resample(f'{ticker_minutes}min').ohlc()
df_new['volume'] = df['amount'].resample(f'{ticker_minutes}min').sum()
df_new['date'] = df_new.index.astype("int64") // 10 ** 6
df_new = df['price'].resample(f'{timeframe_minutes}min').ohlc()
df_new['volume'] = df['amount'].resample(f'{timeframe_minutes}min').sum()
df_new['date'] = df_new.index
# Drop 0 volume rows
df_new = df_new.dropna()
columns = ["date", "open", "high", "low", "close", "volume"]
return list(zip(*[df_new[x].values.tolist() for x in columns]))
return df_new[DEFAULT_DATAFRAME_COLUMNS]
def convert_trades_format(config: Dict[str, Any], convert_from: str, convert_to: str, erase: bool):
"""
Convert trades from one format to another format.
:param config: Config dictionary
:param convert_from: Source format
:param convert_to: Target format
:param erase: Erase souce data (does not apply if source and target format are identical)
"""
from freqtrade.data.history.idatahandler import get_datahandler
src = get_datahandler(config['datadir'], convert_from)
trg = get_datahandler(config['datadir'], convert_to)
if 'pairs' not in config:
config['pairs'] = src.trades_get_pairs(config['datadir'])
logger.info(f"Converting trades for {config['pairs']}")
for pair in config['pairs']:
data = src.trades_load(pair=pair)
logger.info(f"Converting {len(data)} trades for {pair}")
trg.trades_store(pair, data)
if erase and convert_from != convert_to:
logger.info(f"Deleting source Trade data for {pair}.")
src.trades_purge(pair=pair)
def convert_ohlcv_format(config: Dict[str, Any], convert_from: str, convert_to: str, erase: bool):
"""
Convert OHLCV from one format to another
:param config: Config dictionary
:param convert_from: Source format
:param convert_to: Target format
:param erase: Erase souce data (does not apply if source and target format are identical)
"""
from freqtrade.data.history.idatahandler import get_datahandler
src = get_datahandler(config['datadir'], convert_from)
trg = get_datahandler(config['datadir'], convert_to)
timeframes = config.get('timeframes', [config.get('ticker_interval')])
logger.info(f"Converting candle (OHLCV) for timeframe {timeframes}")
if 'pairs' not in config:
config['pairs'] = []
# Check timeframes or fall back to ticker_interval.
for timeframe in timeframes:
config['pairs'].extend(src.ohlcv_get_pairs(config['datadir'],
timeframe))
logger.info(f"Converting candle (OHLCV) data for {config['pairs']}")
for timeframe in timeframes:
for pair in config['pairs']:
data = src.ohlcv_load(pair=pair, timeframe=timeframe,
timerange=None,
fill_missing=False,
drop_incomplete=False,
startup_candles=0)
logger.info(f"Converting {len(data)} candles for {pair}")
trg.ohlcv_store(pair=pair, timeframe=timeframe, data=data)
if erase and convert_from != convert_to:
logger.info(f"Deleting source data for {pair} / {timeframe}")
src.ohlcv_purge(pair=pair, timeframe=timeframe)

View File

@@ -1,7 +1,7 @@
"""
Dataprovider
Responsible to provide data to the bot
including Klines, tickers, historic data
including ticker and orderbook data, live and historical candle (OHLCV) data
Common Interface for bot and strategy to access data.
"""
import logging
@@ -43,10 +43,10 @@ class DataProvider:
def ohlcv(self, pair: str, timeframe: str = None, copy: bool = True) -> DataFrame:
"""
Get ohlcv data for the given pair as DataFrame
Get candle (OHLCV) data for the given pair as DataFrame
Please use the `available_pairs` method to verify which pairs are currently cached.
:param pair: pair to get the data for
:param timeframe: Ticker timeframe to get data for
:param timeframe: Timeframe to get data for
:param copy: copy dataframe before returning if True.
Use False only for read-only operations (where the dataframe is not modified)
"""
@@ -58,7 +58,7 @@ class DataProvider:
def historic_ohlcv(self, pair: str, timeframe: str = None) -> DataFrame:
"""
Get stored historic ohlcv data
Get stored historical candle (OHLCV) data
:param pair: pair to get the data for
:param timeframe: timeframe to get data for
"""
@@ -69,17 +69,17 @@ class DataProvider:
def get_pair_dataframe(self, pair: str, timeframe: str = None) -> DataFrame:
"""
Return pair ohlcv data, either live or cached historical -- depending
Return pair candle (OHLCV) data, either live or cached historical -- depending
on the runmode.
:param pair: pair to get the data for
:param timeframe: timeframe to get data for
:return: Dataframe for this pair
"""
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
# Get live ohlcv data.
# Get live OHLCV data.
data = self.ohlcv(pair=pair, timeframe=timeframe)
else:
# Get historic ohlcv data (cached on disk).
# Get historical OHLCV data (cached on disk).
data = self.historic_ohlcv(pair=pair, timeframe=timeframe)
if len(data) == 0:
logger.warning(f"No data found for ({pair}, {timeframe}).")

View File

@@ -0,0 +1,14 @@
"""
Handle historic data (ohlcv).
Includes:
* load data for a pair (or a list of pairs) from disk
* download data from exchange and store to disk
"""
from .history_utils import (convert_trades_to_ohlcv, # noqa: F401
get_timerange, load_data, load_pair_history,
refresh_backtest_ohlcv_data,
refresh_backtest_trades_data, refresh_data,
validate_backtest_data)
from .idatahandler import get_datahandler # noqa: F401

View File

@@ -1,200 +1,93 @@
"""
Handle historic data (ohlcv).
Includes:
* load data for a pair (or a list of pairs) from disk
* download data from exchange and store to disk
"""
import logging
import operator
from copy import deepcopy
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Dict, List, Optional, Tuple
from typing import Dict, List, Optional, Tuple
import arrow
from pandas import DataFrame
from freqtrade import misc
from freqtrade.configuration import TimeRange
from freqtrade.data.converter import parse_ticker_dataframe, trades_to_ohlcv
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS
from freqtrade.data.converter import ohlcv_to_dataframe, trades_to_ohlcv
from freqtrade.data.history.idatahandler import IDataHandler, get_datahandler
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import (Exchange, timeframe_to_minutes,
timeframe_to_seconds)
from freqtrade.exchange import Exchange
logger = logging.getLogger(__name__)
def trim_tickerlist(tickerlist: List[Dict], timerange: TimeRange) -> List[Dict]:
"""
Trim tickerlist based on given timerange
"""
if not tickerlist:
return tickerlist
start_index = 0
stop_index = len(tickerlist)
if timerange.starttype == 'date':
while (start_index < len(tickerlist) and
tickerlist[start_index][0] < timerange.startts * 1000):
start_index += 1
if timerange.stoptype == 'date':
while (stop_index > 0 and
tickerlist[stop_index-1][0] > timerange.stopts * 1000):
stop_index -= 1
if start_index > stop_index:
raise ValueError(f'The timerange [{timerange.startts},{timerange.stopts}] is incorrect')
return tickerlist[start_index:stop_index]
def trim_dataframe(df: DataFrame, timerange: TimeRange, df_date_col: str = 'date') -> DataFrame:
"""
Trim dataframe based on given timerange
:param df: Dataframe to trim
:param timerange: timerange (use start and end date if available)
:param: df_date_col: Column in the dataframe to use as Date column
:return: trimmed dataframe
"""
if timerange.starttype == 'date':
start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc)
df = df.loc[df[df_date_col] >= start, :]
if timerange.stoptype == 'date':
stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc)
df = df.loc[df[df_date_col] <= stop, :]
return df
def load_tickerdata_file(datadir: Path, pair: str, timeframe: str,
timerange: Optional[TimeRange] = None) -> List[Dict]:
"""
Load a pair from file, either .json.gz or .json
:return: tickerlist or None if unsuccessful
"""
filename = pair_data_filename(datadir, pair, timeframe)
pairdata = misc.file_load_json(filename)
if not pairdata:
return []
if timerange:
pairdata = trim_tickerlist(pairdata, timerange)
return pairdata
def store_tickerdata_file(datadir: Path, pair: str,
timeframe: str, data: list, is_zip: bool = False):
"""
Stores tickerdata to file
"""
filename = pair_data_filename(datadir, pair, timeframe)
misc.file_dump_json(filename, data, is_zip=is_zip)
def load_trades_file(datadir: Path, pair: str,
timerange: Optional[TimeRange] = None) -> List[Dict]:
"""
Load a pair from file, either .json.gz or .json
:return: tradelist or empty list if unsuccesful
"""
filename = pair_trades_filename(datadir, pair)
tradesdata = misc.file_load_json(filename)
if not tradesdata:
return []
return tradesdata
def store_trades_file(datadir: Path, pair: str,
data: list, is_zip: bool = True):
"""
Stores tickerdata to file
"""
filename = pair_trades_filename(datadir, pair)
misc.file_dump_json(filename, data, is_zip=is_zip)
def _validate_pairdata(pair, pairdata, timerange: TimeRange):
if timerange.starttype == 'date' and pairdata[0][0] > timerange.startts * 1000:
logger.warning('Missing data at start for pair %s, data starts at %s',
pair, arrow.get(pairdata[0][0] // 1000).strftime('%Y-%m-%d %H:%M:%S'))
if timerange.stoptype == 'date' and pairdata[-1][0] < timerange.stopts * 1000:
logger.warning('Missing data at end for pair %s, data ends at %s',
pair, arrow.get(pairdata[-1][0] // 1000).strftime('%Y-%m-%d %H:%M:%S'))
def load_pair_history(pair: str,
timeframe: str,
datadir: Path,
datadir: Path, *,
timerange: Optional[TimeRange] = None,
fill_up_missing: bool = True,
drop_incomplete: bool = True,
startup_candles: int = 0,
data_format: str = None,
data_handler: IDataHandler = None,
) -> DataFrame:
"""
Load cached ticker history for the given pair.
Load cached ohlcv history for the given pair.
:param pair: Pair to load data for
:param timeframe: Ticker timeframe (e.g. "5m")
:param timeframe: Timeframe (e.g. "5m")
:param datadir: Path to the data storage location.
:param data_format: Format of the data. Ignored if data_handler is set.
:param timerange: Limit data to be loaded to this timerange
:param fill_up_missing: Fill missing values with "No action"-candles
:param drop_incomplete: Drop last candle assuming it may be incomplete.
:param startup_candles: Additional candles to load at the start of the period
:param data_handler: Initialized data-handler to use.
Will be initialized from data_format if not set
:return: DataFrame with ohlcv data, or empty DataFrame
"""
timerange_startup = deepcopy(timerange)
if startup_candles > 0 and timerange_startup:
timerange_startup.subtract_start(timeframe_to_seconds(timeframe) * startup_candles)
data_handler = get_datahandler(datadir, data_format, data_handler)
pairdata = load_tickerdata_file(datadir, pair, timeframe, timerange=timerange_startup)
if pairdata:
if timerange_startup:
_validate_pairdata(pair, pairdata, timerange_startup)
return parse_ticker_dataframe(pairdata, timeframe, pair=pair,
fill_missing=fill_up_missing,
drop_incomplete=drop_incomplete)
else:
logger.warning(
f'No history data for pair: "{pair}", timeframe: {timeframe}. '
'Use `freqtrade download-data` to download the data'
)
return DataFrame()
return data_handler.ohlcv_load(pair=pair,
timeframe=timeframe,
timerange=timerange,
fill_missing=fill_up_missing,
drop_incomplete=drop_incomplete,
startup_candles=startup_candles,
)
def load_data(datadir: Path,
timeframe: str,
pairs: List[str],
pairs: List[str], *,
timerange: Optional[TimeRange] = None,
fill_up_missing: bool = True,
startup_candles: int = 0,
fail_without_data: bool = False
fail_without_data: bool = False,
data_format: str = 'json',
) -> Dict[str, DataFrame]:
"""
Load ticker history data for a list of pairs.
Load ohlcv history data for a list of pairs.
:param datadir: Path to the data storage location.
:param timeframe: Ticker Timeframe (e.g. "5m")
:param timeframe: Timeframe (e.g. "5m")
:param pairs: List of pairs to load
:param timerange: Limit data to be loaded to this timerange
:param fill_up_missing: Fill missing values with "No action"-candles
:param startup_candles: Additional candles to load at the start of the period
:param fail_without_data: Raise OperationalException if no data is found.
:param data_format: Data format which should be used. Defaults to json
:return: dict(<pair>:<Dataframe>)
"""
result: Dict[str, DataFrame] = {}
if startup_candles > 0 and timerange:
logger.info(f'Using indicator startup period: {startup_candles} ...')
data_handler = get_datahandler(datadir, data_format)
for pair in pairs:
hist = load_pair_history(pair=pair, timeframe=timeframe,
datadir=datadir, timerange=timerange,
fill_up_missing=fill_up_missing,
startup_candles=startup_candles)
startup_candles=startup_candles,
data_handler=data_handler
)
if not hist.empty:
result[pair] = hist
@@ -207,81 +100,62 @@ def refresh_data(datadir: Path,
timeframe: str,
pairs: List[str],
exchange: Exchange,
data_format: str = None,
timerange: Optional[TimeRange] = None,
) -> None:
"""
Refresh ticker history data for a list of pairs.
Refresh ohlcv history data for a list of pairs.
:param datadir: Path to the data storage location.
:param timeframe: Ticker Timeframe (e.g. "5m")
:param timeframe: Timeframe (e.g. "5m")
:param pairs: List of pairs to load
:param exchange: Exchange object
:param timerange: Limit data to be loaded to this timerange
"""
data_handler = get_datahandler(datadir, data_format)
for pair in pairs:
_download_pair_history(pair=pair, timeframe=timeframe,
datadir=datadir, timerange=timerange,
exchange=exchange)
exchange=exchange, data_handler=data_handler)
def pair_data_filename(datadir: Path, pair: str, timeframe: str) -> Path:
pair_s = pair.replace("/", "_")
filename = datadir.joinpath(f'{pair_s}-{timeframe}.json')
return filename
def pair_trades_filename(datadir: Path, pair: str) -> Path:
pair_s = pair.replace("/", "_")
filename = datadir.joinpath(f'{pair_s}-trades.json.gz')
return filename
def _load_cached_data_for_updating(datadir: Path, pair: str, timeframe: str,
timerange: Optional[TimeRange]) -> Tuple[List[Any],
Optional[int]]:
def _load_cached_data_for_updating(pair: str, timeframe: str, timerange: Optional[TimeRange],
data_handler: IDataHandler) -> Tuple[DataFrame, Optional[int]]:
"""
Load cached data to download more data.
If timerange is passed in, checks whether data from an before the stored data will be
downloaded.
If that's the case then what's available should be completely overwritten.
Only used by download_pair_history().
Otherwise downloads always start at the end of the available data to avoid data gaps.
Note: Only used by download_pair_history().
"""
since_ms = None
# user sets timerange, so find the start time
start = None
if timerange:
if timerange.starttype == 'date':
since_ms = timerange.startts * 1000
elif timerange.stoptype == 'line':
num_minutes = timerange.stopts * timeframe_to_minutes(timeframe)
since_ms = arrow.utcnow().shift(minutes=num_minutes).timestamp * 1000
# TODO: convert to date for conversion
start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc)
# read the cached file
# Intentionally don't pass timerange in - since we need to load the full dataset.
data = load_tickerdata_file(datadir, pair, timeframe)
# remove the last item, could be incomplete candle
if data:
data.pop()
else:
data = []
if data:
if since_ms and since_ms < data[0][0]:
data = data_handler.ohlcv_load(pair, timeframe=timeframe,
timerange=None, fill_missing=False,
drop_incomplete=True, warn_no_data=False)
if not data.empty:
if start and start < data.iloc[0]['date']:
# Earlier data than existing data requested, redownload all
data = []
data = DataFrame(columns=DEFAULT_DATAFRAME_COLUMNS)
else:
# a part of the data was already downloaded, so download unexist data only
since_ms = data[-1][0] + 1
start = data.iloc[-1]['date']
return (data, since_ms)
start_ms = int(start.timestamp() * 1000) if start else None
return data, start_ms
def _download_pair_history(datadir: Path,
exchange: Exchange,
pair: str,
pair: str, *,
timeframe: str = '5m',
timerange: Optional[TimeRange] = None) -> bool:
timerange: Optional[TimeRange] = None,
data_handler: IDataHandler = None) -> bool:
"""
Download latest candles from the exchange for the pair and timeframe passed in parameters
The data is downloaded starting from the last correct data that
@@ -291,20 +165,26 @@ def _download_pair_history(datadir: Path,
Based on @Rybolov work: https://github.com/rybolov/freqtrade-data
:param pair: pair to download
:param timeframe: Ticker Timeframe (e.g 5m)
:param timeframe: Timeframe (e.g "5m")
:param timerange: range of time to download
:return: bool with success state
"""
data_handler = get_datahandler(datadir, data_handler=data_handler)
try:
logger.info(
f'Download history data for pair: "{pair}", timeframe: {timeframe} '
f'and store in {datadir}.'
)
data, since_ms = _load_cached_data_for_updating(datadir, pair, timeframe, timerange)
# data, since_ms = _load_cached_data_for_updating_old(datadir, pair, timeframe, timerange)
data, since_ms = _load_cached_data_for_updating(pair, timeframe, timerange,
data_handler=data_handler)
logger.debug("Current Start: %s", misc.format_ms_time(data[1][0]) if data else 'None')
logger.debug("Current End: %s", misc.format_ms_time(data[-1][0]) if data else 'None')
logger.debug("Current Start: %s",
f"{data.iloc[0]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None')
logger.debug("Current End: %s",
f"{data.iloc[-1]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None')
# Default since_ms to 30 days if nothing is given
new_data = exchange.get_historic_ohlcv(pair=pair,
@@ -313,12 +193,20 @@ def _download_pair_history(datadir: Path,
int(arrow.utcnow().shift(
days=-30).float_timestamp) * 1000
)
data.extend(new_data)
# TODO: Maybe move parsing to exchange class (?)
new_dataframe = ohlcv_to_dataframe(new_data, timeframe, pair,
fill_missing=False, drop_incomplete=True)
if data.empty:
data = new_dataframe
else:
data = data.append(new_dataframe)
logger.debug("New Start: %s", misc.format_ms_time(data[0][0]))
logger.debug("New End: %s", misc.format_ms_time(data[-1][0]))
logger.debug("New Start: %s",
f"{data.iloc[0]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None')
logger.debug("New End: %s",
f"{data.iloc[-1]['date']:%Y-%m-%d %H:%M:%S}" if not data.empty else 'None')
store_tickerdata_file(datadir, pair, timeframe, data=data)
data_handler.ohlcv_store(pair, timeframe, data=data)
return True
except Exception as e:
@@ -331,13 +219,14 @@ def _download_pair_history(datadir: Path,
def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes: List[str],
datadir: Path, timerange: Optional[TimeRange] = None,
erase=False) -> List[str]:
erase: bool = False, data_format: str = None) -> List[str]:
"""
Refresh stored ohlcv data for backtesting and hyperopt operations.
Used by freqtrade download-data subcommand.
:return: List of pairs that are not available.
"""
pairs_not_available = []
data_handler = get_datahandler(datadir, data_format)
for pair in pairs:
if pair not in exchange.markets:
pairs_not_available.append(pair)
@@ -345,23 +234,23 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes
continue
for timeframe in timeframes:
dl_file = pair_data_filename(datadir, pair, timeframe)
if erase and dl_file.exists():
logger.info(
f'Deleting existing data for pair {pair}, interval {timeframe}.')
dl_file.unlink()
if erase:
if data_handler.ohlcv_purge(pair, timeframe):
logger.info(
f'Deleting existing data for pair {pair}, interval {timeframe}.')
logger.info(f'Downloading pair {pair}, interval {timeframe}.')
_download_pair_history(datadir=datadir, exchange=exchange,
pair=pair, timeframe=str(timeframe),
timerange=timerange)
timerange=timerange, data_handler=data_handler)
return pairs_not_available
def _download_trades_history(datadir: Path,
exchange: Exchange,
pair: str,
timerange: Optional[TimeRange] = None) -> bool:
def _download_trades_history(exchange: Exchange,
pair: str, *,
timerange: Optional[TimeRange] = None,
data_handler: IDataHandler
) -> bool:
"""
Download trade history from the exchange.
Appends to previously downloaded trades data.
@@ -370,7 +259,7 @@ def _download_trades_history(datadir: Path,
since = timerange.startts * 1000 if timerange and timerange.starttype == 'date' else None
trades = load_trades_file(datadir, pair)
trades = data_handler.trades_load(pair)
from_id = trades[-1]['id'] if trades else None
@@ -385,7 +274,7 @@ def _download_trades_history(datadir: Path,
from_id=from_id,
)
trades.extend(new_trades[1])
store_trades_file(datadir, pair, trades)
data_handler.trades_store(pair, data=trades)
logger.debug("New Start: %s", trades[0]['datetime'])
logger.debug("New End: %s", trades[-1]['datetime'])
@@ -401,47 +290,52 @@ def _download_trades_history(datadir: Path,
def refresh_backtest_trades_data(exchange: Exchange, pairs: List[str], datadir: Path,
timerange: TimeRange, erase=False) -> List[str]:
timerange: TimeRange, erase: bool = False,
data_format: str = 'jsongz') -> List[str]:
"""
Refresh stored trades data for backtesting and hyperopt operations.
Used by freqtrade download-data subcommand.
:return: List of pairs that are not available.
"""
pairs_not_available = []
data_handler = get_datahandler(datadir, data_format=data_format)
for pair in pairs:
if pair not in exchange.markets:
pairs_not_available.append(pair)
logger.info(f"Skipping pair {pair}...")
continue
dl_file = pair_trades_filename(datadir, pair)
if erase and dl_file.exists():
logger.info(
f'Deleting existing data for pair {pair}.')
dl_file.unlink()
if erase:
if data_handler.trades_purge(pair):
logger.info(f'Deleting existing data for pair {pair}.')
logger.info(f'Downloading trades for pair {pair}.')
_download_trades_history(datadir=datadir, exchange=exchange,
_download_trades_history(exchange=exchange,
pair=pair,
timerange=timerange)
timerange=timerange,
data_handler=data_handler)
return pairs_not_available
def convert_trades_to_ohlcv(pairs: List[str], timeframes: List[str],
datadir: Path, timerange: TimeRange, erase=False) -> None:
datadir: Path, timerange: TimeRange, erase: bool = False,
data_format_ohlcv: str = 'json',
data_format_trades: str = 'jsongz') -> None:
"""
Convert stored trades data to ohlcv data
"""
data_handler_trades = get_datahandler(datadir, data_format=data_format_trades)
data_handler_ohlcv = get_datahandler(datadir, data_format=data_format_ohlcv)
for pair in pairs:
trades = load_trades_file(datadir, pair)
trades = data_handler_trades.trades_load(pair)
for timeframe in timeframes:
ohlcv_file = pair_data_filename(datadir, pair, timeframe)
if erase and ohlcv_file.exists():
logger.info(f'Deleting existing data for pair {pair}, interval {timeframe}.')
ohlcv_file.unlink()
if erase:
if data_handler_ohlcv.ohlcv_purge(pair, timeframe):
logger.info(f'Deleting existing data for pair {pair}, interval {timeframe}.')
ohlcv = trades_to_ohlcv(trades, timeframe)
# Store ohlcv
store_tickerdata_file(datadir, pair, timeframe, data=ohlcv)
data_handler_ohlcv.ohlcv_store(pair, timeframe, data=ohlcv)
def get_timerange(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]:
@@ -468,7 +362,7 @@ def validate_backtest_data(data: DataFrame, pair: str, min_date: datetime,
:param pair: pair used for log output.
:param min_date: start-date of the data
:param max_date: end-date of the data
:param timeframe_min: ticker Timeframe in minutes
:param timeframe_min: Timeframe in minutes
"""
# total difference in minutes / timeframe-minutes
expected_frames = int((max_date - min_date).total_seconds() // 60 // timeframe_min)

View File

@@ -0,0 +1,232 @@
"""
Abstract datahandler interface.
It's subclasses handle and storing data from disk.
"""
import logging
from abc import ABC, abstractclassmethod, abstractmethod
from copy import deepcopy
from datetime import datetime, timezone
from pathlib import Path
from typing import Dict, List, Optional, Type
from pandas import DataFrame
from freqtrade.configuration import TimeRange
from freqtrade.data.converter import clean_ohlcv_dataframe, trim_dataframe
from freqtrade.exchange import timeframe_to_seconds
logger = logging.getLogger(__name__)
class IDataHandler(ABC):
def __init__(self, datadir: Path) -> None:
self._datadir = datadir
@abstractclassmethod
def ohlcv_get_pairs(cls, datadir: Path, timeframe: str) -> List[str]:
"""
Returns a list of all pairs with ohlcv data available in this datadir
for the specified timeframe
:param datadir: Directory to search for ohlcv files
:param timeframe: Timeframe to search pairs for
:return: List of Pairs
"""
@abstractmethod
def ohlcv_store(self, pair: str, timeframe: str, data: DataFrame) -> None:
"""
Store data in json format "values".
format looks as follows:
[[<date>,<open>,<high>,<low>,<close>]]
:param pair: Pair - used to generate filename
:timeframe: Timeframe - used to generate filename
:data: Dataframe containing OHLCV data
:return: None
"""
@abstractmethod
def _ohlcv_load(self, pair: str, timeframe: str,
timerange: Optional[TimeRange] = None,
) -> DataFrame:
"""
Internal method used to load data for one pair from disk.
Implements the loading and conversion to a Pandas dataframe.
Timerange trimming and dataframe validation happens outside of this method.
:param pair: Pair to load data
:param timeframe: Timeframe (e.g. "5m")
:param timerange: Limit data to be loaded to this timerange.
Optionally implemented by subclasses to avoid loading
all data where possible.
:return: DataFrame with ohlcv data, or empty DataFrame
"""
@abstractmethod
def ohlcv_purge(self, pair: str, timeframe: str) -> bool:
"""
Remove data for this pair
:param pair: Delete data for this pair.
:param timeframe: Timeframe (e.g. "5m")
:return: True when deleted, false if file did not exist.
"""
@abstractmethod
def ohlcv_append(self, pair: str, timeframe: str, data: DataFrame) -> None:
"""
Append data to existing data structures
:param pair: Pair
:param timeframe: Timeframe this ohlcv data is for
:param data: Data to append.
"""
@abstractclassmethod
def trades_get_pairs(cls, datadir: Path) -> List[str]:
"""
Returns a list of all pairs for which trade data is available in this
:param datadir: Directory to search for ohlcv files
:return: List of Pairs
"""
@abstractmethod
def trades_store(self, pair: str, data: List[Dict]) -> None:
"""
Store trades data (list of Dicts) to file
:param pair: Pair - used for filename
:param data: List of Dicts containing trade data
"""
@abstractmethod
def trades_append(self, pair: str, data: List[Dict]):
"""
Append data to existing files
:param pair: Pair - used for filename
:param data: List of Dicts containing trade data
"""
@abstractmethod
def trades_load(self, pair: str, timerange: Optional[TimeRange] = None) -> List[Dict]:
"""
Load a pair from file, either .json.gz or .json
:param pair: Load trades for this pair
:param timerange: Timerange to load trades for - currently not implemented
:return: List of trades
"""
@abstractmethod
def trades_purge(self, pair: str) -> bool:
"""
Remove data for this pair
:param pair: Delete data for this pair.
:return: True when deleted, false if file did not exist.
"""
def ohlcv_load(self, pair, timeframe: str,
timerange: Optional[TimeRange] = None,
fill_missing: bool = True,
drop_incomplete: bool = True,
startup_candles: int = 0,
warn_no_data: bool = True
) -> DataFrame:
"""
Load cached candle (OHLCV) data for the given pair.
:param pair: Pair to load data for
:param timeframe: Timeframe (e.g. "5m")
:param timerange: Limit data to be loaded to this timerange
:param fill_missing: Fill missing values with "No action"-candles
:param drop_incomplete: Drop last candle assuming it may be incomplete.
:param startup_candles: Additional candles to load at the start of the period
:param warn_no_data: Log a warning message when no data is found
:return: DataFrame with ohlcv data, or empty DataFrame
"""
# Fix startup period
timerange_startup = deepcopy(timerange)
if startup_candles > 0 and timerange_startup:
timerange_startup.subtract_start(timeframe_to_seconds(timeframe) * startup_candles)
pairdf = self._ohlcv_load(pair, timeframe,
timerange=timerange_startup)
if self._check_empty_df(pairdf, pair, timeframe, warn_no_data):
return pairdf
else:
enddate = pairdf.iloc[-1]['date']
if timerange_startup:
self._validate_pairdata(pair, pairdf, timerange_startup)
pairdf = trim_dataframe(pairdf, timerange_startup)
if self._check_empty_df(pairdf, pair, timeframe, warn_no_data):
return pairdf
# incomplete candles should only be dropped if we didn't trim the end beforehand.
pairdf = clean_ohlcv_dataframe(pairdf, timeframe,
pair=pair,
fill_missing=fill_missing,
drop_incomplete=(drop_incomplete and
enddate == pairdf.iloc[-1]['date']))
self._check_empty_df(pairdf, pair, timeframe, warn_no_data)
return pairdf
def _check_empty_df(self, pairdf: DataFrame, pair: str, timeframe: str, warn_no_data: bool):
"""
Warn on empty dataframe
"""
if pairdf.empty:
if warn_no_data:
logger.warning(
f'No history data for pair: "{pair}", timeframe: {timeframe}. '
'Use `freqtrade download-data` to download the data'
)
return True
return False
def _validate_pairdata(self, pair, pairdata: DataFrame, timerange: TimeRange):
"""
Validates pairdata for missing data at start end end and logs warnings.
:param pairdata: Dataframe to validate
:param timerange: Timerange specified for start and end dates
"""
if timerange.starttype == 'date':
start = datetime.fromtimestamp(timerange.startts, tz=timezone.utc)
if pairdata.iloc[0]['date'] > start:
logger.warning(f"Missing data at start for pair {pair}, "
f"data starts at {pairdata.iloc[0]['date']:%Y-%m-%d %H:%M:%S}")
if timerange.stoptype == 'date':
stop = datetime.fromtimestamp(timerange.stopts, tz=timezone.utc)
if pairdata.iloc[-1]['date'] < stop:
logger.warning(f"Missing data at end for pair {pair}, "
f"data ends at {pairdata.iloc[-1]['date']:%Y-%m-%d %H:%M:%S}")
def get_datahandlerclass(datatype: str) -> Type[IDataHandler]:
"""
Get datahandler class.
Could be done using Resolvers, but since this may be called often and resolvers
are rather expensive, doing this directly should improve performance.
:param datatype: datatype to use.
:return: Datahandler class
"""
if datatype == 'json':
from .jsondatahandler import JsonDataHandler
return JsonDataHandler
elif datatype == 'jsongz':
from .jsondatahandler import JsonGzDataHandler
return JsonGzDataHandler
else:
raise ValueError(f"No datahandler for datatype {datatype} available.")
def get_datahandler(datadir: Path, data_format: str = None,
data_handler: IDataHandler = None) -> IDataHandler:
"""
:param datadir: Folder to save data
:data_format: dataformat to use
:data_handler: returns this datahandler if it exists or initializes a new one
"""
if not data_handler:
HandlerClass = get_datahandlerclass(data_format or 'json')
data_handler = HandlerClass(datadir)
return data_handler

View File

@@ -0,0 +1,179 @@
import re
from pathlib import Path
from typing import Dict, List, Optional
import numpy as np
from pandas import DataFrame, read_json, to_datetime
from freqtrade import misc
from freqtrade.configuration import TimeRange
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS
from .idatahandler import IDataHandler
class JsonDataHandler(IDataHandler):
_use_zip = False
_columns = DEFAULT_DATAFRAME_COLUMNS
@classmethod
def ohlcv_get_pairs(cls, datadir: Path, timeframe: str) -> List[str]:
"""
Returns a list of all pairs with ohlcv data available in this datadir
for the specified timeframe
:param datadir: Directory to search for ohlcv files
:param timeframe: Timeframe to search pairs for
:return: List of Pairs
"""
_tmp = [re.search(r'^(\S+)(?=\-' + timeframe + '.json)', p.name)
for p in datadir.glob(f"*{timeframe}.{cls._get_file_extension()}")]
# Check if regex found something and only return these results
return [match[0].replace('_', '/') for match in _tmp if match]
def ohlcv_store(self, pair: str, timeframe: str, data: DataFrame) -> None:
"""
Store data in json format "values".
format looks as follows:
[[<date>,<open>,<high>,<low>,<close>]]
:param pair: Pair - used to generate filename
:timeframe: Timeframe - used to generate filename
:data: Dataframe containing OHLCV data
:return: None
"""
filename = self._pair_data_filename(self._datadir, pair, timeframe)
_data = data.copy()
# Convert date to int
_data['date'] = _data['date'].astype(np.int64) // 1000 // 1000
# Reset index, select only appropriate columns and save as json
_data.reset_index(drop=True).loc[:, self._columns].to_json(
filename, orient="values",
compression='gzip' if self._use_zip else None)
def _ohlcv_load(self, pair: str, timeframe: str,
timerange: Optional[TimeRange] = None,
) -> DataFrame:
"""
Internal method used to load data for one pair from disk.
Implements the loading and conversion to a Pandas dataframe.
Timerange trimming and dataframe validation happens outside of this method.
:param pair: Pair to load data
:param timeframe: Timeframe (e.g. "5m")
:param timerange: Limit data to be loaded to this timerange.
Optionally implemented by subclasses to avoid loading
all data where possible.
:return: DataFrame with ohlcv data, or empty DataFrame
"""
filename = self._pair_data_filename(self._datadir, pair, timeframe)
if not filename.exists():
return DataFrame(columns=self._columns)
pairdata = read_json(filename, orient='values')
pairdata.columns = self._columns
pairdata = pairdata.astype(dtype={'open': 'float', 'high': 'float',
'low': 'float', 'close': 'float', 'volume': 'float'})
pairdata['date'] = to_datetime(pairdata['date'],
unit='ms',
utc=True,
infer_datetime_format=True)
return pairdata
def ohlcv_purge(self, pair: str, timeframe: str) -> bool:
"""
Remove data for this pair
:param pair: Delete data for this pair.
:param timeframe: Timeframe (e.g. "5m")
:return: True when deleted, false if file did not exist.
"""
filename = self._pair_data_filename(self._datadir, pair, timeframe)
if filename.exists():
filename.unlink()
return True
return False
def ohlcv_append(self, pair: str, timeframe: str, data: DataFrame) -> None:
"""
Append data to existing data structures
:param pair: Pair
:param timeframe: Timeframe this ohlcv data is for
:param data: Data to append.
"""
raise NotImplementedError()
@classmethod
def trades_get_pairs(cls, datadir: Path) -> List[str]:
"""
Returns a list of all pairs for which trade data is available in this
:param datadir: Directory to search for ohlcv files
:return: List of Pairs
"""
_tmp = [re.search(r'^(\S+)(?=\-trades.json)', p.name)
for p in datadir.glob(f"*trades.{cls._get_file_extension()}")]
# Check if regex found something and only return these results to avoid exceptions.
return [match[0].replace('_', '/') for match in _tmp if match]
def trades_store(self, pair: str, data: List[Dict]) -> None:
"""
Store trades data (list of Dicts) to file
:param pair: Pair - used for filename
:param data: List of Dicts containing trade data
"""
filename = self._pair_trades_filename(self._datadir, pair)
misc.file_dump_json(filename, data, is_zip=self._use_zip)
def trades_append(self, pair: str, data: List[Dict]):
"""
Append data to existing files
:param pair: Pair - used for filename
:param data: List of Dicts containing trade data
"""
raise NotImplementedError()
def trades_load(self, pair: str, timerange: Optional[TimeRange] = None) -> List[Dict]:
"""
Load a pair from file, either .json.gz or .json
# TODO: respect timerange ...
:param pair: Load trades for this pair
:param timerange: Timerange to load trades for - currently not implemented
:return: List of trades
"""
filename = self._pair_trades_filename(self._datadir, pair)
tradesdata = misc.file_load_json(filename)
if not tradesdata:
return []
return tradesdata
def trades_purge(self, pair: str) -> bool:
"""
Remove data for this pair
:param pair: Delete data for this pair.
:return: True when deleted, false if file did not exist.
"""
filename = self._pair_trades_filename(self._datadir, pair)
if filename.exists():
filename.unlink()
return True
return False
@classmethod
def _pair_data_filename(cls, datadir: Path, pair: str, timeframe: str) -> Path:
pair_s = misc.pair_to_filename(pair)
filename = datadir.joinpath(f'{pair_s}-{timeframe}.{cls._get_file_extension()}')
return filename
@classmethod
def _get_file_extension(cls):
return "json.gz" if cls._use_zip else "json"
@classmethod
def _pair_trades_filename(cls, datadir: Path, pair: str) -> Path:
pair_s = misc.pair_to_filename(pair)
filename = datadir.joinpath(f'{pair_s}-trades.{cls._get_file_extension()}')
return filename
class JsonGzDataHandler(JsonDataHandler):
_use_zip = True

View File

@@ -1,17 +1,17 @@
# pragma pylint: disable=W0603
""" Edge positioning package """
import logging
from typing import Any, Dict, NamedTuple
from typing import Any, Dict, List, NamedTuple
import arrow
import numpy as np
import utils_find_1st as utf1st
from pandas import DataFrame
from freqtrade import constants
from freqtrade.configuration import TimeRange
from freqtrade.data import history
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
from freqtrade.exceptions import OperationalException
from freqtrade.data.history import get_timerange, load_data, refresh_data
from freqtrade.strategy.interface import SellType
logger = logging.getLogger(__name__)
@@ -54,7 +54,7 @@ class Edge:
if self.config['max_open_trades'] != float('inf'):
logger.critical('max_open_trades should be -1 in config !')
if self.config['stake_amount'] != constants.UNLIMITED_STAKE_AMOUNT:
if self.config['stake_amount'] != UNLIMITED_STAKE_AMOUNT:
raise OperationalException('Edge works only with unlimited stake amount')
# Deprecated capital_available_percentage. Will use tradable_balance_ratio in the future.
@@ -96,7 +96,7 @@ class Edge:
logger.info('Using local backtesting data (using whitelist in given config) ...')
if self._refresh_pairs:
history.refresh_data(
refresh_data(
datadir=self.config['datadir'],
pairs=pairs,
exchange=self.exchange,
@@ -104,12 +104,13 @@ class Edge:
timerange=self._timerange,
)
data = history.load_data(
data = load_data(
datadir=self.config['datadir'],
pairs=pairs,
timeframe=self.strategy.ticker_interval,
timerange=self._timerange,
startup_candles=self.strategy.startup_candle_count,
data_format=self.config.get('dataformat_ohlcv', 'json'),
)
if not data:
@@ -118,10 +119,10 @@ class Edge:
logger.critical("No data found. Edge is stopped ...")
return False
preprocessed = self.strategy.tickerdata_to_dataframe(data)
preprocessed = self.strategy.ohlcvdata_to_dataframe(data)
# Print timeframe
min_date, max_date = history.get_timerange(preprocessed)
min_date, max_date = get_timerange(preprocessed)
logger.info(
'Measuring data from %s up to %s (%s days) ...',
min_date.isoformat(),
@@ -136,10 +137,10 @@ class Edge:
pair_data = pair_data.sort_values(by=['date'])
pair_data = pair_data.reset_index(drop=True)
ticker_data = self.strategy.advise_sell(
df_analyzed = self.strategy.advise_sell(
self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy()
trades += self._find_trades_for_stoploss_range(ticker_data, pair, self._stoploss_range)
trades += self._find_trades_for_stoploss_range(df_analyzed, pair, self._stoploss_range)
# If no trade found then exit
if len(trades) == 0:
@@ -181,7 +182,7 @@ class Edge:
'strategy stoploss is returned instead.')
return self.strategy.stoploss
def adjust(self, pairs) -> list:
def adjust(self, pairs: List[str]) -> list:
"""
Filters out and sorts "pairs" according to Edge calculated pairs
"""
@@ -245,7 +246,8 @@ class Edge:
# we set stake amount to an arbitrary amount.
# as it doesn't change the calculation.
# all returned values are relative. they are percentages.
# all returned values are relative.
# they are defined as ratios.
stake = 0.015
fee = self.fee
open_fee = fee / 2
@@ -268,8 +270,8 @@ class Edge:
result['sell_fee'] = result['sell_sum'] * close_fee
result['sell_take'] = result['sell_sum'] - result['sell_fee']
# profit_percent
result['profit_percent'] = (result['sell_take'] - result['buy_spend']) / result['buy_spend']
# profit_ratio
result['profit_ratio'] = (result['sell_take'] - result['buy_spend']) / result['buy_spend']
# Absolute profit
result['profit_abs'] = result['sell_take'] - result['buy_spend']
@@ -315,7 +317,7 @@ class Edge:
}
# Group by (pair and stoploss) by applying above aggregator
df = results.groupby(['pair', 'stoploss'])['profit_abs', 'trade_duration'].agg(
df = results.groupby(['pair', 'stoploss'])[['profit_abs', 'trade_duration']].agg(
groupby_aggregator).reset_index(col_level=1)
# Dropping level 0 as we don't need it
@@ -357,11 +359,11 @@ class Edge:
# Returning a list of pairs in order of "expectancy"
return final
def _find_trades_for_stoploss_range(self, ticker_data, pair, stoploss_range):
buy_column = ticker_data['buy'].values
sell_column = ticker_data['sell'].values
date_column = ticker_data['date'].values
ohlc_columns = ticker_data[['open', 'high', 'low', 'close']].values
def _find_trades_for_stoploss_range(self, df, pair, stoploss_range):
buy_column = df['buy'].values
sell_column = df['sell'].values
date_column = df['date'].values
ohlc_columns = df[['open', 'high', 'low', 'close']].values
result: list = []
for stoploss in stoploss_range:
@@ -398,9 +400,8 @@ class Edge:
# trade opens in reality on the next candle
open_trade_index += 1
stop_price_percentage = stoploss + 1
open_price = ohlc_columns[open_trade_index, 0]
stop_price = (open_price * stop_price_percentage)
stop_price = (open_price * (stoploss + 1))
# Searching for the index where stoploss is hit
stop_index = utf1st.find_1st(
@@ -440,7 +441,7 @@ class Edge:
trade = {'pair': pair,
'stoploss': stoploss,
'profit_percent': '',
'profit_ratio': '',
'profit_abs': '',
'open_time': date_column[open_trade_index],
'close_time': date_column[exit_index],

View File

@@ -1,18 +1,20 @@
from freqtrade.exchange.common import MAP_EXCHANGE_CHILDCLASS # noqa: F401
from freqtrade.exchange.exchange import Exchange # noqa: F401
from freqtrade.exchange.exchange import (get_exchange_bad_reason, # noqa: F401
# flake8: noqa: F401
from freqtrade.exchange.common import MAP_EXCHANGE_CHILDCLASS
from freqtrade.exchange.exchange import Exchange
from freqtrade.exchange.exchange import (get_exchange_bad_reason,
is_exchange_bad,
is_exchange_known_ccxt,
is_exchange_officially_supported,
ccxt_exchanges,
available_exchanges)
from freqtrade.exchange.exchange import (timeframe_to_seconds, # noqa: F401
from freqtrade.exchange.exchange import (timeframe_to_seconds,
timeframe_to_minutes,
timeframe_to_msecs,
timeframe_to_next_date,
timeframe_to_prev_date)
from freqtrade.exchange.exchange import (market_is_active, # noqa: F401
from freqtrade.exchange.exchange import (market_is_active,
symbol_is_pair)
from freqtrade.exchange.kraken import Kraken # noqa: F401
from freqtrade.exchange.binance import Binance # noqa: F401
from freqtrade.exchange.bibox import Bibox # noqa: F401
from freqtrade.exchange.kraken import Kraken
from freqtrade.exchange.binance import Binance
from freqtrade.exchange.bibox import Bibox
from freqtrade.exchange.ftx import Ftx

View File

@@ -32,13 +32,23 @@ class Binance(Exchange):
return super().get_order_book(pair, limit)
def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict:
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
"""
Verify stop_loss against stoploss-order value (limit or price)
Returns True if adjustment is necessary.
"""
return order['type'] == 'stop_loss_limit' and stop_loss > float(order['info']['stopPrice'])
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
"""
creates a stoploss limit order.
this stoploss-limit is binance-specific.
It may work with a limited number of other exchanges, but this has not been tested yet.
"""
# Limit price threshold: As limit price should always be below stop-price
limit_price_pct = order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
rate = stop_price * limit_price_pct
ordertype = "stop_loss_limit"
stop_price = self.price_to_precision(pair, stop_price)
@@ -61,8 +71,8 @@ class Binance(Exchange):
rate = self.price_to_precision(pair, rate)
order = self._api.create_order(pair, ordertype, 'sell',
amount, rate, params)
order = self._api.create_order(symbol=pair, type=ordertype, side='sell',
amount=amount, price=stop_price, params=params)
logger.info('stoploss limit order added for %s. '
'stop price: %s. limit: %s', pair, stop_price, rate)
return order

View File

@@ -18,12 +18,16 @@ from ccxt.base.decimal_to_precision import (ROUND_DOWN, ROUND_UP, TICK_SIZE,
TRUNCATE, decimal_to_precision)
from pandas import DataFrame
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.data.converter import ohlcv_to_dataframe
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
OperationalException, TemporaryError)
from freqtrade.exchange.common import BAD_EXCHANGES, retrier, retrier_async
from freqtrade.misc import deep_merge_dicts
CcxtModuleType = Any
logger = logging.getLogger(__name__)
@@ -51,7 +55,7 @@ class Exchange:
}
_ft_has: Dict = {}
def __init__(self, config: dict, validate: bool = True) -> None:
def __init__(self, config: Dict[str, Any], validate: bool = True) -> None:
"""
Initializes this module with the given config,
it does basic validation whether the specified exchange and pairs are valid.
@@ -62,8 +66,6 @@ class Exchange:
self._config.update(config)
self._cached_ticker: Dict[str, Any] = {}
# Holds last candle refreshed time of each pair
self._pairs_last_refresh_time: Dict[Tuple[str, str], int] = {}
# Timestamp of last markets refresh
@@ -135,7 +137,7 @@ class Exchange:
if self._api_async and inspect.iscoroutinefunction(self._api_async.close):
asyncio.get_event_loop().run_until_complete(self._api_async.close())
def _init_ccxt(self, exchange_config: dict, ccxt_module=ccxt,
def _init_ccxt(self, exchange_config: Dict[str, Any], ccxt_module: CcxtModuleType = ccxt,
ccxt_kwargs: dict = None) -> ccxt.Exchange:
"""
Initialize ccxt with given config and return valid
@@ -224,13 +226,25 @@ class Exchange:
markets = self.markets
return sorted(set([x['quote'] for _, x in markets.items()]))
def klines(self, pair_interval: Tuple[str, str], copy=True) -> DataFrame:
def get_pair_quote_currency(self, pair: str) -> str:
"""
Return a pair's quote currency
"""
return self.markets.get(pair, {}).get('quote', '')
def get_pair_base_currency(self, pair: str) -> str:
"""
Return a pair's quote currency
"""
return self.markets.get(pair, {}).get('base', '')
def klines(self, pair_interval: Tuple[str, str], copy: bool = True) -> DataFrame:
if pair_interval in self._klines:
return self._klines[pair_interval].copy() if copy else self._klines[pair_interval]
else:
return DataFrame()
def set_sandbox(self, api, exchange_config: dict, name: str):
def set_sandbox(self, api: ccxt.Exchange, exchange_config: dict, name: str) -> None:
if exchange_config.get('sandbox'):
if api.urls.get('test'):
api.urls['api'] = api.urls['test']
@@ -240,7 +254,7 @@ class Exchange:
"Please check your config.json")
raise OperationalException(f'Exchange {name} does not provide a sandbox api')
def _load_async_markets(self, reload=False) -> None:
def _load_async_markets(self, reload: bool = False) -> None:
try:
if self._api_async:
asyncio.get_event_loop().run_until_complete(
@@ -273,7 +287,7 @@ class Exchange:
except ccxt.BaseError:
logger.exception("Could not reload markets.")
def validate_stakecurrency(self, stake_currency) -> None:
def validate_stakecurrency(self, stake_currency: str) -> None:
"""
Checks stake-currency against available currencies on the exchange.
:param stake_currency: Stake-currency to validate
@@ -282,8 +296,8 @@ class Exchange:
quote_currencies = self.get_quote_currencies()
if stake_currency not in quote_currencies:
raise OperationalException(
f"{stake_currency} is not available as stake on {self.name}. "
f"Available currencies are: {', '.join(quote_currencies)}")
f"{stake_currency} is not available as stake on {self.name}. "
f"Available currencies are: {', '.join(quote_currencies)}")
def validate_pairs(self, pairs: List[str]) -> None:
"""
@@ -296,7 +310,7 @@ class Exchange:
if not self.markets:
logger.warning('Unable to validate pairs (assuming they are correct).')
return
invalid_pairs = []
for pair in pairs:
# Note: ccxt has BaseCurrency/QuoteCurrency format for pairs
# TODO: add a support for having coins in BTC/USDT format
@@ -318,8 +332,15 @@ class Exchange:
logger.warning(f"Pair {pair} is restricted for some users on this exchange."
f"Please check if you are impacted by this restriction "
f"on the exchange and eventually remove {pair} from your whitelist.")
if (self._config['stake_currency'] and
self.get_pair_quote_currency(pair) != self._config['stake_currency']):
invalid_pairs.append(pair)
if invalid_pairs:
raise OperationalException(
f"Stake-currency '{self._config['stake_currency']}' not compatible with "
f"pair-whitelist. Please remove the following pairs: {invalid_pairs}")
def get_valid_pair_combination(self, curr_1, curr_2) -> str:
def get_valid_pair_combination(self, curr_1: str, curr_2: str) -> str:
"""
Get valid pair combination of curr_1 and curr_2 by trying both combinations.
"""
@@ -330,7 +351,7 @@ class Exchange:
def validate_timeframes(self, timeframe: Optional[str]) -> None:
"""
Checks if ticker interval from config is a supported timeframe on the exchange
Check if timeframe from config is a supported timeframe on the exchange
"""
if not hasattr(self._api, "timeframes") or self._api.timeframes is None:
# If timeframes attribute is missing (or is None), the exchange probably
@@ -343,7 +364,7 @@ class Exchange:
if timeframe and (timeframe not in self.timeframes):
raise OperationalException(
f"Invalid ticker interval '{timeframe}'. This exchange supports: {self.timeframes}")
f"Invalid timeframe '{timeframe}'. This exchange supports: {self.timeframes}")
if timeframe and timeframe_to_minutes(timeframe) < 1:
raise OperationalException(
@@ -373,7 +394,7 @@ class Exchange:
raise OperationalException(
f'Time in force policies are not supported for {self.name} yet.')
def validate_required_startup_candles(self, startup_candles) -> None:
def validate_required_startup_candles(self, startup_candles: int) -> None:
"""
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.
@@ -392,7 +413,7 @@ class Exchange:
"""
return endpoint in self._api.has and self._api.has[endpoint]
def amount_to_precision(self, pair, amount: float) -> float:
def amount_to_precision(self, pair: str, amount: float) -> float:
'''
Returns the amount to buy or sell to a precision the Exchange accepts
Reimplementation of ccxt internal methods - ensuring we can test the result is correct
@@ -406,7 +427,7 @@ class Exchange:
return amount
def price_to_precision(self, pair, price: float) -> float:
def price_to_precision(self, pair: str, price: float) -> float:
'''
Returns the price rounded up to the precision the Exchange accepts.
Partial Reimplementation of ccxt internal method decimal_to_precision(),
@@ -460,7 +481,7 @@ class Exchange:
"status": "closed",
"filled": closed_order["amount"],
"remaining": 0
})
})
if closed_order["type"] in ["stop_loss_limit"]:
closed_order["info"].update({"stopPrice": closed_order["price"]})
self._dry_run_open_orders[closed_order["id"]] = closed_order
@@ -494,7 +515,7 @@ class Exchange:
raise OperationalException(e) from e
def buy(self, pair: str, ordertype: str, amount: float,
rate: float, time_in_force) -> Dict:
rate: float, time_in_force: str) -> Dict:
if self._config['dry_run']:
dry_order = self.dry_run_order(pair, ordertype, "buy", amount, rate)
@@ -507,7 +528,7 @@ class Exchange:
return self.create_order(pair, ordertype, 'buy', amount, rate, params)
def sell(self, pair: str, ordertype: str, amount: float,
rate: float, time_in_force='gtc') -> Dict:
rate: float, time_in_force: str = 'gtc') -> Dict:
if self._config['dry_run']:
dry_order = self.dry_run_order(pair, ordertype, "sell", amount, rate)
@@ -519,9 +540,17 @@ class Exchange:
return self.create_order(pair, ordertype, 'sell', amount, rate, params)
def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict:
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
"""
creates a stoploss limit order.
Verify stop_loss against stoploss-order value (limit or price)
Returns True if adjustment is necessary.
"""
raise OperationalException(f"stoploss is not implemented for {self.name}.")
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
"""
creates a stoploss order.
The precise ordertype is determined by the order_types dict or exchange default.
Since ccxt does not unify stoploss-limit orders yet, this needs to be implemented in each
exchange's subclass.
The exception below should never raise, since we disallow
@@ -529,7 +558,7 @@ class Exchange:
Note: Changes to this interface need to be applied to all sub-classes too.
"""
raise OperationalException(f"stoploss_limit is not implemented for {self.name}.")
raise OperationalException(f"stoploss is not implemented for {self.name}.")
@retrier
def get_balance(self, currency: str) -> float:
@@ -570,7 +599,7 @@ class Exchange:
return self._api.fetch_tickers()
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {self._api.name} does not support fetching tickers in batch.'
f'Exchange {self._api.name} does not support fetching tickers in batch. '
f'Message: {e}') from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
@@ -579,39 +608,28 @@ class Exchange:
raise OperationalException(e) from e
@retrier
def fetch_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict:
if refresh or pair not in self._cached_ticker.keys():
try:
if pair not in self._api.markets or not self._api.markets[pair].get('active'):
raise DependencyException(f"Pair {pair} not available")
data = self._api.fetch_ticker(pair)
try:
self._cached_ticker[pair] = {
'bid': float(data['bid']),
'ask': float(data['ask']),
}
except KeyError:
logger.debug("Could not cache ticker data for %s", pair)
return data
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load ticker due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
else:
logger.info("returning cached ticker-data for %s", pair)
return self._cached_ticker[pair]
def fetch_ticker(self, pair: str) -> dict:
try:
if pair not in self._api.markets or not self._api.markets[pair].get('active'):
raise DependencyException(f"Pair {pair} not available")
data = self._api.fetch_ticker(pair)
return data
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load ticker due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
def get_historic_ohlcv(self, pair: str, timeframe: str,
since_ms: int) -> List:
"""
Gets candle history using asyncio and returns the list of candles.
Handles all async doing.
Async over one pair, assuming we get `_ohlcv_candle_limit` candles per call.
Get candle history using asyncio and returns the list of candles.
Handles all async work for this.
Async over one pair, assuming we get `self._ohlcv_candle_limit` candles per call.
:param pair: Pair to download
:param timeframe: Ticker Timeframe to get
:param timeframe: Timeframe to get data for
:param since_ms: Timestamp in milliseconds to get history from
:returns List of tickers
:returns List with candle (OHLCV) data
"""
return asyncio.get_event_loop().run_until_complete(
self._async_get_historic_ohlcv(pair=pair, timeframe=timeframe,
@@ -631,26 +649,27 @@ class Exchange:
pair, timeframe, since) for since in
range(since_ms, arrow.utcnow().timestamp * 1000, one_call)]
tickers = await asyncio.gather(*input_coroutines, return_exceptions=True)
results = await asyncio.gather(*input_coroutines, return_exceptions=True)
# Combine tickers
# Combine gathered results
data: List = []
for p, timeframe, ticker in tickers:
for p, timeframe, res in results:
if p == pair:
data.extend(ticker)
data.extend(res)
# Sort data again after extending the result - above calls return in "async order"
data = sorted(data, key=lambda x: x[0])
logger.info("downloaded %s with length %s.", pair, len(data))
logger.info("Downloaded data for %s with length %s.", pair, len(data))
return data
def refresh_latest_ohlcv(self, pair_list: List[Tuple[str, str]]) -> List[Tuple[str, List]]:
"""
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).
Only used in the dataprovider.refresh() method.
:param pair_list: List of 2 element tuples containing pair, interval to refresh
:return: Returns a List of ticker-dataframes.
:return: TODO: return value is only used in the tests, get rid of it
"""
logger.debug("Refreshing ohlcv data for %d pairs", len(pair_list))
logger.debug("Refreshing candle (OHLCV) data for %d pairs", len(pair_list))
input_coroutines = []
@@ -661,15 +680,15 @@ class Exchange:
input_coroutines.append(self._async_get_candle_history(pair, timeframe))
else:
logger.debug(
"Using cached ohlcv data for pair %s, timeframe %s ...",
"Using cached candle (OHLCV) data for pair %s, timeframe %s ...",
pair, timeframe
)
tickers = asyncio.get_event_loop().run_until_complete(
results = asyncio.get_event_loop().run_until_complete(
asyncio.gather(*input_coroutines, return_exceptions=True))
# handle caching
for res in tickers:
for res in results:
if isinstance(res, Exception):
logger.warning("Async code raised an exception: %s", res.__class__.__name__)
continue
@@ -680,13 +699,14 @@ class Exchange:
if ticks:
self._pairs_last_refresh_time[(pair, timeframe)] = ticks[-1][0] // 1000
# keeping parsed dataframe in cache
self._klines[(pair, timeframe)] = parse_ticker_dataframe(
self._klines[(pair, timeframe)] = ohlcv_to_dataframe(
ticks, timeframe, pair=pair, fill_missing=True,
drop_incomplete=self._ohlcv_partial_candle)
return tickers
return results
def _now_is_time_to_refresh(self, pair: str, timeframe: str) -> bool:
# Calculating ticker interval in seconds
# Timeframe in seconds
interval_in_sec = timeframe_to_seconds(timeframe)
return not ((self._pairs_last_refresh_time.get((pair, timeframe), 0)
@@ -696,11 +716,11 @@ class Exchange:
async def _async_get_candle_history(self, pair: str, timeframe: str,
since_ms: Optional[int] = None) -> Tuple[str, str, List]:
"""
Asynchronously gets candle histories using fetch_ohlcv
Asynchronously get candle history data using fetch_ohlcv
returns tuple: (pair, timeframe, ohlcv_list)
"""
try:
# fetch ohlcv asynchronously
# Fetch OHLCV asynchronously
s = '(' + arrow.get(since_ms // 1000).isoformat() + ') ' if since_ms is not None else ''
logger.debug(
"Fetching pair %s, interval %s, since %s %s...",
@@ -710,9 +730,9 @@ class Exchange:
data = await self._api_async.fetch_ohlcv(pair, timeframe=timeframe,
since=since_ms)
# Because some exchange sort Tickers ASC and other DESC.
# Ex: Bittrex returns a list of tickers ASC (oldest first, newest last)
# when GDAX returns a list of tickers DESC (newest first, oldest last)
# 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)
# while GDAX returns the list of OHLCV in DESC order (newest first, oldest last)
# Only sort if necessary to save computing time
try:
if data and data[0][0] > data[-1][0]:
@@ -725,13 +745,15 @@ class Exchange:
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {self._api.name} does not support fetching historical candlestick data.'
f'Message: {e}') from e
f'Exchange {self._api.name} does not support fetching historical '
f'candle (OHLCV) data. Message: {e}') from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(f'Could not load ticker history due to {e.__class__.__name__}. '
raise TemporaryError(f'Could not fetch historical candle (OHLCV) data '
f'for pair {pair} due to {e.__class__.__name__}. '
f'Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(f'Could not fetch ticker data. Msg: {e}') from e
raise OperationalException(f'Could not fetch historical candle (OHLCV) data '
f'for pair {pair}. Message: {e}') from e
@retrier_async
async def _async_fetch_trades(self, pair: str,
@@ -864,14 +886,14 @@ class Exchange:
until: Optional[int] = None,
from_id: Optional[str] = None) -> Tuple[str, List]:
"""
Gets candle history using asyncio and returns the list of candles.
Handles all async doing.
Async over one pair, assuming we get `_ohlcv_candle_limit` candles per call.
Get trade history data using asyncio.
Handles all async work and returns the list of candles.
Async over one pair, assuming we get `self._ohlcv_candle_limit` candles per call.
:param pair: Pair to download
:param since: Timestamp in milliseconds to get history from
:param until: Timestamp in milliseconds. Defaults to current timestamp if not defined.
:param from_id: Download data starting with ID (if id is known)
:returns List of tickers
:returns List of trade data
"""
if not self.exchange_has("fetchTrades"):
raise OperationalException("This exchange does not suport downloading Trades.")
@@ -976,8 +998,8 @@ class Exchange:
raise OperationalException(e) from e
@retrier
def get_fee(self, symbol, type='', side='', amount=1,
price=1, taker_or_maker='maker') -> float:
def get_fee(self, symbol: str, type: str = '', side: str = '', amount: float = 1,
price: float = 1, taker_or_maker: str = 'maker') -> float:
try:
# validate that markets are loaded before trying to get fee
if self._api.markets is None or len(self._api.markets) == 0:
@@ -1000,22 +1022,22 @@ def get_exchange_bad_reason(exchange_name: str) -> str:
return BAD_EXCHANGES.get(exchange_name, "")
def is_exchange_known_ccxt(exchange_name: str, ccxt_module=None) -> bool:
def is_exchange_known_ccxt(exchange_name: str, ccxt_module: CcxtModuleType = None) -> bool:
return exchange_name in ccxt_exchanges(ccxt_module)
def is_exchange_officially_supported(exchange_name: str) -> bool:
return exchange_name in ['bittrex', 'binance']
return exchange_name in ['bittrex', 'binance', 'kraken']
def ccxt_exchanges(ccxt_module=None) -> List[str]:
def ccxt_exchanges(ccxt_module: CcxtModuleType = None) -> List[str]:
"""
Return the list of all exchanges known to ccxt
"""
return ccxt_module.exchanges if ccxt_module is not None else ccxt.exchanges
def available_exchanges(ccxt_module=None) -> List[str]:
def available_exchanges(ccxt_module: CcxtModuleType = None) -> List[str]:
"""
Return exchanges available to the bot, i.e. non-bad exchanges in the ccxt list
"""
@@ -1075,7 +1097,8 @@ def timeframe_to_next_date(timeframe: str, date: datetime = None) -> datetime:
return datetime.fromtimestamp(new_timestamp, tz=timezone.utc)
def symbol_is_pair(market_symbol: str, base_currency: str = None, quote_currency: str = None):
def symbol_is_pair(market_symbol: str, base_currency: str = None,
quote_currency: str = None) -> bool:
"""
Check if the market symbol is a pair, i.e. that its symbol consists of the base currency and the
quote currency separated by '/' character. If base_currency and/or quote_currency is passed,
@@ -1088,7 +1111,7 @@ def symbol_is_pair(market_symbol: str, base_currency: str = None, quote_currency
(symbol_parts[1] == quote_currency if quote_currency else len(symbol_parts[1]) > 0))
def market_is_active(market):
def market_is_active(market: Dict) -> bool:
"""
Return True if the market is active.
"""

14
freqtrade/exchange/ftx.py Normal file
View File

@@ -0,0 +1,14 @@
""" FTX exchange subclass """
import logging
from typing import Dict
from freqtrade.exchange import Exchange
logger = logging.getLogger(__name__)
class Ftx(Exchange):
_ft_has: Dict = {
"ohlcv_candle_limit": 1500,
}

View File

@@ -4,7 +4,8 @@ from typing import Dict
import ccxt
from freqtrade.exceptions import OperationalException, TemporaryError
from freqtrade.exceptions import (DependencyException, InvalidOrderException,
OperationalException, TemporaryError)
from freqtrade.exchange import Exchange
from freqtrade.exchange.exchange import retrier
@@ -15,6 +16,7 @@ class Kraken(Exchange):
_params: Dict = {"trading_agreement": "agree"}
_ft_has: Dict = {
"stoploss_on_exchange": True,
"trades_pagination": "id",
"trades_pagination_arg": "since",
}
@@ -48,3 +50,51 @@ class Kraken(Exchange):
f'Could not get balance due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
def stoploss_adjust(self, stop_loss: float, order: Dict) -> bool:
"""
Verify stop_loss against stoploss-order value (limit or price)
Returns True if adjustment is necessary.
"""
return order['type'] == 'stop-loss' and stop_loss > float(order['price'])
def stoploss(self, pair: str, amount: float, stop_price: float, order_types: Dict) -> Dict:
"""
Creates a stoploss market order.
Stoploss market orders is the only stoploss type supported by kraken.
"""
ordertype = "stop-loss"
stop_price = self.price_to_precision(pair, stop_price)
if self._config['dry_run']:
dry_order = self.dry_run_order(
pair, ordertype, "sell", amount, stop_price)
return dry_order
try:
params = self._params.copy()
amount = self.amount_to_precision(pair, amount)
order = self._api.create_order(symbol=pair, type=ordertype, side='sell',
amount=amount, price=stop_price, params=params)
logger.info('stoploss order added for %s. '
'stop price: %s.', pair, stop_price)
return order
except ccxt.InsufficientFunds as e:
raise DependencyException(
f'Insufficient funds to create {ordertype} sell order on market {pair}.'
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
f'Message: {e}') from e
except ccxt.InvalidOrder as e:
raise InvalidOrderException(
f'Could not create {ordertype} sell order on market {pair}. '
f'Tried to create stoploss with amount {amount} at stoploss {stop_price}. '
f'Message: {e}') from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e

View File

@@ -6,11 +6,11 @@ import logging
import traceback
from datetime import datetime
from math import isclose
from os import getpid
from threading import Lock
from typing import Any, Dict, List, Optional, Tuple
import arrow
from cachetools import TTLCache
from requests.exceptions import RequestException
from freqtrade import __version__, constants, persistence
@@ -52,9 +52,8 @@ class FreqtradeBot:
# Init objects
self.config = config
self._heartbeat_msg = 0
self.heartbeat_interval = self.config.get('internals', {}).get('heartbeat_interval', 60)
self._sell_rate_cache = TTLCache(maxsize=100, ttl=5)
self._buy_rate_cache = TTLCache(maxsize=100, ttl=5)
self.strategy: IStrategy = StrategyResolver.load_strategy(self.config)
@@ -159,11 +158,6 @@ class FreqtradeBot:
self.check_handle_timedout()
Trade.session.flush()
if (self.heartbeat_interval
and (arrow.utcnow().timestamp - self._heartbeat_msg > self.heartbeat_interval)):
logger.info(f"Bot heartbeat. PID={getpid()}")
self._heartbeat_msg = arrow.utcnow().timestamp
def _refresh_whitelist(self, trades: List[Trade] = []) -> List[str]:
"""
Refresh whitelist from pairlist or edge and extend it with trades.
@@ -178,8 +172,8 @@ class FreqtradeBot:
_whitelist = self.edge.adjust(_whitelist)
if trades:
# Extend active-pair whitelist with pairs from open trades
# It ensures that tickers are downloaded for open trades
# Extend active-pair whitelist with pairs of open trades
# It ensures that candle (OHLCV) data are downloaded for open trades as well
_whitelist.extend([trade.pair for trade in trades if trade.pair not in _whitelist])
return _whitelist
@@ -234,38 +228,46 @@ class FreqtradeBot:
return trades_created
def get_buy_rate(self, pair: str, tick: Dict = None) -> float:
def get_buy_rate(self, pair: str, refresh: bool) -> float:
"""
Calculates bid target between current ask price and last price
:param pair: Pair to get rate for
:param refresh: allow cached data
:return: float: Price
"""
config_bid_strategy = self.config.get('bid_strategy', {})
if 'use_order_book' in config_bid_strategy and\
config_bid_strategy.get('use_order_book', False):
logger.info('Getting price from order book')
order_book_top = config_bid_strategy.get('order_book_top', 1)
if not refresh:
rate = self._buy_rate_cache.get(pair)
# Check if cache has been invalidated
if rate:
logger.info(f"Using cached buy rate for {pair}.")
return rate
bid_strategy = self.config.get('bid_strategy', {})
if 'use_order_book' in bid_strategy and bid_strategy.get('use_order_book', False):
logger.info(
f"Getting price from order book {bid_strategy['price_side'].capitalize()} side."
)
order_book_top = bid_strategy.get('order_book_top', 1)
order_book = self.exchange.get_order_book(pair, order_book_top)
logger.debug('order_book %s', order_book)
# top 1 = index 0
order_book_rate = order_book['bids'][order_book_top - 1][0]
logger.info('...top %s order book buy rate %0.8f', order_book_top, order_book_rate)
order_book_rate = order_book[f"{bid_strategy['price_side']}s"][order_book_top - 1][0]
logger.info(f'...top {order_book_top} order book buy rate {order_book_rate:.8f}')
used_rate = order_book_rate
else:
if not tick:
logger.info('Using Last Ask / Last Price')
ticker = self.exchange.fetch_ticker(pair)
else:
ticker = tick
if ticker['ask'] < ticker['last']:
ticker_rate = ticker['ask']
else:
logger.info(f"Using Last {bid_strategy['price_side'].capitalize()} / Last Price")
ticker = self.exchange.fetch_ticker(pair)
ticker_rate = ticker[bid_strategy['price_side']]
if ticker['last'] and ticker_rate > ticker['last']:
balance = self.config['bid_strategy']['ask_last_balance']
ticker_rate = ticker['ask'] + balance * (ticker['last'] - ticker['ask'])
ticker_rate = ticker_rate + balance * (ticker['last'] - ticker_rate)
used_rate = ticker_rate
self._buy_rate_cache[pair] = used_rate
return used_rate
def get_trade_stake_amount(self, pair) -> float:
def get_trade_stake_amount(self, pair: str) -> float:
"""
Calculate stake amount for the trade
:return: float: Stake amount
@@ -392,19 +394,21 @@ class FreqtradeBot:
logger.info(f"Pair {pair} is currently locked.")
return False
# get_free_open_trades is checked before create_trade is called
# but it is still used here to prevent opening too many trades within one iteration
if not self.get_free_open_trades():
logger.debug(f"Can't open a new trade for {pair}: max number of trades is reached.")
return False
# running get_signal on historical data fetched
(buy, sell) = self.strategy.get_signal(
pair, self.strategy.ticker_interval,
self.dataprovider.ohlcv(pair, self.strategy.ticker_interval))
if buy and not sell:
if not self.get_free_open_trades():
logger.debug("Can't open a new trade: max number of trades is reached.")
return False
stake_amount = self.get_trade_stake_amount(pair)
if not stake_amount:
logger.debug("Stake amount is 0, ignoring possible trade for {pair}.")
logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.")
return False
logger.info(f"Buy signal found: about create a new trade with stake_amount: "
@@ -414,10 +418,12 @@ class FreqtradeBot:
if ((bid_check_dom.get('enabled', False)) and
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
if self._check_depth_of_market_buy(pair, bid_check_dom):
logger.info(f'Executing Buy for {pair}.')
return self.execute_buy(pair, stake_amount)
else:
return False
logger.info(f'Executing Buy for {pair}')
return self.execute_buy(pair, stake_amount)
else:
return False
@@ -427,23 +433,30 @@ class FreqtradeBot:
Checks depth of market before executing a buy
"""
conf_bids_to_ask_delta = conf.get('bids_to_ask_delta', 0)
logger.info('checking depth of market for %s', pair)
logger.info(f"Checking depth of market for {pair} ...")
order_book = self.exchange.get_order_book(pair, 1000)
order_book_data_frame = order_book_to_dataframe(order_book['bids'], order_book['asks'])
order_book_bids = order_book_data_frame['b_size'].sum()
order_book_asks = order_book_data_frame['a_size'].sum()
bids_ask_delta = order_book_bids / order_book_asks
logger.info('bids: %s, asks: %s, delta: %s', order_book_bids,
order_book_asks, bids_ask_delta)
logger.info(
f"Bids: {order_book_bids}, Asks: {order_book_asks}, Delta: {bids_ask_delta}, "
f"Bid Price: {order_book['bids'][0][0]}, Ask Price: {order_book['asks'][0][0]}, "
f"Immediate Bid Quantity: {order_book['bids'][0][1]}, "
f"Immediate Ask Quantity: {order_book['asks'][0][1]}."
)
if bids_ask_delta >= conf_bids_to_ask_delta:
logger.info(f"Bids to asks delta for {pair} DOES satisfy condition.")
return True
return False
else:
logger.info(f"Bids to asks delta for {pair} does not satisfy condition.")
return False
def execute_buy(self, pair: str, stake_amount: float, price: Optional[float] = None) -> bool:
"""
Executes a limit buy for the given pair
:param pair: pair for which we want to create a LIMIT_BUY
:return: None
:return: True if a buy order is created, false if it fails.
"""
time_in_force = self.strategy.order_time_in_force['buy']
@@ -451,7 +464,7 @@ class FreqtradeBot:
buy_limit_requested = price
else:
# Calculate price
buy_limit_requested = self.get_buy_rate(pair)
buy_limit_requested = self.get_buy_rate(pair, True)
min_stake_amount = self._get_min_pair_stake_amount(pair, buy_limit_requested)
if min_stake_amount is not None and min_stake_amount > stake_amount:
@@ -518,8 +531,6 @@ class FreqtradeBot:
ticker_interval=timeframe_to_minutes(self.config['ticker_interval'])
)
self._notify_buy(trade, order_type)
# Update fees if order is closed
if order_status == 'closed':
self.update_trade_state(trade, order)
@@ -530,9 +541,11 @@ class FreqtradeBot:
# Updating wallets
self.wallets.update()
self._notify_buy(trade, order_type)
return True
def _notify_buy(self, trade: Trade, order_type: str):
def _notify_buy(self, trade: Trade, order_type: str) -> None:
"""
Sends rpc notification when a buy occured.
"""
@@ -545,6 +558,32 @@ class FreqtradeBot:
'stake_amount': trade.stake_amount,
'stake_currency': self.config['stake_currency'],
'fiat_currency': self.config.get('fiat_display_currency', None),
'amount': trade.amount,
'open_date': trade.open_date or datetime.utcnow(),
'current_rate': trade.open_rate_requested,
}
# Send the message
self.rpc.send_msg(msg)
def _notify_buy_cancel(self, trade: Trade, order_type: str) -> None:
"""
Sends rpc notification when a buy cancel occured.
"""
current_rate = self.get_buy_rate(trade.pair, False)
msg = {
'type': RPCMessageType.BUY_CANCEL_NOTIFICATION,
'exchange': self.exchange.name.capitalize(),
'pair': trade.pair,
'limit': trade.open_rate,
'order_type': order_type,
'stake_amount': trade.stake_amount,
'stake_currency': self.config['stake_currency'],
'fiat_currency': self.config.get('fiat_display_currency', None),
'amount': trade.amount,
'open_date': trade.open_date,
'current_rate': current_rate,
}
# Send the message
@@ -580,23 +619,43 @@ class FreqtradeBot:
return trades_closed
def _order_book_gen(self, pair: str, side: str, order_book_max: int = 1,
order_book_min: int = 1):
"""
Helper generator to query orderbook in loop (used for early sell-order placing)
"""
order_book = self.exchange.get_order_book(pair, order_book_max)
for i in range(order_book_min, order_book_max + 1):
yield order_book[side][i - 1][0]
def get_sell_rate(self, pair: str, refresh: bool) -> float:
"""
Get sell rate - either using get-ticker bid or first bid based on orderbook
Get sell rate - either using ticker bid or first bid based on orderbook
The orderbook portion is only used for rpc messaging, which would otherwise fail
for BitMex (has no bid/ask in fetch_ticker)
or remain static in any other case since it's not updating.
:param pair: Pair to get rate for
:param refresh: allow cached data
:return: Bid rate
"""
config_ask_strategy = self.config.get('ask_strategy', {})
if config_ask_strategy.get('use_order_book', False):
logger.debug('Using order book to get sell rate')
if not refresh:
rate = self._sell_rate_cache.get(pair)
# Check if cache has been invalidated
if rate:
logger.info(f"Using cached sell rate for {pair}.")
return rate
order_book = self.exchange.get_order_book(pair, 1)
rate = order_book['bids'][0][0]
ask_strategy = self.config.get('ask_strategy', {})
if ask_strategy.get('use_order_book', False):
# This code is only used for notifications, selling uses the generator directly
logger.info(
f"Getting price from order book {ask_strategy['price_side'].capitalize()} side."
)
rate = next(self._order_book_gen(pair, f"{ask_strategy['price_side']}s"))
else:
rate = self.exchange.fetch_ticker(pair, refresh)['bid']
rate = self.exchange.fetch_ticker(pair)[ask_strategy['price_side']]
self._sell_rate_cache[pair] = rate
return rate
def handle_trade(self, trade: Trade) -> bool:
@@ -614,23 +673,24 @@ class FreqtradeBot:
config_ask_strategy = self.config.get('ask_strategy', {})
if (config_ask_strategy.get('use_sell_signal', True) or
config_ask_strategy.get('ignore_roi_if_buy_signal')):
config_ask_strategy.get('ignore_roi_if_buy_signal', False)):
(buy, sell) = self.strategy.get_signal(
trade.pair, self.strategy.ticker_interval,
self.dataprovider.ohlcv(trade.pair, self.strategy.ticker_interval))
if config_ask_strategy.get('use_order_book', False):
logger.info('Using order book for selling...')
logger.debug(f'Using order book for selling {trade.pair}...')
# logger.debug('Order book %s',orderBook)
order_book_min = config_ask_strategy.get('order_book_min', 1)
order_book_max = config_ask_strategy.get('order_book_max', 1)
order_book = self.exchange.get_order_book(trade.pair, order_book_max)
order_book = self._order_book_gen(trade.pair, f"{config_ask_strategy['price_side']}s",
order_book_min=order_book_min,
order_book_max=order_book_max)
for i in range(order_book_min, order_book_max + 1):
order_book_rate = order_book['asks'][i - 1][0]
logger.info(' order book asks top %s: %0.8f', i, order_book_rate)
sell_rate = order_book_rate
sell_rate = next(order_book)
logger.debug(f" order book {config_ask_strategy['price_side']} top {i}: "
f"{sell_rate:0.8f}")
if self._check_and_execute_sell(trade, sell_rate, buy, sell):
return True
@@ -651,13 +711,10 @@ class FreqtradeBot:
Force-sells the pair (using EmergencySell reason) in case of Problems creating the order.
:return: True if the order succeeded, and False in case of problems.
"""
# Limit price threshold: As limit price should always be below stop-price
LIMIT_PRICE_PCT = self.strategy.order_types.get('stoploss_on_exchange_limit_ratio', 0.99)
try:
stoploss_order = self.exchange.stoploss_limit(pair=trade.pair, amount=trade.amount,
stop_price=stop_price,
rate=rate * LIMIT_PRICE_PCT)
stoploss_order = self.exchange.stoploss(pair=trade.pair, amount=trade.amount,
stop_price=stop_price,
order_types=self.strategy.order_types)
trade.stoploss_order_id = str(stoploss_order['id'])
return True
except InvalidOrderException as e:
@@ -689,8 +746,24 @@ class FreqtradeBot:
except InvalidOrderException as exception:
logger.warning('Unable to fetch stoploss order: %s', exception)
# We check if stoploss order is fulfilled
if stoploss_order and stoploss_order['status'] == 'closed':
trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
trade.update(stoploss_order)
# Lock pair for one candle to prevent immediate rebuys
self.strategy.lock_pair(trade.pair,
timeframe_to_next_date(self.config['ticker_interval']))
self._notify_sell(trade, "stoploss")
return True
if trade.open_order_id or not trade.is_open:
# Trade has an open Buy or Sell order, Stoploss-handling can't happen in this case
# as the Amount on the exchange is tied up in another trade.
# The trade can be closed already (sell-order fill confirmation came in this iteration)
return False
# If buy order is fulfilled but there is no stoploss, we add a stoploss on exchange
if (not trade.open_order_id and not stoploss_order):
if (not stoploss_order):
stoploss = self.edge.stoploss(pair=trade.pair) if self.edge else self.strategy.stoploss
@@ -709,16 +782,6 @@ class FreqtradeBot:
trade.stoploss_order_id = None
logger.warning('Stoploss order was cancelled, but unable to recreate one.')
# We check if stoploss order is fulfilled
if stoploss_order and stoploss_order['status'] == 'closed':
trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
trade.update(stoploss_order)
# Lock pair for one candle to prevent immediate rebuys
self.strategy.lock_pair(trade.pair,
timeframe_to_next_date(self.config['ticker_interval']))
self._notify_sell(trade, "stoploss")
return True
# 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 trailing stoploss is enabled we check if stoploss value has changed
@@ -728,7 +791,7 @@ class FreqtradeBot:
return False
def handle_trailing_stoploss_on_exchange(self, trade: Trade, order):
def handle_trailing_stoploss_on_exchange(self, trade: Trade, order: dict) -> None:
"""
Check to see if stoploss on exchange should be updated
in case of trailing stoploss on exchange
@@ -736,13 +799,12 @@ class FreqtradeBot:
:param order: Current on exchange stoploss order
:return: None
"""
if trade.stop_loss > float(order['info']['stopPrice']):
if self.exchange.stoploss_adjust(trade.stop_loss, order):
# we check if the update is neccesary
update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60)
if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat:
# cancelling the current stoploss on exchange first
logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s})'
logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s}) '
'in order to add another one ...', order['id'])
try:
self.exchange.cancel_order(order['id'], trade.pair)
@@ -751,10 +813,8 @@ class FreqtradeBot:
f"for pair {trade.pair}")
# Create new stoploss order
if self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss,
rate=trade.stop_loss):
return False
else:
if not self.create_stoploss_order(trade=trade, stop_price=trade.stop_loss,
rate=trade.stop_loss):
logger.warning(f"Could not create trailing stoploss order "
f"for pair {trade.pair}.")
@@ -769,8 +829,8 @@ class FreqtradeBot:
)
if should_sell.sell_flag:
logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}')
self.execute_sell(trade, sell_rate, should_sell.sell_type)
logger.info('executed sell, reason: %s', should_sell.sell_type)
return True
return False
@@ -813,41 +873,40 @@ class FreqtradeBot:
if ((order['side'] == 'buy' and order['status'] == 'canceled')
or (self._check_timed_out('buy', order))):
self.handle_timedout_limit_buy(trade, order)
self.wallets.update()
order_type = self.strategy.order_types['buy']
self._notify_buy_cancel(trade, order_type)
elif ((order['side'] == 'sell' and order['status'] == 'canceled')
or (self._check_timed_out('sell', order))):
self.handle_timedout_limit_sell(trade, order)
self.wallets.update()
def handle_buy_order_full_cancel(self, trade: Trade, reason: str) -> None:
"""Close trade in database and send message"""
Trade.session.delete(trade)
Trade.session.flush()
logger.info('Buy order %s for %s.', reason, trade)
self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Unfilled buy order for {trade.pair} {reason}'
})
order_type = self.strategy.order_types['sell']
self._notify_sell_cancel(trade, order_type)
def handle_timedout_limit_buy(self, trade: Trade, order: Dict) -> bool:
"""
Buy timeout - cancel order
:return: True if order was fully cancelled
"""
reason = "cancelled due to timeout"
if order['status'] != 'canceled':
reason = "cancelled due to timeout"
corder = self.exchange.cancel_order(trade.open_order_id, trade.pair)
# Some exchanges don't return a dict here.
if not isinstance(corder, dict):
corder = {}
logger.info('Buy order %s for %s.', reason, trade)
else:
# Order was cancelled already, so we can reuse the existing dict
corder = order
reason = "canceled on Exchange"
reason = "cancelled on exchange"
logger.info('Buy order %s for %s.', reason, trade)
if corder.get('remaining', order['remaining']) == order['amount']:
# if trade is not partially completed, just delete the trade
self.handle_buy_order_full_cancel(trade, reason)
Trade.session.delete(trade)
Trade.session.flush()
return True
# if trade is partially complete, edit the stake details for the trade
@@ -882,24 +941,23 @@ class FreqtradeBot:
Sell timeout - cancel order and update trade
:return: True if order was fully cancelled
"""
# if trade is not partially completed, just cancel the trade
if order['remaining'] == order['amount']:
# if trade is not partially completed, just cancel the trade
if order["status"] != "canceled":
reason = "due to timeout"
reason = "cancelled due to timeout"
# if trade is not partially completed, just delete the trade
self.exchange.cancel_order(trade.open_order_id, trade.pair)
logger.info('Sell order timeout for %s.', trade)
logger.info('Sell order %s for %s.', reason, trade)
else:
reason = "on exchange"
logger.info('Sell order canceled on exchange for %s.', trade)
reason = "cancelled on exchange"
logger.info('Sell order %s for %s.', reason, trade)
trade.close_rate = None
trade.close_profit = None
trade.close_profit_abs = None
trade.close_date = None
trade.is_open = True
trade.open_order_id = None
self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Unfilled sell order for {trade.pair} cancelled {reason}'
})
return True
@@ -919,8 +977,8 @@ class FreqtradeBot:
"""
# Update wallets to ensure amounts tied up in a stoploss is now free!
self.wallets.update()
wallet_amount = self.wallets.get_free(pair.split('/')[0])
trade_base_currency = self.exchange.get_pair_base_currency(pair)
wallet_amount = self.wallets.get_free(trade_base_currency)
logger.debug(f"{pair} - Wallet: {wallet_amount} - Trade-amount: {amount}")
if wallet_amount >= amount:
return amount
@@ -931,13 +989,13 @@ class FreqtradeBot:
raise DependencyException(
f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}")
def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> None:
def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> bool:
"""
Executes a limit sell for the given trade and limit
:param trade: Trade instance
:param limit: limit rate for the sell order
:param sellreason: Reason the sell was triggered
:return: None
:return: True if it succeeds (supported) False (not supported)
"""
sell_type = 'sell'
if sell_reason in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
@@ -958,7 +1016,7 @@ class FreqtradeBot:
order_type = self.strategy.order_types[sell_type]
if sell_reason == SellType.EMERGENCY_SELL:
# Emergencysells (default to market!)
# Emergency sells (default to market!)
order_type = self.strategy.order_types.get("emergencysell", "market")
amount = self._safe_sell_amount(trade.pair, trade.amount)
@@ -983,33 +1041,73 @@ class FreqtradeBot:
self._notify_sell(trade, order_type)
def _notify_sell(self, trade: Trade, order_type: str):
return True
def _notify_sell(self, trade: Trade, order_type: str) -> None:
"""
Sends rpc notification when a sell occured.
"""
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
profit_trade = trade.calc_profit(rate=profit_rate)
# Use cached ticker here - it was updated seconds ago.
# Use cached rates here - it was updated seconds ago.
current_rate = self.get_sell_rate(trade.pair, False)
profit_percent = trade.calc_profit_ratio(profit_rate)
gain = "profit" if profit_percent > 0 else "loss"
profit_ratio = trade.calc_profit_ratio(profit_rate)
gain = "profit" if profit_ratio > 0 else "loss"
msg = {
'type': RPCMessageType.SELL_NOTIFICATION,
'exchange': trade.exchange.capitalize(),
'pair': trade.pair,
'gain': gain,
'limit': trade.close_rate_requested,
'limit': profit_rate,
'order_type': order_type,
'amount': trade.amount,
'open_rate': trade.open_rate,
'current_rate': current_rate,
'profit_amount': profit_trade,
'profit_percent': profit_percent,
'profit_ratio': profit_ratio,
'sell_reason': trade.sell_reason,
'open_date': trade.open_date,
'close_date': trade.close_date or datetime.utcnow(),
'stake_currency': self.config['stake_currency'],
'fiat_currency': self.config.get('fiat_display_currency', None),
}
if 'fiat_display_currency' in self.config:
msg.update({
'fiat_currency': self.config['fiat_display_currency'],
})
# Send the message
self.rpc.send_msg(msg)
def _notify_sell_cancel(self, trade: Trade, order_type: str) -> None:
"""
Sends rpc notification when a sell cancel occured.
"""
profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
profit_trade = trade.calc_profit(rate=profit_rate)
current_rate = self.get_sell_rate(trade.pair, False)
profit_ratio = trade.calc_profit_ratio(profit_rate)
gain = "profit" if profit_ratio > 0 else "loss"
msg = {
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
'exchange': trade.exchange.capitalize(),
'pair': trade.pair,
'gain': gain,
'limit': profit_rate,
'order_type': order_type,
'amount': trade.amount,
'open_rate': trade.open_rate,
'current_rate': current_rate,
'profit_amount': profit_trade,
'profit_ratio': profit_ratio,
'sell_reason': trade.sell_reason,
'open_date': trade.open_date,
'close_date': trade.close_date,
'stake_currency': self.config['stake_currency'],
'fiat_currency': self.config.get('fiat_display_currency', None),
}
if 'fiat_display_currency' in self.config:
@@ -1024,7 +1122,7 @@ class FreqtradeBot:
# Common update trade state methods
#
def update_trade_state(self, trade, action_order: dict = None):
def update_trade_state(self, trade: Trade, action_order: dict = None) -> None:
"""
Checks trades with open orders and updates the amount if necessary
"""
@@ -1066,12 +1164,13 @@ class FreqtradeBot:
if trade.fee_open == 0 or order['status'] == 'open':
return order_amount
trade_base_currency = self.exchange.get_pair_base_currency(trade.pair)
# use fee from order-dict if possible
if ('fee' in order and order['fee'] is not None and
(order['fee'].keys() >= {'currency', 'cost'})):
if (order['fee']['currency'] is not None and
order['fee']['cost'] is not None and
trade.pair.startswith(order['fee']['currency'])):
trade_base_currency == order['fee']['currency']):
new_amount = order_amount - order['fee']['cost']
logger.info("Applying fee on amount for %s (from %s to %s) from Order",
trade, order['amount'], new_amount)
@@ -1093,7 +1192,7 @@ class FreqtradeBot:
# only applies if fee is in quote currency!
if (exectrade['fee']['currency'] is not None and
exectrade['fee']['cost'] is not None and
trade.pair.startswith(exectrade['fee']['currency'])):
trade_base_currency == exectrade['fee']['currency']):
fee_abs += exectrade['fee']['cost']
if not isclose(amount, order_amount, abs_tol=constants.MATH_CLOSE_PREC):

View File

@@ -38,8 +38,8 @@ def main(sysargv: List[str] = None) -> None:
# No subcommand was issued.
raise OperationalException(
"Usage of Freqtrade requires a subcommand to be specified.\n"
"To have the previous behavior (bot executing trades in live/dry-run modes, "
"depending on the value of the `dry_run` setting in the config), run freqtrade "
"To have the bot executing trades in live/dry-run modes, "
"depending on the value of the `dry_run` setting in the config, run Freqtrade "
"as `freqtrade trade [options...]`.\n"
"To see the full list of options available, please use "
"`freqtrade --help` or `freqtrade <command> --help`."

View File

@@ -6,6 +6,7 @@ import logging
import re
from datetime import datetime
from pathlib import Path
from typing import Any
from typing.io import IO
import numpy as np
@@ -40,28 +41,30 @@ def datesarray_to_datetimearray(dates: np.ndarray) -> np.ndarray:
return dates.dt.to_pydatetime()
def file_dump_json(filename: Path, data, is_zip=False) -> None:
def file_dump_json(filename: Path, data: Any, is_zip: bool = False) -> None:
"""
Dump JSON data into a file
:param filename: file to create
:param data: JSON Data to save
:return:
"""
logger.info(f'dumping json to "{filename}"')
if is_zip:
if filename.suffix != '.gz':
filename = filename.with_suffix('.gz')
logger.info(f'dumping json to "{filename}"')
with gzip.open(filename, 'w') as fp:
rapidjson.dump(data, fp, default=str, number_mode=rapidjson.NM_NATIVE)
else:
logger.info(f'dumping json to "{filename}"')
with open(filename, 'w') as fp:
rapidjson.dump(data, fp, default=str, number_mode=rapidjson.NM_NATIVE)
logger.debug(f'done json to "{filename}"')
def json_load(datafile: IO):
def json_load(datafile: IO) -> Any:
"""
load data with rapidjson
Use this to have a consistent experience,
@@ -78,18 +81,24 @@ def file_load_json(file):
gzipfile = file
# Try gzip file first, otherwise regular json file.
if gzipfile.is_file():
logger.debug('Loading ticker data from file %s', gzipfile)
with gzip.open(gzipfile) as tickerdata:
pairdata = json_load(tickerdata)
logger.debug(f"Loading historical data from file {gzipfile}")
with gzip.open(gzipfile) as datafile:
pairdata = json_load(datafile)
elif file.is_file():
logger.debug('Loading ticker data from file %s', file)
with open(file) as tickerdata:
pairdata = json_load(tickerdata)
logger.debug(f"Loading historical data from file {file}")
with open(file) as datafile:
pairdata = json_load(datafile)
else:
return None
return pairdata
def pair_to_filename(pair: str) -> str:
for ch in ['/', '-', ' ', '.', '@', '$', '+', ':']:
pair = pair.replace(ch, '_')
return pair
def format_ms_time(date: int) -> str:
"""
convert MS date to readable format.
@@ -125,11 +134,11 @@ def round_dict(d, n):
return {k: (round(v, n) if isinstance(v, float) else v) for k, v in d.items()}
def plural(num, singular: str, plural: str = None) -> str:
def plural(num: float, singular: str, plural: str = None) -> str:
return singular if (num == 1 or num == -1) else plural or singular + 's'
def render_template(templatefile: str, arguments: dict = {}):
def render_template(templatefile: str, arguments: dict = {}) -> str:
from jinja2 import Environment, PackageLoader, select_autoescape
@@ -138,5 +147,4 @@ def render_template(templatefile: str, arguments: dict = {}):
autoescape=select_autoescape(['html', 'xml'])
)
template = env.get_template(templatefile)
return template.render(**arguments)

View File

@@ -6,25 +6,24 @@ This module contains the backtesting logic
import logging
from copy import deepcopy
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, Dict, List, NamedTuple, Optional
from typing import Any, Dict, List, NamedTuple, Optional, Tuple
import arrow
from pandas import DataFrame
from freqtrade.configuration import (TimeRange, remove_credentials,
validate_config_consistency)
from freqtrade.data import history
from freqtrade.data.converter import trim_dataframe
from freqtrade.data.dataprovider import DataProvider
from freqtrade.exceptions import OperationalException
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_seconds
from freqtrade.misc import file_dump_json
from freqtrade.optimize.optimize_reports import (
generate_text_table, generate_text_table_sell_reason,
generate_text_table_strategy)
from freqtrade.optimize.optimize_reports import (show_backtest_results,
store_backtest_result)
from freqtrade.persistence import Trade
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.state import RunMode
from freqtrade.strategy.interface import IStrategy, SellType
from freqtrade.strategy.interface import IStrategy, SellCheckTuple, SellType
logger = logging.getLogger(__name__)
@@ -86,8 +85,8 @@ class Backtesting:
validate_config_consistency(self.config)
if "ticker_interval" not in self.config:
raise OperationalException("Ticker-interval needs to be set in either configuration "
"or as cli argument `--ticker-interval 5m`")
raise OperationalException("Timeframe (ticker interval) needs to be set in either "
"configuration or as cli argument `--ticker-interval 5m`")
self.timeframe = str(self.config.get('ticker_interval'))
self.timeframe_min = timeframe_to_minutes(self.timeframe)
@@ -106,7 +105,7 @@ class Backtesting:
# And the regular "stoploss" function would not apply to that case
self.strategy.order_types['stoploss_on_exchange'] = False
def load_bt_data(self):
def load_bt_data(self) -> Tuple[Dict[str, DataFrame], TimeRange]:
timerange = TimeRange.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange')))
@@ -117,6 +116,7 @@ class Backtesting:
timerange=timerange,
startup_candles=self.required_startup,
fail_without_data=True,
data_format=self.config.get('dataformat_ohlcv', 'json'),
)
min_date, max_date = history.get_timerange(data)
@@ -131,51 +131,36 @@ class Backtesting:
return data, timerange
def _store_backtest_result(self, recordfilename: Path, results: DataFrame,
strategyname: Optional[str] = None) -> None:
records = [(t.pair, t.profit_percent, t.open_time.timestamp(),
t.close_time.timestamp(), t.open_index - 1, t.trade_duration,
t.open_rate, t.close_rate, t.open_at_end, t.sell_reason.value)
for index, t in results.iterrows()]
if records:
if strategyname:
# Inject strategyname to filename
recordfilename = Path.joinpath(
recordfilename.parent,
f'{recordfilename.stem}-{strategyname}').with_suffix(recordfilename.suffix)
logger.info(f'Dumping backtest results to {recordfilename}')
file_dump_json(recordfilename, records)
def _get_ticker_list(self, processed) -> Dict[str, DataFrame]:
def _get_ohlcv_as_lists(self, processed: Dict) -> Dict[str, DataFrame]:
"""
Helper function to convert a processed tickerlist into a list for performance reasons.
Helper function to convert a processed dataframes into lists for performance reasons.
Used by backtest() - so keep this optimized for performance.
"""
headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high']
ticker: Dict = {}
# Create ticker dict
data: Dict = {}
# Create dict with data
for pair, pair_data in processed.items():
pair_data.loc[:, 'buy'] = 0 # cleanup from previous run
pair_data.loc[:, 'sell'] = 0 # cleanup from previous run
ticker_data = self.strategy.advise_sell(
df_analyzed = self.strategy.advise_sell(
self.strategy.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy()
# to avoid using data from future, we buy/sell with signal from previous candle
ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1)
ticker_data.loc[:, 'sell'] = ticker_data['sell'].shift(1)
# To avoid using data from future, we use buy/sell signals shifted
# from the previous candle
df_analyzed.loc[:, 'buy'] = df_analyzed['buy'].shift(1)
df_analyzed.loc[:, 'sell'] = df_analyzed['sell'].shift(1)
ticker_data.drop(ticker_data.head(1).index, inplace=True)
df_analyzed.drop(df_analyzed.head(1).index, inplace=True)
# Convert from Pandas to list for performance reasons
# (Looping Pandas is slow.)
ticker[pair] = [x for x in ticker_data.itertuples()]
return ticker
data[pair] = [x for x in df_analyzed.itertuples()]
return data
def _get_close_rate(self, sell_row, trade: Trade, sell, trade_dur) -> float:
def _get_close_rate(self, sell_row, trade: Trade, sell: SellCheckTuple,
trade_dur: int) -> float:
"""
Get close rate for backtesting result
"""
@@ -216,7 +201,7 @@ class Backtesting:
def _get_sell_trade_entry(
self, pair: str, buy_row: DataFrame,
partial_ticker: List, trade_count_lock: Dict,
partial_ohlcv: List, trade_count_lock: Dict,
stake_amount: float, max_open_trades: int) -> Optional[BacktestResult]:
trade = Trade(
@@ -231,7 +216,7 @@ class Backtesting:
)
logger.debug(f"{pair} - Backtesting emulates creation of new trade: {trade}.")
# calculate win/lose forwards from buy point
for sell_row in partial_ticker:
for sell_row in partial_ohlcv:
if max_open_trades > 0:
# Increase trade_count_lock for every iteration
trade_count_lock[sell_row.date] = trade_count_lock.get(sell_row.date, 0) + 1
@@ -255,9 +240,9 @@ class Backtesting:
close_rate=closerate,
sell_reason=sell.sell_type
)
if partial_ticker:
if partial_ohlcv:
# no sell condition found - trade stil open at end of backtest period
sell_row = partial_ticker[-1]
sell_row = partial_ohlcv[-1]
bt_res = BacktestResult(pair=pair,
profit_percent=trade.calc_profit_ratio(rate=sell_row.open),
profit_abs=trade.calc_profit(rate=sell_row.open),
@@ -280,7 +265,7 @@ class Backtesting:
return None
def backtest(self, processed: Dict, stake_amount: float,
start_date, end_date,
start_date: arrow.Arrow, end_date: arrow.Arrow,
max_open_trades: int = 0, position_stacking: bool = False) -> DataFrame:
"""
Implement backtesting functionality
@@ -304,8 +289,9 @@ class Backtesting:
trades = []
trade_count_lock: Dict = {}
# Dict of ticker-lists for performance (looping lists is a lot faster than dataframes)
ticker: Dict = self._get_ticker_list(processed)
# Use dict of lists with data for performance
# (looping lists is a lot faster than pandas DataFrames)
data: Dict = self._get_ohlcv_as_lists(processed)
lock_pair_until: Dict = {}
# Indexes per pair, so some pairs are allowed to have a missing start.
@@ -315,12 +301,12 @@ class Backtesting:
# Loop timerange and get candle for each pair at that point in time
while tmp < end_date:
for i, pair in enumerate(ticker):
for i, pair in enumerate(data):
if pair not in indexes:
indexes[pair] = 0
try:
row = ticker[pair][indexes[pair]]
row = data[pair][indexes[pair]]
except IndexError:
# missing Data for one pair at the end.
# Warnings for this are shown during data loading
@@ -348,7 +334,7 @@ class Backtesting:
# since indexes has been incremented before, we need to go one step back to
# also check the buying candle for sell conditions.
trade_entry = self._get_sell_trade_entry(pair, row, ticker[pair][indexes[pair]-1:],
trade_entry = self._get_sell_trade_entry(pair, row, data[pair][indexes[pair]-1:],
trade_count_lock, stake_amount,
max_open_trades)
@@ -391,11 +377,11 @@ class Backtesting:
self._set_strategy(strat)
# need to reprocess data every time to populate signals
preprocessed = self.strategy.tickerdata_to_dataframe(data)
preprocessed = self.strategy.ohlcvdata_to_dataframe(data)
# Trim startup period from analyzed dataframe
for pair, df in preprocessed.items():
preprocessed[pair] = history.trim_dataframe(df, timerange)
preprocessed[pair] = trim_dataframe(df, timerange)
min_date, max_date = history.get_timerange(preprocessed)
logger.info(
@@ -404,40 +390,15 @@ class Backtesting:
)
# Execute backtest and print results
all_results[self.strategy.get_strategy_name()] = self.backtest(
processed=preprocessed,
stake_amount=self.config['stake_amount'],
start_date=min_date,
end_date=max_date,
max_open_trades=max_open_trades,
position_stacking=position_stacking,
processed=preprocessed,
stake_amount=self.config['stake_amount'],
start_date=min_date,
end_date=max_date,
max_open_trades=max_open_trades,
position_stacking=position_stacking,
)
for strategy, results in all_results.items():
if self.config.get('export', False):
self._store_backtest_result(Path(self.config['exportfilename']), results,
strategy if len(self.strategylist) > 1 else None)
print(f"Result for strategy {strategy}")
print(' BACKTESTING REPORT '.center(133, '='))
print(generate_text_table(data,
stake_currency=self.config['stake_currency'],
max_open_trades=self.config['max_open_trades'],
results=results))
print(' SELL REASON STATS '.center(133, '='))
print(generate_text_table_sell_reason(data, results))
print(' LEFT OPEN TRADES REPORT '.center(133, '='))
print(generate_text_table(data,
stake_currency=self.config['stake_currency'],
max_open_trades=self.config['max_open_trades'],
results=results.loc[results.open_at_end], skip_nan=True))
print()
if len(all_results) > 1:
# Print Strategy summary table
print(' Strategy Summary '.center(133, '='))
print(generate_text_table_strategy(self.config['stake_currency'],
self.config['max_open_trades'],
all_results=all_results))
print('\nFor more details, please look at the detail tables above')
if self.config.get('export', False):
store_backtest_result(self.config['exportfilename'], all_results)
# Show backtest results
show_backtest_results(self.config, data, all_results)

View File

@@ -9,6 +9,7 @@ import logging
import random
import sys
import warnings
from math import ceil
from collections import OrderedDict
from operator import itemgetter
from pathlib import Path
@@ -20,9 +21,13 @@ from colorama import Fore, Style
from colorama import init as colorama_init
from joblib import (Parallel, cpu_count, delayed, dump, load,
wrap_non_picklable_objects)
from pandas import DataFrame
from pandas import DataFrame, json_normalize, isna
import tabulate
from os import path
import io
from freqtrade.data.history import get_timerange, trim_dataframe
from freqtrade.data.converter import trim_dataframe
from freqtrade.data.history import get_timerange
from freqtrade.exceptions import OperationalException
from freqtrade.misc import plural, round_dict
from freqtrade.optimize.backtesting import Backtesting
@@ -59,6 +64,7 @@ class Hyperopt:
hyperopt = Hyperopt(config)
hyperopt.start()
"""
def __init__(self, config: Dict[str, Any]) -> None:
self.config = config
@@ -71,8 +77,8 @@ class Hyperopt:
self.trials_file = (self.config['user_data_dir'] /
'hyperopt_results' / 'hyperopt_results.pickle')
self.tickerdata_pickle = (self.config['user_data_dir'] /
'hyperopt_results' / 'hyperopt_tickerdata.pkl')
self.data_pickle_file = (self.config['user_data_dir'] /
'hyperopt_results' / 'hyperopt_tickerdata.pkl')
self.total_epochs = config.get('epochs', 0)
self.current_best_loss = 100
@@ -90,13 +96,13 @@ class Hyperopt:
# Populate functions here (hasattr is slow so should not be run during "regular" operations)
if hasattr(self.custom_hyperopt, 'populate_indicators'):
self.backtesting.strategy.advise_indicators = \
self.custom_hyperopt.populate_indicators # type: ignore
self.custom_hyperopt.populate_indicators # type: ignore
if hasattr(self.custom_hyperopt, 'populate_buy_trend'):
self.backtesting.strategy.advise_buy = \
self.custom_hyperopt.populate_buy_trend # type: ignore
self.custom_hyperopt.populate_buy_trend # type: ignore
if hasattr(self.custom_hyperopt, 'populate_sell_trend'):
self.backtesting.strategy.advise_sell = \
self.custom_hyperopt.populate_sell_trend # type: ignore
self.custom_hyperopt.populate_sell_trend # type: ignore
# Use max_open_trades for hyperopt as well, except --disable-max-market-positions is set
if self.config.get('use_max_market_positions', True):
@@ -113,19 +119,20 @@ class Hyperopt:
self.config['ask_strategy']['use_sell_signal'] = True
self.print_all = self.config.get('print_all', False)
self.hyperopt_table_header = 0
self.print_colorized = self.config.get('print_colorized', False)
self.print_json = self.config.get('print_json', False)
@staticmethod
def get_lock_filename(config) -> str:
def get_lock_filename(config: Dict[str, Any]) -> str:
return str(config['user_data_dir'] / 'hyperopt.lock')
def clean_hyperopt(self):
def clean_hyperopt(self) -> None:
"""
Remove hyperopt pickle files to restart hyperopt.
"""
for f in [self.tickerdata_pickle, self.trials_file]:
for f in [self.data_pickle_file, self.trials_file]:
p = Path(f)
if p.is_file():
logger.info(f"Removing `{p}`.")
@@ -150,7 +157,7 @@ class Hyperopt:
"""
num_trials = len(self.trials)
if num_trials > self.num_trials_saved:
logger.info(f"Saving {num_trials} {plural(num_trials, 'epoch')}.")
logger.debug(f"Saving {num_trials} {plural(num_trials, 'epoch')}.")
dump(self.trials, self.trials_file)
self.num_trials_saved = num_trials
if final:
@@ -158,7 +165,7 @@ class Hyperopt:
f"saved to '{self.trials_file}'.")
@staticmethod
def _read_trials(trials_file) -> List:
def _read_trials(trials_file: Path) -> List:
"""
Read hyperopt trials file
"""
@@ -189,7 +196,7 @@ class Hyperopt:
return result
@staticmethod
def print_epoch_details(results, total_epochs, print_json: bool,
def print_epoch_details(results, total_epochs: int, print_json: bool,
no_header: bool = False, header_str: str = None) -> None:
"""
Display details of the hyperopt result
@@ -218,7 +225,7 @@ class Hyperopt:
Hyperopt._params_pretty_print(params, 'trailing', "Trailing stop:")
@staticmethod
def _params_update_for_json(result_dict, params, space: str):
def _params_update_for_json(result_dict, params, space: str) -> None:
if space in params:
space_params = Hyperopt._space_params(params, space)
if space in ['buy', 'sell']:
@@ -235,7 +242,7 @@ class Hyperopt:
result_dict.update(space_params)
@staticmethod
def _params_pretty_print(params, space: str, header: str):
def _params_pretty_print(params, space: str, header: str) -> None:
if space in params:
space_params = Hyperopt._space_params(params, space, 5)
if space == 'stoploss':
@@ -251,7 +258,7 @@ class Hyperopt:
return round_dict(d, r) if r else d
@staticmethod
def is_best_loss(results, current_best_loss) -> bool:
def is_best_loss(results, current_best_loss: float) -> bool:
return results['loss'] < current_best_loss
def print_results(self, results) -> None:
@@ -269,8 +276,10 @@ class Hyperopt:
if not self.print_all:
# Separate the results explanation string from dots
print("\n")
self.print_results_explanation(results, self.total_epochs, self.print_all,
self.print_colorized)
self.print_result_table(self.config, results, self.total_epochs,
self.print_all, self.print_colorized,
self.hyperopt_table_header)
self.hyperopt_table_header = 2
@staticmethod
def print_results_explanation(results, total_epochs, highlight_best: bool,
@@ -294,6 +303,142 @@ class Hyperopt:
f"{results['results_explanation']} " +
f"Objective: {results['loss']:.5f}")
@staticmethod
def print_result_table(config: dict, results: list, total_epochs: int, highlight_best: bool,
print_colorized: bool, remove_header: int) -> None:
"""
Log result table
"""
if not results:
return
tabulate.PRESERVE_WHITESPACE = True
trials = json_normalize(results, max_level=1)
trials['Best'] = ''
trials = trials[['Best', 'current_epoch', 'results_metrics.trade_count',
'results_metrics.avg_profit', 'results_metrics.total_profit',
'results_metrics.profit', 'results_metrics.duration',
'loss', 'is_initial_point', 'is_best']]
trials.columns = ['Best', 'Epoch', 'Trades', 'Avg profit', 'Total profit',
'Profit', 'Avg duration', 'Objective', 'is_initial_point', 'is_best']
trials['is_profit'] = False
trials.loc[trials['is_initial_point'], 'Best'] = '*'
trials.loc[trials['is_best'], 'Best'] = 'Best'
trials.loc[trials['Total profit'] > 0, 'is_profit'] = True
trials['Trades'] = trials['Trades'].astype(str)
trials['Epoch'] = trials['Epoch'].apply(
lambda x: '{}/{}'.format(str(x).rjust(len(str(total_epochs)), ' '), total_epochs)
)
trials['Avg profit'] = trials['Avg profit'].apply(
lambda x: '{:,.2f}%'.format(x).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ')
)
trials['Avg duration'] = trials['Avg duration'].apply(
lambda x: '{:,.1f} m'.format(x).rjust(7, ' ') if not isna(x) else "--".rjust(7, ' ')
)
trials['Objective'] = trials['Objective'].apply(
lambda x: '{:,.5f}'.format(x).rjust(8, ' ') if x != 100000 else "N/A".rjust(8, ' ')
)
trials['Profit'] = trials.apply(
lambda x: '{:,.8f} {} {}'.format(
x['Total profit'], config['stake_currency'],
'({:,.2f}%)'.format(x['Profit']).rjust(10, ' ')
).rjust(25+len(config['stake_currency']))
if x['Total profit'] != 0.0 else '--'.rjust(25+len(config['stake_currency'])),
axis=1
)
trials = trials.drop(columns=['Total profit'])
if print_colorized:
for i in range(len(trials)):
if trials.loc[i]['is_profit']:
for j in range(len(trials.loc[i])-3):
trials.iat[i, j] = "{}{}{}".format(Fore.GREEN,
str(trials.loc[i][j]), Fore.RESET)
if trials.loc[i]['is_best'] and highlight_best:
for j in range(len(trials.loc[i])-3):
trials.iat[i, j] = "{}{}{}".format(Style.BRIGHT,
str(trials.loc[i][j]), Style.RESET_ALL)
trials = trials.drop(columns=['is_initial_point', 'is_best', 'is_profit'])
if remove_header > 0:
table = tabulate.tabulate(
trials.to_dict(orient='list'), tablefmt='orgtbl',
headers='keys', stralign="right"
)
table = table.split("\n", remove_header)[remove_header]
elif remove_header < 0:
table = tabulate.tabulate(
trials.to_dict(orient='list'), tablefmt='psql',
headers='keys', stralign="right"
)
table = "\n".join(table.split("\n")[0:remove_header])
else:
table = tabulate.tabulate(
trials.to_dict(orient='list'), tablefmt='psql',
headers='keys', stralign="right"
)
print(table)
@staticmethod
def export_csv_file(config: dict, results: list, total_epochs: int, highlight_best: bool,
csv_file: str) -> None:
"""
Log result to csv-file
"""
if not results:
return
# Verification for overwrite
if path.isfile(csv_file):
logger.error("CSV-File already exists!")
return
try:
io.open(csv_file, 'w+').close()
except IOError:
logger.error("Filed to create CSV-File!")
return
trials = json_normalize(results, max_level=1)
trials['Best'] = ''
trials['Stake currency'] = config['stake_currency']
trials = trials[['Best', 'current_epoch', 'results_metrics.trade_count',
'results_metrics.avg_profit', 'results_metrics.total_profit',
'Stake currency', 'results_metrics.profit', 'results_metrics.duration',
'loss', 'is_initial_point', 'is_best']]
trials.columns = ['Best', 'Epoch', 'Trades', 'Avg profit', 'Total profit', 'Stake currency',
'Profit', 'Avg duration', 'Objective', 'is_initial_point', 'is_best']
trials['is_profit'] = False
trials.loc[trials['is_initial_point'], 'Best'] = '*'
trials.loc[trials['is_best'], 'Best'] = 'Best'
trials.loc[trials['Total profit'] > 0, 'is_profit'] = True
trials['Epoch'] = trials['Epoch'].astype(str)
trials['Trades'] = trials['Trades'].astype(str)
trials['Total profit'] = trials['Total profit'].apply(
lambda x: '{:,.8f}'.format(x) if x != 0.0 else ""
)
trials['Profit'] = trials['Profit'].apply(
lambda x: '{:,.2f}'.format(x) if not isna(x) else ""
)
trials['Avg profit'] = trials['Avg profit'].apply(
lambda x: '{:,.2f}%'.format(x) if not isna(x) else ""
)
trials['Avg duration'] = trials['Avg duration'].apply(
lambda x: '{:,.1f} m'.format(x) if not isna(x) else ""
)
trials['Objective'] = trials['Objective'].apply(
lambda x: '{:,.5f}'.format(x) if x != 100000 else ""
)
trials = trials.drop(columns=['is_initial_point', 'is_best', 'is_profit'])
trials.to_csv(csv_file, index=False, header=True, mode='w', encoding='UTF-8')
print("CSV-File created!")
def has_space(self, space: str) -> bool:
"""
Tell if the space value is contained in the configuration
@@ -345,15 +490,15 @@ class Hyperopt:
if self.has_space('roi'):
self.backtesting.strategy.minimal_roi = \
self.custom_hyperopt.generate_roi_table(params_dict)
self.custom_hyperopt.generate_roi_table(params_dict)
if self.has_space('buy'):
self.backtesting.strategy.advise_buy = \
self.custom_hyperopt.buy_strategy_generator(params_dict)
self.custom_hyperopt.buy_strategy_generator(params_dict)
if self.has_space('sell'):
self.backtesting.strategy.advise_sell = \
self.custom_hyperopt.sell_strategy_generator(params_dict)
self.custom_hyperopt.sell_strategy_generator(params_dict)
if self.has_space('stoploss'):
self.backtesting.strategy.stoploss = params_dict['stoploss']
@@ -367,17 +512,17 @@ class Hyperopt:
self.backtesting.strategy.trailing_only_offset_is_reached = \
d['trailing_only_offset_is_reached']
processed = load(self.tickerdata_pickle)
processed = load(self.data_pickle_file)
min_date, max_date = get_timerange(processed)
backtesting_results = self.backtesting.backtest(
processed=processed,
stake_amount=self.config['stake_amount'],
start_date=min_date,
end_date=max_date,
max_open_trades=self.max_open_trades,
position_stacking=self.position_stacking,
processed=processed,
stake_amount=self.config['stake_amount'],
start_date=min_date,
end_date=max_date,
max_open_trades=self.max_open_trades,
position_stacking=self.position_stacking,
)
return self._get_results_dict(backtesting_results, min_date, max_date,
params_dict, params_details)
@@ -438,7 +583,7 @@ class Hyperopt:
random_state=self.random_state,
)
def fix_optimizer_models_list(self):
def fix_optimizer_models_list(self) -> None:
"""
WORKAROUND: Since skopt is not actively supported, this resolves problems with skopt
memory usage, see also: https://github.com/scikit-optimize/scikit-optimize/pull/746
@@ -460,7 +605,7 @@ class Hyperopt:
wrap_non_picklable_objects(self.generate_optimizer))(v, i) for v in asked)
@staticmethod
def load_previous_results(trials_file) -> List:
def load_previous_results(trials_file: Path) -> List:
"""
Load data for epochs from the file if we have one
"""
@@ -469,8 +614,8 @@ class Hyperopt:
trials = Hyperopt._read_trials(trials_file)
if trials[0].get('is_best') is None:
raise OperationalException(
"The file with Hyperopt results is incompatible with this version "
"of Freqtrade and cannot be loaded.")
"The file with Hyperopt results is incompatible with this version "
"of Freqtrade and cannot be loaded.")
logger.info(f"Loaded {len(trials)} previous evaluations from disk.")
return trials
@@ -480,10 +625,10 @@ class Hyperopt:
def start(self) -> None:
self.random_state = self._set_random_state(self.config.get('hyperopt_random_state', None))
logger.info(f"Using optimizer random state: {self.random_state}")
self.hyperopt_table_header = -1
data, timerange = self.backtesting.load_bt_data()
preprocessed = self.backtesting.strategy.tickerdata_to_dataframe(data)
preprocessed = self.backtesting.strategy.ohlcvdata_to_dataframe(data)
# Trim startup period from analyzed dataframe
for pair, df in preprocessed.items():
@@ -494,7 +639,7 @@ class Hyperopt:
'Hyperopting with data from %s up to %s (%s days)..',
min_date.isoformat(), max_date.isoformat(), (max_date - min_date).days
)
dump(preprocessed, self.tickerdata_pickle)
dump(preprocessed, self.data_pickle_file)
# We don't need exchange instance anymore while running hyperopt
self.backtesting.exchange = None # type: ignore
@@ -516,16 +661,21 @@ class Hyperopt:
with Parallel(n_jobs=config_jobs) as parallel:
jobs = parallel._effective_n_jobs()
logger.info(f'Effective number of parallel workers used: {jobs}')
EVALS = max(self.total_epochs // jobs, 1)
EVALS = ceil(self.total_epochs / jobs)
for i in range(EVALS):
asked = self.opt.ask(n_points=jobs)
# Correct the number of epochs to be processed for the last
# iteration (should not exceed self.total_epochs in total)
n_rest = (i + 1) * jobs - self.total_epochs
current_jobs = jobs - n_rest if n_rest > 0 else jobs
asked = self.opt.ask(n_points=current_jobs)
f_val = self.run_optimizer_parallel(parallel, asked, i)
self.opt.tell(asked, [v['loss'] for v in f_val])
self.fix_optimizer_models_list()
for j in range(jobs):
for j, val in enumerate(f_val):
# Use human-friendly indexes here (starting from 1)
current = i * jobs + j + 1
val = f_val[j]
val['current_epoch'] = current
val['is_initial_point'] = current <= INITIAL_POINTS
logger.debug(f"Optimizer epoch evaluated: {val}")

View File

@@ -207,7 +207,7 @@ class IHyperOpt(ABC):
# so this intermediate parameter is used as the value of the difference between
# them. The value of the 'trailing_stop_positive_offset' is constructed in the
# generate_trailing_params() method.
# # This is similar to the hyperspace dimensions used for constructing the ROI tables.
# This is similar to the hyperspace dimensions used for constructing the ROI tables.
Real(0.001, 0.1, name='trailing_stop_positive_offset_p1'),
Categorical([True, False], name='trailing_only_offset_is_reached'),

View File

@@ -28,18 +28,19 @@ class SharpeHyperOptLoss(IHyperOptLoss):
Uses Sharpe Ratio calculation.
"""
total_profit = results.profit_percent
total_profit = results["profit_percent"]
days_period = (max_date - min_date).days
# adding slippage of 0.1% per trade
total_profit = total_profit - 0.0005
expected_yearly_return = total_profit.sum() / days_period
expected_returns_mean = total_profit.sum() / days_period
up_stdev = np.std(total_profit)
if (np.std(total_profit) != 0.):
sharp_ratio = expected_yearly_return / np.std(total_profit) * np.sqrt(365)
if up_stdev != 0:
sharp_ratio = expected_returns_mean / up_stdev * np.sqrt(365)
else:
# Define high (negative) sharpe ratio to be clear that this is NOT optimal.
sharp_ratio = -20.
# print(expected_yearly_return, np.std(total_profit), sharp_ratio)
# print(expected_returns_mean, up_stdev, sharp_ratio)
return -sharp_ratio

View File

@@ -0,0 +1,62 @@
"""
SharpeHyperOptLossDaily
This module defines the alternative HyperOptLoss class which can be used for
Hyperoptimization.
"""
import math
from datetime import datetime
from pandas import DataFrame, date_range
from freqtrade.optimize.hyperopt import IHyperOptLoss
class SharpeHyperOptLossDaily(IHyperOptLoss):
"""
Defines the loss function for hyperopt.
This implementation uses the Sharpe Ratio calculation.
"""
@staticmethod
def hyperopt_loss_function(results: DataFrame, trade_count: int,
min_date: datetime, max_date: datetime,
*args, **kwargs) -> float:
"""
Objective function, returns smaller number for more optimal results.
Uses Sharpe Ratio calculation.
"""
resample_freq = '1D'
slippage_per_trade_ratio = 0.0005
days_in_year = 365
annual_risk_free_rate = 0.0
risk_free_rate = annual_risk_free_rate / days_in_year
# apply slippage per trade to profit_percent
results.loc[:, 'profit_percent_after_slippage'] = \
results['profit_percent'] - slippage_per_trade_ratio
# create the index within the min_date and end max_date
t_index = date_range(start=min_date, end=max_date, freq=resample_freq,
normalize=True)
sum_daily = (
results.resample(resample_freq, on='close_time').agg(
{"profit_percent_after_slippage": sum}).reindex(t_index).fillna(0)
)
total_profit = sum_daily["profit_percent_after_slippage"] - risk_free_rate
expected_returns_mean = total_profit.mean()
up_stdev = total_profit.std()
if up_stdev != 0:
sharp_ratio = expected_returns_mean / up_stdev * math.sqrt(days_in_year)
else:
# Define high (negative) sharpe ratio to be clear that this is NOT optimal.
sharp_ratio = -20.
# print(t_index, sum_daily, total_profit)
# print(risk_free_rate, expected_returns_mean, up_stdev, sharp_ratio)
return -sharp_ratio

View File

@@ -0,0 +1,49 @@
"""
SortinoHyperOptLoss
This module defines the alternative HyperOptLoss class which can be used for
Hyperoptimization.
"""
from datetime import datetime
from pandas import DataFrame
import numpy as np
from freqtrade.optimize.hyperopt import IHyperOptLoss
class SortinoHyperOptLoss(IHyperOptLoss):
"""
Defines the loss function for hyperopt.
This implementation uses the Sortino Ratio calculation.
"""
@staticmethod
def hyperopt_loss_function(results: DataFrame, trade_count: int,
min_date: datetime, max_date: datetime,
*args, **kwargs) -> float:
"""
Objective function, returns smaller number for more optimal results.
Uses Sortino Ratio calculation.
"""
total_profit = results["profit_percent"]
days_period = (max_date - min_date).days
# adding slippage of 0.1% per trade
total_profit = total_profit - 0.0005
expected_returns_mean = total_profit.sum() / days_period
results['downside_returns'] = 0
results.loc[total_profit < 0, 'downside_returns'] = results['profit_percent']
down_stdev = np.std(results['downside_returns'])
if down_stdev != 0:
sortino_ratio = expected_returns_mean / down_stdev * np.sqrt(365)
else:
# Define high (negative) sortino ratio to be clear that this is NOT optimal.
sortino_ratio = -20.
# print(expected_returns_mean, down_stdev, sortino_ratio)
return -sortino_ratio

View File

@@ -0,0 +1,70 @@
"""
SortinoHyperOptLossDaily
This module defines the alternative HyperOptLoss class which can be used for
Hyperoptimization.
"""
import math
from datetime import datetime
from pandas import DataFrame, date_range
from freqtrade.optimize.hyperopt import IHyperOptLoss
class SortinoHyperOptLossDaily(IHyperOptLoss):
"""
Defines the loss function for hyperopt.
This implementation uses the Sortino Ratio calculation.
"""
@staticmethod
def hyperopt_loss_function(results: DataFrame, trade_count: int,
min_date: datetime, max_date: datetime,
*args, **kwargs) -> float:
"""
Objective function, returns smaller number for more optimal results.
Uses Sortino Ratio calculation.
Sortino Ratio calculated as described in
http://www.redrockcapital.com/Sortino__A__Sharper__Ratio_Red_Rock_Capital.pdf
"""
resample_freq = '1D'
slippage_per_trade_ratio = 0.0005
days_in_year = 365
minimum_acceptable_return = 0.0
# apply slippage per trade to profit_percent
results.loc[:, 'profit_percent_after_slippage'] = \
results['profit_percent'] - slippage_per_trade_ratio
# create the index within the min_date and end max_date
t_index = date_range(start=min_date, end=max_date, freq=resample_freq,
normalize=True)
sum_daily = (
results.resample(resample_freq, on='close_time').agg(
{"profit_percent_after_slippage": sum}).reindex(t_index).fillna(0)
)
total_profit = sum_daily["profit_percent_after_slippage"] - minimum_acceptable_return
expected_returns_mean = total_profit.mean()
sum_daily['downside_returns'] = 0
sum_daily.loc[total_profit < 0, 'downside_returns'] = total_profit
total_downside = sum_daily['downside_returns']
# Here total_downside contains min(0, P - MAR) values,
# where P = sum_daily["profit_percent_after_slippage"]
down_stdev = math.sqrt((total_downside**2).sum() / len(total_downside))
if down_stdev != 0:
sortino_ratio = expected_returns_mean / down_stdev * math.sqrt(days_in_year)
else:
# Define high (negative) sortino ratio to be clear that this is NOT optimal.
sortino_ratio = -20.
# print(t_index, sum_daily, total_profit)
# print(minimum_acceptable_return, expected_returns_mean, down_stdev, sortino_ratio)
return -sortino_ratio

View File

@@ -1,9 +1,37 @@
import logging
from datetime import timedelta
from pathlib import Path
from typing import Dict
from pandas import DataFrame
from tabulate import tabulate
from freqtrade.misc import file_dump_json
logger = logging.getLogger(__name__)
def store_backtest_result(recordfilename: Path, all_results: Dict[str, DataFrame]) -> None:
"""
Stores backtest results to file (one file per strategy)
:param recordfilename: Destination filename
:param all_results: Dict of Dataframes, one results dataframe per strategy
"""
for strategy, results in all_results.items():
records = [(t.pair, t.profit_percent, t.open_time.timestamp(),
t.close_time.timestamp(), t.open_index - 1, t.trade_duration,
t.open_rate, t.close_rate, t.open_at_end, t.sell_reason.value)
for index, t in results.iterrows()]
if records:
if len(all_results) > 1:
# Inject strategy to filename
recordfilename = Path.joinpath(
recordfilename.parent,
f'{recordfilename.stem}-{strategy}').with_suffix(recordfilename.suffix)
logger.info(f'Dumping backtest results to {recordfilename}')
file_dump_json(recordfilename, records)
def generate_text_table(data: Dict[str, Dict], stake_currency: str, max_open_trades: int,
results: DataFrame, skip_nan: bool = False) -> str:
@@ -19,9 +47,18 @@ def generate_text_table(data: Dict[str, Dict], stake_currency: str, max_open_tra
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', '.2f', 'd', '.1f', '.1f')
tabular_data = []
headers = ['pair', 'buy count', 'avg profit %', 'cum profit %',
f'tot profit {stake_currency}', 'tot profit %', 'avg duration',
'profit', 'loss']
headers = [
'Pair',
'Buys',
'Avg Profit %',
'Cum Profit %',
f'Tot Profit {stake_currency}',
'Tot Profit %',
'Avg Duration',
'Wins',
'Draws',
'Losses'
]
for pair in data:
result = results[results.pair == pair]
if skip_nan and result.profit_abs.isnull().all():
@@ -37,6 +74,7 @@ def generate_text_table(data: Dict[str, Dict], stake_currency: str, max_open_tra
str(timedelta(
minutes=round(result.trade_duration.mean()))) if not result.empty else '0:00',
len(result[result.profit_abs > 0]),
len(result[result.profit_abs == 0]),
len(result[result.profit_abs < 0])
])
@@ -51,29 +89,58 @@ def generate_text_table(data: Dict[str, Dict], stake_currency: str, max_open_tra
str(timedelta(
minutes=round(results.trade_duration.mean()))) if not results.empty else '0:00',
len(results[results.profit_abs > 0]),
len(results[results.profit_abs == 0]),
len(results[results.profit_abs < 0])
])
# Ignore type as floatfmt does allow tuples but mypy does not know that
return tabulate(tabular_data, headers=headers,
floatfmt=floatfmt, tablefmt="pipe") # type: ignore
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore
def generate_text_table_sell_reason(data: Dict[str, Dict], results: DataFrame) -> str:
def generate_text_table_sell_reason(stake_currency: str, max_open_trades: int,
results: DataFrame) -> str:
"""
Generate small table outlining Backtest results
:param data: Dict of <pair: dataframe> containing data that was used during backtesting.
:param stake_currency: Stakecurrency used
:param max_open_trades: Max_open_trades parameter
:param results: Dataframe containing the backtest results
:return: pretty printed table with tabulate as string
"""
tabular_data = []
headers = ['Sell Reason', 'Count', 'Profit', 'Loss', 'Profit %']
headers = [
"Sell Reason",
"Sells",
"Wins",
"Draws",
"Losses",
"Avg Profit %",
"Cum Profit %",
f"Tot Profit {stake_currency}",
"Tot Profit %",
]
for reason, count in results['sell_reason'].value_counts().iteritems():
result = results.loc[results['sell_reason'] == reason]
profit = len(result[result['profit_abs'] >= 0])
wins = len(result[result['profit_abs'] > 0])
draws = len(result[result['profit_abs'] == 0])
loss = len(result[result['profit_abs'] < 0])
profit_mean = round(result['profit_percent'].mean() * 100.0, 2)
tabular_data.append([reason.value, count, profit, loss, profit_mean])
return tabulate(tabular_data, headers=headers, tablefmt="pipe")
profit_sum = round(result["profit_percent"].sum() * 100.0, 2)
profit_tot = result['profit_abs'].sum()
profit_percent_tot = round(result['profit_percent'].sum() * 100.0 / max_open_trades, 2)
tabular_data.append(
[
reason.value,
count,
wins,
draws,
loss,
profit_mean,
profit_sum,
profit_tot,
profit_percent_tot,
]
)
return tabulate(tabular_data, headers=headers, tablefmt="orgtbl", stralign="right")
def generate_text_table_strategy(stake_currency: str, max_open_trades: str,
@@ -88,9 +155,9 @@ def generate_text_table_strategy(stake_currency: str, max_open_trades: str,
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', '.2f', 'd', '.1f', '.1f')
tabular_data = []
headers = ['Strategy', 'buy count', 'avg profit %', 'cum profit %',
f'tot profit {stake_currency}', 'tot profit %', 'avg duration',
'profit', 'loss']
headers = ['Strategy', 'Buys', 'Avg Profit %', 'Cum Profit %',
f'Tot Profit {stake_currency}', 'Tot Profit %', 'Avg Duration',
'Wins', 'Draws', 'Losses']
for strategy, results in all_results.items():
tabular_data.append([
strategy,
@@ -102,20 +169,21 @@ def generate_text_table_strategy(stake_currency: str, max_open_trades: str,
str(timedelta(
minutes=round(results.trade_duration.mean()))) if not results.empty else '0:00',
len(results[results.profit_abs > 0]),
len(results[results.profit_abs == 0]),
len(results[results.profit_abs < 0])
])
# Ignore type as floatfmt does allow tuples but mypy does not know that
return tabulate(tabular_data, headers=headers,
floatfmt=floatfmt, tablefmt="pipe") # type: ignore
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore
def generate_edge_table(results: dict) -> str:
floatfmt = ('s', '.10g', '.2f', '.2f', '.2f', '.2f', 'd', '.d')
tabular_data = []
headers = ['pair', 'stoploss', 'win rate', 'risk reward ratio',
'required risk reward', 'expectancy', 'total number of trades',
'average duration (min)']
headers = ['Pair', 'Stoploss', 'Win Rate', 'Risk Reward Ratio',
'Required Risk Reward', 'Expectancy', 'Total Number of Trades',
'Average Duration (min)']
for result in results.items():
if result[1].nb_trades > 0:
@@ -132,4 +200,44 @@ def generate_edge_table(results: dict) -> str:
# Ignore type as floatfmt does allow tuples but mypy does not know that
return tabulate(tabular_data, headers=headers,
floatfmt=floatfmt, tablefmt="pipe") # type: ignore
floatfmt=floatfmt, tablefmt="orgtbl", stralign="right") # type: ignore
def show_backtest_results(config: Dict, btdata: Dict[str, DataFrame],
all_results: Dict[str, DataFrame]):
for strategy, results in all_results.items():
print(f"Result for strategy {strategy}")
table = generate_text_table(btdata, stake_currency=config['stake_currency'],
max_open_trades=config['max_open_trades'],
results=results)
if isinstance(table, str):
print(' BACKTESTING REPORT '.center(len(table.splitlines()[0]), '='))
print(table)
table = generate_text_table_sell_reason(stake_currency=config['stake_currency'],
max_open_trades=config['max_open_trades'],
results=results)
if isinstance(table, str):
print(' SELL REASON STATS '.center(len(table.splitlines()[0]), '='))
print(table)
table = generate_text_table(btdata,
stake_currency=config['stake_currency'],
max_open_trades=config['max_open_trades'],
results=results.loc[results.open_at_end], skip_nan=True)
if isinstance(table, str):
print(' LEFT OPEN TRADES REPORT '.center(len(table.splitlines()[0]), '='))
print(table)
if isinstance(table, str):
print('=' * len(table.splitlines()[0]))
print()
if len(all_results) > 1:
# Print Strategy summary table
table = generate_text_table_strategy(config['stake_currency'],
config['max_open_trades'],
all_results=all_results)
print(' STRATEGY SUMMARY '.center(len(table.splitlines()[0]), '='))
print(table)
print('=' * len(table.splitlines()[0]))
print('\nFor more details, please look at the detail tables above')

View File

@@ -7,7 +7,7 @@ Provides lists as configured in config.json
import logging
from abc import ABC, abstractmethod, abstractproperty
from copy import deepcopy
from typing import Dict, List
from typing import Any, Dict, List
from freqtrade.exchange import market_is_active
@@ -16,7 +16,8 @@ logger = logging.getLogger(__name__)
class IPairList(ABC):
def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict,
def __init__(self, exchange, pairlistmanager,
config: Dict[str, Any], pairlistconfig: Dict[str, Any],
pairlist_pos: int) -> None:
"""
:param exchange: Exchange instance
@@ -66,21 +67,37 @@ class IPairList(ABC):
"""
@staticmethod
def verify_blacklist(pairlist: List[str], blacklist: List[str]) -> List[str]:
def verify_blacklist(pairlist: List[str], blacklist: List[str],
aswarning: bool) -> List[str]:
"""
Verify and remove items from pairlist - returning a filtered pairlist.
Logs a warning or info depending on `aswarning`.
Pairlists explicitly using this method shall use `aswarning=False`!
:param pairlist: Pairlist to validate
:param blacklist: Blacklist to validate pairlist against
:param aswarning: Log message as Warning or info
:return: pairlist - blacklisted pairs
"""
for pair in deepcopy(pairlist):
if pair in blacklist:
logger.warning(f"Pair {pair} in your blacklist. Removing it from whitelist...")
if aswarning:
logger.warning(f"Pair {pair} in your blacklist. Removing it from whitelist...")
else:
logger.info(f"Pair {pair} in your blacklist. Removing it from whitelist...")
pairlist.remove(pair)
return pairlist
def _verify_blacklist(self, pairlist: List[str]) -> List[str]:
def _verify_blacklist(self, pairlist: List[str], aswarning: bool = True) -> List[str]:
"""
Proxy method to verify_blacklist for easy access for child classes.
Logs a warning or info depending on `aswarning`.
Pairlists explicitly using this method shall use aswarning=False!
:param pairlist: Pairlist to validate
:param aswarning: Log message as Warning or info.
:return: pairlist - blacklisted pairs
"""
return IPairList.verify_blacklist(pairlist, self._pairlistmanager.blacklist)
return IPairList.verify_blacklist(pairlist, self._pairlistmanager.blacklist,
aswarning=aswarning)
def _whitelist_for_active_markets(self, pairlist: List[str]) -> List[str]:
"""
@@ -98,7 +115,8 @@ class IPairList(ABC):
logger.warning(f"Pair {pair} is not compatible with exchange "
f"{self._exchange.name}. Removing it from whitelist..")
continue
if not pair.endswith(self._config['stake_currency']):
if self._exchange.get_pair_quote_currency(pair) != self._config['stake_currency']:
logger.warning(f"Pair {pair} is not compatible with your stake currency "
f"{self._config['stake_currency']}. Removing it from whitelist..")
continue
@@ -111,6 +129,5 @@ class IPairList(ABC):
if pair not in sanitized_whitelist:
sanitized_whitelist.append(pair)
sanitized_whitelist = self._verify_blacklist(sanitized_whitelist)
# We need to remove pairs that are unknown
return sanitized_whitelist

View File

@@ -48,10 +48,10 @@ class PrecisionFilter(IPairList):
"""
Filters and sorts pairlists and assigns and returns them again.
"""
stoploss = None
if self._config.get('stoploss') is not None:
stoploss = self._config.get('stoploss')
if stoploss is not None:
# Precalculate sanitized stoploss value to avoid recalculation for every pair
stoploss = 1 - abs(self._config.get('stoploss'))
stoploss = 1 - abs(stoploss)
# Copy list since we're modifying this list
for p in deepcopy(pairlist):
ticker = tickers.get(p)

View File

@@ -1,6 +1,6 @@
import logging
from copy import deepcopy
from typing import Dict, List
from typing import Any, Dict, List
from freqtrade.pairlist.IPairList import IPairList
@@ -9,7 +9,8 @@ logger = logging.getLogger(__name__)
class PriceFilter(IPairList):
def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict,
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)

View File

@@ -0,0 +1,59 @@
import logging
from copy import deepcopy
from typing import Dict, List
from freqtrade.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__)
class SpreadFilter(IPairList):
def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict,
pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
self._max_spread_ratio = pairlistconfig.get('max_spread_ratio', 0.005)
@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 True
def short_desc(self) -> str:
"""
Short whitelist method description - used for startup-messages
"""
return (f"{self.name} - Filtering pairs with ask/bid diff above "
f"{self._max_spread_ratio * 100}%.")
def filter_pairlist(self, pairlist: List[str], tickers: Dict) -> List[str]:
"""
Filters and sorts pairlist and returns the whitelist 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 whitelist
"""
# Copy list since we're modifying this list
spread = None
for p in deepcopy(pairlist):
ticker = tickers.get(p)
assert ticker is not None
if 'bid' in ticker and 'ask' in ticker:
spread = 1 - ticker['bid'] / ticker['ask']
if not ticker or spread > self._max_spread_ratio:
logger.info(f"Removed {ticker['symbol']} from whitelist, "
f"because spread {spread * 100:.3f}% >"
f"{self._max_spread_ratio * 100}%")
pairlist.remove(p)
else:
pairlist.remove(p)
return pairlist

View File

@@ -6,7 +6,7 @@ Provides lists as configured in config.json
"""
import logging
from datetime import datetime
from typing import Dict, List
from typing import Any, Dict, List
from freqtrade.exceptions import OperationalException
from freqtrade.pairlist.IPairList import IPairList
@@ -18,7 +18,7 @@ SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume']
class VolumePairList(IPairList):
def __init__(self, exchange, pairlistmanager, config, pairlistconfig: dict,
def __init__(self, exchange, pairlistmanager, config: Dict[str, Any], pairlistconfig: dict,
pairlist_pos: int) -> None:
super().__init__(exchange, pairlistmanager, config, pairlistconfig, pairlist_pos)
@@ -28,6 +28,7 @@ class VolumePairList(IPairList):
'for "pairlist.config.number_assets"')
self._number_pairs = self._pairlistconfig['number_assets']
self._sort_key = self._pairlistconfig.get('sort_key', 'quoteVolume')
self._min_value = self._pairlistconfig.get('min_value', 0)
self.refresh_period = self._pairlistconfig.get('refresh_period', 1800)
if not self._exchange.exchange_has('fetchTickers'):
@@ -73,11 +74,13 @@ class VolumePairList(IPairList):
tickers,
self._config['stake_currency'],
self._sort_key,
self._min_value
)
else:
return pairlist
def _gen_pair_whitelist(self, pairlist, tickers, base_currency: str, key: str) -> List[str]:
def _gen_pair_whitelist(self, pairlist: List[str], tickers: Dict,
base_currency: str, key: str, min_val: int) -> List[str]:
"""
Updates the whitelist with with a dynamically generated list
:param base_currency: base currency as str
@@ -88,19 +91,22 @@ class VolumePairList(IPairList):
if self._pairlist_pos == 0:
# If VolumePairList is the first in the list, use fresh pairlist
# check length so that we make sure that '/' is actually in the string
# Check if pair quote currency equals to the stake currency.
filtered_tickers = [v for k, v in tickers.items()
if (len(k.split('/')) == 2 and k.split('/')[1] == base_currency
if (self._exchange.get_pair_quote_currency(k) == base_currency
and v[key] is not None)]
else:
# If other pairlist is in front, use the incomming pairlist.
filtered_tickers = [v for k, v in tickers.items() if k in pairlist]
if min_val > 0:
filtered_tickers = list(filter(lambda t: t[key] > min_val, filtered_tickers))
sorted_tickers = sorted(filtered_tickers, reverse=True, key=lambda t: t[key])
# Validate whitelist to only have active market pairs
pairs = self._whitelist_for_active_markets([s['symbol'] for s in sorted_tickers])
pairs = self._verify_blacklist(pairs)
pairs = self._verify_blacklist(pairs, aswarning=False)
# Limit to X number of pairs
pairs = pairs[:self._number_pairs]
logger.info(f"Searching {self._number_pairs} pairs: {pairs}")

View File

@@ -91,6 +91,6 @@ class PairListManager():
pairlist = pl.filter_pairlist(pairlist, tickers)
# Validation against blacklist happens after the pairlists to ensure blacklist is respected.
pairlist = IPairList.verify_blacklist(pairlist, self.blacklist)
pairlist = IPairList.verify_blacklist(pairlist, self.blacklist, True)
self._whitelist = pairlist

View File

@@ -64,11 +64,11 @@ def init(db_url: str, clean_open_orders: bool = False) -> None:
clean_dry_run_db()
def has_column(columns, searchname: str) -> bool:
def has_column(columns: List, searchname: str) -> bool:
return len(list(filter(lambda x: x["name"] == searchname, columns))) == 1
def get_column_def(columns, column: str, default: str) -> str:
def get_column_def(columns: List, column: str, default: str) -> str:
return default if not has_column(columns, column) else column
@@ -86,7 +86,7 @@ def check_migrate(engine) -> None:
logger.debug(f'trying {table_back_name}')
# Check for latest column
if not has_column(cols, 'open_trade_price'):
if not has_column(cols, 'close_profit_abs'):
logger.info(f'Running database migration - backup available as {table_back_name}')
fee_open = get_column_def(cols, 'fee_open', 'fee')
@@ -106,6 +106,9 @@ def check_migrate(engine) -> None:
ticker_interval = get_column_def(cols, 'ticker_interval', 'null')
open_trade_price = get_column_def(cols, 'open_trade_price',
f'amount * open_rate * (1 + {fee_open})')
close_profit_abs = get_column_def(
cols, 'close_profit_abs',
f"(amount * close_rate * (1 - {fee_close})) - {open_trade_price}")
# Schema migration necessary
engine.execute(f"alter table trades rename to {table_back_name}")
@@ -123,7 +126,7 @@ def check_migrate(engine) -> None:
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
stoploss_order_id, stoploss_last_update,
max_rate, min_rate, sell_reason, strategy,
ticker_interval, open_trade_price
ticker_interval, open_trade_price, close_profit_abs
)
select id, lower(exchange),
case
@@ -143,7 +146,7 @@ def check_migrate(engine) -> None:
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
{max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason,
{strategy} strategy, {ticker_interval} ticker_interval,
{open_trade_price} open_trade_price
{open_trade_price} open_trade_price, {close_profit_abs} close_profit_abs
from {table_back_name}
""")
@@ -190,6 +193,7 @@ class Trade(_DECL_BASE):
close_rate = Column(Float)
close_rate_requested = Column(Float)
close_profit = Column(Float)
close_profit_abs = Column(Float)
stake_amount = Column(Float, nullable=False)
amount = Column(Float)
open_date = Column(DateTime, nullable=False, default=datetime.utcnow)
@@ -246,14 +250,15 @@ class Trade(_DECL_BASE):
if self.initial_stop_loss_pct else None),
}
def adjust_min_max_rates(self, current_price: float):
def adjust_min_max_rates(self, current_price: float) -> None:
"""
Adjust the max_rate and min_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)
def adjust_stop_loss(self, current_price: float, stoploss: float, initial: bool = False):
def adjust_stop_loss(self, current_price: float, stoploss: float,
initial: bool = False) -> None:
"""
This adjusts the stop loss to it's most recently observed setting
:param current_price: Current rate the asset is traded
@@ -317,10 +322,10 @@ class Trade(_DECL_BASE):
elif order_type in ('market', 'limit') and order['side'] == 'sell':
self.close(order['price'])
logger.info('%s_SELL has been fulfilled for %s.', order_type.upper(), self)
elif order_type == 'stop_loss_limit':
elif order_type in ('stop_loss_limit', 'stop-loss'):
self.stoploss_order_id = None
self.close_rate_requested = self.stop_loss
logger.info('STOP_LOSS_LIMIT is hit for %s.', self)
logger.info('%s is hit for %s.', order_type.upper(), self)
self.close(order['average'])
else:
raise ValueError(f'Unknown order type: {order_type}')
@@ -333,6 +338,7 @@ class Trade(_DECL_BASE):
"""
self.close_rate = Decimal(rate)
self.close_profit = self.calc_profit_ratio()
self.close_profit_abs = self.calc_profit()
self.close_date = datetime.utcnow()
self.is_open = False
self.open_order_id = None
@@ -404,8 +410,8 @@ class Trade(_DECL_BASE):
rate=(rate or self.close_rate),
fee=(fee or self.fee_close)
)
profit_percent = (close_trade_price / self.open_trade_price) - 1
return float(f"{profit_percent:.8f}")
profit_ratio = (close_trade_price / self.open_trade_price) - 1
return float(f"{profit_ratio:.8f}")
@staticmethod
def get_trades(trade_filter=None) -> Query:

View File

@@ -3,11 +3,15 @@ from pathlib import Path
from typing import Any, Dict, List
import pandas as pd
from freqtrade.configuration import TimeRange
from freqtrade.data import history
from freqtrade.data.btanalysis import (combine_tickers_with_mean,
from freqtrade.data.btanalysis import (calculate_max_drawdown,
combine_dataframes_with_mean,
create_cum_profit,
extract_trades_of_period, load_trades)
from freqtrade.data.converter import trim_dataframe
from freqtrade.data.history import load_data
from freqtrade.misc import pair_to_filename
from freqtrade.resolvers import StrategyResolver
logger = logging.getLogger(__name__)
@@ -25,7 +29,7 @@ except ImportError:
def init_plotscript(config):
"""
Initialize objects needed for plotting
:return: Dict with tickers, trades and pairs
:return: Dict with candle (OHLCV) data, trades and pairs
"""
if "pairs" in config:
@@ -36,19 +40,30 @@ def init_plotscript(config):
# Set timerange to use
timerange = TimeRange.parse_timerange(config.get("timerange"))
tickers = history.load_data(
data = load_data(
datadir=config.get("datadir"),
pairs=pairs,
timeframe=config.get('ticker_interval', '5m'),
timerange=timerange,
data_format=config.get('dataformat_ohlcv', 'json'),
)
trades = load_trades(config['trade_source'],
db_url=config.get('db_url'),
exportfilename=config.get('exportfilename'),
)
trades = history.trim_dataframe(trades, timerange, 'open_time')
return {"tickers": tickers,
no_trades = False
if config.get('no_trades', False):
no_trades = True
elif not config['exportfilename'].is_file() and config['trade_source'] == 'file':
logger.warning("Backtest file is missing skipping trades.")
no_trades = True
trades = load_trades(
config['trade_source'],
db_url=config.get('db_url'),
exportfilename=config.get('exportfilename'),
no_trades=no_trades
)
trades = trim_dataframe(trades, timerange, 'open_time')
return {"ohlcv": data,
"trades": trades,
"pairs": pairs,
}
@@ -107,6 +122,36 @@ def add_profit(fig, row, data: pd.DataFrame, column: str, name: str) -> make_sub
return fig
def add_max_drawdown(fig, row, trades: pd.DataFrame, df_comb: pd.DataFrame) -> make_subplots:
"""
Add scatter points indicating max drawdown
"""
try:
max_drawdown, highdate, lowdate = calculate_max_drawdown(trades)
drawdown = go.Scatter(
x=[highdate, lowdate],
y=[
df_comb.loc[highdate, 'cum_profit'],
df_comb.loc[lowdate, 'cum_profit'],
],
mode='markers',
name=f"Max drawdown {max_drawdown:.2f}%",
text=f"Max drawdown {max_drawdown:.2f}%",
marker=dict(
symbol='square-open',
size=9,
line=dict(width=2),
color='green'
)
)
fig.add_trace(drawdown, row, 1)
except ValueError:
logger.warning("No trades found - not plotting max drawdown.")
return fig
def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
"""
Add trades to "fig"
@@ -333,10 +378,10 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
return fig
def generate_profit_graph(pairs: str, tickers: Dict[str, pd.DataFrame],
def generate_profit_graph(pairs: str, data: Dict[str, pd.DataFrame],
trades: pd.DataFrame, timeframe: str) -> go.Figure:
# Combine close-values for all pairs, rename columns to "pair"
df_comb = combine_tickers_with_mean(tickers, "close")
df_comb = combine_dataframes_with_mean(data, "close")
# Add combined cumulative profit
df_comb = create_cum_profit(df_comb, trades, 'cum_profit', timeframe)
@@ -360,6 +405,7 @@ def generate_profit_graph(pairs: str, tickers: Dict[str, pd.DataFrame],
fig.add_trace(avgclose, 1, 1)
fig = add_profit(fig, 2, df_comb, 'cum_profit', 'Profit')
fig = add_max_drawdown(fig, 2, trades, df_comb)
for pair in pairs:
profit_col = f'cum_profit_{pair}'
@@ -370,12 +416,12 @@ def generate_profit_graph(pairs: str, tickers: Dict[str, pd.DataFrame],
return fig
def generate_plot_filename(pair, timeframe) -> str:
def generate_plot_filename(pair: str, timeframe: str) -> str:
"""
Generate filenames per pair/timeframe to be used for storing plots
"""
pair_name = pair.replace("/", "_")
file_name = 'freqtrade-plot-' + pair_name + '-' + timeframe + '.html'
pair_s = pair_to_filename(pair)
file_name = 'freqtrade-plot-' + pair_s + '-' + timeframe + '.html'
logger.info('Generate plot file for %s', pair)
@@ -403,7 +449,7 @@ def load_and_plot_trades(config: Dict[str, Any]):
"""
From configuration provided
- Initializes plot-script
- Get tickers data
- Get candle (OHLCV) data
- Generate Dafaframes populated with indicators and signals based on configured strategy
- Load trades excecuted during the selected period
- Generate Plotly plot objects
@@ -415,19 +461,17 @@ def load_and_plot_trades(config: Dict[str, Any]):
plot_elements = init_plotscript(config)
trades = plot_elements['trades']
pair_counter = 0
for pair, data in plot_elements["tickers"].items():
for pair, data in plot_elements["ohlcv"].items():
pair_counter += 1
logger.info("analyse pair %s", pair)
tickers = {}
tickers[pair] = data
dataframe = strategy.analyze_ticker(tickers[pair], {'pair': pair})
df_analyzed = strategy.analyze_ticker(data, {'pair': pair})
trades_pair = trades.loc[trades['pair'] == pair]
trades_pair = extract_trades_of_period(dataframe, trades_pair)
trades_pair = extract_trades_of_period(df_analyzed, trades_pair)
fig = generate_candlestick_graph(
pair=pair,
data=dataframe,
data=df_analyzed,
trades=trades_pair,
indicators1=config.get("indicators1", []),
indicators2=config.get("indicators2", []),
@@ -458,7 +502,7 @@ def plot_profit(config: Dict[str, Any]) -> None:
# Create an average close price of all the pairs that were involved.
# this could be useful to gauge the overall market trend
fig = generate_profit_graph(plot_elements["pairs"], plot_elements["tickers"],
fig = generate_profit_graph(plot_elements["pairs"], plot_elements["ohlcv"],
trades, config.get('ticker_interval', '5m'))
store_plot_file(fig, filename='freqtrade-profit-plot.html',
directory=config['user_data_dir'] / "plot", auto_open=True)

View File

@@ -7,7 +7,7 @@ import importlib.util
import inspect
import logging
from pathlib import Path
from typing import Any, Dict, Generator, List, Optional, Tuple, Type, Union
from typing import Any, Dict, Iterator, List, Optional, Tuple, Type, Union
from freqtrade.exceptions import OperationalException
@@ -22,13 +22,15 @@ class IResolver:
object_type: Type[Any]
object_type_str: str
user_subdir: Optional[str] = None
initial_search_path: Path
initial_search_path: Optional[Path]
@classmethod
def build_search_paths(cls, config, user_subdir: Optional[str] = None,
def build_search_paths(cls, config: Dict[str, Any], user_subdir: Optional[str] = None,
extra_dir: Optional[str] = None) -> List[Path]:
abs_paths: List[Path] = [cls.initial_search_path]
abs_paths: List[Path] = []
if cls.initial_search_path:
abs_paths.append(cls.initial_search_path)
if user_subdir:
abs_paths.insert(0, config['user_data_dir'].joinpath(user_subdir))
@@ -40,12 +42,14 @@ class IResolver:
return abs_paths
@classmethod
def _get_valid_object(cls, module_path: Path,
object_name: Optional[str]) -> Generator[Any, None, None]:
def _get_valid_object(cls, module_path: Path, object_name: Optional[str],
enum_failed: bool = False) -> Iterator[Any]:
"""
Generator returning objects with matching object_type and object_name in the path given.
:param module_path: absolute path to the module
:param object_name: Class name of the object
:param enum_failed: If True, will return None for modules which fail.
Otherwise, failing modules are skipped.
:return: generator containing matching objects
"""
@@ -58,10 +62,13 @@ class IResolver:
except (ModuleNotFoundError, SyntaxError) as err:
# Catch errors in case a specific module is not installed
logger.warning(f"Could not import {module_path} due to '{err}'")
if enum_failed:
return iter([None])
valid_objects_gen = (
obj for name, obj in inspect.getmembers(module, inspect.isclass)
if (object_name is None or object_name == name) and cls.object_type in obj.__bases__
if ((object_name is None or object_name == name) and
issubclass(obj, cls.object_type) and obj is not cls.object_type)
)
return valid_objects_gen
@@ -135,10 +142,13 @@ class IResolver:
)
@classmethod
def search_all_objects(cls, directory: Path) -> List[Dict[str, Any]]:
def search_all_objects(cls, directory: Path,
enum_failed: bool) -> List[Dict[str, Any]]:
"""
Searches a directory for valid objects
:param directory: Path to search
:param enum_failed: If True, will return None for modules which fail.
Otherwise, failing modules are skipped.
:return: List of dicts containing 'name', 'class' and 'location' entires
"""
logger.debug(f"Searching for {cls.object_type.__name__} '{directory}'")
@@ -150,9 +160,10 @@ class IResolver:
continue
module_path = entry.resolve()
logger.debug(f"Path {module_path}")
for obj in cls._get_valid_object(module_path, object_name=None):
for obj in cls._get_valid_object(module_path, object_name=None,
enum_failed=enum_failed):
objects.append(
{'name': obj.__name__,
{'name': obj.__name__ if obj is not None else '',
'class': obj,
'location': entry,
})

View File

@@ -9,10 +9,10 @@ from base64 import urlsafe_b64decode
from collections import OrderedDict
from inspect import getfullargspec
from pathlib import Path
from typing import Dict, Optional
from typing import Any, Dict, Optional
from freqtrade.constants import (REQUIRED_ORDERTIF, REQUIRED_ORDERTYPES,
USERPATH_STRATEGY)
USERPATH_STRATEGIES)
from freqtrade.exceptions import OperationalException
from freqtrade.resolvers import IResolver
from freqtrade.strategy.interface import IStrategy
@@ -26,11 +26,11 @@ class StrategyResolver(IResolver):
"""
object_type = IStrategy
object_type_str = "Strategy"
user_subdir = USERPATH_STRATEGY
initial_search_path = Path(__file__).parent.parent.joinpath('strategy').resolve()
user_subdir = USERPATH_STRATEGIES
initial_search_path = None
@staticmethod
def load_strategy(config: Optional[Dict] = None) -> IStrategy:
def load_strategy(config: Dict[str, Any] = None) -> IStrategy:
"""
Load the custom class from config parameter
:param config: configuration dictionary or None
@@ -96,7 +96,8 @@ class StrategyResolver(IResolver):
return strategy
@staticmethod
def _override_attribute_helper(strategy, config, attribute: str, default):
def _override_attribute_helper(strategy, config: Dict[str, Any],
attribute: str, default: Any):
"""
Override attributes in the strategy.
Prevalence:
@@ -140,7 +141,7 @@ class StrategyResolver(IResolver):
"""
abs_paths = StrategyResolver.build_search_paths(config,
user_subdir=USERPATH_STRATEGY,
user_subdir=USERPATH_STRATEGIES,
extra_dir=extra_dir)
if ":" in strategy_name:

View File

@@ -7,7 +7,7 @@ import logging
import time
from typing import Dict, List
from coinmarketcap import Market
from pycoingecko import CoinGeckoAPI
from freqtrade.constants import SUPPORTED_FIAT
@@ -38,8 +38,8 @@ class CryptoFiat:
# Private attributes
self._expiration = 0.0
self.crypto_symbol = crypto_symbol.upper()
self.fiat_symbol = fiat_symbol.upper()
self.crypto_symbol = crypto_symbol.lower()
self.fiat_symbol = fiat_symbol.lower()
self.set_price(price=price)
def set_price(self, price: float) -> None:
@@ -67,17 +67,20 @@ class CryptoToFiatConverter:
This object is also a Singleton
"""
__instance = None
_coinmarketcap: Market = None
_coingekko: CoinGeckoAPI = None
_cryptomap: Dict = {}
def __new__(cls):
"""
This class is a singleton - cannot be instantiated twice.
"""
if CryptoToFiatConverter.__instance is None:
CryptoToFiatConverter.__instance = object.__new__(cls)
try:
CryptoToFiatConverter._coinmarketcap = Market()
CryptoToFiatConverter._coingekko = CoinGeckoAPI()
except BaseException:
CryptoToFiatConverter._coinmarketcap = None
CryptoToFiatConverter._coingekko = None
return CryptoToFiatConverter.__instance
def __init__(self) -> None:
@@ -86,14 +89,12 @@ class CryptoToFiatConverter:
def _load_cryptomap(self) -> None:
try:
coinlistings = self._coinmarketcap.listings()
self._cryptomap = dict(map(lambda coin: (coin["symbol"], str(coin["id"])),
coinlistings["data"]))
except (BaseException) as exception:
coinlistings = self._coingekko.get_coins_list()
# Create mapping table from synbol to coingekko_id
self._cryptomap = {x['symbol']: x['id'] for x in coinlistings}
except (Exception) as exception:
logger.error(
"Could not load FIAT Cryptocurrency map for the following problem: %s",
type(exception).__name__
)
f"Could not load FIAT Cryptocurrency map for the following problem: {exception}")
def convert_amount(self, crypto_amount: float, crypto_symbol: str, fiat_symbol: str) -> float:
"""
@@ -115,8 +116,8 @@ class CryptoToFiatConverter:
:param fiat_symbol: FIAT currency you want to convert to (e.g USD)
:return: Price in FIAT
"""
crypto_symbol = crypto_symbol.upper()
fiat_symbol = fiat_symbol.upper()
crypto_symbol = crypto_symbol.lower()
fiat_symbol = fiat_symbol.lower()
# Check if the fiat convertion you want is supported
if not self._is_supported_fiat(fiat=fiat_symbol):
@@ -170,15 +171,13 @@ class CryptoToFiatConverter:
:return: bool, True supported, False not supported
"""
fiat = fiat.upper()
return fiat in SUPPORTED_FIAT
return fiat.upper() in SUPPORTED_FIAT
def _find_price(self, crypto_symbol: str, fiat_symbol: str) -> float:
"""
Call CoinMarketCap API to retrieve the price in the FIAT
:param crypto_symbol: Crypto-currency you want to convert (e.g BTC)
:param fiat_symbol: FIAT currency you want to convert to (e.g USD)
Call CoinGekko API to retrieve the price in the FIAT
:param crypto_symbol: Crypto-currency you want to convert (e.g btc)
:param fiat_symbol: FIAT currency you want to convert to (e.g usd)
:return: float, price of the crypto-currency in Fiat
"""
# Check if the fiat convertion you want is supported
@@ -195,12 +194,13 @@ class CryptoToFiatConverter:
return 0.0
try:
_gekko_id = self._cryptomap[crypto_symbol]
return float(
self._coinmarketcap.ticker(
currency=self._cryptomap[crypto_symbol],
convert=fiat_symbol
)['data']['quotes'][fiat_symbol.upper()]['price']
self._coingekko.get_price(
ids=_gekko_id,
vs_currencies=fiat_symbol
)[_gekko_id][fiat_symbol]
)
except BaseException as exception:
except Exception as exception:
logger.error("Error in _find_price: %s", exception)
return 0.0

View File

@@ -26,7 +26,9 @@ class RPCMessageType(Enum):
WARNING_NOTIFICATION = 'warning'
CUSTOM_NOTIFICATION = 'custom'
BUY_NOTIFICATION = 'buy'
BUY_CANCEL_NOTIFICATION = 'buy_cancel'
SELL_NOTIFICATION = 'sell'
SELL_CANCEL_NOTIFICATION = 'sell_cancel'
def __repr__(self):
return self.value
@@ -39,6 +41,7 @@ class RPCException(Exception):
raise RPCException('*Status:* `no active trade`')
"""
def __init__(self, message: str) -> None:
super().__init__(self)
self.message = message
@@ -139,7 +142,8 @@ class RPC:
results.append(trade_dict)
return results
def _rpc_status_table(self, stake_currency, fiat_display_currency: str) -> Tuple[List, List]:
def _rpc_status_table(self, stake_currency: str,
fiat_display_currency: str) -> Tuple[List, List]:
trades = Trade.get_open_trades()
if not trades:
raise RPCException('no active trade')
@@ -151,20 +155,22 @@ class RPC:
current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
except DependencyException:
current_rate = NAN
trade_perc = (100 * trade.calc_profit_ratio(current_rate))
trade_percent = (100 * trade.calc_profit_ratio(current_rate))
trade_profit = trade.calc_profit(current_rate)
profit_str = f'{trade_perc:.2f}%'
profit_str = f'{trade_percent:.2f}%'
if self._fiat_converter:
fiat_profit = self._fiat_converter.convert_amount(
trade_profit,
stake_currency,
fiat_display_currency
)
trade_profit,
stake_currency,
fiat_display_currency
)
if fiat_profit and not isnan(fiat_profit):
profit_str += f" ({fiat_profit:.2f})"
trades_list.append([
trade.id,
trade.pair,
trade.pair + ('*' if (trade.open_order_id is not None
and trade.close_rate_requested is None) else '')
+ ('**' if (trade.close_rate_requested is not None) else ''),
shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)),
profit_str
])
@@ -191,7 +197,7 @@ class RPC:
Trade.close_date >= profitday,
Trade.close_date < (profitday + timedelta(days=1))
]).order_by(Trade.close_date).all()
curdayprofit = sum(trade.calc_profit() for trade in trades)
curdayprofit = sum(trade.close_profit_abs for trade in trades)
profit_days[profitday] = {
'amount': f'{curdayprofit:.8f}',
'trades': len(trades)
@@ -226,9 +232,9 @@ class RPC:
trades = Trade.get_trades().order_by(Trade.id).all()
profit_all_coin = []
profit_all_perc = []
profit_all_ratio = []
profit_closed_coin = []
profit_closed_perc = []
profit_closed_ratio = []
durations = []
for trade in trades:
@@ -240,21 +246,21 @@ class RPC:
durations.append((trade.close_date - trade.open_date).total_seconds())
if not trade.is_open:
profit_percent = trade.calc_profit_ratio()
profit_closed_coin.append(trade.calc_profit())
profit_closed_perc.append(profit_percent)
profit_ratio = trade.close_profit
profit_closed_coin.append(trade.close_profit_abs)
profit_closed_ratio.append(profit_ratio)
else:
# Get current rate
try:
current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
except DependencyException:
current_rate = NAN
profit_percent = trade.calc_profit_ratio(rate=current_rate)
profit_ratio = trade.calc_profit_ratio(rate=current_rate)
profit_all_coin.append(
trade.calc_profit(rate=trade.close_rate or current_rate)
)
profit_all_perc.append(profit_percent)
profit_all_ratio.append(profit_ratio)
best_pair = Trade.get_best_pair()
@@ -265,7 +271,7 @@ class RPC:
# Prepare data to display
profit_closed_coin_sum = round(sum(profit_closed_coin), 8)
profit_closed_percent = (round(mean(profit_closed_perc) * 100, 2) if profit_closed_perc
profit_closed_percent = (round(mean(profit_closed_ratio) * 100, 2) if profit_closed_ratio
else 0.0)
profit_closed_fiat = self._fiat_converter.convert_amount(
profit_closed_coin_sum,
@@ -274,7 +280,7 @@ class RPC:
) if self._fiat_converter else 0
profit_all_coin_sum = round(sum(profit_all_coin), 8)
profit_all_percent = round(mean(profit_all_perc) * 100, 2) if profit_all_perc else 0.0
profit_all_percent = round(mean(profit_all_ratio) * 100, 2) if profit_all_ratio else 0.0
profit_all_fiat = self._fiat_converter.convert_amount(
profit_all_coin_sum,
stake_currency,
@@ -385,7 +391,7 @@ class RPC:
return {'status': 'No more buy will occur from now. Run /reload_conf to reset.'}
def _rpc_forcesell(self, trade_id) -> Dict[str, str]:
def _rpc_forcesell(self, trade_id: str) -> Dict[str, str]:
"""
Handler for forcesell <id>.
Sells the given trade at current price
@@ -454,9 +460,9 @@ class RPC:
if self._freqtrade.state != State.RUNNING:
raise RPCException('trader is not running')
# Check pair is in stake currency
# Check if pair quote currency equals to the stake currency.
stake_currency = self._freqtrade.config.get('stake_currency')
if not pair.endswith(stake_currency):
if not self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency:
raise RPCException(
f'Wrong pair selected. Please pairs with stake {stake_currency} pairs only')
# check if valid pair
@@ -511,7 +517,7 @@ class RPC:
if add:
stake_currency = self._freqtrade.config.get('stake_currency')
for pair in add:
if (pair.endswith(stake_currency)
if (self._freqtrade.exchange.get_pair_quote_currency(pair) == stake_currency
and pair not in self._freqtrade.pairlists.blacklist):
self._freqtrade.pairlists.blacklist.append(pair)

View File

@@ -61,7 +61,7 @@ class RPCManager:
except NotImplementedError:
logger.error(f"Message type {msg['type']} not implemented by handler {mod.name}.")
def startup_messages(self, config, pairlist) -> None:
def startup_messages(self, config: Dict[str, Any], pairlist) -> None:
if config['dry_run']:
self.send_msg({
'type': RPCMessageType.WARNING_NOTIFICATION,

View File

@@ -134,25 +134,30 @@ class Telegram(RPC):
msg['stake_amount_fiat'] = 0
message = ("*{exchange}:* Buying {pair}\n"
"at rate `{limit:.8f}\n"
"({stake_amount:.6f} {stake_currency}").format(**msg)
"*Amount:* `{amount:.8f}`\n"
"*Open Rate:* `{limit:.8f}`\n"
"*Current Rate:* `{current_rate:.8f}`\n"
"*Total:* `({stake_amount:.6f} {stake_currency}").format(**msg)
if msg.get('fiat_currency', None):
message += ",{stake_amount_fiat:.3f} {fiat_currency}".format(**msg)
message += ", {stake_amount_fiat:.3f} {fiat_currency}".format(**msg)
message += ")`"
elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION:
message = "*{exchange}:* Cancelling Open Buy Order for {pair}".format(**msg)
elif msg['type'] == RPCMessageType.SELL_NOTIFICATION:
msg['amount'] = round(msg['amount'], 8)
msg['profit_percent'] = round(msg['profit_percent'] * 100, 2)
msg['profit_percent'] = round(msg['profit_ratio'] * 100, 2)
msg['duration'] = msg['close_date'].replace(
microsecond=0) - msg['open_date'].replace(microsecond=0)
msg['duration_min'] = msg['duration'].total_seconds() / 60
message = ("*{exchange}:* Selling {pair}\n"
"*Rate:* `{limit:.8f}`\n"
"*Amount:* `{amount:.8f}`\n"
"*Open Rate:* `{open_rate:.8f}`\n"
"*Current Rate:* `{current_rate:.8f}`\n"
"*Close Rate:* `{limit:.8f}`\n"
"*Sell Reason:* `{sell_reason}`\n"
"*Duration:* `{duration} ({duration_min:.1f} min)`\n"
"*Profit:* `{profit_percent:.2f}%`").format(**msg)
@@ -163,8 +168,11 @@ class Telegram(RPC):
and self._fiat_converter):
msg['profit_fiat'] = self._fiat_converter.convert_amount(
msg['profit_amount'], msg['stake_currency'], msg['fiat_currency'])
message += ('` ({gain}: {profit_amount:.8f} {stake_currency}`'
'` / {profit_fiat:.3f} {fiat_currency})`').format(**msg)
message += (' `({gain}: {profit_amount:.8f} {stake_currency}'
' / {profit_fiat:.3f} {fiat_currency})`').format(**msg)
elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION:
message = "*{exchange}:* Cancelling Open Sell Order for {pair}".format(**msg)
elif msg['type'] == RPCMessageType.STATUS_NOTIFICATION:
message = '*Status:* `{status}`'.format(**msg)
@@ -553,6 +561,8 @@ class Telegram(RPC):
"*/stop:* `Stops the trader`\n" \
"*/status [table]:* `Lists all open trades`\n" \
" *table :* `will display trades in a table`\n" \
" `pending buy orders are marked with an asterisk (*)`\n" \
" `pending sell orders are marked with a double asterisk (**)`\n" \
"*/profit:* `Lists cumulative profit from all finished trades`\n" \
"*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, " \
"regardless of profit`\n" \

View File

@@ -41,8 +41,12 @@ class Webhook(RPC):
if msg['type'] == RPCMessageType.BUY_NOTIFICATION:
valuedict = self._config['webhook'].get('webhookbuy', None)
elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION:
valuedict = self._config['webhook'].get('webhookbuycancel', None)
elif msg['type'] == RPCMessageType.SELL_NOTIFICATION:
valuedict = self._config['webhook'].get('webhooksell', None)
elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION:
valuedict = self._config['webhook'].get('webhooksellcancel', None)
elif msg['type'] in(RPCMessageType.STATUS_NOTIFICATION,
RPCMessageType.CUSTOM_NOTIFICATION,
RPCMessageType.WARNING_NOTIFICATION):

View File

@@ -59,7 +59,7 @@ class IStrategy(ABC):
Attributes you can use:
minimal_roi -> Dict: Minimal ROI designed for the strategy
stoploss -> float: optimal stoploss designed for the strategy
ticker_interval -> str: value of the ticker interval to use for the strategy
ticker_interval -> str: value of the timeframe (ticker interval) to use with the strategy
"""
# Strategy interface version
# Default to version 2
@@ -125,7 +125,7 @@ class IStrategy(ABC):
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Populate indicators that will be used in the Buy and Sell strategy
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
:param dataframe: DataFrame with data from the exchange
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
"""
@@ -180,7 +180,7 @@ class IStrategy(ABC):
if pair not in self._pair_locked_until or self._pair_locked_until[pair] < until:
self._pair_locked_until[pair] = until
def unlock_pair(self, pair) -> None:
def unlock_pair(self, pair: str) -> None:
"""
Unlocks a pair previously locked using lock_pair.
Not used by freqtrade itself, but intended to be used if users lock pairs
@@ -200,11 +200,11 @@ class IStrategy(ABC):
def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Parses the given ticker history and returns a populated DataFrame
Parses the given candle (OHLCV) data and returns a populated DataFrame
add several TA indicators and buy signal to it
:param dataframe: Dataframe containing ticker data
:param dataframe: Dataframe containing data from exchange
:param metadata: Metadata dictionary with additional data (e.g. 'pair')
:return: DataFrame with ticker data and indicator data
:return: DataFrame of candle (OHLCV) data with indicator data and signals added
"""
logger.debug("TA Analysis Launched")
dataframe = self.advise_indicators(dataframe, metadata)
@@ -214,12 +214,12 @@ class IStrategy(ABC):
def _analyze_ticker_internal(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Parses the given ticker history and returns a populated DataFrame
Parses the given candle (OHLCV) data and returns a populated DataFrame
add several TA indicators and buy signal to it
WARNING: Used internally only, may skip analysis if `process_only_new_candles` is set.
:param dataframe: Dataframe containing ticker data
:param dataframe: Dataframe containing data from exchange
:param metadata: Metadata dictionary with additional data (e.g. 'pair')
:return: DataFrame with ticker data and indicator data
:return: DataFrame of candle (OHLCV) data with indicator data and signals added
"""
pair = str(metadata.get('pair'))
@@ -251,21 +251,21 @@ class IStrategy(ABC):
:return: (Buy, Sell) A bool-tuple indicating buy/sell signal
"""
if not isinstance(dataframe, DataFrame) or dataframe.empty:
logger.warning('Empty ticker history for pair %s', pair)
logger.warning('Empty candle (OHLCV) data for pair %s', pair)
return False, False
try:
dataframe = self._analyze_ticker_internal(dataframe, {'pair': pair})
except ValueError as error:
logger.warning(
'Unable to analyze ticker for pair %s: %s',
'Unable to analyze candle (OHLCV) data for pair %s: %s',
pair,
str(error)
)
return False, False
except Exception as error:
logger.exception(
'Unexpected error when analyzing ticker for pair %s: %s',
'Unexpected error when analyzing candle (OHLCV) data for pair %s: %s',
pair,
str(error)
)
@@ -364,7 +364,7 @@ class IStrategy(ABC):
"""
Based on current profit of the trade and configured (trailing) stoploss,
decides to sell or not
:param current_profit: current profit in percent
:param current_profit: current profit as ratio
"""
stop_loss_value = force_stoploss if force_stoploss else self.stoploss
@@ -427,8 +427,9 @@ class IStrategy(ABC):
def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool:
"""
Based on trade duration, current price and ROI configuration, decides whether bot should
sell. Requires current_profit to be in percent!!
Based on trade duration, current profit of the trade and ROI configuration,
decides whether bot should sell.
:param current_profit: current profit as ratio
:return: True if bot should sell at current rate
"""
# Check if time matches and current rate is above threshold
@@ -439,19 +440,19 @@ class IStrategy(ABC):
else:
return current_profit > roi
def tickerdata_to_dataframe(self, tickerdata: Dict[str, List]) -> Dict[str, DataFrame]:
def ohlcvdata_to_dataframe(self, data: Dict[str, DataFrame]) -> Dict[str, DataFrame]:
"""
Creates a dataframe and populates indicators for given ticker data
Creates a dataframe and populates indicators for given candle (OHLCV) data
Used by optimize operations only, not during dry / live runs.
"""
return {pair: self.advise_indicators(pair_data, {'pair': pair})
for pair, pair_data in tickerdata.items()}
for pair, pair_data in data.items()}
def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Populate indicators that will be used in the Buy and Sell strategy
This method should not be overridden.
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
:param dataframe: Dataframe with data from the exchange
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
"""

View File

@@ -0,0 +1,60 @@
{
"max_open_trades": {{ max_open_trades }},
"stake_currency": "{{ stake_currency }}",
"stake_amount": {{ stake_amount }},
"tradable_balance_ratio": 0.99,
"fiat_display_currency": "{{ fiat_display_currency }}",
"ticker_interval": "{{ ticker_interval }}",
"dry_run": {{ dry_run | lower }},
"unfilledtimeout": {
"buy": 10,
"sell": 30
},
"bid_strategy": {
"price_side": "bid",
"ask_last_balance": 0.0,
"use_order_book": false,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"ask_strategy": {
"price_side": "ask",
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 1,
"use_sell_signal": true,
"sell_profit_only": false,
"ignore_roi_if_buy_signal": false
},
{{ exchange | indent(4) }},
"pairlists": [
{"method": "StaticPairList"}
],
"edge": {
"enabled": false,
"process_throttle_secs": 3600,
"calculate_since_number_of_days": 7,
"allowed_risk": 0.01,
"stoploss_range_min": -0.01,
"stoploss_range_max": -0.1,
"stoploss_range_step": -0.01,
"minimum_winrate": 0.60,
"minimum_expectancy": 0.20,
"min_trade_number": 10,
"max_trade_duration_minute": 1440,
"remove_pumps": false
},
"telegram": {
"enabled": {{ telegram | lower }},
"token": "{{ telegram_token }}",
"chat_id": "{{ telegram_chat_id }}"
},
"initial_state": "running",
"forcebuy_enable": false,
"internals": {
"process_throttle_secs": 5
}
}

View File

@@ -21,7 +21,7 @@ class {{ hyperopt }}(IHyperOpt):
"""
This is a Hyperopt template to get you started.
More information in https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md
More information in the documentation: https://www.freqtrade.io/en/latest/hyperopt/
You should:
- Add any lib you need to build your hyperopt.
@@ -29,11 +29,14 @@ class {{ hyperopt }}(IHyperOpt):
You must keep:
- The prototypes for the methods: populate_indicators, indicator_space, buy_strategy_generator.
The roi_space, generate_roi_table, stoploss_space methods are no longer required to be
copied in every custom hyperopt. However, you may override them if you need the
'roi' and the 'stoploss' spaces that differ from the defaults offered by Freqtrade.
Sample implementation of these methods can be found in
https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt_advanced.py
The methods roi_space, generate_roi_table and stoploss_space are not required
and are provided by default.
However, you may override them if you need 'roi' and 'stoploss' spaces that
differ from the defaults offered by Freqtrade.
Sample implementation of these methods will be copied to `user_data/hyperopts` when
creating the user-data directory using `freqtrade create-userdir --userdir user_data`,
or is available online under the following URL:
https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/templates/sample_hyperopt_advanced.py.
"""
@staticmethod
@@ -63,6 +66,9 @@ class {{ hyperopt }}(IHyperOpt):
dataframe['close'], dataframe['sar']
))
# Check that the candle had volume
conditions.append(dataframe['volume'] > 0)
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
@@ -108,6 +114,9 @@ class {{ hyperopt }}(IHyperOpt):
dataframe['sar'], dataframe['close']
))
# Check that the candle had volume
conditions.append(dataframe['volume'] > 0)
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),

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