Compare commits

...

1768 Commits

Author SHA1 Message Date
Matthias
ab3e3797a5 Merge pull request #2205 from freqtrade/master_add_2199
Release 2019.8-1 - hotfix data-dir not including exchange
2019-08-31 19:13:05 +02:00
Matthias
7c36e571d2 version bump to 2019.8-1 2019-08-31 15:38:38 +02:00
hroff-1902
040ba5662c Merge pull request #2199 from freqtrade/fix_datadir_init
Fix datadir init to always include exchange
2019-08-31 15:37:29 +02:00
Matthias
9634e516a9 Merge pull request #2196 from freqtrade/new_release
New release 2018-8
2019-08-28 19:26:35 +02:00
Matthias
44780837f1 Version bump to 2019-8 2019-08-28 06:33:10 +02:00
Matthias
95920f3b6b Merge pull request #2177 from freqtrade/fix/stoplosshandling
[minor]improvements to stoploss-on-exchange handling
2019-08-25 09:33:39 +02:00
Matthias
365b9c3e9c Add test to correctly handle unsuccessfull ordercreation 2019-08-24 18:06:33 +02:00
Matthias
3f6eeda3f0 Reset stoploss_order_id when recreating fails 2019-08-24 18:06:14 +02:00
Matthias
3820a38e79 Merge pull request #2175 from hroff-1902/hyperopt-split-backtesting
Hyperopt redesign
2019-08-24 14:39:46 +02:00
Matthias
60bc9f4f5e Merge pull request #2173 from freqtrade/improve/trailing_validation
improve stoploss validation
2019-08-24 09:15:43 +02:00
Matthias
a8842f38ca Fix wrong exception message 2019-08-24 09:08:08 +02:00
hroff-1902
667a623310 adjust tests 2019-08-24 00:10:55 +03:00
hroff-1902
067208bc9d make backtesting an attribute of Hyperopt 2019-08-24 00:10:35 +03:00
Matthias
70ebd09de4 Add checks verifying that stoploss is not 0 (and positive-stoploss is
also not 0).
2019-08-22 20:04:44 +02:00
Matthias
782f4112cd Add test checking stoploss == 0 values 2019-08-22 19:49:30 +02:00
Matthias
447bcf98e1 Merge pull request #2172 from hroff-1902/exchange-cosmetics
exchange cosmetics
2019-08-22 19:18:22 +02:00
hroff-1902
d19b11a00f exchange cosmetics 2019-08-22 20:01:41 +03:00
Matthias
ad6de07d2b Merge pull request #2155 from jraviotta/analysis
split example notebooks
2019-08-22 15:54:08 +02:00
Matthias
0e81d7204c Clense jupyter notebook 2019-08-22 15:43:39 +02:00
Matthias
91b0394433 Merge pull request #2156 from freqtrade/remove_live
Remove deprecated option live  - deprecate -r
2019-08-22 15:33:39 +02:00
Matthias
b2ef8f4e14 Add additional header 2019-08-22 15:26:18 +02:00
Matthias
81925dfadf Fix some doc inconsistencies 2019-08-22 13:01:10 +02:00
Matthias
098159ad41 Merge pull request #2170 from freqtrade/fix/docboxes
Fix documentation boxes
2019-08-22 12:44:35 +02:00
Matthias
fe12d2e3b7 Fix documentation syntax 2019-08-22 06:57:32 +02:00
Matthias
df1f57392c use seperate job for doc test 2019-08-22 06:56:41 +02:00
Matthias
949ca1abf8 Fail travis if doc-test fails 2019-08-22 06:53:51 +02:00
Matthias
e52d5e32aa Merge pull request #2067 from freqtrade/align_userdata
Align userdata usage
2019-08-21 19:55:42 +02:00
Matthias
aaeeb9c0c6 Merge branch 'develop' into align_userdata 2019-08-21 19:41:10 +02:00
Matthias
d2958fc0f5 Merge pull request #2168 from freqtrade/fix/downloadscript_pairs
Fix downloadscript pair handling
2019-08-21 09:09:03 +02:00
Matthias
f8235aec74 Merge pull request #2167 from hroff-1902/fix-download-script
minor: fix download replacement script
2019-08-21 07:03:13 +02:00
Matthias
13ffb39245 Adjust tests to fixed loading method 2019-08-21 06:59:07 +02:00
Matthias
75b2db4424 FIx loading pairs-list 2019-08-21 06:58:56 +02:00
hroff-1902
14aaf8976f fix download replacement script 2019-08-21 02:26:58 +03:00
Matthias
eebf39a1df Merge pull request #2165 from freqtrade/xmatthias-patch-1
Fix grammar error in documentation
2019-08-20 19:40:07 +02:00
Matthias
210f66e48b Improve wording 2019-08-20 19:34:18 +02:00
Matthias
91e72ba081 small formatting issue 2019-08-20 19:32:26 +02:00
Matthias
be308ff914 Fix grammar error in documentation 2019-08-20 09:45:28 +02:00
Matthias
4ee35438a7 Improve deprecated docs 2019-08-20 07:07:05 +02:00
Matthias
11dab2b9ca Deprecate documentation for --refresh-pairs-cached 2019-08-20 07:02:30 +02:00
Matthias
f02adf2a45 Deprecate --refresh-pairs-cached 2019-08-20 07:00:43 +02:00
Matthias
9e24992835 Remove calls to load_data using live= 2019-08-20 07:00:43 +02:00
Matthias
e9e2a83436 remove --live references 2019-08-20 07:00:43 +02:00
Matthias
af51ff4162 Merge pull request #2146 from freqtrade/download_module
Download module
2019-08-20 06:59:30 +02:00
Matthias
e8ee087e9d Merge branch 'develop' into download_module 2019-08-20 06:49:18 +02:00
Jonathan Raviotta
8cc477f353 edits 2019-08-20 00:47:10 -04:00
Matthias
c63856dac4 Merge pull request #2158 from freqtrade/config_consistency
Config consistency checking improvements
2019-08-20 06:44:41 +02:00
Matthias
8d1a575a9b Reword documentation 2019-08-20 06:39:28 +02:00
Matthias
9e8ca8d4bf Merge pull request #2138 from freqtrade/history_docstrings
Refactorings to history
2019-08-20 06:35:54 +02:00
Matthias
491d742bf9 Merge pull request #2163 from hroff-1902/dataprovider-get-pair-dataframe
get_pair_dataframe(): example in the docs changed
2019-08-20 06:33:59 +02:00
Matthias
dc35a8022b Merge pull request #2157 from freqtrade/fix/create_order_crash
create market order crash if exchange raises an exception
2019-08-20 06:22:43 +02:00
hroff-1902
70b1a05d97 example in the docs changed 2019-08-20 01:32:02 +03:00
Matthias
785c3e9e61 Merge pull request #2161 from freqtrade/dependabot/pip/develop/ccxt-1.18.1068
Bump ccxt from 1.18.1063 to 1.18.1068
2019-08-19 16:41:07 +02:00
dependabot-preview[bot]
9ad9ce0da1 Bump ccxt from 1.18.1063 to 1.18.1068
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.18.1063 to 1.18.1068.
- [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.18.1063...1.18.1068)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-19 10:52:53 +00:00
Matthias
042e47543c Merge pull request #2159 from freqtrade/fix/pairlist_logging
Fix pairlist logging
2019-08-19 09:48:42 +02:00
Matthias
71d612f6e4 Merge pull request #2160 from freqtrade/fix/dryrun_crashes
Gracefully handle problems with dry-run orders
2019-08-19 09:06:44 +02:00
Matthias
a4ede02ced Gracefully handle problems with dry-run orders 2019-08-18 19:38:23 +02:00
Matthias
ea4db0ffb6 Pass object-name to loader to fix logging 2019-08-18 18:11:34 +02:00
Matthias
d785d76370 make VolumePairlist less verbose
no need to print the full whitelist on every iteration
2019-08-18 18:11:24 +02:00
Matthias
b6462cd51f Add explaining comment 2019-08-18 16:22:18 +02:00
Matthias
611850bf91 Add edge/dynamic_whitelist validation 2019-08-18 16:19:24 +02:00
Matthias
ddfadbb69e Validate configuration consistency after loading strategy 2019-08-18 16:10:10 +02:00
Matthias
045ac1019e Split test for buy-orders too 2019-08-18 15:58:53 +02:00
Matthias
ee7ba96e85 Don't do calculations in exception handlers when one element can be None
fixes #2011
2019-08-18 15:46:38 +02:00
Matthias
8e96ac8765 Split exception tests for create_order 2019-08-18 15:45:30 +02:00
Matthias
acf1e734ec Adapt lg_has calls to new standard 2019-08-18 15:09:44 +02:00
Matthias
0a478bc0dc Merge branch 'develop' into align_userdata 2019-08-18 15:00:12 +02:00
Matthias
9005447590 Merge pull request #2149 from hroff-1902/dataprovider-get-pair-dataframe
Dataprovider: get_pair_dataframe() helper method, cleanup
2019-08-18 13:57:49 +02:00
hroff-1902
d300964691 code formatting in test_dataprovider.py 2019-08-18 13:06:21 +03:00
hroff-1902
407a3bca62 implementation of ohlcv optimized 2019-08-18 13:00:37 +03:00
hroff-1902
310e438706 logging message improved 2019-08-18 12:55:31 +03:00
hroff-1902
8a2a8ab8b5 docstring for ohlcv improved 2019-08-18 12:47:19 +03:00
Matthias
5e440a4cdc Improve docs to point to freqtrade download-data 2019-08-18 06:55:19 +02:00
Matthias
3a1b641db1 Merge pull request #2154 from freqtrade/doc/docker_updatefreq
[minor] Explain docker image rebuilding
2019-08-18 06:40:30 +02:00
Jonathan Raviotta
2cffc3228a split example notebooks 2019-08-17 19:37:34 -04:00
Matthias
7fa6d804ce Add note explaining how / when docker images are rebuild 2019-08-17 19:48:55 +02:00
Matthias
a398eea244 Merge pull request #2153 from freqtrade/enable/dependabot
Enable/dependabot
2019-08-17 19:40:36 +02:00
Matthias
0e87cc8c84 Remove pyup.yml 2019-08-17 19:30:03 +02:00
Matthias
764bab8eb9 Merge pull request #2152 from freqtrade/dependabot/pip/ccxt-1.18.1063
Bump ccxt from 1.18.1043 to 1.18.1063
2019-08-17 19:29:24 +02:00
Matthias
351740fc80 Change pyup to every month (should ideally not find anything ...) 2019-08-17 17:27:14 +02:00
dependabot-preview[bot]
9143ea13ad Bump ccxt from 1.18.1043 to 1.18.1063
Bumps [ccxt](https://github.com/ccxt/ccxt) from 1.18.1043 to 1.18.1063.
- [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.18.1043...1.18.1063)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-17 15:26:07 +00:00
Matthias
4711d66cab Merge pull request #2150 from freqtrade/dependabot/pip/pytest-5.1.0
Bump pytest from 5.0.1 to 5.1.0
2019-08-17 17:25:12 +02:00
Matthias
09967d4ff8 Merge pull request #2151 from freqtrade/dependabot/pip/sqlalchemy-1.3.7
Bump sqlalchemy from 1.3.6 to 1.3.7
2019-08-17 17:24:54 +02:00
Matthias
e0335705b2 Add dependabot config yaml 2019-08-17 17:19:02 +02:00
dependabot-preview[bot]
4ce3cc66d5 Bump sqlalchemy from 1.3.6 to 1.3.7
Bumps [sqlalchemy](https://github.com/sqlalchemy/sqlalchemy) from 1.3.6 to 1.3.7.
- [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>
2019-08-17 15:14:01 +00:00
dependabot-preview[bot]
fce3d7586f Bump pytest from 5.0.1 to 5.1.0
Bumps [pytest](https://github.com/pytest-dev/pytest) from 5.0.1 to 5.1.0.
- [Release notes](https://github.com/pytest-dev/pytest/releases)
- [Changelog](https://github.com/pytest-dev/pytest/blob/master/CHANGELOG.rst)
- [Commits](https://github.com/pytest-dev/pytest/compare/5.0.1...5.1.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2019-08-17 15:13:39 +00:00
hroff-1902
cda912bd8c test added 2019-08-17 13:05:13 +03:00
hroff-1902
84a0f9ea42 get_pair_dataframe helper method added 2019-08-17 12:57:44 +03:00
Matthias
08fa5136e1 use copy of minimal_config ... 2019-08-17 07:19:46 +02:00
Matthias
7a79b292e4 Fix bug in pairs fallback resolving 2019-08-17 07:05:42 +02:00
Matthias
a53e9e3a98 improve tests for download_module 2019-08-17 07:01:20 +02:00
Matthias
f7d5280f47 Replace ARGS_DOWNLOADER with ARGS_DOWNLOAD_DATA 2019-08-17 06:48:34 +02:00
Matthias
29c56f4447 Replace download_backtest_data script with warning message 2019-08-17 06:48:31 +02:00
Matthias
c9207bcc00 Remove blank line at end 2019-08-16 16:01:30 +02:00
Matthias
132f28ad44 Add tests to correctly load / override pair-lists 2019-08-16 15:52:59 +02:00
Matthias
b2c215029d Add tests for download_data entrypoint 2019-08-16 15:28:11 +02:00
Matthias
89257832d7 Don't use internal _API methods 2019-08-16 15:27:59 +02:00
Matthias
219d0b7fb0 Adjust documentation to removed download-script 2019-08-16 15:27:48 +02:00
Matthias
4e308a1a3e Resolve pairlist in configuration 2019-08-16 14:56:57 +02:00
Matthias
3c15e3ebdd Default load minimal config 2019-08-16 14:56:38 +02:00
Matthias
8655e521d7 Adapt some tests 2019-08-16 14:53:46 +02:00
Matthias
05deb9e09b Migrate download-script logic to utils.py 2019-08-16 14:42:44 +02:00
Matthias
91886120a7 use nargs for --pairs argument 2019-08-16 14:39:29 +02:00
Matthias
09286d4918 file_dump_json accepts Path - so we should feed it that 2019-08-16 13:04:48 +02:00
Matthias
161db08745 Merge pull request #2142 from hroff-1902/hyperopt-print-json
Hyperopt: --print-json option
2019-08-16 11:08:54 +02:00
Matthias
8aaaab4163 Merge pull request #2145 from freqtrade/update_docker_image
Update dockerfile python version
2019-08-16 10:24:55 +02:00
Matthias
53db382695 Update dockerfile python version 2019-08-16 10:19:06 +02:00
Matthias
1b6051e4df Merge pull request #2144 from freqtrade/strategy_doc
Fix wrong warning box
2019-08-16 09:40:42 +02:00
Matthias
8d206f8308 Fix wrong warning box 2019-08-16 06:57:46 +02:00
hroff-1902
b94f3e80c4 tests fixed 2019-08-16 04:20:12 +03:00
hroff-1902
2a842778e3 tests added 2019-08-16 01:05:34 +03:00
hroff-1902
e525275d10 make flake and mypy happy 2019-08-15 23:13:46 +03:00
hroff-1902
4fa92ec0fa hyperopt: --print-json option added 2019-08-15 21:39:04 +03:00
Matthias
69eff89049 Improve comment in test_history to explain what is tested 2019-08-15 20:28:32 +02:00
Matthias
12677f2d42 Adjust docstring to match functioning of load_cached_data 2019-08-15 20:13:19 +02:00
Matthias
a94a89086f Don't forward timerange to load_ticker_file
when loading cached data for updating.
We always want to get all data, not just a fraction (we would end up
overwriting the non-loaded part of the data).
2019-08-15 20:09:00 +02:00
Matthias
80a71323cc Merge pull request #2141 from ahonnecke/fstring-runtime
f the string
2019-08-15 19:33:57 +02:00
Ashton Honnecke
fd77f699df f the string 2019-08-15 10:41:02 -06:00
Matthias
93cf2cd19b Merge pull request #2135 from freqtrade/ohlcv_docstring
[minor] Improve docstring for some downloading methods
2019-08-15 16:23:42 +02:00
Matthias
585536835a Merge pull request #2131 from freqtrade/lock_pairs
Lock pairs
2019-08-15 07:21:00 +02:00
Matthias
f5e437d8c7 Change create_trade to create_trades for new test 2019-08-15 06:59:45 +02:00
Matthias
14c4854987 Merge branch 'develop' into lock_pairs 2019-08-15 06:56:39 +02:00
Matthias
3af5691b91 Merge pull request #2124 from freqtrade/fix/sell_order_hanging
Fix/sell order hanging
2019-08-15 06:52:37 +02:00
Matthias
9f26c4ebdc Merge branch 'develop' into fix/sell_order_hanging 2019-08-15 06:46:12 +02:00
Matthias
11790fbf01 Fix typos in docstrings 2019-08-15 06:37:26 +02:00
Matthias
f3e6bcb20c Avoid using negative indexes 2019-08-15 06:35:50 +02:00
Matthias
e0e50115d2 Merge pull request #2136 from freqtrade/timerange_fix
[refactor] Move Timerange parsing to it's own class
2019-08-15 06:35:37 +02:00
Matthias
b2a22f1afb Fix samll errors 2019-08-14 21:39:53 +02:00
Matthias
9d3322df8c Adapt history-tests to new load_cached_data header 2019-08-14 20:49:13 +02:00
Matthias
91d1061c73 Abstract tickerdata storing 2019-08-14 20:49:06 +02:00
Matthias
0ffb184eba Change some docstrings and formatting from history 2019-08-14 20:45:24 +02:00
Matthias
096a6426db Override equality operator 2019-08-14 10:22:54 +02:00
Matthias
84baef922c Rename get_history to get_historic_ohlcv 2019-08-14 10:14:54 +02:00
Matthias
51c3a31bb5 Correct imports and calls to parse_timerange 2019-08-14 10:07:32 +02:00
Matthias
06fa07e73e Move parse_timerange to TimeRange class 2019-08-14 10:07:14 +02:00
Matthias
4da2bfefb7 Improve docstring for some downloading methods 2019-08-14 09:37:17 +02:00
Matthias
3b30aab8a7 Merge pull request #2132 from freqtrade/process_return_value
allow create_trade() to create multiple trades per iteration
2019-08-14 07:23:05 +02:00
Matthias
c2e9685e04 Merge pull request #2121 from hroff-1902/config-allow-comments
Allow comments in config files
2019-08-14 06:37:33 +02:00
Matthias
d6f5f6b7ba Add test with preexisting trades 2019-08-14 06:21:15 +02:00
Matthias
a4ab42560f improve docstring for create_trades 2019-08-14 06:16:59 +02:00
Matthias
a76136c010 Rename create_trade to create_trades 2019-08-14 06:16:43 +02:00
Matthias
e35a349229 Fix spelling of interface.py docstring 2019-08-14 06:07:03 +02:00
hroff-1902
3d36747b92 preface in configuration.md reworked 2019-08-13 21:52:50 +03:00
Matthias
c0784b7c33 Merge pull request #2089 from hroff-1902/hyperopt-print-colorized
Hyperopt print colorized results
2019-08-13 19:36:06 +02:00
Matthias
828315f675 Merge pull request #2130 from freqtrade/bad_exchanges
fail for bad exchanges
2019-08-13 19:34:35 +02:00
hroff-1902
4c4ba08e85 colorama added to install_requires 2019-08-13 19:47:38 +03:00
hroff-1902
94196c84e9 docs: explanation for --no-color and colorization schema for results 2019-08-13 14:25:56 +03:00
Matthias
9d476b5ab2 Also check 0 open trades 2019-08-13 10:34:27 +02:00
Matthias
0a07dfc5cf Add test verifying that multiple trades are opened in one iteration 2019-08-13 10:20:32 +02:00
Matthias
d69f7ae471 Adapt final tests to support multi-trade creation 2019-08-13 10:15:31 +02:00
Matthias
974d899b33 Adapt some more tests 2019-08-13 10:12:12 +02:00
Matthias
6948e0ba84 Handle orderbook_depth check correctly 2019-08-13 10:12:02 +02:00
Matthias
a325f1ce2b adapt some tests
since create_trade() can now buy multiple times, we need to use
execute_buy() to create a single trade
2019-08-13 10:01:43 +02:00
Matthias
997eb7574a Support creating multiple trades in one iteration 2019-08-13 10:01:29 +02:00
Matthias
8873e0072c process_maybe_execute_buy does not need to return bool 2019-08-13 09:42:22 +02:00
Matthias
c29389f5f3 Remove process() checks from tests 2019-08-13 09:38:21 +02:00
Matthias
4b8eaaf7aa freqtradebot.process() does not need to return anything 2019-08-13 09:37:56 +02:00
Matthias
8d813fa728 Remove return-value for _process 2019-08-13 09:36:52 +02:00
Matthias
28e318b646 Lock pairs for stoploss_on_exchange fills too 2019-08-13 08:47:11 +02:00
Matthias
2961efdc18 Initial test for locked pair 2019-08-13 08:38:19 +02:00
Matthias
3c589bb877 fail if known bad exchanges are detcted 2019-08-13 08:27:46 +02:00
Matthias
d8dbea9d5b Add exchange_reasons to bad exchanges 2019-08-13 08:20:35 +02:00
Matthias
f960ea039e Remove duplicate test 2019-08-13 08:05:51 +02:00
hroff-1902
de80234165 hyperopt options updated in bot-usage.md 2019-08-13 00:23:41 +03:00
hroff-1902
906be7be7c Merge branch 'develop' into config-allow-comments 2019-08-13 00:14:19 +03:00
hroff-1902
482847a994 docs adjusted; various fixes to bot-usage.md and configuration.md 2019-08-13 00:10:33 +03:00
hroff-1902
58d308fd05 fix handling --no-color for edge and backtesting 2019-08-12 23:13:04 +03:00
Matthias
59acd5ec7c Lock pair for the rest of the candle in case of sells 2019-08-12 20:39:34 +02:00
Matthias
ca739f71fb Fix default argument handling for timeframe_to_nextdate 2019-08-12 20:39:24 +02:00
Matthias
23a70932d2 Remove pointless tests (without config?? really?) 2019-08-12 20:36:45 +02:00
hroff-1902
1a34b9b61c --no-color option introduced 2019-08-12 21:08:34 +03:00
hroff-1902
8f92912852 final colorization schema
colorization schema-2: red, green, bright/dim

colorization schema-3: red, green, bright only green bests

colorization schema-4: no red, green for profit, bright for bests
2019-08-12 21:08:52 +03:00
Matthias
2600cb7b64 simplify timeframe_next_date calculation 2019-08-12 20:04:19 +02:00
Matthias
200b6ea10f Add is_pair_locked 2019-08-12 19:50:38 +02:00
Matthias
8c1efec43a Merge pull request #2125 from freqtrade/pyup/scheduled-update-2019-08-12
Scheduled weekly dependency update for week 32
2019-08-12 17:41:25 +02:00
pyup-bot
dd30d74688 Update python-rapidjson from 0.7.2 to 0.8.0 2019-08-12 15:25:09 +00:00
pyup-bot
6f42d6658f Update arrow from 0.14.4 to 0.14.5 2019-08-12 15:25:08 +00:00
pyup-bot
c4cdd85e80 Update ccxt from 1.18.1021 to 1.18.1043 2019-08-12 15:25:06 +00:00
pyup-bot
0bd71db5df Update scipy from 1.3.0 to 1.3.1 2019-08-12 15:25:05 +00:00
Matthias
feced71a6d Test closing sell-orders immediately 2019-08-12 16:47:00 +02:00
Matthias
444ee274d7 close dry-run orders in case of market orders 2019-08-12 16:46:45 +02:00
Matthias
bb0b160001 Remove duplicate test 2019-08-12 16:39:21 +02:00
Matthias
241d510096 Handle and update sell-orders immediately if they are closed 2019-08-12 16:34:55 +02:00
Matthias
c042d08bb7 Add lock_pairs to interface 2019-08-12 16:29:09 +02:00
Matthias
1ce63b5b42 Reformat tests to be easier readable 2019-08-12 16:25:01 +02:00
Matthias
dd0ba183f8 Add timeframe_to_prev_candle 2019-08-12 16:11:43 +02:00
Matthias
933a553dd4 Convert timeframe to next date 2019-08-12 16:08:23 +02:00
Matthias
af67bbde31 Test timeframe_to_x 2019-08-12 15:43:10 +02:00
Matthias
6310b40fc6 Merge pull request #2123 from freqtrade/hyperoptloss_help
[minor] Improve hyperopt-loss docs
2019-08-12 14:08:32 +02:00
Matthias
2463a4af2a Merge pull request #2120 from freqtrade/log_has_ref
[minor, tests] - use caplog instead of caplog.record_tuples
2019-08-12 07:02:10 +02:00
Matthias
51ad8f5ab4 Merge branch 'develop' into log_has_ref 2019-08-12 06:49:41 +02:00
Matthias
615ce6aa69 Merge pull request #2118 from freqtrade/config_standalone
Config standalone loading
2019-08-12 06:47:52 +02:00
Matthias
43b41324e2 Improve hyperopt-loss docs 2019-08-12 06:45:27 +02:00
Matthias
91b0db138a Merge pull request #2122 from hroff-1902/hyperopt-cleanup3
Minor: cosmetics in sample_hyperopt and default_hyperopt
2019-08-12 06:41:00 +02:00
Matthias
197ce0b670 Improve documentation wording for multiconfig files 2019-08-12 06:35:47 +02:00
Matthias
002003292e Merge branch 'develop' into log_has_ref 2019-08-12 06:34:49 +02:00
Matthias
0b367a14f1 Merge pull request #2119 from freqtrade/disable_sloE_dry
Disable stoploss on exchange during dry-runs
2019-08-12 06:12:22 +02:00
hroff-1902
e5dcd520ba cosmetics in sample_hyperopt and default_hyperopt 2019-08-12 02:19:50 +03:00
hroff-1902
90b75afdb1 test added to load config with comments and trailing commas 2019-08-12 00:33:34 +03:00
hroff-1902
2d60e4b18b allow comments and trailing commas in config files 2019-08-12 00:32:03 +03:00
Matthias
c5d8499ad2 Improve documentation regarding tests 2019-08-11 20:30:15 +02:00
Matthias
b77c0d2813 Replace all "logentry" in caplog_record_tuples
use log_has to have checking log-entries standardized.
2019-08-11 20:22:50 +02:00
Matthias
a636dda07d Fix remaining tests using log_has 2019-08-11 20:17:39 +02:00
Matthias
dc5719e1f4 Adapt rpc to new log_has method 2019-08-11 20:17:22 +02:00
Matthias
d53f63023a Change log_has to get caplog instead of caplog.record_tuples in more
tests
2019-08-11 20:16:52 +02:00
Matthias
0221607318 Change log_has for some tests 2019-08-11 20:16:34 +02:00
Matthias
a1b5c7242e Change log-has to use record_tuples itself 2019-08-11 20:14:58 +02:00
Matthias
a225672c87 Add tests for dry-run stoposs_on_exchange 2019-08-11 19:45:31 +02:00
Matthias
4b4fcc7034 Change stoploss_on_exchange in freqtradebot 2019-08-11 19:43:57 +02:00
Matthias
85094a59e6 Merge pull request #2063 from hroff-1902/remove-pytest-warning2
tests: don't mask numpy errors as warnings in tests
2019-08-11 19:29:27 +02:00
Matthias
e02e64fc07 Add test to make sure dry-run disables stoploss on exchange 2019-08-11 14:15:04 +02:00
Matthias
176beefa88 Disable stoploss on exchange for dry-runs 2019-08-11 14:14:51 +02:00
Matthias
1a85e3b4cd Fix numpy warning 2019-08-11 13:48:41 +02:00
hroff-1902
5209ce5bfa tests: don't mask numpy errors as warnings in tests 2019-08-11 13:46:41 +02:00
Matthias
2c5a499a8b Merge branch 'develop' into align_userdata 2019-08-10 20:15:07 +02:00
Matthias
6d89da45b0 Add test for from_config 2019-08-10 20:02:11 +02:00
Matthias
eb328037b7 combine normalize method and config validation to in_files 2019-08-10 19:58:04 +02:00
Matthias
afba31c3f9 change method from _load_config_Files to from_files() 2019-08-10 19:57:49 +02:00
Matthias
c4cbe79b48 Adjust documentation 2019-08-10 19:55:33 +02:00
Matthias
8ba7657007 Merge pull request #2117 from hroff-1902/config-load-config
Minor configuration cleanup
2019-08-10 19:34:03 +02:00
hroff-1902
48d8376878 tests fixed 2019-08-10 18:47:58 +03:00
Matthias
74e583a612 Merge pull request #2094 from hroff-1902/hyperopt-roi-stoploss
Simplify custom hyperopts -- no need to copy ugly methods in every custom implementation
2019-08-10 15:49:52 +02:00
Matthias
29619ccf1c Merge pull request #2108 from jraviotta/nbdocs
Added jupyter notebook example and doc edits
2019-08-10 15:47:06 +02:00
Matthias
ab092fc77f Reinstate comment on backesting data 2019-08-10 15:45:41 +02:00
hroff-1902
28d8fc871a tests adjusted 2019-08-10 16:07:30 +03:00
hroff-1902
ad6a249832 download_backtest_data.py adjusted 2019-08-10 15:14:37 +03:00
hroff-1902
50c9679e23 move load_config_file() to separate module 2019-08-10 14:24:14 +03:00
Jonathan Raviotta
8eb39178ea code block instructions. removed extra packages 2019-08-09 17:24:17 -04:00
Jonathan Raviotta
dd35ba5e81 added imports to doc code blocks. 2019-08-09 17:06:19 -04:00
Jonathan Raviotta
3cc772c8e9 added reminders 2019-08-09 11:53:29 -04:00
Jonathan Raviotta
247d7475e1 fixes to example notebook. 2019-08-09 11:41:05 -04:00
Jonathan Raviotta
51d59e673b fixed another instance of Path in docs and nb 2019-08-09 11:36:53 -04:00
hroff-1902
ae39f6fba5 use of termcolor eliminated 2019-08-09 14:51:03 +03:00
hroff-1902
15cf5ac2d7 docs improved 2019-08-09 09:31:30 +03:00
Matthias
de99942499 Merge pull request #2114 from CedricSchmeits/negativeSharpeLoss
As -sharp_ratio is returned the value should be nagative.
2019-08-09 06:19:59 +02:00
Jonathan Raviotta
ccf3c69874 edits to clarify backtesting analysis 2019-08-08 22:09:15 -04:00
Cedric Schmeits
8ad5afd3a1 As -sharp_ratio is returned the value should be nagative.
This leads in a high positive result of the loss function, as it is a minimal optimizer
2019-08-08 22:10:51 +02:00
hroff-1902
0d4a2c6c3a advanced sample hyperopt added; changes to helpstrings 2019-08-08 22:51:37 +03:00
Matthias
02b2de5c73 Merge pull request #2113 from freqtrade/improve_setup.sh
Improve setup.sh
2019-08-08 11:14:51 +02:00
Jonathan Raviotta
2bc67b4a96 missed a call of os.path. removed it. 2019-08-07 20:47:37 -04:00
Jonathan Raviotta
9df1c23c71 changed Path, added jupyter 2019-08-07 19:48:55 -04:00
Matthias
7a47d81b7b Ensure git reset --hard is realy desired 2019-08-07 21:45:58 +02:00
Matthias
831e708897 Detect virtualenv and quit in that case 2019-08-07 21:45:45 +02:00
Matthias
757538f114 Run ldconfig to add /usr/local/lib to path 2019-08-07 21:35:52 +02:00
Matthias
cc4900f66c Doublecheck if virtualenv IS present 2019-08-07 21:19:16 +02:00
Matthias
7d02580a2b setup.sh script shall fail if venv initialization fails 2019-08-07 21:03:03 +02:00
Matthias
3d3b0938e5 Merge pull request #2101 from freqtrade/backtest_ticker_interval_unset
Backtest ticker interval unset
2019-08-07 14:20:36 +02:00
Matthias
9c5773ca0a Merge pull request #2111 from freqtrade/pyup-update-plotly-4.0.0-to-4.1.0
Update plotly to 4.1.0
2019-08-07 10:13:08 +02:00
Matthias
092776442b Merge pull request #2109 from freqtrade/pyup-update-mkdocs-material-3.1.0-to-4.4.0
Update mkdocs-material to 4.4.0
2019-08-07 09:57:33 +02:00
Matthias
0267976044 Merge pull request #2110 from freqtrade/pyup-update-ccxt-1.18.1008-to-1.18.1021
Update ccxt to 1.18.1021
2019-08-07 09:57:01 +02:00
pyup-bot
5864968ce9 Update plotly from 4.0.0 to 4.1.0 2019-08-07 07:02:25 +00:00
pyup-bot
33bc8a2404 Update ccxt from 1.18.1008 to 1.18.1021 2019-08-07 07:02:19 +00:00
pyup-bot
dfce202034 Update mkdocs-material from 3.1.0 to 4.4.0 2019-08-07 07:02:15 +00:00
Matthias
ea46bb3b84 Merge pull request #2103 from freqtrade/since_int
Since arguments are in milliseconds integer throughout ccxt.
2019-08-07 06:19:26 +02:00
Jonathan Raviotta
8418dfbaed edits for jupyter notebook example 2019-08-06 22:35:14 -04:00
Matthias
caf4580346 Use UTC Timezone for test 2019-08-06 20:23:32 +02:00
Matthias
a90ced1f38 Since arguments are in milliseconds integer throughout ccxt.
Explained here: https://github.com/ccxt/ccxt/issues/5636

fixes #2093
2019-08-06 20:09:09 +02:00
Matthias
6c0c77b3a1 Merge pull request #2096 from freqtrade/fix/cons_buys_1971
Evaluate current candle during backtesting
2019-08-06 13:46:16 +02:00
Matthias
16d4a4723f Merge pull request #2102 from freqtrade/optimize/travis
Update install-script to use parameter
2019-08-06 13:38:32 +02:00
Matthias
327e653fae Merge pull request #2100 from freqtrade/strategy_list_doc
Fix documentation for strategy-list
2019-08-06 13:31:11 +02:00
Matthias
81f773054d Add test to verify ticker_inteval is set 2019-08-06 06:56:08 +02:00
Matthias
7e91a0f4a8 Fail gracefully if ticker-interval is not set 2019-08-06 06:45:44 +02:00
Matthias
9d471f3c9a Fix documentation for strategy-list 2019-08-06 06:32:31 +02:00
Matthias
7e46a9833b Merge pull request #2097 from freqtrade/urllib3
Update urllib to latest version
2019-08-06 06:05:29 +02:00
Matthias
988a0245c2 Update install-script to use parameter
Use --prefix /usr/local for install-script too
2019-08-05 20:37:38 +02:00
Matthias
0376630f7a Update urllib to latest version 2019-08-05 20:25:20 +02:00
Matthias
c7d0329754 Clean up comments of detail-backtests 2019-08-05 20:19:19 +02:00
Matthias
bc2e920ae2 Adjust code to verify "current" candle for buy/sells 2019-08-05 20:07:29 +02:00
Matthias
3721610a63 Add new detailed trade-scenario tests
covers cases raised in #1971
2019-08-05 20:06:42 +02:00
Matthias
e060516cc7 Merge pull request #2049 from jraviotta/conda
Conda / makefile
2019-08-05 19:49:25 +02:00
Matthias
20abd4b833 Merge pull request #2095 from freqtrade/pyup/scheduled-update-2019-08-05
Scheduled weekly dependency update for week 31
2019-08-05 19:28:42 +02:00
Matthias
904381058c Add documentation for conda install 2019-08-05 19:25:43 +02:00
pyup-bot
5e64d629a3 Update coveralls from 1.8.1 to 1.8.2 2019-08-05 15:26:19 +00:00
pyup-bot
d71102c45a Update py_find_1st from 1.1.3 to 1.1.4 2019-08-05 15:26:17 +00:00
pyup-bot
403f7668d5 Update jsonschema from 3.0.1 to 3.0.2 2019-08-05 15:26:16 +00:00
pyup-bot
930c25f7f1 Update scikit-learn from 0.21.2 to 0.21.3 2019-08-05 15:26:11 +00:00
pyup-bot
187d029d20 Update arrow from 0.14.3 to 0.14.4 2019-08-05 15:26:10 +00:00
pyup-bot
9914198a6c Update ccxt from 1.18.992 to 1.18.1008 2019-08-05 15:26:09 +00:00
hroff-1902
c6444a10a8 move roi_space, stoploss_space, generate_roi_table to IHyperOpt 2019-08-05 18:07:25 +03:00
Matthias
383b24ab84 Merge branch 'develop' into align_userdata 2019-08-05 06:55:51 +02:00
hroff-1902
9cbab35de0 colorization by means of termcolor and colorama 2019-08-04 22:54:19 +03:00
Matthias
eeecdd4e5a Merge pull request #2092 from freqtrade/split_analyze_ticker
Split analyze_ticker
2019-08-04 19:37:52 +02:00
Matthias
2af663dccb rename _analyze_ticker_int to _analyze_ticker_internal 2019-08-04 12:55:03 +02:00
Matthias
0be7e2ef70 Merge pull request #2090 from freqtrade/fix/plotting_DB
load_trades_db should give as many columns as possible
2019-08-04 12:52:39 +02:00
Matthias
4d1ce8178c intend if to be clearer 2019-08-04 10:38:37 +02:00
Matthias
c5ccf44750 Remove generate_dataframe from plot_dataframe script 2019-08-04 10:26:04 +02:00
Matthias
e4380b533b Print plot filename so it can be easily opened 2019-08-04 10:25:46 +02:00
Matthias
62262d0bb5 improve docstring of _analyze_ticker_int 2019-08-04 10:21:22 +02:00
Matthias
52d92cba90 Split analyze_ticker and _analyze_ticker_int 2019-08-04 10:20:31 +02:00
Matthias
0df5932593 Merge pull request #2091 from freqtrade/adjust_issuetemplate
add Operating system to issue template
2019-08-04 09:32:56 +02:00
Matthias
d1838dceec Merge pull request #2086 from freqtrade/fix_restricted_markets
Restricted pairs warning
2019-08-04 09:25:59 +02:00
Matthias
c6bd143785 add Operating system to issue template 2019-08-03 20:04:49 +02:00
Matthias
d51fd1a5d0 fix typo 2019-08-03 19:56:41 +02:00
Matthias
c4e30862ee load_trades_db should give as many columns as possible 2019-08-03 19:55:54 +02:00
hroff-1902
3dd6fe2703 wording 2019-08-03 19:44:32 +03:00
hroff-1902
fe796c46c3 test adjusted 2019-08-03 19:13:18 +03:00
hroff-1902
f200f52a16 hyperopt print colorized results 2019-08-03 19:09:42 +03:00
Matthias
d59608f764 adjust some documentation wordings 2019-08-03 17:19:37 +02:00
Matthias
b3e6e710d8 Merge pull request #2084 from hroff-1902/hyperopt-print-params4
Improvements to hyperopt output
2019-08-03 13:24:47 +02:00
Matthias
8ab07e0451 Add FAQ section about restricted markets 2019-08-03 13:22:44 +02:00
Matthias
ad55faafa8 Fix odd test 2019-08-03 13:18:37 +02:00
Matthias
bbd58e772e Warn when using restricted pairs
As noted in https://github.com/ccxt/ccxt/issues/5624, there is currently
no way to detect if a user is impacted by this or not prior to creating
a order.
2019-08-03 13:14:36 +02:00
hroff-1902
e8b2ae0b85 tests adjusted 2019-08-03 11:34:09 +03:00
hroff-1902
13620df717 'with values:' line removed 2019-08-03 11:05:05 +03:00
Matthias
fb103dd162 Merge pull request #2085 from hroff-1902/remove-pytest-warning6
tests: hide deprecation warning due to use of --live
2019-08-03 09:35:22 +02:00
hroff-1902
3b65c986ee wordings fixed 2019-08-03 10:20:20 +03:00
hroff-1902
cad7d9135a tests: hide deprecation warning due to use of --live 2019-08-03 09:24:27 +03:00
hroff-1902
b152d1a7ab docs agjusted, plus minor fixes 2019-08-02 22:23:48 +03:00
hroff-1902
aa8f44f68c improvements to hyperopt output 2019-08-02 22:22:58 +03:00
Matthias
1810d86555 Merge pull request #2080 from freqtrade/add_strategy_docs
docs: Create detailed section about strategy problem analysis
2019-08-02 20:29:09 +02:00
Matthias
39e8e507d9 Merge branch 'develop' into align_userdata 2019-08-02 20:08:26 +02:00
Matthias
3eb571f34c recommended ... 2019-08-02 20:04:18 +02:00
Matthias
e8be357624 Merge pull request #2079 from hroff-1902/hyperopt-print-params3
minor: cleanup in hyperopt
2019-08-02 20:02:46 +02:00
Matthias
32605fa10a small improvements 2019-08-02 19:52:56 +02:00
Matthias
0b9b5f3993 Improve document wording 2019-08-02 19:50:12 +02:00
Matthias
86aa18efe6 Merge pull request #2082 from freqtrade/fix/missintfstring
Fix/missintfstring
2019-08-02 10:27:10 +02:00
Matthias
76d22bc743 Show correct valueerror message 2019-08-02 09:41:24 +02:00
Matthias
01cd30984b Improve wording 2019-08-02 06:47:03 +02:00
Matthias
fceb411154 Create detailed section about strategy problem analysis 2019-08-02 06:44:31 +02:00
Jonathan Raviotta
0413598d7b adding environment.yml for conda builds 2019-08-01 19:30:45 -04:00
hroff-1902
3ccfe88ad8 tests adjusted 2019-08-01 23:57:50 +03:00
hroff-1902
065ebd39ef cleanup in hyperopt 2019-08-01 23:57:26 +03:00
Matthias
bcccdda7c0 Merge branch 'develop' into align_userdata 2019-08-01 19:33:45 +02:00
Matthias
4c005e7086 Merge pull request #2075 from hroff-1902/hyperopt-cleanup2
minor: hyperopt cleanups and output improvements
2019-08-01 07:08:50 +02:00
Matthias
2a141af42e Only create userdir when explicitly requested 2019-07-31 19:39:54 +02:00
Matthias
472690a55f Merge pull request #2073 from freqtrade/update/setuppy
Improve setup.py to allow "extras" installations
2019-07-31 19:25:21 +02:00
Matthias
8cef567abc create and use hyperopt-results folder 2019-07-31 07:10:17 +02:00
Matthias
5d22d541f2 Add forgotten directory 2019-07-31 06:58:26 +02:00
Matthias
c3d14ab9b9 don't use "folder" ... 2019-07-31 06:54:45 +02:00
Matthias
0488525888 Fix some documentation errors 2019-07-31 06:49:25 +02:00
Matthias
b8713a515e Merge pull request #2071 from freqtrade/new-dev
New develop version 2019.7-dev
2019-07-30 11:31:22 +02:00
hroff-1902
b976f24672 tests adjusted 2019-07-30 11:47:46 +03:00
hroff-1902
8f1f416a52 hyperopt cleanup and output improvements 2019-07-30 11:47:28 +03:00
Matthias
0d9d23a888 Merge pull request #2070 from freqtrade/new_release
New release 2019.7
2019-07-30 06:19:43 +02:00
Matthias
a5fb3e08f7 Merge pull request #2072 from freqtrade/improve_dev_docs
Improve release documentation
2019-07-30 06:12:47 +02:00
Matthias
59caff8fb1 UPdate developer docs 2019-07-29 20:57:57 +02:00
Matthias
f825e81d0e developers need all dependencies! 2019-07-29 20:54:35 +02:00
Matthias
7bea0007c7 Allow installing via submodules
freqtrade can be installed using `pip install -e .[all]` to include all
dependencies
2019-07-29 20:53:26 +02:00
Matthias
8dd8addd3a Sort requirements-dev file 2019-07-29 20:52:38 +02:00
Matthias
e14dd4974f Improve release documentation 2019-07-29 20:32:28 +02:00
Matthias
7a97995d81 2017.7-dev version bump 2019-07-29 20:30:14 +02:00
Matthias
e64509f1b4 Version bump to 2019.7 2019-07-29 20:27:50 +02:00
Matthias
0ac5440fc2 Merge pull request #2069 from freqtrade/pyup/scheduled-update-2019-07-29
Scheduled weekly dependency update for week 30
2019-07-29 20:07:44 +02:00
Matthias
fde3411c8b Merge branch 'develop' into pyup/scheduled-update-2019-07-29 2019-07-29 19:39:09 +02:00
Matthias
8066aba6fe Merge pull request #2044 from freqtrade/pyup/scheduled-update-2019-07-22
Scheduled weekly dependency update for week 29
2019-07-29 19:37:28 +02:00
pyup-bot
5ba0aa8082 Update plotly from 3.10.0 to 4.0.0 2019-07-29 15:25:16 +00:00
pyup-bot
3e95b7d8a5 Update mypy from 0.711 to 0.720 2019-07-29 15:25:15 +00:00
pyup-bot
0f632201e0 Update pytest from 5.0.0 to 5.0.1 2019-07-29 15:25:14 +00:00
pyup-bot
ebca1e4357 Update flake8 from 3.7.7 to 3.7.8 2019-07-29 15:25:12 +00:00
pyup-bot
a3620c60ad Update flask from 1.0.3 to 1.1.1 2019-07-29 15:25:11 +00:00
pyup-bot
9f70ebecf1 Update arrow from 0.14.2 to 0.14.3 2019-07-29 15:25:10 +00:00
pyup-bot
0fd91e4450 Update sqlalchemy from 1.3.5 to 1.3.6 2019-07-29 15:25:09 +00:00
pyup-bot
fe088dc8c3 Update ccxt from 1.18.860 to 1.18.992 2019-07-29 15:25:08 +00:00
pyup-bot
5a6e20a6aa Update pandas from 0.24.2 to 0.25.0 2019-07-29 15:25:07 +00:00
pyup-bot
02bfe2dad3 Update numpy from 1.16.4 to 1.17.0 2019-07-29 15:25:06 +00:00
Matthias
50edd4cfdd Merge pull request #2046 from freqtrade/pyup/fix_update_07_22
Pyup/fix update 07 22
2019-07-29 13:28:40 +02:00
Matthias
03e60b9ea4 Rename folder_Operations to directory_operations 2019-07-29 06:15:49 +02:00
Matthias
0677472c56 Merge pull request #2066 from freqtrade/hyperopt/tests
Fix some hyperopt tests
2019-07-28 19:33:18 +02:00
Matthias
c1bc1e3137 Add documentation for user_data_dir 2019-07-28 15:34:49 +02:00
Matthias
b691fb7f2d Fix some hyperopt tests 2019-07-28 15:19:17 +02:00
Matthias
73ac98da80 Small fixes while tsting 2019-07-28 15:11:41 +02:00
Matthias
14b43b504b Use user_data_dir for hyperopt 2019-07-28 15:05:17 +02:00
Matthias
a3c605f147 PairListResovler to use user_data_dir 2019-07-28 14:58:06 +02:00
Matthias
333413d298 Add default_conf to strategy tests 2019-07-28 14:58:06 +02:00
Matthias
9de8d7276e have strategyresolver use user_data_dir 2019-07-28 14:57:05 +02:00
Matthias
432b106d58 Improve docstring, remove unneeded method 2019-07-28 14:57:05 +02:00
Matthias
2c7a248307 Use user_data_dir in hyperopt 2019-07-28 14:57:05 +02:00
Matthias
113947132c user_data_dir is PATH in config, not str 2019-07-28 14:57:05 +02:00
Matthias
0a253d66d0 Remove os.path from hyperopt 2019-07-28 14:57:05 +02:00
Matthias
ae0e001187 Fix some bugs in tests 2019-07-28 14:57:05 +02:00
Matthias
eab82fdec7 plot-scripts use user_data_dir 2019-07-28 14:57:05 +02:00
Matthias
da755d1c83 Remove obsolete variable 2019-07-28 14:57:05 +02:00
Matthias
1b2581f0cb Add user_data_dir to configuration 2019-07-28 14:57:05 +02:00
Matthias
56c8bdbaa2 Test create-userdir command line option 2019-07-28 14:57:05 +02:00
Matthias
23435512c4 Add create-userdir command to initialize a user directory 2019-07-28 14:57:05 +02:00
Matthias
6c3a0eb1d6 add create_userdir function 2019-07-28 14:55:19 +02:00
Matthias
c85cd13ca1 Change default backtest result to "backtest_results" - backtest_data is
misleading
2019-07-28 14:55:19 +02:00
Matthias
e4b994381b Merge pull request #2060 from hroff-1902/improve-logging
Improve logging: output divider in logs between throttles
2019-07-28 14:45:16 +02:00
Matthias
de2a2473f5 Merge pull request #2050 from mrsegen/patch-1
Resolve issue #2042
2019-07-28 14:11:03 +02:00
Matthias
e6b036b413 Merge pull request #2064 from hroff-1902/remove-pytest-warning4
get rid of pandas warning in pytest
2019-07-28 14:10:16 +02:00
Leif Segen
08a3d26328 Update bot-usage.md
Update in response to feedback.
2019-07-27 18:35:21 -05:00
hroff-1902
bc299067aa get rid of pandas warning in pytest 2019-07-27 23:24:06 +03:00
Matthias
908a0277e5 Merge pull request #2062 from hroff-1902/remove-pytest-warning1
minor: eliminate warnings in pytest
2019-07-26 14:40:11 +02:00
hroff-1902
c2deb1db25 eliminate warnings in pytest when testing handling of the deprecated strategy interfaces 2019-07-26 14:23:00 +03:00
Matthias
16716ad028 Merge pull request #2057 from freqtrade/refactor/argument_location
Move argument definitions to their own file
2019-07-26 06:19:04 +02:00
Matthias
fef8fe8525 Merge pull request #2055 from freqtrade/get_order_exception
Get order exception
2019-07-26 06:17:15 +02:00
Matthias
3d5268368f Merge pull request #2059 from hroff-1902/docs-minor-fixes
Docs minor fixes
2019-07-26 06:08:09 +02:00
Matthias
20b51da180 Merge pull request #2056 from freqtrade/deprecate_live_bt
Deprecate live bt
2019-07-26 06:02:27 +02:00
hroff-1902
785a7a22bc output divider in logs between throttles 2019-07-26 04:02:34 +03:00
hroff-1902
1ac4a7e116 rendering for a Note fixed 2019-07-26 02:59:10 +03:00
hroff-1902
327e505273 non-working link to misc.py removed 2019-07-26 02:57:51 +03:00
hroff-1902
bf1c197a37 import errors fixed 2019-07-26 02:21:31 +03:00
Matthias
3c3a902a69 Move argument definitions to their own file 2019-07-25 20:42:08 +02:00
Matthias
0c14176cd7 Deprecate --live 2019-07-25 20:36:19 +02:00
Matthias
7ee971c3e3 Add simple method to add deprecations to cmd line options 2019-07-25 20:35:20 +02:00
Matthias
098a23adc6 Merge pull request #2048 from hroff-1902/hyperopt-loss-onlyprofit2
minor: add OnlyProfitHyperOptLoss
2019-07-25 20:18:05 +02:00
hroff-1902
10c69387fd docs adjusted 2019-07-25 21:07:17 +03:00
Matthias
4b8b2f7c5b Use raise xxx from e to have a nicer traceback 2019-07-25 20:06:20 +02:00
Matthias
e1b8ff798f Add test to verify that get_order was successfully cought 2019-07-25 20:05:48 +02:00
Matthias
05b1854946 Gracefully handle InvalidOrderException. 2019-07-25 19:56:59 +02:00
hroff-1902
f58668fd67 test added 2019-07-25 20:54:12 +03:00
Matthias
e8843c31e6 Merge pull request #2045 from hroff-1902/add-hyperopt-path
add --hyperopt-path option
2019-07-25 10:42:23 +02:00
hroff-1902
05be16e9e1 helpstring alignment fixed 2019-07-25 08:49:33 +03:00
hroff-1902
e9b77298a7 max() removed 2019-07-25 08:17:41 +03:00
Matthias
a0cecc6c52 Fix test after pandas 0.25.0 update 2019-07-24 06:29:50 +02:00
Leif Segen
cf6113068c Resolve issue #2042
Issue #2042 noted that the terminal output from `setup.sh` regarding an option use the bot was missing from the documentation. This has been added.
2019-07-23 22:52:42 -05:00
hroff-1902
0c2c094db6 minor: add OnlyProfitHyperOptLoss 2019-07-23 18:51:24 +03:00
Matthias
60cf56e235 Adapt tests to always provide message for ccxt exceptions
Changes introduced in https://github.com/ccxt/ccxt/pull/5470
2019-07-22 20:59:49 +02:00
Matthias
482f5f7a26 Update plotly dependencies (will break 3.x installations) 2019-07-22 20:39:38 +02:00
hroff-1902
04382d4b44 add --hyperopt-path option 2019-07-22 20:23:18 +03:00
pyup-bot
44b2261c34 Update plotly from 3.10.0 to 4.0.0 2019-07-22 15:23:13 +00:00
pyup-bot
76b9d781ee Update mypy from 0.711 to 0.720 2019-07-22 15:23:12 +00:00
pyup-bot
bd0faaf702 Update pytest from 5.0.0 to 5.0.1 2019-07-22 15:23:11 +00:00
pyup-bot
e0cd34c9e1 Update flake8 from 3.7.7 to 3.7.8 2019-07-22 15:23:09 +00:00
pyup-bot
6c41ca4b8c Update flask from 1.0.3 to 1.1.1 2019-07-22 15:23:08 +00:00
pyup-bot
7add015a75 Update sqlalchemy from 1.3.5 to 1.3.6 2019-07-22 15:23:07 +00:00
pyup-bot
d6b6e59ab8 Update ccxt from 1.18.860 to 1.18.965 2019-07-22 15:23:06 +00:00
pyup-bot
a213674a98 Update pandas from 0.24.2 to 0.25.0 2019-07-22 15:23:05 +00:00
Matthias
41f24898e5 Merge pull request #2043 from freqtrade/combine/resolvers
Combine/resolvers
2019-07-22 06:19:31 +02:00
Matthias
d2ad32eef8 partially revert last commit(DefaultStrategy import IS needed).
* don't run functions in travis in a way we don't support
2019-07-21 19:56:43 +02:00
Matthias
1fea6d394a Import DefaultStrategy from the correct file 2019-07-21 19:31:50 +02:00
Matthias
dcddfce5bc Fix small mistakes 2019-07-21 19:21:50 +02:00
Matthias
e6528be63d Config is not optional for hyperopt resolver 2019-07-21 16:20:45 +02:00
Matthias
08ca260e82 Simplify return valuef rom _load_object 2019-07-21 15:29:17 +02:00
Matthias
88eb93da52 Fix base64 strategy test to make sure strategy was loaded via base64 2019-07-21 15:16:19 +02:00
Matthias
b35efd96dc Extract load_object from multiple paths to iResolver 2019-07-21 15:03:12 +02:00
Matthias
89db5c6bab Extract strategy-specific stuff from search logic
will allow extracting all to IResolver
2019-07-21 14:52:59 +02:00
Matthias
790838d897 Merge pull request #2024 from freqtrade/custom_hyperopt_loss
Custom hyperopt loss function (and sharpe-ratio)
2019-07-20 12:48:26 +02:00
Matthias
4d0cf9ec8e Merge pull request #2033 from hroff-1902/remove-dynamic-whitelist-option
remove deprecated --dynamic-whitelist option
2019-07-19 06:38:54 +02:00
Matthias
299f673a8e Merge pull request #2029 from freqtrade/create_datadir_pathlib
[minor] Convert create_datadir to Pathlib
2019-07-19 06:36:11 +02:00
Matthias
fa8904978b Don't use --hyperopt-loss-class, but --hyperopt-loss instead 2019-07-19 06:31:49 +02:00
hroff-1902
4a144d1c18 docs: description for whitelist and blacklist fixed 2019-07-18 22:43:36 +03:00
Matthias
415c96204a Merge pull request #2035 from hroff-1902/cleanup-arguments
minor: cleanup Arguments
2019-07-18 20:56:51 +02:00
hroff-1902
7af24dc486 cleanup Arguments: name attrs and methods as non-public 2019-07-18 21:43:40 +03:00
Matthias
e01c0ab4d6 Improve doc wording 2019-07-18 20:02:28 +02:00
Matthias
8b4827ad85 Convert create_datadir to Pathlib 2019-07-18 19:48:19 +02:00
hroff-1902
43d5ec2d4a docs: removed historical excursus which can confuse new users 2019-07-18 18:15:51 +03:00
hroff-1902
75a0998ed2 docs: restore link to #dynamic-pairlists. 2019-07-18 18:08:02 +03:00
Matthias
fbd229810f Merge pull request #2034 from hroff-1902/option-version
minor: add -V alias for --version
2019-07-18 14:06:05 +02:00
Matthias
d27e791f32 Merge pull request #2031 from freqtrade/randomize_tests_again
Randomize tests again
2019-07-18 13:53:48 +02:00
hroff-1902
50d2950e6b add -V alias for --version 2019-07-18 12:12:34 +03:00
hroff-1902
96564d0dad remove deprecated --dynamic-whitelist option 2019-07-18 10:45:47 +03:00
Matthias
3e5abd18ca Randomize tests again
this used to be enabled, but the plugin changed how it works
> From v1.0.0 onwards, this plugin no longer randomises tests by default.
2019-07-18 06:56:52 +02:00
Matthias
545ff6f9f1 Fix typo 2019-07-18 06:31:44 +02:00
Matthias
49b95fe008 use Path.cwd() instead of odd parent.parent.parent structure 2019-07-17 20:52:17 +02:00
Matthias
b8704e12b7 Add sample hyperopt loss file 2019-07-17 20:51:44 +02:00
Matthias
639a4d5cf7 Allow importing interface from hyperopt.py 2019-07-17 07:15:43 +02:00
Matthias
0e500de1a0 Add sample loss and improve docstring 2019-07-17 06:32:24 +02:00
Matthias
c5b244419d Merge branch 'develop' into custom_hyperopt_loss 2019-07-17 06:27:42 +02:00
Matthias
8ccfc0f316 Remove unused variables 2019-07-17 06:24:40 +02:00
Matthias
e126c55a5a Merge pull request #2023 from hroff-1902/refactor/config3
minor: configuration cleanup
2019-07-17 06:20:21 +02:00
hroff-1902
be26ba8f8f rename _load_*_config() methods to _process_*_options() 2019-07-16 23:00:19 +03:00
Matthias
1493771087 improve description 2019-07-16 19:40:42 +02:00
Matthias
192d7ad735 Add column description to hyperopt documentation 2019-07-16 06:54:38 +02:00
Matthias
12679da5da Add test for hyperoptresolver 2019-07-16 06:50:25 +02:00
Matthias
ec49b22af3 Add sharpe ratio hyperopt loss 2019-07-16 06:45:13 +02:00
Matthias
d23179e25c Update hyperopt-loss to use resolver 2019-07-16 06:27:43 +02:00
Matthias
7d62bb8c53 Revert --clean argument to --continue 2019-07-16 05:51:26 +02:00
Matthias
c4e55d78d5 reword documentation 2019-07-16 05:41:39 +02:00
Matthias
07a1c48e8c Fix wrong intendation for custom-hyperopt check 2019-07-15 23:14:07 +02:00
Matthias
7be25313a5 Add some mypy ignores 2019-07-15 22:59:28 +02:00
Matthias
55e8092cbf Add sharpe ratio as loss function 2019-07-15 22:52:33 +02:00
Matthias
e5170582de Adapt tests to new loss-function method 2019-07-15 22:45:14 +02:00
Matthias
710443d200 Add documentation for custom hyperopt 2019-07-15 21:38:49 +02:00
Matthias
2a20423be6 Allow loading custom hyperopt loss functions 2019-07-15 21:35:42 +02:00
hroff-1902
8096a1fb04 minor: configuration cleanup 2019-07-15 22:17:57 +03:00
Matthias
2fedae6060 Move unnecessary things out of generate_optimizer 2019-07-15 20:31:55 +02:00
Matthias
b1b4048f97 Add test for hyperopt 2019-07-15 20:28:02 +02:00
Matthias
107f00ff8f Add hyperopt option to clean temporary pickle files 2019-07-15 20:17:15 +02:00
Matthias
5144e98a82 Merge pull request #2015 from hroff-1902/refactor/config2
Make configuration a module
2019-07-15 19:41:57 +02:00
Matthias
210d70b0c7 Merge pull request #2022 from freqtrade/fix/2020
Remove wrong import in legacy startup sript
2019-07-15 19:36:16 +02:00
Matthias
3ae94520c3 Merge pull request #2019 from freqtrade/small/cleanups
[Minor] Small code cleanups
2019-07-15 17:29:32 +02:00
Matthias
cbe25178d7 Merge pull request #2009 from hroff-1902/fix-2008
fix #2008
2019-07-15 10:55:33 +02:00
Matthias
a3b7e1f774 Update wording in docs 2019-07-15 06:59:20 +02:00
Matthias
bbab5fef0c Remove wrong import in legacy startup sript 2019-07-15 06:27:43 +02:00
hroff-1902
007703156b do not export ARGS_* from configuration 2019-07-15 01:55:35 +03:00
hroff-1902
9cae2900d4 get rid of patched_configuration_open() in tests 2019-07-15 01:44:25 +03:00
hroff-1902
876cae2807 docs adjusted to current default values; more detailed description of --eps and --dmmp added 2019-07-14 22:48:15 +03:00
Matthias
e955b1ae09 Use log_has_re instead of plain regex filters for log messages 2019-07-14 20:21:57 +02:00
Matthias
dadf8adb3e Replace filter usage 2019-07-14 20:14:35 +02:00
Matthias
4238ee090d Cleanup some code
after deepcode.ai suggestions
2019-07-14 20:05:28 +02:00
hroff-1902
65f77306d3 using logger.debug, info was too noisy 2019-07-14 21:00:48 +03:00
hroff-1902
efbc7cccb1 enable --dmmp for hyperopt 2019-07-14 20:56:17 +03:00
Matthias
f0206a90b1 Merge pull request #2018 from freqtrade/market_orders_with_price
Market orders with price
2019-07-14 19:29:44 +02:00
Matthias
a8f3f2bc1a Extend test to cover market orders with price too 2019-07-14 14:23:23 +02:00
Matthias
25822d1717 Add empty options dict to all tests using create_order 2019-07-14 14:18:30 +02:00
Matthias
9887cb997e Check if Price is needed for market orders
This is currently the case for:
cex, coinex, cointiger, fcoin, fcoinjp, hadax, huobipro, huobiru, uex,
2019-07-14 14:17:09 +02:00
Matthias
7e2be96516 Merge pull request #2017 from hroff-1902/resolver-filename
minor: improvements to resolvers
2019-07-14 13:37:00 +02:00
Matthias
2e1269c474 Revert comment for Exception that's not changed 2019-07-14 13:30:57 +02:00
hroff-1902
b499e74502 minor improvements to resolvers 2019-07-12 23:45:49 +03:00
Matthias
7536f6adbd Merge pull request #2004 from freqtrade/doc/starting
Don't run the bot with python3 freqtrade
2019-07-12 09:02:41 +02:00
Matthias
4be02bc207 Merge pull request #2014 from hroff-1902/fix-2013
Fix #2013
2019-07-12 08:14:46 +02:00
hroff-1902
bbfbd87a9f move create_datadir() to separate file 2019-07-12 03:31:36 +03:00
hroff-1902
7e103e34f8 flake happy 2019-07-12 01:41:09 +03:00
hroff-1902
94e6fb89b3 tests happy 2019-07-12 00:49:23 +03:00
hroff-1902
1bdffcc73b make configuration a sep. module, including arguments 2019-07-12 00:49:23 +03:00
hroff-1902
e993e010f4 Fix #2013 2019-07-11 23:02:57 +03:00
Matthias
bc1b5f477d Merge pull request #2010 from freqtrade/fix/docs
Fix non-rendering docs
2019-07-11 00:51:54 +02:00
Matthias
6a43128019 Fix non-rendering docs 2019-07-10 08:49:42 +02:00
hroff-1902
c474e2ac86 fix #2008 2019-07-10 01:53:40 +03:00
Matthias
7763b4cf5b Merge pull request #2007 from hroff-1902/fix-2005
fix #2005
2019-07-09 10:33:42 +02:00
hroff-1902
322227bf67 fix #2005 2019-07-09 00:59:34 +03:00
Matthias
27cb1a4174 Add FAQ section explaining "module not found" errors 2019-07-08 17:08:14 +02:00
Matthias
c4fb0fd6ca Don't run the bot with python3 freqtrade
* we can either use `python3 -m freqtrade ...` or `freqtrade ...` - and
shorter should be better.
2019-07-08 17:01:25 +02:00
Matthias
87ff1e8cb0 Merge pull request #2002 from hroff-1902/refactor/arguments2
minor: refactoring arguments and configuration
2019-07-08 16:56:25 +02:00
Matthias
61b24180f0 Merge pull request #1998 from freqtrade/fix/pax_balance
Support all types of pairs for /balance
2019-07-08 16:31:57 +02:00
hroff-1902
15d2cbd6df loggers: wording improved 2019-07-07 10:17:01 +03:00
hroff-1902
f7a2428deb max_open_trades may be -1 2019-07-07 10:13:00 +03:00
Matthias
6c2415d32f Rename parameters from pair to curr 2019-07-07 06:36:35 +02:00
hroff-1902
84d3868994 rename loglevel --> verbosity, because it's not logging level 2019-07-07 02:53:13 +03:00
hroff-1902
f89b2a18e0 fix loglevel in conftest -- it's actually the verbosity level 2019-07-07 02:42:03 +03:00
hroff-1902
8114d790a5 commit forgotten loggers.py 2019-07-07 01:40:52 +03:00
hroff-1902
082065cd50 minor cosmetics in arguments.py 2019-07-07 01:20:26 +03:00
hroff-1902
a65b5f8e02 make some more arguments positive integers 2019-07-07 01:10:41 +03:00
hroff-1902
d8f133aaf3 remove duplicated loglevel option 2019-07-07 00:51:01 +03:00
hroff-1902
8e272e5774 minor: cosmetics in arguments.py 2019-07-07 00:48:39 +03:00
hroff-1902
ce2a5b2838 move loggers setup out of configuration 2019-07-07 00:31:48 +03:00
Matthias
bcf2bc6f8c Merge pull request #1999 from freqtrade/minor/datadir
minor - Folders are not Directories
2019-07-04 20:25:44 +02:00
Matthias
17800c8ca5 Remove folder references (it's directory!) 2019-07-04 19:57:38 +02:00
Matthias
5c6039fd8b Fix #1997 - rename folder to dir 2019-07-04 19:53:50 +02:00
Matthias
40fe2d2c16 Test get_valid_pair_combination 2019-07-03 20:20:12 +02:00
Matthias
1bcf2737fe Add tests for new behaviour 2019-07-03 20:07:26 +02:00
Matthias
fcdbe846e5 Fix #1981 - Detect reverted currency pairs 2019-07-03 20:06:50 +02:00
Matthias
d055dc0c6e Merge pull request #1993 from freqtrade/refactor/arguments
Remove duplicate keyword from arguments
2019-07-03 12:01:41 +02:00
Matthias
e19c192570 Merge pull request #1994 from hroff-1902/fix-validate_timeframes
fix validate_timeframes()
2019-07-03 11:11:28 +02:00
hroff-1902
b80cef964e fix validate_timeframes(); test added 2019-07-03 11:18:39 +03:00
Matthias
b43594e4eb Merge pull request #1996 from hroff-1902/fix/1995
fix #1995
2019-07-03 06:44:23 +02:00
Matthias
0908863e07 Merge pull request #1987 from freqtrade/plot_script_changes
Plot script changes
2019-07-03 06:43:34 +02:00
Matthias
b3644f7fa0 Fix typo in docstring 2019-07-03 06:26:39 +02:00
hroff-1902
d41b8cc96e catch ccxt.BaseError 2019-07-03 05:13:41 +03:00
hroff-1902
91fb9d0113 fix #1995 2019-07-03 05:02:44 +03:00
Matthias
85ac217abc Remove duplicate keyword from arguments 2019-07-02 20:33:27 +02:00
Matthias
687381f42c Merge pull request #1991 from freqtrade/pyup/scheduled-update-2019-07-01
Scheduled weekly dependency update for week 26
2019-07-01 22:06:29 +02:00
pyup-bot
c91add203d Update mypy from 0.710 to 0.711 2019-07-01 18:28:32 +00:00
pyup-bot
1e4f459a26 Update pytest from 4.6.3 to 5.0.0 2019-07-01 18:28:31 +00:00
pyup-bot
06ad04e5fa Update ccxt from 1.18.805 to 1.18.860 2019-07-01 18:28:30 +00:00
Matthias
80bf5c9756 Merge pull request #1988 from freqtrade/fix/timeframes_crash
Gracefully fail on timeframes exception
2019-07-01 11:19:37 +02:00
Matthias
0d601fd111 Remove logger message 2019-07-01 06:18:28 +02:00
Matthias
01904d3c1e Test not having timeframe available on exchange object 2019-06-30 20:30:57 +02:00
Matthias
0c7d14fe50 Check if timeframes is available and fail gracefully otherwise 2019-06-30 20:30:31 +02:00
Matthias
cdeb649d0b Merge pull request #1967 from freqtrade/modify/setup.sh
Modify handling of pip in setup.sh
2019-06-30 19:52:50 +02:00
Matthias
79ae3c2f2e Merge pull request #1977 from hroff-1902/cleanup/freqtradebot
partial freqtradebot cleanup
2019-06-30 19:52:35 +02:00
Matthias
59818af69c Remove common_datearray function 2019-06-30 13:18:22 +02:00
Matthias
44e0500958 Test init_plotscript 2019-06-30 13:01:12 +02:00
Matthias
db59d39e2c Don't use class for plotting
This will allow easy usage of the methods from jupter notebooks
2019-06-30 11:08:02 +02:00
Matthias
587d71efb5 Test generate_profit_plot 2019-06-30 10:47:55 +02:00
Matthias
c7a4a16eec Create generate_plot_graph 2019-06-30 10:31:36 +02:00
Matthias
0b517584aa Use add_profit in script 2019-06-30 10:26:53 +02:00
Matthias
5a11ffcad8 Add test for add_profit 2019-06-30 10:24:10 +02:00
Matthias
0a184d380e create add_profit function 2019-06-30 10:14:33 +02:00
Matthias
6b387d320e extract combine_tickers to btanalysis 2019-06-30 10:04:43 +02:00
Matthias
348513c151 Improve formatting of plotting.py 2019-06-30 09:47:07 +02:00
Matthias
0d5e94b147 Rename generate_row to add_indicators 2019-06-30 09:44:50 +02:00
Matthias
88545d882c Use FTPlots class in plot-scripts 2019-06-30 09:42:10 +02:00
Matthias
42ea0a19d2 create FTPlots class to combine duplicate script code 2019-06-30 09:41:43 +02:00
Matthias
c87d27048b align plot_profit to plot_dataframe 2019-06-30 09:28:49 +02:00
Matthias
700bab7279 Rename generate_plot_file to store_plot_file 2019-06-30 09:28:34 +02:00
Matthias
c3db4ebbc3 Revise plot_profit to use pandas functions where possible 2019-06-29 20:52:33 +02:00
Matthias
8aa327cb8a Add load_trades abstraction (to load trades from either DB or file) 2019-06-29 20:52:23 +02:00
Matthias
4218d569de Only read trades once 2019-06-29 20:41:22 +02:00
Matthias
e50eee59cf Seperate plot-name generation and plotting 2019-06-29 20:38:49 +02:00
Matthias
4506832925 Update docstring 2019-06-29 20:07:25 +02:00
Matthias
a0cdc63a5d Merge pull request #1984 from asmodehn/bitstamp_bad
adding bitstamp to list of bad exchanges.
2019-06-29 19:51:01 +02:00
Matthias
79b4e2dc85 Rename generate_graph to generate_candlestick_graph 2019-06-29 17:23:33 +02:00
Matthias
edd3fc8825 Add test for create_cum_profit 2019-06-29 17:22:47 +02:00
AlexV
e8796e009c adding bitstamp to list of bad exchanges. 2019-06-29 17:20:10 +02:00
Matthias
044be3b93e Add create_cum_profit column 2019-06-29 16:57:04 +02:00
Matthias
0436811cf0 Use mode OTHER, nto backtesting 2019-06-28 06:47:40 +02:00
Matthias
152e138c17 Merge pull request #1979 from hroff-1902/fix/1978
fix #1978
2019-06-28 06:04:32 +02:00
hroff-1902
4f5e212f87 fix #1978 2019-06-28 01:01:51 +03:00
hroff-1902
21bf01a24c partial freqtradebot cleanup 2019-06-27 22:29:17 +03:00
Matthias
16a9e6b72f Improve install documentation 2019-06-27 19:51:04 +02:00
Matthias
700bc087d3 Merge pull request #1952 from hroff-1902/fix/1948
Fix #1948
2019-06-27 19:36:06 +02:00
Matthias
8b99348e98 Merge pull request #1975 from freqtrade/fix/dry_run_bal
Show different message for balance during dry-run
2019-06-27 19:34:51 +02:00
Matthias
045f34e851 Merge pull request #1974 from hroff-1902/fix/1963
fix #1963
2019-06-27 19:34:17 +02:00
hroff-1902
e5a8030dd7 comment added 2019-06-27 16:42:10 +03:00
Matthias
6643b83afe Update tests to test both balance versions 2019-06-27 07:06:35 +02:00
Matthias
98681b78b4 Show ifferent message for balance in dry-run 2019-06-27 07:06:11 +02:00
Matthias
f8dd0b0cb3 Use parenteses instead of \ seperators 2019-06-27 06:32:26 +02:00
Matthias
f04d49886b Add test to verify behaviour if currency in fee-dict is None 2019-06-27 06:29:18 +02:00
Matthias
3043a8d9c9 Be more explicit about what's missing 2019-06-27 06:20:22 +02:00
Matthias
4459fdf1b1 Merge pull request #1961 from freqtrade/feat/config_refactor
Argument handling refactor
2019-06-27 06:06:23 +02:00
Matthias
086d690df7 Merge pull request #1973 from hroff-1902/minor-typos-1
minor: couple of typos fixed
2019-06-27 05:49:58 +02:00
hroff-1902
05d93cda16 fix #1963 2019-06-27 01:03:38 +03:00
hroff-1902
6fc6eaf742 minor: couple of typos fixed 2019-06-26 22:23:16 +03:00
Matthias
596cee2dc1 Merge pull request #1972 from freqtrade/update_qtpylib
Update qtpylib from source
2019-06-26 20:34:28 +02:00
Matthias
1d5c3f34ae Update qtpylib from source 2019-06-26 20:00:16 +02:00
Matthias
ca7080c2bb Merge pull request #1958 from freqtrade/new_release_dev
Version bump develop
2019-06-26 06:11:00 +02:00
Matthias
21f6493b02 Merge pull request #1957 from freqtrade/new_release
New release - 2019.6
2019-06-26 06:05:43 +02:00
Matthias
a89112a133 Merge pull request #1969 from freqtrade/developer_doc_improve
[minor] Improve developer-document
2019-06-25 07:04:06 +02:00
Matthias
353437bbd1 07 is July!! 2019-06-24 21:08:40 +02:00
Matthias
8e92fc62a3 Use correct new versioning now 2019-06-24 20:18:06 +02:00
Matthias
c106534663 Improve developer-document
to include a note to keep both branches uptodate while creating a changelog.

Cost me ~5 minutes doing the 2019.6 release...
2019-06-24 20:13:40 +02:00
Matthias
b92c6cdf35 Cleanup arguments and test_arguments 2019-06-24 20:10:50 +02:00
Matthias
ca5093901b Use build_args for plot script 2019-06-24 20:08:17 +02:00
Matthias
ba7a0dde06 Use build_args for download script 2019-06-24 20:08:17 +02:00
Matthias
27798c1683 Remove main_options 2019-06-24 20:08:15 +02:00
Matthias
ee312ac230 Use build_args for plot_dataframe script 2019-06-24 20:07:04 +02:00
Matthias
7e82be53cd Use build_args to build subcomand arguments 2019-06-24 20:05:17 +02:00
Matthias
7017e46ba1 Add dict with all possible cli arguments 2019-06-24 20:05:13 +02:00
Matthias
7166674d6c Move check_int_positive out of arguments class 2019-06-24 19:55:16 +02:00
Matthias
e1daf02735 UPdate version for develop 2019-06-24 19:46:39 +02:00
Matthias
56e6294873 Version bump to 2019.6 2019-06-24 19:44:14 +02:00
Matthias
1b15e5dd64 Merge branch 'master' into new_release 2019-06-24 19:43:59 +02:00
Matthias
31a2aac627 Merge pull request #1959 from freqtrade/split_btanalysis_load_trades
Split btanalysis load trades
2019-06-24 19:41:56 +02:00
Matthias
158569f5e8 Merge pull request #1968 from freqtrade/pyup/scheduled-update-2019-06-24
Scheduled weekly dependency update for week 25
2019-06-24 19:26:00 +02:00
Matthias
e83f8941a1 Fix documentation grammar 2019-06-24 19:20:42 +02:00
pyup-bot
d6dbb21a34 Update mypy from 0.701 to 0.710 2019-06-24 15:24:09 +00:00
pyup-bot
90ada0649c Update wrapt from 1.11.1 to 1.11.2 2019-06-24 15:24:08 +00:00
pyup-bot
e8429bd230 Update sqlalchemy from 1.3.4 to 1.3.5 2019-06-24 15:24:07 +00:00
pyup-bot
5a30f0462f Update ccxt from 1.18.725 to 1.18.805 2019-06-24 15:24:06 +00:00
Matthias
11d39bb0d3 Improve wording 2019-06-24 17:20:41 +02:00
Matthias
a517779dd7 Merge pull request #1964 from hroff-1902/fix-help-strings-2
minor: fix help strings
2019-06-24 14:33:46 +02:00
Matthias
eba7327058 Merge branch 'develop' into split_btanalysis_load_trades 2019-06-24 07:15:14 +02:00
Matthias
1b156e0f34 Don't install python to a system, it's error-prone and may not work 2019-06-24 07:10:24 +02:00
Matthias
c1ee5d69c9 Try to get travis cache to work correctly 2019-06-24 07:09:54 +02:00
Matthias
1f8dc7f845 Merge pull request #1936 from freqtrade/fix/validate_dataframe
Properly warn if data is incomplete
2019-06-24 06:50:48 +02:00
Matthias
a07653a6cc Merge branch 'develop' into fix/validate_dataframe 2019-06-24 06:21:08 +02:00
Matthias
c9a76be532 Merge pull request #1943 from freqtrade/fix/tests_windows
Fix tests on windows
2019-06-24 06:18:17 +02:00
Matthias
9d2b6db97b Merge pull request #1954 from freqtrade/fix/stoploss_cancel_error
Trailing stoploss cancel orders should be handled gracefully
2019-06-24 06:17:44 +02:00
Matthias
12d2db5e7b Merge pull request #1966 from hroff-1902/fix-docstrings
minor: typos in docstrings fixed
2019-06-24 06:17:11 +02:00
Matthias
1add8ecd0c Merge pull request #1960 from freqtrade/plot_df_stripping
Plot datafame simplification
2019-06-24 06:15:54 +02:00
Matthias
f23a8a8cd1 Merge pull request #1965 from freqtrade/hroff-1902-patch-1
minor: typo fixed in docs
2019-06-24 06:14:27 +02:00
hroff-1902
116d8e853e typos in docstrings fixed 2019-06-23 23:10:37 +03:00
hroff-1902
5b84cb39ac typo fixed 2019-06-23 22:51:33 +03:00
hroff-1902
7f018839f8 diverse cosmetics to options help strings 2019-06-23 21:42:46 +03:00
hroff-1902
3716c04ed4 fix help string for --db-url 2019-06-23 20:34:53 +03:00
hroff-1902
7fbdf36c64 avoid code duplication while selecting min_roi entries 2019-06-23 19:23:51 +03:00
Matthias
da5f77c96f Merge pull request #1962 from hroff-1902/fix-help-strings
minor: fix help strings shown to the user
2019-06-23 10:56:11 +02:00
hroff-1902
451d4a400e fix help strings shown to the user 2019-06-22 23:51:29 +03:00
Matthias
4cbcb5f36f Move .title to ExchangeResolver (it does not make sense to do this over
and over again)
2019-06-22 16:52:14 +02:00
Matthias
026784efac remove get_tickers_data from plot_dataframe 2019-06-22 16:45:38 +02:00
Matthias
cc56d0e0fc Remove unneeded initialization 2019-06-22 16:40:33 +02:00
Matthias
559d5ebd1d Remove combined load-method since it's confusing 2019-06-22 16:20:41 +02:00
Matthias
3e61ada34a Be explicit in what is used, db or trades 2019-06-22 16:18:49 +02:00
Matthias
8758218b09 Add data-analysis documentation 2019-06-22 16:18:22 +02:00
Matthias
de38aea164 Fix sequence of loading trades 2019-06-22 15:45:20 +02:00
Matthias
d8286d7a98 Merge pull request #1937 from xmatthias/feat/plot_module
move parts of scripts/plot_dataframe.py to main bot code
2019-06-22 13:06:30 +02:00
Matthias
101ad71be1 Merge pull request #1955 from freqtrade/ticker_interval_to_hyperopt
Ticker interval to hyperopt
2019-06-22 12:55:17 +02:00
Matthias
db17b20e26 Don't require pairs but fall back to pair_whitelist instead 2019-06-21 20:21:03 +02:00
Matthias
a581ca66bf Adapt test after merging develop 2019-06-21 19:31:18 +02:00
Matthias
5d6819bb28 Merge branch 'develop' into feat/plot_module 2019-06-21 19:28:38 +02:00
Matthias
7a0d86660e Mypy type errors 2019-06-21 07:10:30 +02:00
Matthias
1a27ae8a81 Add tests to verify that ticker_interval is there 2019-06-21 07:07:39 +02:00
Matthias
f907a487c8 make ticker_interval available to hyperopt functions 2019-06-21 07:07:21 +02:00
Matthias
a75f08cf17 Merge pull request #1947 from hroff-1902/arguments-cleanup
arguments cleanup
2019-06-21 06:41:46 +02:00
Matthias
89ba649ddb Test handling errors while trailing stop loss 2019-06-20 20:57:15 +02:00
Matthias
63640518da Gracefully handle errosr when cancelling stoploss orders
fixes #1933
2019-06-20 20:56:58 +02:00
Matthias
a8dcfc05c5 Add test to verify InvalidOrder is handled correctly 2019-06-20 20:36:39 +02:00
Matthias
dd379c4192 Cancelling stoploss order should not kill the bot 2019-06-20 20:32:46 +02:00
Matthias
911e71cd9b remove redundant test-functions 2019-06-20 20:30:05 +02:00
Matthias
b8fb38b92c Merge pull request #1951 from hroff-1902/pipe-config
allow reading config from stdin
2019-06-20 19:29:14 +02:00
hroff-1902
144e053a4e fix for #1948 2019-06-20 03:26:25 +03:00
hroff-1902
a8efb1e1c8 test for #1948 added 2019-06-20 03:26:02 +03:00
hroff-1902
0866b5f29f allow reading config from stdin 2019-06-20 00:04:11 +03:00
Matthias
38712f8120 Merge pull request #1946 from hroff-1902/validator-cosmetics
minor: json validator cosmetics
2019-06-19 19:32:22 +02:00
hroff-1902
860e056366 --datadir is now handled in arguments.common_options() 2019-06-19 02:49:12 +03:00
hroff-1902
c6fed4e493 make flake happy 2019-06-19 02:42:29 +03:00
hroff-1902
8c40a406b6 arguments cleanup 2019-06-19 01:53:38 +03:00
hroff-1902
6f950bbd66 json validator cosmetics 2019-06-18 01:46:30 +03:00
Matthias
aa2cce020e Merge pull request #1944 from freqtrade/pyup/scheduled-update-2019-06-17
Scheduled weekly dependency update for week 24
2019-06-17 19:16:39 +02:00
pyup-bot
0e7ea1dada Update coveralls from 1.8.0 to 1.8.1 2019-06-17 15:23:15 +00:00
pyup-bot
6973087d5b Update pytest from 4.6.2 to 4.6.3 2019-06-17 15:23:14 +00:00
pyup-bot
25755f6adf Update ccxt from 1.18.667 to 1.18.725 2019-06-17 15:23:13 +00:00
Matthias
0d360167f3 Merge pull request #1942 from freqtrade/fix/rpc_market_buy
RPC: don't use limit for rates that could be market orders
2019-06-17 14:55:31 +02:00
Matthias
ba4890d303 Fix tests on windows 2019-06-17 14:36:58 +02:00
Matthias
7cd36239a4 UPdate documentation with new value 2019-06-17 07:03:33 +02:00
Matthias
06afb3f155 Don't use "limit" for sell-orders either 2019-06-17 07:01:17 +02:00
Matthias
557122921a Add order_type to sell-notification 2019-06-17 06:56:52 +02:00
Matthias
475e76b272 Add order_type to buy_notification 2019-06-17 06:55:30 +02:00
Matthias
b0c5286e8a Merge pull request #1938 from hroff-1902/cleanup-setup-configuration
minor: setup_configuration() cleanup
2019-06-17 06:41:19 +02:00
Matthias
bffa9fbfbd Merge pull request #1941 from hroff-1902/fix-typo
minor: fix typo
2019-06-17 06:07:07 +02:00
hroff-1902
d217f32bbc minor: fix typo in freqtradebot.py 2019-06-17 04:35:39 +03:00
hroff-1902
195bf5a4cc tests adjusted 2019-06-16 22:10:39 +03:00
hroff-1902
813c008af2 setup_configuration() cleanup 2019-06-16 21:37:43 +03:00
Matthias
765eff23f0 Fix typo 2019-06-16 20:14:31 +02:00
Matthias
0eb109f8f7 Improve some tests 2019-06-16 19:53:48 +02:00
Matthias
fc3e3c468c File existence is checked in load_backtest_data 2019-06-16 19:35:21 +02:00
Matthias
4b7dfc64c6 Add test for generate_plot_file 2019-06-16 19:35:21 +02:00
Matthias
488bb971ff Get rid of global conf object 2019-06-16 19:35:21 +02:00
Matthias
907c2f1e6b Copy plot options to config 2019-06-16 19:35:21 +02:00
Matthias
3f04930f38 Require pairs argument 2019-06-16 19:35:21 +02:00
Matthias
0300128cb8 Move plot-options to arguments.py 2019-06-16 19:35:15 +02:00
Matthias
bf2c0390e7 Adjust some imports 2019-06-16 19:33:48 +02:00
Matthias
1cd8415723 Move extract_trades_of_period to btanlaysis 2019-06-16 19:33:48 +02:00
Matthias
1c53aa5687 Add tests for load_trades 2019-06-16 19:33:48 +02:00
Matthias
c7643e142b Move load_trades to bt_anlaysis 2019-06-16 19:33:48 +02:00
Matthias
9f5ca82f48 Add more tests 2019-06-16 19:33:48 +02:00
Matthias
6db4e05aef Improve plotting tests 2019-06-16 19:33:48 +02:00
Matthias
2891d7cccb Add initial plotting test 2019-06-16 19:33:48 +02:00
Matthias
cae2185460 Move generate_plot to plotting.py 2019-06-16 19:33:48 +02:00
Matthias
6347161975 don't use print in plot_dataframe 2019-06-16 19:33:48 +02:00
Matthias
b1a01345f9 Add better hover tip 2019-06-16 19:33:48 +02:00
Matthias
e0a1e5417f sanity checks before plotting, cleanup 2019-06-16 19:33:48 +02:00
Matthias
6df0b39f81 Cleanup plot_dataframe a bit 2019-06-16 19:33:48 +02:00
Matthias
68af6d4151 Move plot-functions to plotting module 2019-06-16 19:33:48 +02:00
xmatthias
583d70ec9c add plot module proto 2019-06-16 19:33:48 +02:00
Matthias
2369161bb0 Merge pull request #1927 from hroff-1902/list-exchanges-module
list-exchanges subcommand added
2019-06-16 19:25:23 +02:00
Matthias
9035e0b695 Update function due to merge of #1926 2019-06-16 10:39:43 +02:00
Matthias
4ef309bc6c Merge branch 'develop' into pr/hroff-1902/1927 2019-06-16 10:37:28 +02:00
Matthias
114de8a025 Remove unused imports 2019-06-16 10:13:56 +02:00
Matthias
442339cd27 Add tests for utils.py 2019-06-16 10:13:24 +02:00
Matthias
e6cab6d710 Move get_args from multiple locations to conftest 2019-06-16 10:13:12 +02:00
Matthias
472e7f80a0 Fix Line too long error 2019-06-15 16:58:17 +02:00
Misagh
2a682f858e Merge pull request #1935 from freqtrade/update_slack_link
Update slack link since the old one expired
2019-06-15 14:30:31 +02:00
Misagh
c43edf98d4 Merge pull request #1934 from freqtrade/edge_override_stake_amount
Edge cli should override stake_amount
2019-06-15 14:28:16 +02:00
Matthias
a0415aea83 Merge pull request #1926 from hroff-1902/check-exchange
Enhance check_exchange()
2019-06-15 13:52:30 +02:00
Matthias
4a916125a0 Tests need to pass pair to parse_ticker_dataframe 2019-06-15 13:48:08 +02:00
Matthias
89ff614e1d Add pair as parameter, and warn when fillup was necessary 2019-06-15 13:46:19 +02:00
Matthias
55079831a1 Don't explicitly validate backtest data (it's done while loading now). 2019-06-15 13:45:50 +02:00
Matthias
d047a9d836 Adapt tests for new validate_backtest signature 2019-06-15 13:32:05 +02:00
Matthias
cd4cf215e1 Convert validate_backtest_data to take dataframe directly 2019-06-15 13:31:27 +02:00
Matthias
01b5ece642 Log missing data filllup if necessary 2019-06-15 13:31:14 +02:00
Matthias
36dd061be7 Update slack link since the old one expired 2019-06-15 13:19:18 +02:00
Matthias
a77d75eb43 Check log output since that's whats shown to users 2019-06-15 13:14:07 +02:00
Matthias
707118a636 Test stake changed to unlimited 2019-06-15 13:04:15 +02:00
Misagh
ad9dc349e4 edge cli should override stake_amount 2019-06-15 12:20:32 +02:00
hroff-1902
09cd7db9b1 make flake happy 2019-06-14 22:04:29 +03:00
hroff-1902
1af988711b add --one-column as an alias option 2019-06-14 21:59:16 +03:00
hroff-1902
cedd38455f remove configuration from list-exchanges 2019-06-14 21:54:38 +03:00
Matthias
2965931a78 Merge pull request #1893 from hroff-1902/refactor-download-script
refactoring download_backtest_data.py
2019-06-14 20:12:07 +02:00
Matthias
1afe6c1437 Don't run validation per strategy, it's only eneded once 2019-06-14 19:37:54 +02:00
Matthias
3240d4e70e Merge pull request #1925 from hroff-1902/strategy-advise-logging
debug logging for IStrategy.advise_*()
2019-06-14 19:24:14 +02:00
hroff-1902
941fb4ebbb tests added 2019-06-14 18:40:25 +03:00
hroff-1902
ee113ab8ed log messages aligned 2019-06-14 18:40:02 +03:00
Misagh
24f86e9ff3 Merge pull request #1931 from freqtrade/fix/trailing_stoploss_offset
Fix/trailing stoploss offset
2019-06-14 14:32:32 +02:00
hroff-1902
04ea66c977 fix handling timeframes 2019-06-14 02:58:34 +03:00
Matthias
9657b1a17f explict parse to string for ticker-interval 2019-06-13 20:37:17 +02:00
Matthias
e08fda074a Fix bug with timeframe handling 2019-06-13 20:26:47 +02:00
Matthias
550fbad53e Add test-cases with trailing_stop_offsets 2019-06-13 20:05:49 +02:00
Matthias
160894c031 Calculate profit_high to make sure stoploss_positive_offset is correct 2019-06-13 20:04:52 +02:00
Matthias
578180f45b Add test for sell-signal sell 2019-06-13 20:00:56 +02:00
Matthias
b64b6a2583 Support trailing_stop_positive options in BTContainer 2019-06-13 20:00:00 +02:00
Matthias
a4d8424268 trailing_stop_positive should only be set when needed, and
none/undefined otherwise
2019-06-13 19:34:46 +02:00
hroff-1902
a65c89f090 test adjusted 2019-06-12 23:37:02 +03:00
hroff-1902
0cc2210f22 wording fixed 2019-06-12 22:53:43 +03:00
hroff-1902
8df40a6ff9 make flake happy 2019-06-12 22:40:50 +03:00
hroff-1902
9c64965808 list-exchanges subcommand added 2019-06-12 12:33:20 +03:00
Misagh
0d8b572a17 Merge pull request #1921 from freqtrade/minor/backtest_optimize
[minor] Small cleanup to reduce dict lookups during backtesting/hyperopt
2019-06-12 10:31:44 +02:00
Misagh
1f3406b29b Merge pull request #1868 from freqtrade/stoploss_restart
Stoploss restart
2019-06-12 10:29:17 +02:00
hroff-1902
dc7f883751 no need to duplicate this long error message 2019-06-11 13:47:04 +03:00
hroff-1902
db6ccef6bd return back check in init_ccxt() 2019-06-11 13:43:29 +03:00
hroff-1902
676e730013 enhance check_exchange 2019-06-11 13:18:35 +03:00
Matthias
08105641d9 Merge pull request #1901 from yperfanov/bid_ask_strategy
Bid ask strategy
2019-06-11 11:14:39 +02:00
hroff-1902
7322a34fa4 fix metadata in tests 2019-06-11 10:58:19 +03:00
hroff-1902
4801af4c77 debug logging for IStrategy.advise_*() added 2019-06-11 10:42:14 +03:00
hroff-1902
d55f2be942 make flake happy 2019-06-11 10:21:59 +03:00
hroff-1902
cd60d6d99a make --days positive int only 2019-06-11 10:10:21 +03:00
hroff-1902
dc0326db27 fix handling --exchange 2019-06-11 10:09:30 +03:00
Matthias
50c7a2445b Merge pull request #1922 from freqtrade/pyup/scheduled-update-2019-06-10
Scheduled weekly dependency update for week 23
2019-06-10 17:55:36 +02:00
pyup-bot
6636f0c71b Update pytest from 4.6.1 to 4.6.2 2019-06-10 15:19:09 +00:00
pyup-bot
1a41d4e6cd Update python-rapidjson from 0.7.1 to 0.7.2 2019-06-10 15:19:08 +00:00
pyup-bot
9961c0e15b Update arrow from 0.14.1 to 0.14.2 2019-06-10 15:19:06 +00:00
pyup-bot
5c5b0effc1 Update ccxt from 1.18.615 to 1.18.667 2019-06-10 15:19:05 +00:00
Matthias
4dc3a0ca1d Small cleanup to reduce dict lookups during backtesting/hyperopt 2019-06-10 16:20:19 +02:00
Matthias
99cceeea70 Merge pull request #1915 from freqtrade/feat/drop_incomplete_optional
Make dropping the last candle optional (configured per exchange)
2019-06-10 14:58:19 +02:00
Matthias
839734a988 Merge pull request #1917 from hroff-1902/minor-optimize
minor optimize cleanup
2019-06-10 13:15:54 +02:00
hroff-1902
90b0f1daa8 minor optimize cleanup 2019-06-10 02:08:54 +03:00
Matthias
792390e815 Add missing parameter for exchange-verify snippet 2019-06-09 15:03:26 +02:00
Matthias
9f2e0b11d1 Parametrize ohlcv_candle_limit (per call) 2019-06-09 14:52:17 +02:00
Matthias
3380543878 Add test for drop_incomplete option 2019-06-09 14:51:58 +02:00
Matthias
ce317b62f9 Add docstrings to load_pair_history 2019-06-09 14:40:45 +02:00
Matthias
6ad94684d5 Add WIP document of steps to test a new exchange 2019-06-09 14:36:08 +02:00
Matthias
fdbbefdddd Make drop_incomplete optional 2019-06-09 14:35:58 +02:00
Matthias
3fe5388d4c Document _ft_has_params override 2019-06-09 14:13:03 +02:00
Matthias
7108a2e57d Add deep_merge for _ft_has and test 2019-06-09 14:06:29 +02:00
Matthias
9c497bf15c Improve docstring for deep_merge_dicts 2019-06-09 14:04:19 +02:00
Matthias
d7c63347e1 Use kwarg for parse_ticker_dataframe 2019-06-09 13:19:01 +02:00
Matthias
adc12ed043 Fix new test after develop merge 2019-06-08 20:26:25 +02:00
Matthias
9ea887dbd0 Merge branch 'develop' into stoploss_restart 2019-06-08 20:23:13 +02:00
Matthias
9967df8f45 Merge pull request #1902 from freqtrade/fix_tsl_offset_on_reason
Trailing stoploss sell reason fixed.
2019-06-08 20:21:51 +02:00
Matthias
71b7b2482f Merge pull request #1905 from freqtrade/pyup/scheduled-update-2019-06-03
Scheduled weekly dependency update for week 22
2019-06-08 19:43:54 +02:00
Matthias
5273540a93 Fix test failure (double-trailing newlines are removed now) 2019-06-08 19:32:31 +02:00
Yuliyan Perfanov
f9fe266364 check for runmode before retrieving the orderbook 2019-06-06 18:52:14 +03:00
Yuliyan Perfanov
a9ed5da369 added doc for DataProvider.orderbook() 2019-06-06 18:48:26 +03:00
Yuliyan Perfanov
2e6ded06a9 removed redundant print() 2019-06-06 18:25:58 +03:00
pyup-bot
7134273918 Update plotly from 3.9.0 to 3.10.0 2019-06-03 17:19:26 +02:00
pyup-bot
f75e97e9b0 Update coveralls from 1.7.0 to 1.8.0 2019-06-03 17:19:25 +02:00
pyup-bot
a132517f0a Update pytest from 4.5.0 to 4.6.1 2019-06-03 17:19:24 +02:00
pyup-bot
3c1ae07f92 Update flask from 1.0.2 to 1.0.3 2019-06-03 17:19:20 +02:00
pyup-bot
4ef8a74977 Update arrow from 0.13.2 to 0.14.1 2019-06-03 17:19:19 +02:00
pyup-bot
51113dae0e Update sqlalchemy from 1.3.3 to 1.3.4 2019-06-03 17:19:16 +02:00
pyup-bot
c04a8a1024 Update ccxt from 1.18.578 to 1.18.615 2019-06-03 17:19:13 +02:00
pyup-bot
bd8edd61fd Update numpy from 1.16.3 to 1.16.4 2019-06-03 17:19:12 +02:00
Misagh
92113ce1c9 Merge pull request #1903 from freqtrade/fix/testfailure
Fix test-failure introduced in #1891
2019-06-02 15:52:19 +02:00
Matthias
107c3beb20 Fix test-failure introduced in #1891 2019-06-02 15:28:29 +02:00
Matthias
4e45aa1564 Merge pull request #1863 from xmatthias/feat/flask_rest_retry
Add REST API to control the bot
2019-06-02 15:20:12 +02:00
Matthias
e0e5cfa266 Merge pull request #1891 from freqtrade/simplify/persistence_init
persistence.init does not need the config dict
2019-06-02 15:13:06 +02:00
Misagh
36dae7cc6c trailing stoploss reason fixed 2019-06-02 13:27:31 +02:00
Yuliyan Perfanov
c68fe7a685 example how to use best bid and ask in strategy 2019-06-02 13:27:44 +03:00
Yuliyan Perfanov
199426460a implemented DataProvider.orderbook() 2019-06-02 13:25:09 +03:00
Matthias
338f2a2322 Use kwarg to call persistence.init() 2019-06-01 06:26:03 +02:00
Matthias
f04089ef1e Merge pull request #1892 from freqtrade/ref/live_data
refactor `--live` handling
2019-06-01 06:20:11 +02:00
hroff-1902
1add432673 docs adjusted 2019-05-30 23:00:19 +03:00
Matthias
f15f03428e Merge pull request #1896 from hroff-1902/fix-help-traceback
fix handling of SystemExit
2019-05-30 20:14:08 +02:00
hroff-1902
e4e22167bb make mypy happy 2019-05-30 21:00:16 +03:00
hroff-1902
6b144150c7 fix handling of SystemExit 2019-05-30 20:38:04 +03:00
hroff-1902
ef15f2bdc6 log messages slightly improved 2019-05-30 11:19:27 +03:00
hroff-1902
39932627bd typo in log message fixed 2019-05-30 11:03:17 +03:00
hroff-1902
11f535e79f change prints to logging 2019-05-30 10:56:57 +03:00
hroff-1902
f463817c88 change metavar for --pairs-file 2019-05-30 10:56:48 +03:00
Matthias
b6e8fecbf5 Change persistence.init parameter
It should describe what it does
2019-05-30 06:33:16 +02:00
Matthias
d6cf314481 Don't default to false for init() 2019-05-30 06:30:06 +02:00
hroff-1902
fb88953be3 refactoring download_backtest_data.py 2019-05-29 21:57:14 +03:00
Matthias
15984b5c43 Adjust some tests - implement new "live" method to plot_script 2019-05-29 20:25:07 +02:00
Matthias
c2f6897d8b Move download of live data to load_data
Avoids code duplication in backtesting and plot_dataframe
2019-05-29 20:20:20 +02:00
Matthias
28c796a234 Merge pull request #1877 from freqtrade/eliminate_freqtradebin
[proposal] Eliminate bin/freqtrade
2019-05-29 20:06:02 +02:00
Matthias
d7bebc4385 persistence.init does not need the config dict 2019-05-29 19:54:59 +02:00
Matthias
7b367818fc Remove duplicate code 2019-05-29 19:46:46 +02:00
Matthias
9e4dd6f37f Read bin/freqtrade with deprecation warning 2019-05-29 19:46:26 +02:00
Matthias
22144d89fc Fix mypy error 2019-05-29 19:46:26 +02:00
Matthias
c5ef700eb7 Use autogenerated entrypoint 2019-05-29 19:46:26 +02:00
Matthias
17d614c66a Remove binary script - allow None arguemnts 2019-05-29 19:46:26 +02:00
Matthias
7406edfd8f Move set_loggers to main() 2019-05-29 19:46:26 +02:00
Matthias
6451feee0e Merge pull request #1830 from hroff-1902/python-version
check python version
2019-05-29 19:24:25 +02:00
hroff-1902
912b06b34b Merge branch 'develop' into python-version 2019-05-29 20:07:46 +03:00
Matthias
9fab7e6122 Merge pull request #1888 from freqtrade/fix_ta_on_candle
ta_on_candle removed
2019-05-29 18:07:55 +02:00
Misagh
ea83b2b1d0 legacy code removed. 2019-05-29 14:17:09 +02:00
Matthias
f6a88d71c6 Merge pull request #1884 from freqtrade/doc/plotting
[minor] Improve plotting documentation
2019-05-29 06:19:13 +02:00
Matthias
4fed263885 Merge pull request #1879 from freqtrade/refactor_optimize__init__
Speed up startup time
2019-05-29 06:18:57 +02:00
hroff-1902
db2e6f2d1c tests adjusted 2019-05-28 23:25:53 +03:00
hroff-1902
58477dcd82 cleanup: return after cmd removed in main() 2019-05-28 23:25:19 +03:00
hroff-1902
536c8fa454 move python version check to the top 2019-05-28 23:04:39 +03:00
Matthias
55bdd26439 Edgecli -> Edge for Runmode and start_edge() 2019-05-28 19:25:01 +02:00
Matthias
89f44c10a1 Fix grammar error 2019-05-28 19:20:41 +02:00
Matthias
8b028068bb Fix typos, add section for custom indicators 2019-05-28 07:07:09 +02:00
Matthias
f7766d305b Improve plotting documentation 2019-05-27 19:42:12 +02:00
Matthias
1b7ee7cf5a Merge pull request #1883 from freqtrade/pyup/scheduled-update-2019-05-27
Scheduled weekly dependency update for week 21
2019-05-27 19:15:23 +02:00
pyup-bot
09e037c96e Update scikit-learn from 0.21.1 to 0.21.2 2019-05-27 15:29:09 +00:00
pyup-bot
bfb6dc4a8e Update cachetools from 3.1.0 to 3.1.1 2019-05-27 15:29:07 +00:00
pyup-bot
196a1bcc26 Update ccxt from 1.18.551 to 1.18.578 2019-05-27 15:29:06 +00:00
Matthias
73f1d9bb66 Merge pull request #1882 from freqtrade/fix/plot_script
Update plot-script to work with exported trades
2019-05-27 08:21:31 +02:00
Matthias
1988662607 Update plot-script to work with exported trades 2019-05-26 20:19:06 +02:00
Matthias
3e2c808b4b Merge pull request #1880 from hroff-1902/exchange-debuglog
minor: exchange debug logging humanized
2019-05-26 19:26:19 +02:00
Matthias
dab4307e04 Add secure way to genreate password, warn if no password is defined 2019-05-26 14:40:03 +02:00
Matthias
dd03e0acc6 Merge pull request #1878 from freqtrade/doc/docker
Cleanup installation documentation
2019-05-26 13:49:00 +02:00
Matthias
e335e6c480 Fix some wordings 2019-05-26 13:40:07 +02:00
hroff-1902
0e228acbfb minor: exchange debug logging humanized 2019-05-25 22:42:17 +03:00
Matthias
201e02e73f Add test for Timeout - move tests to test_history 2019-05-25 20:31:21 +02:00
Matthias
71447e55aa Update missing import 2019-05-25 20:14:31 +02:00
Matthias
8ad30e2625 Adapt tests 2019-05-25 20:06:18 +02:00
Matthias
104f1212e6 Move edge_cli_start to optimize 2019-05-25 20:06:15 +02:00
Matthias
65a4862d1f Adapt tests to load start_* methods from optimize 2019-05-25 20:01:43 +02:00
Matthias
236c392d28 Don't load hyperopts / optimize dependency tree if that module is not
used
2019-05-25 20:00:31 +02:00
Matthias
b38c43141c Adjust imports to new location 2019-05-25 16:53:35 +02:00
Matthias
9225cdea8a Move validate_backtest_data and get_timeframe to histoyr 2019-05-25 16:51:52 +02:00
Matthias
26a8cdcc03 Move telegram-setup to telegram page 2019-05-25 16:27:36 +02:00
Matthias
3e0a71f69f Add docker install script to mkdocs index 2019-05-25 16:27:18 +02:00
Matthias
4394701de3 Seperate docker-documentation 2019-05-25 16:13:18 +02:00
Matthias
b6484cb2b4 Replace technical link 2019-05-25 15:54:35 +02:00
Matthias
90ece09ee9 require username/password for API server 2019-05-25 14:42:13 +02:00
Matthias
febcc3dddc Adapt tests and rest_client to basic_auth 2019-05-25 14:25:36 +02:00
Matthias
2da7145132 Switch auth to real basic auth 2019-05-25 14:25:16 +02:00
Matthias
6adc8f7ea7 Merge branch 'develop' into feat/flask_rest_retry 2019-05-25 14:17:04 +02:00
Matthias
5bbd3c6158 Add documentation 2019-05-25 14:16:59 +02:00
Matthias
1fab884a2f use Authorization for client 2019-05-25 14:15:07 +02:00
Matthias
04c35b465e Add authorization to tests 2019-05-25 14:13:59 +02:00
Matthias
7e952b028a Add basic auth to rest-api 2019-05-25 14:11:30 +02:00
Matthias
b7686d06a7 Merge pull request #1873 from freqtrade/add_some_tests
Add some tests
2019-05-25 13:26:34 +02:00
Matthias
c30c4ef266 Merge pull request #1875 from hroff-1902/hyperopts-bugfix-reduce
fix TypeError from reduce() in hyperopts
2019-05-25 13:26:07 +02:00
Matthias
469c0b6a55 Adjust check_int_positive tests 2019-05-25 13:16:00 +02:00
hroff-1902
c3e93e7593 fix reduce() TypeError in hyperopts 2019-05-24 23:08:56 +03:00
Matthias
7bbe8b2483 Add a few more testcases for check_int_positive 2019-05-24 06:22:27 +02:00
hroff-1902
7b968a2401 logger.exception cleanup 2019-05-24 04:04:07 +03:00
Matthias
253025c0fe Add tests for check_int_positive 2019-05-23 19:53:42 +02:00
Matthias
7b074765ab Improve edge tests - cleanup test file 2019-05-23 19:48:22 +02:00
Matthias
1a5dbd29e0 Merge pull request #1871 from hroff-1902/edge-no-trades
edge: handle properly the 'No trades' case
2019-05-23 19:32:02 +02:00
Matthias
b87b3dc38a Merge pull request #1870 from hroff-1902/dataprovider-history-2
minor: data/history slight cleanup/imrovement
2019-05-22 19:25:34 +02:00
hroff-1902
6e1da13920 Log message changed 2019-05-22 17:19:11 +03:00
hroff-1902
406e266bb4 typo in comment fixed 2019-05-22 14:34:35 +03:00
hroff-1902
2c9a519c5e edge: handle properly the 'No trades' case 2019-05-22 14:21:36 +03:00
hroff-1902
98eeec3145 renaming of make_testdata_path reverted 2019-05-22 14:04:58 +03:00
hroff-1902
7cb753754b tests adjusted 2019-05-21 20:49:19 +03:00
hroff-1902
11dce91281 data/history minor cleanup 2019-05-21 20:49:02 +03:00
Matthias
51aa469f67 Cleanups 2019-05-20 20:29:23 +02:00
Matthias
58ced36445 Add documentation for stoploss updates 2019-05-20 20:11:50 +02:00
Matthias
11fd8a59af cleanup stoploss documentations 2019-05-20 20:11:50 +02:00
Matthias
a39cdd3b2b Exclude Edge from startup-stoploss calc
Edge would recalculate / reevaluate stoploss values on startup, so these
values are not reliable
2019-05-20 20:11:50 +02:00
Matthias
53af8f331d Deep-copy default_conf for edge config 2019-05-20 20:11:50 +02:00
Matthias
9f54181494 Add test for stoploss_reinit 2019-05-20 20:11:50 +02:00
Matthias
6a5daab520 add logic for stoploss reinitialization after startup 2019-05-20 20:11:50 +02:00
Matthias
349c0619aa Move startup to freqtradebot 2019-05-20 20:11:50 +02:00
Matthias
6dc2175e1f Merge pull request #1867 from freqtrade/pyup/scheduled-update-2019-05-20
Scheduled weekly dependency update for week 20
2019-05-20 20:02:46 +02:00
Matthias
96a34f753b Adapt test to new output from arrow 2019-05-20 19:48:12 +02:00
pyup-bot
04e13eed7d Update filelock from 3.0.10 to 3.0.12 2019-05-20 15:36:13 +00:00
pyup-bot
5b24ac7898 Update scikit-learn from 0.21.0 to 0.21.1 2019-05-20 15:36:11 +00:00
pyup-bot
34c7ac8926 Update requests from 2.21.0 to 2.22.0 2019-05-20 15:36:10 +00:00
pyup-bot
3404bb1865 Update arrow from 0.13.1 to 0.13.2 2019-05-20 15:36:09 +00:00
pyup-bot
de95e50804 Update ccxt from 1.18.523 to 1.18.551 2019-05-20 15:36:08 +00:00
pyup-bot
703fdb2bc6 Update scipy from 1.2.1 to 1.3.0 2019-05-20 15:36:07 +00:00
Matthias
5d93946365 Merge pull request #1866 from hroff-1902/persist-debug
minor: remove noisy useless debug message
2019-05-20 12:16:57 +02:00
hroff-1902
e7b9bc6808 minor: remove noisy useless debug message 2019-05-20 12:27:30 +03:00
Misagh
46b347b661 Merge pull request #1864 from freqtrade/doc/backtest_future
Improve documentation to point out usage of future data during backtesting
2019-05-19 19:30:27 +02:00
Matthias
fc96da869a Fix grammar messup 2019-05-19 16:07:16 +02:00
Matthias
f93e6ad0f6 Rename strategy customization file 2019-05-19 09:07:43 +02:00
Matthias
8d8b4a69b7 Clearly warn about using future data during strategy development 2019-05-19 09:03:56 +02:00
Matthias
2cf07e2185 rename exception handlers 2019-05-18 13:39:12 +02:00
Matthias
e6ae890def small adjustments after first feedback 2019-05-18 13:36:51 +02:00
Matthias
79cac36b34 Reference reest api in main documentation page 2019-05-18 10:42:18 +02:00
Matthias
9385a27ff0 Sort imports 2019-05-18 10:34:30 +02:00
Matthias
f2e4689d0c Cleanup script 2019-05-18 10:31:50 +02:00
Matthias
70fabebcb3 Document rest api 2019-05-18 10:24:22 +02:00
Matthias
c272e1ccdf Add default rest config 2019-05-18 10:24:01 +02:00
Matthias
fd5012c04e Add test for api cleanup 2019-05-18 10:00:07 +02:00
Matthias
bfc57a6f6d Adapt tests to new method of starting flask 2019-05-18 10:00:07 +02:00
Matthias
540d4bef1e gracefully shutdown flask 2019-05-18 10:00:07 +02:00
Matthias
5149ff7b12 Move api to /api/v1 2019-05-18 10:00:07 +02:00
Matthias
01cd68a5aa Test forcesell 2019-05-18 10:00:07 +02:00
Matthias
b700c64dc2 Test forcebuy - cleanup some tests 2019-05-18 10:00:07 +02:00
Matthias
350c903793 Test falsk crash 2019-05-18 10:00:07 +02:00
Matthias
39afe4c7bd Test flask app .run() 2019-05-18 10:00:07 +02:00
Matthias
b9435e3cea Add more tests 2019-05-18 10:00:07 +02:00
Matthias
a7329e5cc9 Test api-server start from manager 2019-05-18 10:00:07 +02:00
Matthias
a146c5bf78 Improve jsonification 2019-05-18 10:00:07 +02:00
Matthias
557f849519 Improve 404 handling 2019-05-18 10:00:07 +02:00
Matthias
03dc6d92ae Remove hello() 2019-05-18 10:00:07 +02:00
Matthias
3c46870109 Test /count for api-server 2019-05-18 10:00:07 +02:00
Matthias
88dd18e045 Move patch_signal to conftest 2019-05-18 10:00:07 +02:00
Matthias
6b426e78f6 Tests for balance 2019-05-18 10:00:07 +02:00
Matthias
70a3c2c648 Actions - Add tests 2019-05-18 09:57:10 +02:00
Matthias
6ea0895803 Fix docstrings 2019-05-18 09:57:10 +02:00
Matthias
b1a14401c2 Add some initial tests for apiserver 2019-05-18 09:57:10 +02:00
Matthias
e0486ea68e Make app a instance object 2019-05-18 09:57:10 +02:00
Matthias
0ac434da78 Add forcebuy jsonification 2019-05-18 09:57:10 +02:00
Matthias
6e4b159611 Add forcebuy and forcesell 2019-05-18 09:57:10 +02:00
Matthias
bc4342b2d0 small cleanup 2019-05-18 09:57:10 +02:00
Matthias
cb271f51d1 Add client actions for actions 2019-05-18 09:57:10 +02:00
Matthias
ea8b8eec1c Add edge handler 2019-05-18 09:57:10 +02:00
Matthias
b1964851c9 Add performance handlers 2019-05-18 09:57:10 +02:00
Matthias
393e4ac90e Sort methods 2019-05-18 09:57:10 +02:00
Matthias
0163edc868 rest-client more methods 2019-05-18 09:57:10 +02:00
Matthias
3efdd55fb8 Support blacklist adding 2019-05-18 09:57:10 +02:00
Matthias
122cf4c897 Default add to None for blacklist rpc calls 2019-05-18 09:57:10 +02:00
Matthias
938d7275ba implement some methods 2019-05-18 09:57:10 +02:00
Matthias
8f9b9d31e2 Reorder arguments 2019-05-18 09:57:10 +02:00
Matthias
d1fffab235 Rename internal methods to _ 2019-05-18 09:57:10 +02:00
Matthias
ebebf94750 Change commands to post 2019-05-18 09:57:10 +02:00
Matthias
b0ac98a7cd Clean up rest client 2019-05-18 09:57:10 +02:00
Matthias
a132d6e141 Refactor client into class 2019-05-18 09:57:10 +02:00
Matthias
a1043121fc Add blacklist handler 2019-05-18 09:57:10 +02:00
Matthias
5ba189ffb4 Add more commands to rest client, fix bug in config handling 2019-05-18 09:57:10 +02:00
Matthias
d2c2811249 Move rest-client to scripts 2019-05-18 09:57:10 +02:00
Matthias
99875afcc0 Add default argument 2019-05-18 09:57:10 +02:00
Matthias
ae8660fe06 Extract exception handling to decorator 2019-05-18 09:57:10 +02:00
Matthias
01c93a2ee3 Load rest-client config from file 2019-05-18 09:57:10 +02:00
Matthias
d8549fe09a add balance handler 2019-05-18 09:57:10 +02:00
Matthias
a12e093417 Api server - custom json encoder 2019-05-18 09:57:10 +02:00
Matthias
2f8088432c All handlers should be private 2019-05-18 09:57:10 +02:00
Matthias
3cf6c6ee0c Implement a few more methods 2019-05-18 09:57:10 +02:00
Matthias
8993882dcb Sort imports 2019-05-18 09:57:10 +02:00
Matthias
c6c2893e2c Improve rest-client interface 2019-05-18 09:57:10 +02:00
Matthias
96a260b027 rest_dump 2019-05-18 09:57:10 +02:00
Matthias
6bb2fad9b0 Reorder some things 2019-05-18 09:57:10 +02:00
Matthias
9d95ae9341 Add flask to dependencies 2019-05-18 09:57:10 +02:00
Matthias
68743012e4 Patch api server for tests 2019-05-18 09:57:10 +02:00
Matthias
ef2950bca2 Load api-server in rpc_manager 2019-05-18 09:57:10 +02:00
Matthias
6f67ea44dc Enable config-check for rest server 2019-05-18 09:57:10 +02:00
Matthias
26c42bd559 Add apiserver tests 2019-05-18 09:57:10 +02:00
Matthias
c3c745ca19 Get new files from old branch 2019-05-18 09:57:10 +02:00
Matthias
2463f0257e Merge pull request #1862 from hroff-1902/dataprovider-history
minor: data/history cleanup
2019-05-18 09:35:27 +02:00
hroff-1902
e2b83624a3 data/history cleanup 2019-05-17 19:05:36 +03:00
Matthias
e0310906c7 Merge pull request #1859 from hroff-1902/freqtrade-exceptions
minor: inherit freqtrade exceptions from Exception instead of BaseException
2019-05-17 06:28:48 +02:00
hroff-1902
2741c5c330 inherit freqtrade exceptions from Exception i.o. BaseException 2019-05-16 22:38:59 +03:00
Matthias
175fc8591e Merge pull request #1845 from freqtrade/fix/1840
Fix #1840 - Support balances other than USDT
2019-05-15 19:39:46 +02:00
Matthias
3c62586878 Merge pull request #1852 from hroff-1902/hyperopt-verify
Minor: hyperopt verify ticker data
2019-05-15 19:39:17 +02:00
hroff-1902
8b95e12468 log message adjusted in backtesting and hyperopt 2019-05-15 12:05:35 +03:00
hroff-1902
90a52e4602 tests adjusted; new test_start_no_data() added for hyperopt 2019-05-14 09:23:09 +03:00
hroff-1902
5677c4882e minor: add ticker data validation; log backtesting interval 2019-05-13 23:56:59 +03:00
Misagh
6d17cd50fe Merge pull request #1851 from freqtrade/pyup/weekly
Update pyup only weekly
2019-05-13 20:01:21 +02:00
Matthias
1cd98665de Update pyup only weekly 2019-05-13 19:50:56 +02:00
Matthias
cfcf97b616 Merge pull request #1837 from hroff-1902/hyperopt-minor-1
minor: hyperopt output improvements
2019-05-13 19:49:23 +02:00
Misagh
6efebef714 Merge pull request #1848 from freqtrade/pyup/scheduled-update-2019-05-13
Scheduled daily dependency update on Monday
2019-05-13 15:39:11 +02:00
pyup-bot
600f660f5e Update ccxt from 1.18.522 to 1.18.523 2019-05-13 12:41:06 +00:00
hroff-1902
003461ec96 tests adjusted 2019-05-12 21:19:20 +03:00
hroff-1902
00b4501c59 avg profit and total profit corrected (to be %, not ratio); comments cleaned up a bit; typo in the log msg fixed 2019-05-12 21:14:00 +03:00
Matthias
8142794447 Merge pull request #1846 from freqtrade/pyup/scheduled-update-2019-05-12
Scheduled daily dependency update on Sunday
2019-05-12 19:33:15 +02:00
Misagh
ea2ef78ceb Merge pull request #1843 from freqtrade/small_fixes
Small fixes
2019-05-12 17:36:34 +02:00
pyup-bot
11dca0bd29 Update pytest from 4.4.2 to 4.5.0 2019-05-12 12:41:06 +00:00
pyup-bot
dccd6b4a91 Update ccxt from 1.18.519 to 1.18.522 2019-05-12 12:41:05 +00:00
Matthias
46b1ecc77d Fix #1840 - Support balances other than USDT 2019-05-11 15:27:09 +02:00
Matthias
8a319e90c6 Merge pull request #1844 from freqtrade/pyup/scheduled-update-2019-05-11
Scheduled daily dependency update on Saturday
2019-05-11 15:04:38 +02:00
pyup-bot
652914a67b Update python-rapidjson from 0.7.0 to 0.7.1 2019-05-11 12:41:07 +00:00
pyup-bot
22f902f0f7 Update ccxt from 1.18.516 to 1.18.519 2019-05-11 12:41:06 +00:00
Matthias
131b232155 Add sample for order_types in config (slightly different syntax) 2019-05-11 14:33:35 +02:00
Matthias
52da64b6dc Align configuration files 2019-05-11 14:33:26 +02:00
hroff-1902
75306b7a6e tests adjusted 2019-05-11 10:17:46 +03:00
Matthias
867f9ae362 Merge pull request #1838 from freqtrade/pyup/scheduled-update-2019-05-10
Scheduled daily dependency update on Friday
2019-05-10 19:32:37 +02:00
pyup-bot
ab23db2fa1 Update scikit-learn from 0.20.3 to 0.21.0 2019-05-10 12:42:14 +00:00
pyup-bot
349d556339 Update ccxt from 1.18.514 to 1.18.516 2019-05-10 12:42:13 +00:00
Misagh
7bfd0ecbb5 Merge pull request #1835 from freqtrade/docs/print-dataframe
Add printing dataframe to documentation
2019-05-10 14:24:37 +02:00
hroff-1902
0f43e0bb7d minor hyperopt output improvements 2019-05-10 10:54:44 +03:00
Misagh
43c2cf8e1c Merge pull request #1836 from freqtrade/pyup/scheduled-update-2019-05-09
Scheduled daily dependency update on Thursday
2019-05-09 16:05:20 +02:00
pyup-bot
00383b9438 Update pytest from 4.4.1 to 4.4.2 2019-05-09 12:42:09 +00:00
pyup-bot
f36ccdd9fa Update ccxt from 1.18.512 to 1.18.514 2019-05-09 12:42:08 +00:00
Matthias
909df0d7bb Improve doc wording 2019-05-09 08:56:27 +02:00
Matthias
0410654c2c Add printing dataframe to documentation 2019-05-09 06:53:10 +02:00
Matthias
0dbe9cb586 Merge pull request #1823 from hroff-1902/update-qtpylib
Update qtpylib
2019-05-09 06:47:14 +02:00
hroff-1902
45e5867736 heikinashi loop optimized; reset_index moved to tests 2019-05-08 23:41:45 +03:00
Matthias
1ccc25b486 Fix test-data indexing 2019-05-08 20:33:22 +02:00
Matthias
d6aa63bd97 Merge pull request #1834 from freqtrade/pyup/scheduled-update-2019-05-08
Scheduled daily dependency update on Wednesday
2019-05-08 15:10:50 +02:00
pyup-bot
cf1ad3fd8c Update ccxt from 1.18.509 to 1.18.512 2019-05-08 12:42:06 +00:00
hroff-1902
2554ebf273 fixed: heikinashi worked in backtesting, but failed in tests with testing arrays 2019-05-08 00:00:44 +03:00
hroff-1902
d642e03cd0 heikinashi performance problem resolved 2019-05-07 23:39:42 +03:00
Matthias
c8d75fbd8a Merge pull request #1832 from freqtrade/pyup/scheduled-update-2019-05-07
Scheduled daily dependency update on Tuesday
2019-05-07 15:53:00 +02:00
pyup-bot
db0644eddf Update plotly from 3.8.1 to 3.9.0 2019-05-07 12:42:10 +00:00
pyup-bot
a8c4bed4e8 Update ccxt from 1.18.508 to 1.18.509 2019-05-07 12:42:07 +00:00
Misagh
a70830a7b7 Merge pull request #1825 from freqtrade/doc/docker
Improve docker documentation
2019-05-07 14:04:50 +02:00
Misagh
4bb004c6f4 Merge pull request #1828 from freqtrade/rpc/trade_tojson
Refactor trade to_json to persistence
2019-05-07 14:03:58 +02:00
hroff-1902
6467d3b58e check python version 2019-05-06 18:27:05 +03:00
Misagh
194ab5aa92 Merge pull request #1829 from freqtrade/pyup/scheduled-update-2019-05-06
Scheduled daily dependency update on Monday
2019-05-06 14:57:10 +02:00
pyup-bot
c8b8806fed Update ccxt from 1.18.507 to 1.18.508 2019-05-06 12:42:06 +00:00
Matthias
1a677c7441 Add explicit test for to_json 2019-05-06 06:58:17 +02:00
Matthias
2b78f73fe5 Adapt tests to to_json method 2019-05-06 06:56:07 +02:00
Matthias
31d271084f Move json to persistence 2019-05-06 06:55:12 +02:00
hroff-1902
2200a0223b fixed heikinashi 2019-05-06 00:30:21 +03:00
Matthias
1e056ee415 Move trade jsonification to trade class 2019-05-05 14:07:08 +02:00
Matthias
4ae743ecb6 Merge pull request #1826 from freqtrade/pyup/scheduled-update-2019-05-04
Scheduled daily dependency update on Saturday
2019-05-04 15:38:57 +02:00
pyup-bot
6c03246ec8 Update ccxt from 1.18.502 to 1.18.507 2019-05-04 12:42:04 +00:00
Matthias
f506644a8c Improve docker documentation 2019-05-04 09:10:25 +02:00
Matthias
b83a0f9a9c Merge pull request #1822 from freqtrade/pyup/scheduled-update-2019-05-03
Scheduled daily dependency update on Friday
2019-05-04 00:09:48 +02:00
hroff-1902
66c2bdd65a flake happy 2019-05-03 16:58:51 +03:00
hroff-1902
1be4c59481 qtpylib/indicators.py updated 2019-05-03 16:48:07 +03:00
pyup-bot
32e4b0b1b2 Update pytest-cov from 2.6.1 to 2.7.1 2019-05-03 12:43:09 +00:00
pyup-bot
dad55fe7a8 Update ccxt from 1.18.500 to 1.18.502 2019-05-03 12:43:08 +00:00
Matthias
9147e6c5bf Merge pull request #1821 from freqtrade/pyup/scheduled-update-2019-05-02
Scheduled daily dependency update on Thursday
2019-05-02 19:06:39 +02:00
pyup-bot
6c2301ec39 Update ccxt from 1.18.497 to 1.18.500 2019-05-02 12:43:05 +00:00
Matthias
7e96d57627 Merge pull request #1819 from hroff-1902/hyperopt-min-trades
hyperopt --min-trades parameter
2019-05-02 09:36:13 +02:00
Misagh
de6112adb7 Merge pull request #1814 from freqtrade/rpc/forcesell
immediately confirm forcesell
2019-05-01 16:47:22 +02:00
Matthias
46214ce7cd Fix typo after feedback 2019-05-01 16:22:52 +02:00
Matthias
ee619f2919 Merge pull request #1820 from freqtrade/pyup/scheduled-update-2019-05-01
Scheduled daily dependency update on Wednesday
2019-05-01 15:09:46 +02:00
hroff-1902
269699988b test adjusted 2019-05-01 15:55:56 +03:00
pyup-bot
4cecf04639 Update ccxt from 1.18.496 to 1.18.497 2019-05-01 14:43:05 +02:00
hroff-1902
e7b81e4d46 hyperopt --min-trades parameter 2019-05-01 15:27:58 +03:00
Matthias
e1acf0a94d Merge pull request #1804 from hroff-1902/hyperopt-lock
prevent hyperopt from running multiple instances simultaneously
2019-05-01 12:55:32 +02:00
Matthias
b9d7bb2d8e Merge branch 'develop' into pr/hroff-1902/1804 2019-05-01 12:54:36 +02:00
Matthias
90f357db6f Merge pull request #1817 from freqtrade/cover/reload_conf
Improve test for reload_conf with a "realistic" workflow
2019-05-01 12:42:25 +02:00
Matthias
3c376c8e9b Merge pull request #1816 from freqtrade/pyup/scheduled-update-2019-04-30
Scheduled daily dependency update on Tuesday
2019-04-30 19:34:08 +02:00
Matthias
b24bbb2cb1 Improve test for reload_conf with a "realistic" workflow 2019-04-30 19:32:03 +02:00
Matthias
97f2c74dd8 Merge pull request #1815 from hroff-1902/fix-1810
Fix for #1810
2019-04-30 19:31:23 +02:00
hroff-1902
5665426e6b better type hints in worker 2019-04-30 19:47:55 +03:00
pyup-bot
6150679736 Update ccxt from 1.18.493 to 1.18.496 2019-04-30 12:42:06 +00:00
Matthias
4804f45156 Merge pull request #1802 from freqtrade/refactor/config
Refactor config
2019-04-30 12:13:40 +02:00
hroff-1902
537c03504f fix #1810 2019-04-30 10:29:49 +03:00
Matthias
91642b2bd9 Add tsts for forcesell-answers 2019-04-30 06:25:02 +02:00
Matthias
f71eda1c2f Have forcesell return a result 2019-04-30 06:23:14 +02:00
Matthias
c347013eef Merge pull request #1812 from freqtrade/pyup/scheduled-update-2019-04-29
Scheduled daily dependency update on Monday
2019-04-29 15:54:40 +02:00
pyup-bot
59bd081e92 Update ccxt from 1.18.492 to 1.18.493 2019-04-29 12:42:12 +00:00
Matthias
6166e19405 Merge pull request #1808 from freqtrade/pyup/scheduled-update-2019-04-27
Scheduled daily dependency update on Saturday
2019-04-27 16:55:01 +02:00
pyup-bot
21b31f11b8 Update ccxt from 1.18.491 to 1.18.492 2019-04-27 12:42:05 +00:00
Matthias
dd2e05b33f Merge pull request #1807 from freqtrade/fix/travis
fix dockerfile building
2019-04-27 09:12:27 +02:00
Matthias
40c0207377 revert erroneous refactor 2019-04-26 19:59:05 +02:00
Matthias
dc12cacd50 Rename requirements-pi to requirements.common 2019-04-26 19:57:09 +02:00
Matthias
99b08fbd13 Remove unused Hyperopt test lines 2019-04-26 19:51:57 +02:00
Matthias
bf2a39b76d Fix add requirements-pi.txt in dockerfile earlier
Avoids docker-build failure
2019-04-26 19:50:18 +02:00
Matthias
b84b52202e Merge pull request #1806 from freqtrade/pyup/scheduled-update-2019-04-26
Scheduled daily dependency update on Friday
2019-04-26 19:16:56 +02:00
pyup-bot
eaf5547b88 Update ccxt from 1.18.489 to 1.18.491 2019-04-26 12:42:07 +00:00
hroff-1902
ea44bbff9f prevent hyperopt from running simultaneously 2019-04-25 11:11:04 +03:00
Matthias
cc0c96af50 Merge pull request #1801 from freqtrade/catch_network_timeout_1789
Catch errors on reload_markets
2019-04-24 22:40:52 +02:00
Matthias
ef3b244c1a Merge pull request #1798 from freqtrade/downgrade_urllib
Downgrade urllib3, cleanup requirements files
2019-04-24 22:30:59 +02:00
Matthias
45ecbc91e8 Use BaseError, not NetworkError in exception handler 2019-04-24 22:20:05 +02:00
Matthias
401caaabb4 Merge branch 'develop' into downgrade_urllib 2019-04-24 22:17:20 +02:00
Matthias
22eb6cb5fa Fix typo in args_to_config 2019-04-24 22:08:56 +02:00
Matthias
65dcb6acea Catch errors on reload_markets 2019-04-24 21:56:24 +02:00
Matthias
b4630c403d Add typehints 2019-04-24 21:32:33 +02:00
Matthias
86313b337a Combine optimize configurations, eliminate duplicates 2019-04-24 21:27:32 +02:00
Matthias
87329c689d Change ticker_interval too 2019-04-24 21:24:00 +02:00
Matthias
ca3b8ef2e7 Remove duplicate argument 2019-04-24 21:13:57 +02:00
Matthias
a0413b5d91 Only log one message per call 2019-04-24 21:12:23 +02:00
Matthias
d6276a15d2 Convert all optimize to args_to_config 2019-04-24 21:12:08 +02:00
Matthias
39f60c4740 Add some more arguments to args_to_config 2019-04-24 21:02:05 +02:00
Matthias
17cf9d33cf add _args_to_conig 2019-04-24 20:44:36 +02:00
Matthias
fa7866291a Merge pull request #1799 from freqtrade/pyup/scheduled-update-2019-04-24
Scheduled daily dependency update on Wednesday
2019-04-24 15:18:07 +02:00
pyup-bot
59f905a573 Update ccxt from 1.18.486 to 1.18.489 2019-04-24 12:41:09 +00:00
pyup-bot
060571290a Update ccxt from 1.18.486 to 1.18.489 2019-04-24 12:41:08 +00:00
Matthias
30888cf5ca have pyup ignore outdated dependency 2019-04-24 14:07:55 +02:00
Matthias
eb89b65b59 Downgrade urllib3, cleanup requirements files
every requirement should be there only once
2019-04-24 14:04:12 +02:00
Matthias
bf56e25404 Merge pull request #1746 from hroff-1902/json-defaults
Support for defaults in json schema
2019-04-24 12:20:39 +02:00
Matthias
34fa2011be Merge pull request #1792 from hroff-1902/hyperopt-jobs
hyperopt: -j/--job-workers command line option added
2019-04-24 12:19:07 +02:00
hroff-1902
a8e787fda8 test adjusted 2019-04-24 11:25:15 +03:00
Matthias
ad692c185e Improve comment 2019-04-24 09:55:53 +02:00
Matthias
d16ccd7e37 Merge branch 'develop' into json-defaults 2019-04-24 09:51:04 +02:00
Matthias
a92d5f3569 Parametrize default-param tests 2019-04-24 09:48:25 +02:00
hroff-1902
95ebd07735 an attempt to fix mocking 2019-04-24 10:38:50 +03:00
hroff-1902
6a0f527e0e merge --job-workers and commit printing debug log messages with the opt state 2019-04-24 10:35:04 +03:00
Matthias
65a82d7ee6 Add some missing default parameters 2019-04-24 09:31:13 +02:00
hroff-1902
2898067318 Merge branch 'develop' into hyperopt-jobs 2019-04-24 10:31:03 +03:00
Matthias
6d2a1cfb44 remove full-config in tests and load full_config file 2019-04-24 09:30:59 +02:00
Matthias
bced53966e Merge pull request #1795 from hroff-1902/hyperopt-opt-params
hyperopt: --random-state for optimizer to get reproducible results
2019-04-24 07:10:02 +02:00
hroff-1902
a429f83f5e flake happy; check_positive() renamed 2019-04-23 22:16:24 +03:00
hroff-1902
2f0ad0d28c test adjusted 2019-04-23 22:03:41 +03:00
hroff-1902
fc4ef2b430 Merge branch 'develop' into hyperopt-opt-params 2019-04-23 21:58:27 +03:00
hroff-1902
e3b0474901 Merge branch 'develop' into hyperopt-jobs 2019-04-23 21:34:38 +03:00
hroff-1902
cc9f899cd6 removed explicit dependency on multiprocessing module 2019-04-23 21:25:36 +03:00
hroff-1902
a022b1a6c1 --random-state for optimzer to get reproducible results added 2019-04-23 21:18:52 +03:00
Matthias
4971b9fc39 Merge pull request #1793 from hroff-1902/hyperopt-debug-state
hyperopt: print optimizer state in debug log messages
2019-04-23 20:11:04 +02:00
Matthias
939bf66a80 Merge pull request #1791 from hroff-1902/hyperopt-refresh-pairs
hyperopt: --refresh-pairs-cached added
2019-04-23 20:07:57 +02:00
Matthias
309a54ba69 Merge pull request #1794 from freqtrade/pyup/scheduled-update-2019-04-23
Scheduled daily dependency update on Tuesday
2019-04-23 15:05:01 +02:00
pyup-bot
8568459c74 Update urllib3 from 1.24.2 to 1.25 2019-04-23 12:41:16 +00:00
pyup-bot
9a2eb46cea Update urllib3 from 1.24.2 to 1.25 2019-04-23 12:41:14 +00:00
pyup-bot
48e2bd5114 Update ccxt from 1.18.485 to 1.18.486 2019-04-23 12:41:13 +00:00
pyup-bot
a2a70bd6d0 Update ccxt from 1.18.485 to 1.18.486 2019-04-23 12:41:12 +00:00
hroff-1902
3e3fce5f38 print optimizer state in debug log messages 2019-04-23 09:49:24 +03:00
hroff-1902
7c8e26c717 -j/--job-workers option added for controlling the number of joblib parallel worker processes used in hyperopt
docs refreshed
2019-04-23 00:52:07 +03:00
hroff-1902
8dad8f25cf docs refreshed 2019-04-22 22:11:56 +03:00
hroff-1902
ad85ac3dde make --refresh-pairs-cached common option for optimization; added support for it into hyperopt 2019-04-22 21:24:45 +03:00
Matthias
d3e956f7cc Merge pull request #1790 from freqtrade/pyup/scheduled-update-2019-04-22
Scheduled daily dependency update on Monday
2019-04-22 16:04:40 +02:00
pyup-bot
3da1b24b6a Update numpy from 1.16.2 to 1.16.3 2019-04-22 12:41:08 +00:00
pyup-bot
42d2b24d48 Update ccxt from 1.18.483 to 1.18.485 2019-04-22 12:41:07 +00:00
pyup-bot
8685fcd593 Update ccxt from 1.18.483 to 1.18.485 2019-04-22 12:41:06 +00:00
Matthias
45aa93e73d Merge pull request #1787 from NatanNMB15/walletsync-fix-sell
Wallet Sync fix after any trade is closed
2019-04-22 13:44:40 +02:00
Matthias
676cd6ffee Add assert to make sure trade was closed 2019-04-22 13:36:14 +02:00
Matthias
a9de2f80f2 Add tests to update wallets after closing a limit-sell 2019-04-22 13:31:07 +02:00
Matthias
86ec88b8fe Merge pull request #1788 from hroff-1902/hyperopt-print-all
--print-all command line option for hyperopt
2019-04-22 13:14:46 +02:00
hroff-1902
6b87d94bb0 --print-all command line option added for hyperopt 2019-04-22 01:10:01 +03:00
NatanNMB15
706b30f4d2 Fix "if" condition with "if not" for check if trade is open. 2019-04-21 14:54:24 -03:00
NatanNMB15
3bcc60333d Added command for Wallets Sync after a trade is closed in "update_trade" method in "freqtradebot" class, this will help the Wallets get updated after a trade is sold and closed, specifically LIMIT_SELL trades, then bot can work properly with new trades. 2019-04-21 13:49:07 -03:00
Misagh
bf6c435ae6 Merge pull request #1786 from freqtrade/pyup/scheduled-update-2019-04-21
Scheduled daily dependency update on Sunday
2019-04-21 15:04:29 +02:00
pyup-bot
abc4840d16 Update ccxt from 1.18.481 to 1.18.483 2019-04-21 12:41:05 +00:00
pyup-bot
a118003d0a Update ccxt from 1.18.481 to 1.18.483 2019-04-21 12:41:04 +00:00
Misagh
ccc91403c5 Merge pull request #1784 from freqtrade/doc/release
Improve developer documentation
2019-04-20 21:08:11 +02:00
Matthias
9b0b1c3cc2 Merge pull request #1785 from freqtrade/pyup/scheduled-update-2019-04-20
Scheduled daily dependency update on Saturday
2019-04-20 19:40:59 +02:00
pyup-bot
395aed5f97 Update plotly from 3.8.0 to 3.8.1 2019-04-20 12:40:07 +00:00
pyup-bot
278e5f4cc6 Update ccxt from 1.18.480 to 1.18.481 2019-04-20 12:40:06 +00:00
pyup-bot
7fa5046575 Update ccxt from 1.18.480 to 1.18.481 2019-04-20 12:40:05 +00:00
Matthias
9b8067cbc3 Improve developer documentation 2019-04-20 12:50:10 +02:00
Matthias
e252f0feba Merge pull request #1782 from mishaker/v-18-5
version to 0.18.5-dev
2019-04-20 09:25:20 +02:00
Misagh
8e8ec2fba6 version to 0.18.5-dev 2019-04-19 16:01:26 +02:00
Misagh
41e698c482 Merge pull request #1777 from freqtrade/new_release
Version to 0.18.5
2019-04-19 15:57:07 +02:00
Matthias
82127d8406 Merge pull request #1781 from freqtrade/pyup/scheduled-update-2019-04-19
Scheduled daily dependency update on Friday
2019-04-19 15:41:40 +02:00
pyup-bot
5a65b6caee Update ccxt from 1.18.475 to 1.18.480 2019-04-19 12:40:06 +00:00
pyup-bot
ed6a92cd0f Update ccxt from 1.18.475 to 1.18.480 2019-04-19 12:40:05 +00:00
Matthias
577ccd32f0 Merge pull request #1750 from hroff-1902/ccxt-to-exchange-only
minor: limit usage of ccxt to freqtrade/exchange only
2019-04-19 06:51:08 +02:00
Matthias
72657758d5 Restore get_market_pairs from develop 2019-04-19 06:43:12 +02:00
Matthias
f9ba0483ca Merge pull request #1778 from freqtrade/pyup/scheduled-update-2019-04-18
Scheduled daily dependency update on Thursday
2019-04-18 15:51:26 +02:00
pyup-bot
d82fb57223 Update pytest-mock from 1.10.3 to 1.10.4 2019-04-18 12:40:16 +00:00
pyup-bot
5c10e9a7fa Update urllib3 from 1.24.1 to 1.24.2 2019-04-18 12:40:11 +00:00
pyup-bot
578ad903bc Update urllib3 from 1.24.1 to 1.24.2 2019-04-18 12:40:10 +00:00
pyup-bot
789b445815 Update ccxt from 1.18.472 to 1.18.475 2019-04-18 12:40:08 +00:00
pyup-bot
c299d9249f Update ccxt from 1.18.472 to 1.18.475 2019-04-18 12:40:07 +00:00
Misagh
795c2e4aa2 version to 0.18.5 2019-04-18 08:07:43 +02:00
Misagh
031a63d5c2 Merge pull request #1771 from freqtrade/enable_ratelimit
Enable ratelimit
2019-04-17 17:31:21 +02:00
Misagh
f5ef8f5bc0 Merge pull request #1772 from freqtrade/fix/staticmethod_import
Gracefully handle pickle-errors when @staticmethod is used
2019-04-17 17:24:56 +02:00
Matthias
30f7536cbe Merge pull request #1773 from freqtrade/pyup/scheduled-update-2019-04-17
Scheduled daily dependency update on Wednesday
2019-04-17 15:03:20 +02:00
pyup-bot
8abdbc41e1 Update mypy from 0.700 to 0.701 2019-04-17 12:40:10 +00:00
pyup-bot
7f229bbf39 Update ccxt from 1.18.470 to 1.18.472 2019-04-17 12:40:09 +00:00
pyup-bot
d4947ba0ee Update ccxt from 1.18.470 to 1.18.472 2019-04-17 12:40:07 +00:00
Matthias
2cee716181 Gracefully handle pickle-errors when @staticmethod is used
pOinted out in https://github.com/freqtrade/freqtrade-strategies/issues/28
2019-04-16 20:25:48 +02:00
Matthias
a7383ad35d enable ratelimit in download-backtest-data too 2019-04-16 19:54:24 +02:00
Matthias
52cc2d224e improve documentation for exchange configuration 2019-04-16 19:51:56 +02:00
Matthias
5db10bdcc7 Add rateLimit parameters for different exchanges 2019-04-16 19:51:42 +02:00
Matthias
43119efaf0 Remove ccxt_rate_limit completely (was deprecated) 2019-04-16 19:41:02 +02:00
Matthias
16bf7aa3ab Merge pull request #1770 from freqtrade/pyup/scheduled-update-2019-04-16
Scheduled daily dependency update on Tuesday
2019-04-16 15:25:34 +02:00
pyup-bot
b2a623ee16 Update plotly from 3.7.1 to 3.8.0 2019-04-16 12:39:12 +00:00
pyup-bot
c40406d26e Update pytest from 4.4.0 to 4.4.1 2019-04-16 12:39:09 +00:00
pyup-bot
87ff5ad1e0 Update sqlalchemy from 1.3.2 to 1.3.3 2019-04-16 12:39:07 +00:00
pyup-bot
aa63f2be1f Update sqlalchemy from 1.3.2 to 1.3.3 2019-04-16 12:39:06 +00:00
pyup-bot
5cb90bdf77 Update ccxt from 1.18.468 to 1.18.470 2019-04-16 12:39:05 +00:00
pyup-bot
4f557af6cb Update ccxt from 1.18.468 to 1.18.470 2019-04-16 12:39:04 +00:00
Misagh
5f63797f17 Merge pull request #1762 from freqtrade/update_imageversion
Version bump to 3.7.3 in docker file
2019-04-15 19:45:21 +02:00
Misagh
bbb32ada4a Merge pull request #1763 from freqtrade/pi/docs
Update documentation for Raspberry
2019-04-15 19:44:53 +02:00
Misagh
fc33f19b06 Merge pull request #1767 from freqtrade/pyup/scheduled-update-2019-04-15
Scheduled daily dependency update on Monday
2019-04-15 15:20:33 +02:00
pyup-bot
7efab85b10 Update sqlalchemy from 1.3.1 to 1.3.2 2019-04-15 12:39:08 +00:00
pyup-bot
0ece168833 Update ccxt from 1.18.353 to 1.18.468 2019-04-15 12:39:06 +00:00
pyup-bot
6be4c6af0e Update ccxt from 1.18.466 to 1.18.468 2019-04-15 12:39:05 +00:00
Matthias
4f6df73156 Update documentation for Raspberry install since we now have a
rpi-requirements file
2019-04-14 15:57:44 +02:00
Matthias
cd20078bef Merge pull request #1742 from tl-nguyen/feature/add-dockerfile-for-pi
Add Dockerfile.pi for building docker image for raspberry pi
2019-04-14 15:53:37 +02:00
Matthias
5e0e8de4f6 Version bump to 3.7.3 in docker file 2019-04-14 13:13:28 +02:00
Matthias
ed5e76adac Merge pull request #1755 from hroff-1902/scripts-get_market_pairs
Minor: impoved argument and exception handling in scripts
2019-04-14 10:40:57 +02:00
Matthias
12265b245d Merge pull request #1738 from konqueror1/develop
Added command line options to override max_open_trades and stake_amount
2019-04-14 10:34:27 +02:00
Matthias
37b1389f12 Fix flake8 2019-04-14 10:17:06 +02:00
Matthias
b679eb1a95 Merge pull request #1761 from freqtrade/pyup/scheduled-update-2019-04-13
Scheduled daily dependency update on Saturday
2019-04-13 15:59:48 +02:00
pyup-bot
2f79cf1304 Update ccxt from 1.18.460 to 1.18.466 2019-04-13 12:39:05 +00:00
Misagh
3fe0cb9281 Merge pull request #1760 from freqtrade/pyup/scheduled-update-2019-04-12
Scheduled daily dependency update on Friday
2019-04-12 15:05:41 +02:00
pyup-bot
9f828224bc Update ccxt from 1.18.458 to 1.18.460 2019-04-12 12:39:05 +00:00
Misagh
2153e43969 Merge pull request #1759 from hroff-1902/patch-20
Docs: wrong rendering at freqtrade.io fixed
2019-04-12 10:48:02 +02:00
Misagh
c6d19a4afb Merge pull request #1758 from freqtrade/fix/rpcheader
Missing /daily header
2019-04-12 10:45:56 +02:00
hroff-1902
016e8fde89 wrong rendering at freqtrade.io fixed; other cosmetics in docs/
* Titles render wrong both in the docs dir and at freqtrade.io
* Last list of links renders wring at freqtrade.io
2019-04-12 10:54:28 +03:00
Matthias
d87db70ed0 Fix missing column header 2019-04-12 07:05:15 +02:00
Matthias
c3b9d69919 Add docstring explaining the source of the script 2019-04-12 07:05:00 +02:00
hroff-1902
c3a9db6488 change comments to docstrings 2019-04-11 22:22:33 +03:00
hroff-1902
8bdbfbf194 tests for options added 2019-04-11 18:07:51 +03:00
Matthias
f204af173d Merge pull request #1757 from freqtrade/pyup/scheduled-update-2019-04-11
Scheduled daily dependency update on Thursday
2019-04-11 15:52:05 +02:00
pyup-bot
12ca103f9f Update ccxt from 1.18.456 to 1.18.458 2019-04-11 12:38:06 +00:00
hroff-1902
c2ca899c7e fixed printed message; cosmetic changes in the code in scripts/download_backtest_data.py 2019-04-11 00:59:53 +03:00
hroff-1902
902ffa6853 impoved argument and exception handling in scripts/get_market_pairs.py 2019-04-11 00:15:17 +03:00
hroff-1902
f03acce84c typing of return value corrected 2019-04-11 00:07:27 +03:00
Misagh
93ebf163cb Merge pull request #1754 from freqtrade/pyup/scheduled-update-2019-04-10
Scheduled daily dependency update on Wednesday
2019-04-10 14:55:59 +02:00
pyup-bot
f736646ac6 Update ccxt from 1.18.445 to 1.18.456 2019-04-10 12:38:05 +00:00
Misagh
262113f9ee Merge pull request #1749 from freqtrade/telegram_long_msg
Telegram long /balance message
2019-04-10 10:30:49 +02:00
Matthias
e75cdd4c27 Rename variable, add more tests 2019-04-10 06:59:10 +02:00
Matthias
559257ed33 Merge pull request #1752 from freqtrade/pyup/scheduled-update-2019-04-09
Scheduled daily dependency update on Tuesday
2019-04-09 16:59:48 +02:00
pyup-bot
71e671f053 Update ccxt from 1.18.442 to 1.18.445 2019-04-09 12:38:06 +00:00
hroff-1902
9fbe573cca limit usage of ccxt to freqtrade/exchange only 2019-04-09 12:27:35 +03:00
Matthias
6856848efc Merge pull request #1744 from hroff-1902/ccxt-parse_timeframe
cosmetic: rename interval, tick_interval, etc --> ticker_interval
2019-04-08 20:26:36 +02:00
Matthias
ff6967de9e Add test for too large balance 2019-04-08 19:59:54 +02:00
Matthias
5c4170951a Don't send too large messages 2019-04-08 19:59:30 +02:00
Misagh
500eb17449 Merge pull request #1747 from freqtrade/pyup/scheduled-update-2019-04-08
Scheduled daily dependency update on Monday
2019-04-08 16:30:12 +02:00
pyup-bot
ffdc33d964 Update ccxt from 1.18.437 to 1.18.442 2019-04-08 12:39:06 +00:00
hroff-1902
3e4dd5019d docs adjusted 2019-04-08 11:20:15 +03:00
hroff-1902
cb2f422e1c make name option required again 2019-04-08 11:19:45 +03:00
hroff-1902
4559a38172 PoC: use defaults in json schema for some exchange options 2019-04-08 04:42:28 +03:00
hroff-1902
91dc2b96fc support for defaults in json.schema 2019-04-08 04:23:29 +03:00
Matthias
fb8555a6cc Merge pull request #1743 from freqtrade/pyup/scheduled-update-2019-04-07
Scheduled daily dependency update on Sunday
2019-04-07 19:17:31 +02:00
hroff-1902
ebf1126351 cosmetic: rename interval, tick_interval, etc --> ticker_interval 2019-04-07 16:28:53 +03:00
pyup-bot
3a81eb7d48 Update ccxt from 1.18.435 to 1.18.437 2019-04-07 12:38:05 +00:00
TL Nguyen
3ad4d937c5 Correct Dockerfile.pi file to use requirements-pi.txt 2019-04-07 14:07:26 +03:00
TL Nguyen
c35e5ca7dd Add back requirements-pi.txt file and put it into .pyup.yml 2019-04-07 14:05:41 +03:00
Matthias
4a6c8f3cb2 Merge pull request #1735 from hroff-1902/ccxt-parse_timeframe
Resolution for #1137
2019-04-07 12:52:13 +02:00
TL Nguyen
e7c8e62d75 Remove requirements-pi.txt, change Dockerfile.pi to utilize the requirements.txt instead 2019-04-07 10:31:03 +03:00
hroff-1902
d6d16b4696 docstrings improved 2019-04-07 00:22:02 +03:00
hroff-1902
dc1968b968 docstrings added 2019-04-06 23:36:55 +03:00
Misagh
4fef9448bf Merge pull request #1727 from mishaker/fix_cancel_order
Adding invalid order exception and fix #1726
2019-04-06 20:32:44 +02:00
Misagh
4bb5345e13 Merge pull request #1741 from freqtrade/abstract_count
rpc Count should be in rpc.py
2019-04-06 20:32:15 +02:00
Misagh
d294cab933 adding order id to invalidorder exception message 2019-04-06 20:27:03 +02:00
Matthias
f139178136 rpc_counts should be in .rpc 2019-04-06 20:11:41 +02:00
TL Nguyen
4eb0ed9f2f Add Dockerfile.pi for building docker image for raspberry pi 2019-04-06 21:11:14 +03:00
Matthias
7a598f32dc Move rpc-count calculation to _rpc class 2019-04-06 19:58:45 +02:00
Matthias
b776336ebf Merge pull request #1740 from freqtrade/pyup/scheduled-update-2019-04-06
Scheduled daily dependency update on Saturday
2019-04-06 14:57:54 +02:00
pyup-bot
481df98f58 Update ccxt from 1.18.432 to 1.18.435 2019-04-06 12:38:04 +00:00
hroff-1902
8cb1024ff6 Merge branch 'develop' into ccxt-parse_timeframe 2019-04-05 23:16:27 +03:00
Misagh
41ff2a9276 TemporaryError removed 2019-04-05 20:40:44 +02:00
Misagh
acb99a03e3 adding stoploss on exchange manual cancel note 2019-04-05 20:30:54 +02:00
Misagh
4b2eb22989 conflict with develop resolved 2019-04-05 20:23:15 +02:00
Misagh
a505826ec9 flake8 2019-04-05 20:20:41 +02:00
Misagh
54d068de44 missing test added 2019-04-05 20:20:16 +02:00
Misagh
25d8e93a90 remove unnecessary comment 2019-04-05 19:53:15 +02:00
Misagh
9712fb2d57 removing unnecessary comment 2019-04-05 19:49:02 +02:00
Misagh
2b49a11b2a returning InvalidOrder exception for get_order 2019-04-05 19:46:43 +02:00
Matthias
1bfc667515 Merge pull request #1737 from freqtrade/doc/simplify
Improve documentation formatting
2019-04-05 19:21:30 +02:00
Your Name
4c5432be6f Added command line options in backtesting to override max_open_trades and stake_amount 2019-04-05 16:48:14 +03:00
Misagh
9dc2a30793 Merge pull request #1683 from gianlup/fix_bt_partial_data
Fix backtest problem with partial data
2019-04-05 07:28:57 +02:00
Matthias
13e8f25ca9 Improve docs layout 2019-04-05 06:51:16 +02:00
Matthias
ac1964edb1 Remove unnecessary comment 2019-04-05 06:49:15 +02:00
Matthias
dbb1bbf101 Fix webhook documentation 2019-04-05 06:47:03 +02:00
Matthias
0ac80aacd1 Merge pull request #1736 from iuvbio/update/docs
Update/docs
2019-04-04 21:15:28 +02:00
iuvbio
7486cb7c64 fix admonitions 2019-04-04 21:05:26 +02:00
iuvbio
e3cdc0a05b typos and visual fixes 2019-04-04 20:53:28 +02:00
hroff-1902
6913bce6a1 flake8, import in script/plot_profit.py 2019-04-04 21:39:38 +03:00
Matthias
7010c835d2 Improve commentign 2019-04-04 20:23:10 +02:00
hroff-1902
2aa1b43f01 get rid of TICKER_INTERVAL_MINUTES dict, use ccxt's parse_timeframe() instead 2019-04-04 20:56:40 +03:00
Matthias
32cbb714f9 Improve commenting on backtsting and backtest_multi_tst 2019-04-04 19:44:03 +02:00
Misagh
7f4fd6168a test for canceled SL on exchange added 2019-04-04 17:23:21 +02:00
Misagh
647534a4f8 flake8 2019-04-04 17:17:21 +02:00
Misagh
31fa857319 typo 2019-04-04 17:15:51 +02:00
Misagh
a363d443bf stoploss on exchange canceled handled 2019-04-04 17:13:54 +02:00
Misagh
75c522e082 Merge pull request #1734 from freqtrade/pyup/scheduled-update-2019-04-04
Scheduled daily dependency update on Thursday
2019-04-04 14:56:55 +02:00
pyup-bot
ebeaf64fbb Update mypy from 0.670 to 0.700 2019-04-04 12:38:06 +00:00
pyup-bot
6afe232c4d Update ccxt from 1.18.430 to 1.18.432 2019-04-04 12:38:05 +00:00
Matthias
05df7f3394 Merge pull request #1733 from mishaker/stake_amount_bug
"stake amount" not "amount" should be shown for stake_amount :)
2019-04-04 14:13:00 +02:00
Misagh
0cdbe714d2 stake amount not amount 2019-04-04 12:06:45 +02:00
Misagh
9d6d60dcf0 Merge pull request #1689 from hroff-1902/main_refactoring
Main.py and freqtradebot refactoring
2019-04-04 11:19:15 +02:00
hroff-1902
65350ad552 final flake happy 2019-04-03 22:14:42 +03:00
Matthias
b437c3cf0c Merge pull request #1729 from mishaker/telegram_sl
Removing % sign from telegram message as it is already a pct.
2019-04-03 21:09:36 +02:00
Misagh
5488c66f53 flake8 2019-04-03 20:35:37 +02:00
Misagh
ef48193fad Merge pull request #1721 from hroff-1902/fix_1704
Fix #1704
2019-04-03 20:32:38 +02:00
Misagh
9ee1dd99eb tests fixed 2019-04-03 20:28:03 +02:00
Matthias
0307ba7883 Remove one branch - python does lazy evaluation 2019-04-03 20:04:04 +02:00
Matthias
1a5b0969b9 Fix tests (both tests where testing the same thing) 2019-04-03 19:53:10 +02:00
Matthias
3c399fbe3f Improve whitelist wordings 2019-04-03 19:51:46 +02:00
Matthias
a9a5c4a052 Merge pull request #1731 from mishaker/msg_stake
This adds stake amount in base currency to the RPC status message
2019-04-03 19:31:24 +02:00
Misagh
d5498c8712 adding % 2019-04-03 19:29:44 +02:00
Matthias
09321ccc9c Merge pull request #1728 from mishaker/edge_rpc_msg
Filtering edge pairs for RPC
2019-04-03 19:27:14 +02:00
Misagh
a3fe5f5757 adding stake amount to telegram message 2019-04-03 16:28:44 +02:00
Matthias
dfed713647 Merge pull request #1730 from freqtrade/pyup/scheduled-update-2019-04-03
Scheduled daily dependency update on Wednesday
2019-04-03 15:32:15 +02:00
pyup-bot
92dc3c89af Update sqlalchemy from 1.3.1 to 1.3.2 2019-04-03 12:38:07 +00:00
pyup-bot
eb610441b5 Update ccxt from 1.18.425 to 1.18.430 2019-04-03 12:38:06 +00:00
Misagh
67eeb145e1 flake8 2019-04-03 14:31:00 +02:00
Misagh
a3835b1279 flake8 2019-04-03 14:14:47 +02:00
Misagh
5f38d5ee63 removing % sign as it is already a pct 2019-04-03 14:07:33 +02:00
Misagh
53eaf85969 filtering edge pairs for RPC 2019-04-03 14:03:28 +02:00
hroff-1902
d54acca53a move tests back to original codebase to minimize changes 2019-04-03 00:55:59 +03:00
hroff-1902
2959156070 Merge branch 'develop' into main_refactoring 2019-04-03 00:50:33 +03:00
hroff-1902
b0ddb33acc tests cleanup: Worker --> FreqtradeBot where the Worker object is not really needed 2019-04-02 22:36:30 +03:00
hroff-1902
62141d3d27 test cloned, separate tests for worker and freqtrade states 2019-04-02 21:57:52 +03:00
Matthias
478c149bbb Merge pull request #1724 from mishaker/telegram_pct
Added percentage to telegram messages + documentation
2019-04-02 20:15:01 +02:00
Misagh
7b39a3084f formatting and readability 2019-04-02 20:08:10 +02:00
Misagh
a6daf0d991 formatting pct 2019-04-02 20:00:58 +02:00
Misagh
54f11ad603 enriching TSL log 2019-04-02 18:57:06 +02:00
Misagh
40df0dcf3d tests fixed 2019-04-02 18:45:18 +02:00
Misagh
99d256422e adding InvalidOrder to exchange 2019-04-02 18:31:03 +02:00
Misagh
389feda65f Invalid order exception added 2019-04-02 18:25:17 +02:00
Misagh
5a8f0f3557 Merge pull request #1725 from freqtrade/pyup/scheduled-update-2019-04-02
Scheduled daily dependency update on Tuesday
2019-04-02 16:37:58 +02:00
pyup-bot
b9b76977b6 Update ccxt from 1.18.420 to 1.18.425 2019-04-02 12:38:06 +00:00
Misagh
27917c2d89 Merge pull request #1720 from freqtrade/fix/fee_not_adjusted
Fix/fee not adjusted
2019-04-02 12:23:08 +02:00
Matthias
0cfdce0d5e Update function name from update_open_order to update_trade_state 2019-04-02 07:12:48 +02:00
hroff-1902
ab0e657d77 Check for empty whitelist moved to _process() 2019-04-01 21:36:53 +03:00
hroff-1902
34b40500c3 Check whitelist fetched from config for emptiness 2019-04-01 20:45:59 +03:00
Misagh
a3b0135557 documentation added for telegram 2019-04-01 19:25:13 +02:00
hroff-1902
8546db9dfd wording in the log message 2019-04-01 20:23:13 +03:00
Misagh
ab579587f2 adding percentage to telegram status messages 2019-04-01 19:13:45 +02:00
Matthias
ecd75e43b0 Merge pull request #1722 from freqtrade/pyup/scheduled-update-2019-04-01
Scheduled daily dependency update on Monday
2019-04-01 16:03:31 +02:00
pyup-bot
061f91ba41 Update pytest from 4.3.1 to 4.4.0 2019-04-01 12:38:07 +00:00
pyup-bot
97b31352c2 Update ccxt from 1.18.418 to 1.18.420 2019-04-01 12:38:06 +00:00
hroff-1902
77d2479c75 tests adjusted 2019-04-01 14:08:41 +03:00
hroff-1902
f0b2798c37 fix #1704 2019-04-01 14:08:03 +03:00
Misagh
8002936fe3 Merge pull request #1712 from freqtrade/log/tofile
Allow logging to file
2019-04-01 12:55:19 +02:00
Misagh
f440bb193d Merge pull request #1714 from freqtrade/cleanup_conftest
Cleanup tests a bit
2019-04-01 12:52:49 +02:00
Misagh
faa5883f09 Merge pull request #1716 from freqtrade/fix-jsonfull
fix typos in full_json_example
2019-04-01 12:50:47 +02:00
hroff-1902
7251e5bd62 bot state moved back to freqtradebot from worker 2019-03-31 23:39:55 +03:00
Matthias
7be90f71d3 Add test as called from execute_buy 2019-03-31 19:56:01 +02:00
Matthias
19d3a0cbac Update comment 2019-03-31 19:41:17 +02:00
Matthias
0ddafeeabf Split test for open_orders from maybe_sell 2019-03-31 16:05:40 +02:00
Matthias
b2ad402df4 Split tests for update-open_order 2019-03-31 15:51:45 +02:00
Matthias
e46dac3fbd Test stoploss does not raise dependencyexception 2019-03-31 15:45:22 +02:00
Matthias
5c8fbe2c6f Handle exception for stoploss independently of sell order 2019-03-31 15:41:10 +02:00
Matthias
f11a1b0122 Call update_open_order inline with buy
captures FOK / market orders
2019-03-31 15:40:43 +02:00
Matthias
8f4cca47e9 Refactor update_open_order into it's own function 2019-03-31 15:39:41 +02:00
Matthias
4fa736114c Don't set order_id to none here - it's used in "update_open_order".
should fix bugs observed in #1371 connected to stoploss
2019-03-31 15:38:25 +02:00
Misagh
13ac1e1957 Merge pull request #1719 from freqtrade/pyup/scheduled-update-2019-03-31
Scheduled daily dependency update on Sunday
2019-03-31 14:58:16 +02:00
pyup-bot
c28a0374f1 Update pytest-mock from 1.10.2 to 1.10.3 2019-03-31 12:38:04 +00:00
pyup-bot
93229fc54b Update ccxt from 1.18.415 to 1.18.418 2019-03-31 12:38:03 +00:00
Matthias
997190a050 Merge pull request #1709 from mishaker/sl_pct
Adding stoploss percentage to DB
2019-03-31 13:47:42 +02:00
Matthias
707a5fca91 ifix typos in full_json_example 2019-03-31 13:30:22 +02:00
Misagh
6d92b9b910 Merge branch 'develop' of https://github.com/freqtrade/freqtrade into sl_pct 2019-03-31 13:20:25 +02:00
Misagh
9b38c04579 negating SL pct and adding tests 2019-03-31 13:15:35 +02:00
hroff-1902
06144a1fc4 Wording in a comment 2019-03-30 23:33:52 +03:00
Matthias
0d152eb907 Merge pull request #1713 from freqtrade/pyup/scheduled-update-2019-03-30
Scheduled daily dependency update on Saturday
2019-03-30 13:53:43 +01:00
Matthias
1a61bf7bff sort imports 2019-03-30 13:48:30 +01:00
Matthias
87a296f728 No need to call patch_coinmarketcap each tim 2019-03-30 13:48:03 +01:00
Matthias
e98c0621d3 We don't need to call patch_coinmarketcap each time. 2019-03-30 13:47:30 +01:00
Matthias
40c0b4ef2e Autopatch coinmarketcap 2019-03-30 13:47:21 +01:00
pyup-bot
44142706c3 Update ccxt from 1.18.412 to 1.18.415 2019-03-30 12:38:03 +00:00
hroff-1902
208832e847 flake8, mypy resolved 2019-03-30 02:19:43 +03:00
Matthias
12066411db Update docs with logfile methods 2019-03-29 20:19:40 +01:00
Matthias
e5008fbf93 Add test for logfile attribute 2019-03-29 20:16:52 +01:00
Matthias
d4ffdaffc2 Correctly add types 2019-03-29 20:16:41 +01:00
Matthias
bb5a310aec Add --logfile argument 2019-03-29 20:13:15 +01:00
Matthias
ba558b2d75 Merge pull request #1711 from freqtrade/pyup/scheduled-update-2019-03-29
Scheduled daily dependency update on Friday
2019-03-29 15:16:53 +01:00
pyup-bot
82b344db1b Update ccxt from 1.18.407 to 1.18.412 2019-03-29 12:38:05 +00:00
Misagh
f2599ffe90 pct default to None 2019-03-29 08:08:29 +01:00
Misagh
50fc63251e added SL pct to DB 2019-03-28 21:18:26 +01:00
Misagh
b1ef39927c Merge pull request #1673 from freqtrade/refactor/persistance_stoplossupdate
trailing stop backtest problems
2019-03-28 20:44:24 +01:00
Matthias
b4472a165e Merge pull request #1707 from mishaker/telegram_msg
Telegram status message refactoring
2019-03-28 19:45:48 +01:00
Matthias
a87fc5f863 Fix tests - freqtrade should not be patched in this case 2019-03-28 19:37:50 +01:00
Misagh
2f3f5f19cd sl percentage removed form rpc test 2019-03-28 16:26:59 +01:00
Misagh
e11eb4775e stoploss precentage in telegram msg removed 2019-03-28 16:21:49 +01:00
Matthias
a15a3ae810 Merge pull request #1708 from freqtrade/pyup/scheduled-update-2019-03-28
Scheduled daily dependency update on Thursday
2019-03-28 14:45:56 +01:00
pyup-bot
daeb172ba1 Update ccxt from 1.18.406 to 1.18.407 2019-03-28 12:38:05 +00:00
Misagh
0e5b0ebda6 adding SL and SL percentage to telegram msg 2019-03-28 12:09:07 +01:00
hroff-1902
d5254dff7b Merge branch 'develop' into main_refactoring 2019-03-28 11:10:21 +03:00
Matthias
146d6bf7fb Merge pull request #1698 from mishaker/edge_rpc
Edge RPC
2019-03-28 06:22:38 +01:00
Matthias
0a8c1528cf Merge pull request #1686 from iuvbio/refactor/binance
Refactor/binance
2019-03-28 06:22:02 +01:00
Misagh
941921dd0f initial SL and SL added to RPC 2019-03-27 22:00:46 +01:00
Misagh
0ca3a38ba6 moved date to top and show open order only if it is not none 2019-03-27 21:39:17 +01:00
Misagh
1678a039ae removing close profit is trade is open 2019-03-27 21:32:56 +01:00
Misagh
e5406ed3cf typo in docs and comments 2019-03-27 21:22:25 +01:00
Misagh
4d9ca71c82 shifting edge help message a line lower 2019-03-27 21:20:09 +01:00
Misagh
6045f07a9c telegram message concatenation refactored 2019-03-27 21:12:57 +01:00
Matthias
9b22d5cab1 Fix typo, add test for validate_order_tif 2019-03-27 20:51:55 +01:00
Misagh
753b03d581 rolback on removing MD whitespaces 2019-03-27 18:19:42 +01:00
Misagh
1e37d8ccb3 flake8 2019-03-27 16:58:53 +01:00
Misagh
4038cdf70a "Edge" test for rpc telegram 2019-03-27 16:04:05 +01:00
Matthias
d09b33ae93 Merge pull request #1706 from freqtrade/pyup/scheduled-update-2019-03-27
Scheduled daily dependency update on Wednesday
2019-03-27 14:15:51 +01:00
Misagh
0687051ffb Update test_rpc.py
flake8
2019-03-27 14:04:33 +01:00
Misagh
8641da13b9 added RPC tests in case of edge enabled/disabled 2019-03-27 14:02:37 +01:00
pyup-bot
cc32566c92 Update ccxt from 1.18.400 to 1.18.406 2019-03-27 12:38:05 +00:00
Misagh
955e2d2826 Update test_rpc_telegram.py
telegram test_init fixed
2019-03-27 12:59:59 +01:00
Misagh
4e57969e4e documentation added 2019-03-27 12:54:00 +01:00
Misagh
52012003e9 Merge pull request #1700 from freqtrade/dataprovider/backtesting
Dataprovider during backtesting
2019-03-27 12:43:59 +01:00
Matthias
3bdc7b9a88 add missed "check" in docs 2019-03-27 10:51:13 +01:00
Misagh
a2a2489a97 Merge pull request #1701 from freqtrade/fix/blacklist_rpc_check
Check if added pair has correct stake-currency
2019-03-27 10:29:54 +01:00
Gianluca Puglia
b2c2b42408 Removed unwanted comment 2019-03-26 18:53:16 +01:00
hroff-1902
f5744cc9bf fix in the tests 2019-03-26 18:34:50 +03:00
Matthias
56264ea52a Merge pull request #1705 from freqtrade/pyup/scheduled-update-2019-03-26
Scheduled daily dependency update on Tuesday
2019-03-26 14:30:40 +01:00
pyup-bot
1f50bc79bc Update ccxt from 1.18.398 to 1.18.400 2019-03-26 13:37:03 +01:00
hroff-1902
c6d2c1e520 rest of telegram tests adjusted 2019-03-26 12:45:19 +03:00
hroff-1902
8aee009a0a test _reconfigure() adjusted 2019-03-26 12:42:19 +03:00
hroff-1902
5ccd618189 tests adjusted 2019-03-26 11:07:24 +03:00
hroff-1902
5161e1abb3 Allow to pass config into worker, as it's used in the tests 2019-03-26 11:07:02 +03:00
iuvbio
e15f2ef11a add order_time_in_force in _ft_has and revert binance 2019-03-26 00:49:39 +01:00
iuvbio
8dea640e9a remove exchange urls 2019-03-25 23:58:02 +01:00
iuvbio
4005b8d1d2 remove the if condition for binance 2019-03-25 23:57:14 +01:00
iuvbio
85ac99aee0 move exchange urls to constants 2019-03-25 23:57:14 +01:00
Matthias
e085fd9e95 Disable dataprovider from hyperopt.
Dataprovider uses weak links to initialize, which cannot be pickled, and
therefore cannot be used during hyperopt.
2019-03-25 19:49:58 +01:00
Matthias
f26ed1c8c1 Check if added pair has correct stake-currency 2019-03-25 19:40:21 +01:00
Matthias
4cf7282027 Update dataprovider docs 2019-03-25 19:31:10 +01:00
Matthias
0ae81d4115 Provide dataprovider access during backtesting 2019-03-25 19:26:51 +01:00
Matthias
226fc3d99b Check that dataprovider is part of strategy 2019-03-25 19:26:51 +01:00
Matthias
bd29b7d031 Test that dataprovider is loaded to strategy 2019-03-25 19:26:51 +01:00
hroff-1902
c8b0c9af0a Worker moved to new worker.py 2019-03-25 17:45:03 +03:00
Matthias
01c4f243d4 Merge pull request #1699 from freqtrade/pyup/scheduled-update-2019-03-25
Scheduled daily dependency update on Monday
2019-03-25 14:20:01 +01:00
pyup-bot
fe9322ecd5 Update pytest-mock from 1.10.1 to 1.10.2 2019-03-25 13:36:06 +01:00
pyup-bot
904b3008a9 Update ccxt from 1.18.395 to 1.18.398 2019-03-25 13:36:04 +01:00
Misagh
66f1e0f4cd help added 2019-03-25 10:25:07 +01:00
Misagh
e8bfeae048 conflict with develop resolved 2019-03-25 10:16:09 +01:00
Misagh
fd7278517d using items() 2019-03-25 09:48:41 +01:00
Misagh
b13735e4cc Merge pull request #1697 from freqtrade/feat/rpc_blacklist
add pairs to blacklist dynamically
2019-03-25 09:44:12 +01:00
Misagh
a8be277ca0 cached pairs iteration fixed + help added 2019-03-24 22:56:42 +01:00
Misagh
1dfbf6eed6 darfting edge rpc messages 2019-03-24 22:36:33 +01:00
Matthias
29b9bb96f3 Fix test to support adding things to pairlist 2019-03-24 19:49:49 +01:00
Matthias
14167f826b Fix typehints 2019-03-24 19:44:52 +01:00
Misagh
96ea27322d Merge pull request #1694 from freqtrade/doc/dataprovider
Add stake_currency to strategy, fix  documentation typo
2019-03-24 17:13:03 +01:00
Misagh
71d3a7de40 Merge pull request #1692 from freqtrade/feat/scripts_flake_mypy
run flake8 and mypy against scripts folder as well.
2019-03-24 17:08:52 +01:00
Misagh
fe3836b497 Merge pull request #1696 from freqtrade/docs/1521
Update documentation with correct way of calling
2019-03-24 17:06:46 +01:00
Matthias
49559f1a1a Improve documentation and help message 2019-03-24 16:33:21 +01:00
Matthias
042354d00f Test blacklist-adding 2019-03-24 16:30:11 +01:00
Matthias
f0d3901b6b Add blacklist-pair to documentation 2019-03-24 16:29:58 +01:00
Matthias
9d6f629f6a Support adding pairs to blacklist 2019-03-24 16:28:14 +01:00
Matthias
7b99d5ebcb Add blacklist and whitelist commands to telegram docs 2019-03-24 16:16:39 +01:00
Matthias
8b2174d249 Add tests for /blacklist handler 2019-03-24 16:09:20 +01:00
Matthias
ffdca7eea7 Add blacklist to default_config 2019-03-24 16:09:04 +01:00
Matthias
684727b32e Add black blacklist handler (ro) 2019-03-24 16:08:48 +01:00
Matthias
3a8b69d69b also support dry_run 2019-03-24 15:37:58 +01:00
Matthias
1bba9fcc53 Update documentation to use freqtrade, not freqtrade/main.py
fixes #1521
2019-03-24 15:13:17 +01:00
Matthias
f7fc9adc63 Run travis with freqtrade, not main.py 2019-03-24 15:13:03 +01:00
Matthias
e60d1788b2 Add new options to docu 2019-03-24 15:06:17 +01:00
Matthias
a7e13e96e4 Merge pull request #1695 from freqtrade/pyup/scheduled-update-2019-03-24
Scheduled daily dependency update on Sunday
2019-03-24 14:20:24 +01:00
pyup-bot
e644493e02 Update ccxt from 1.18.387 to 1.18.395 2019-03-24 13:35:03 +01:00
Matthias
06f4e627fc Add stake_currency to strategy, fix documentation typo 2019-03-23 20:40:07 +01:00
Misagh
e0775546f6 Merge pull request #1693 from freqtrade/fix/doc_formatting
Fix Documentation Boxes
2019-03-23 20:05:27 +01:00
Matthias
0dc96210b6 Fix formatting of boxes 2 2019-03-23 19:43:23 +01:00
Matthias
a95f30ce45 Fix custom boxes on documentation 2019-03-23 19:40:52 +01:00
Matthias
83a2427a61 Fix mypy in scripts 2019-03-23 19:37:17 +01:00
Matthias
184b13f2fb Flake8 for scripts 2019-03-23 19:18:10 +01:00
Matthias
9a632d9b7c Formatting 2019-03-23 16:51:36 +01:00
Matthias
c404e9ffd0 Simplify trailing_stop logic 2019-03-23 16:48:17 +01:00
Matthias
b1fe8c5325 Simplify stoploss_reached 2019-03-23 16:46:03 +01:00
Matthias
7307084dfd Move stoploss-adjustment to the top 2019-03-23 16:44:58 +01:00
Matthias
40899d08dd Fix failing test (all timezones are in UTC, so we should not convert to
None)
2019-03-23 15:24:11 +01:00
Matthias
00e6749d8b Refactor backtest() to be a bit more concise 2019-03-23 15:00:07 +01:00
Matthias
05466d318a Modify test to check for this condition 2019-03-23 14:50:18 +01:00
Matthias
6312d785d8 Merge pull request #1691 from freqtrade/pyup/scheduled-update-2019-03-23
Scheduled daily dependency update on Saturday
2019-03-23 13:52:00 +01:00
pyup-bot
34ff946f4d Update ccxt from 1.18.386 to 1.18.387 2019-03-23 13:35:03 +01:00
hroff-1902
158cb307f6 further refactoring of FreqtradeBot.process() 2019-03-23 00:20:20 +03:00
hroff-1902
e35daf95c0 minor cleanup 2019-03-22 23:41:48 +03:00
hroff-1902
b448890210 test_main.py adjusted (only beginning) 2019-03-22 22:03:15 +03:00
hroff-1902
be6836b0ef resolve python module circular dependency 2019-03-22 21:49:19 +03:00
hroff-1902
60afba5592 move worker stuff to main.py 2019-03-22 20:16:54 +03:00
Matthias
d043542094 Merge pull request #1688 from freqtrade/pyup/scheduled-update-2019-03-22
Scheduled daily dependency update on Friday
2019-03-22 15:30:33 +01:00
pyup-bot
89145a7711 Update ccxt from 1.18.385 to 1.18.386 2019-03-22 13:35:06 +01:00
Matthias
7744989583 Merge pull request #1661 from iuvbio/validate_whitelist
validate whitelist vs. validate pairs
2019-03-21 06:34:31 +01:00
Matthias
35d65bc7d7 Merge branch 'develop' into 'validate_whitelist' 2019-03-21 06:22:48 +01:00
Matthias
7fdb099097 Reformat log statement 2019-03-21 06:14:43 +01:00
Matthias
1f55356744 Merge pull request #1685 from hroff-1902/patch-20
docs for dry_run_wallet
2019-03-21 06:03:12 +01:00
hroff-1902
00821036bb docs for dry_run_wallet 2019-03-20 23:57:49 +03:00
Gianluca Puglia
6b89e86a97 Removed Timestamp cast 2019-03-20 19:44:59 +01:00
Matthias
65f5aa59e6 Merge pull request #1680 from hroff-1902/wallets_and_exchange_cleanup
Minor: Wallet and exchange cleanup
2019-03-20 19:31:02 +01:00
Gianluca Puglia
0eff324ce0 Use dedicated index for every pair 2019-03-20 18:38:10 +01:00
Matthias
676c6a784d Merge pull request #1681 from freqtrade/pyup/scheduled-update-2019-03-20
Scheduled daily dependency update on Wednesday
2019-03-20 14:18:38 +01:00
pyup-bot
cc369f41f5 Update coveralls from 1.6.0 to 1.7.0 2019-03-20 13:35:07 +01:00
pyup-bot
6c889895bd Update ccxt from 1.18.376 to 1.18.385 2019-03-20 13:35:05 +01:00
hroff-1902
580ada8c4f exchange cleanup 2019-03-19 20:52:35 +03:00
hroff-1902
aa15312670 wallets cleanup 2019-03-19 20:51:27 +03:00
Misagh
df6f3f6f32 Merge pull request #1679 from freqtrade/pyup/scheduled-update-2019-03-19
Scheduled daily dependency update on Tuesday
2019-03-19 13:49:46 +01:00
pyup-bot
2b09e3ca3d Update plotly from 3.7.0 to 3.7.1 2019-03-19 13:32:05 +01:00
pyup-bot
9a61067367 Update ccxt from 1.18.372 to 1.18.376 2019-03-19 13:32:04 +01:00
Matthias
c8617e70a8 Merge pull request #1668 from freqtrade/fix/1658_no_telegram_updates
No telegram rate updates when orderbook is enabled
2019-03-18 19:40:32 +01:00
Misagh
38b959f1a9 Merge pull request #1677 from freqtrade/pyup/scheduled-update-2019-03-18
Scheduled daily dependency update on Monday
2019-03-18 13:50:35 +01:00
pyup-bot
50ea4c39da Update ccxt from 1.18.368 to 1.18.372 2019-03-18 13:32:05 +01:00
Misagh
ff08416b12 Merge pull request #1674 from freqtrade/feat/stopbuy
Telegram `/stopbuy`
2019-03-18 09:00:47 +01:00
Matthias
8d173efe2d reword stopbuy message 2019-03-18 06:29:08 +01:00
Matthias
aa698a8412 rename /stopbuy message 2019-03-18 06:27:44 +01:00
Misagh
e6bfedb58b Merge pull request #1672 from freqtrade/doc/remove_double
Remove duplicate backtest-result-analysis documentation
2019-03-17 21:58:12 +01:00
Matthias
37e6b262eb Update docs to include /stopbuy 2019-03-17 19:36:25 +01:00
Matthias
9373d0c915 Add tests for /stopbuy 2019-03-17 19:36:02 +01:00
Matthias
a467d76832 Add /stopbuy command to telegram
fixes #1607
2019-03-17 19:35:25 +01:00
iuvbio
937399606e fix flake8 2019-03-17 18:24:29 +01:00
iuvbio
c2076af43b update tests 2019-03-17 18:18:44 +01:00
iuvbio
4de4a70be7 update log messages 2019-03-17 18:18:35 +01:00
Matthias
8afce7e651 Add testcase for Testcase 2 2019-03-17 16:26:38 +01:00
Matthias
2bf7f2feae Remove duplicate backtest-result-analysi documentation 2019-03-17 16:14:49 +01:00
iuvbio
8386496456 remove tests that are no longer applicable 2019-03-17 16:04:09 +01:00
iuvbio
7f9c76a6fc move stake check to the same condition as the other checks 2019-03-17 16:04:09 +01:00
iuvbio
d4d37667e1 use pairname for stake cur comparison 2019-03-17 16:04:09 +01:00
iuvbio
d4543be8eb edit comment 2019-03-17 16:04:09 +01:00
iuvbio
e38a3051a1 update docstring 2019-03-17 16:04:09 +01:00
iuvbio
c907e80c10 make sure no dups 2019-03-17 16:04:09 +01:00
iuvbio
a241e950f2 prune validate_pairs 2019-03-17 16:04:09 +01:00
iuvbio
39232cbcbb loop over whitelist only instead of all markets 2019-03-17 16:04:09 +01:00
Matthias
a7b60f6780 update trailing_stop with high in case of backtesting 2019-03-17 16:03:44 +01:00
Matthias
05ab1c2e0a Fix some comments 2019-03-17 16:02:13 +01:00
Matthias
8c7e8255bb Add detailed test for trailing stop 2019-03-17 16:01:34 +01:00
Matthias
f0e5113a7f Use Magicmock instead of lambda for mocking 2019-03-17 15:39:05 +01:00
Matthias
a830bee9c7 Enable trailing_stop for BTContainer tests 2019-03-17 15:28:04 +01:00
Matthias
bdc0134e88 Merge pull request #1671 from freqtrade/pyup/scheduled-update-2019-03-17
Scheduled daily dependency update on Sunday
2019-03-17 15:25:28 +01:00
pyup-bot
190ecb7ada Update ccxt from 1.18.367 to 1.18.368 2019-03-17 13:32:05 +01:00
Matthias
a77d513513 Fix backteest detail numbering ... 2019-03-17 13:27:32 +01:00
Matthias
7b99daebd7 Update docstring for adjust_stoploss 2019-03-17 13:19:24 +01:00
Matthias
2d4a2fd10b Use oppen_rate instead of artificial defaults 2019-03-17 13:12:04 +01:00
Matthias
a0e6cd93b6 Use bids, not asks for sell-rate detection 2019-03-17 11:27:01 +01:00
Misagh
b3f42dc51e Merge pull request #1635 from freqtrade/feat/btanlaysis
BTAnalysis - simplify backtest result analysis
2019-03-16 21:16:59 +01:00
Misagh
b0cad30796 Merge pull request #1670 from freqtrade/doc_update
Add 15min to documentation, fix link to "parameters in THE strategy"
2019-03-16 21:00:16 +01:00
Matthias
fc360608b7 Rename function to adjust_min_max 2019-03-16 20:14:45 +01:00
Matthias
01733c94fa Split up tests for adjust_stoploss and adjust_highlow 2019-03-16 20:04:55 +01:00
Matthias
68a9b14eca Min-rate should not default to 0 2019-03-16 20:04:39 +01:00
Matthias
738ed93221 call new function 2019-03-16 19:54:34 +01:00
Matthias
7166a474ae Add min_rate - always update min/max rates 2019-03-16 19:54:16 +01:00
Matthias
e632539b61 Add 15min to documentation, fix link to "parameters in THE strategy" 2019-03-16 19:51:39 +01:00
Matthias
e7f6df46e8 Add missing bt file 2019-03-16 19:15:20 +01:00
Matthias
a123246ac9 Add test for load_backtest_data 2019-03-16 17:50:57 +01:00
Matthias
ddb9933c91 Remove duplicate-check from test - it's in btanalysis 2019-03-16 17:28:28 +01:00
Matthias
9f7f089d8a adjust plot_dataframe to use btanalysis 2019-03-16 17:28:28 +01:00
Matthias
e1f48c2b46 Add btanalysis file 2019-03-16 17:28:28 +01:00
Matthias
d7017ce1e4 Document backtest-result loading 2019-03-16 17:28:28 +01:00
Matthias
6666d31ee9 Merge pull request #1648 from hroff-1902/sd-watchdog
Support for systemd watchdog
2019-03-16 13:46:04 +01:00
Matthias
29aa159827 Add test for get_sell_rate 2019-03-16 13:32:26 +01:00
Matthias
6bfc37309e refactor getting sell/current rate for telegram and selling
fix #1658
2019-03-16 13:24:10 +01:00
Matthias
71c530590e Merge pull request #1666 from freqtrade/telegram_help
Telegram help
2019-03-16 12:44:17 +01:00
Matthias
d596a877fa Update docs to link to ocnfiguration piece necessary 2019-03-16 11:07:16 +01:00
Matthias
b9b15e5f32 Align help message for forcebuy 2019-03-16 11:04:24 +01:00
Matthias
d66e6510e3 Merge pull request #1645 from mishaker/trailing_only_offset
Adding an option for trailing stoploss: "trailing_only_offset_is_reached"
2019-03-16 10:43:56 +01:00
Matthias
a233a8cc82 Be explicit in the documentation 2019-03-16 10:38:32 +01:00
Matthias
d42ebab575 Rename function and add test 2019-03-16 10:38:25 +01:00
Misagh
51af8c27f6 Merge pull request #1665 from freqtrade/catch/syntxerror
Catch syntaxerror on import
2019-03-16 08:15:09 +01:00
Matthias
44acf2f471 Catch syntaxerror on import 2019-03-15 19:50:38 +01:00
Matthias
ceb1e4c4f7 Merge pull request #1664 from freqtrade/pyup/scheduled-update-2019-03-15
Scheduled daily dependency update on Friday
2019-03-15 13:46:13 +01:00
pyup-bot
6db6c3b2cc Update ccxt from 1.18.362 to 1.18.367 2019-03-15 13:32:05 +01:00
Matthias
2e02e24e70 Merge pull request #1663 from iuvbio/fix/1662
Fix sort key not populated
2019-03-15 06:11:53 +01:00
iuvbio
95a3b5c41e check if ticker sort key is populated 2019-03-14 22:48:42 +01:00
Matthias
3fe06b3548 Merge pull request #1660 from freqtrade/pyup/scheduled-update-2019-03-14
Scheduled daily dependency update on Thursday
2019-03-14 19:36:41 +01:00
pyup-bot
1a83eed38f Update pandas from 0.24.1 to 0.24.2 2019-03-14 13:32:09 +01:00
pyup-bot
4fa1604230 Update ccxt from 1.18.361 to 1.18.362 2019-03-14 13:32:05 +01:00
misagh
edf2cd0b92 configuration test fixed 2019-03-14 09:26:31 +01:00
misagh
b5034cf535 TSL validator removed from exchange 2019-03-14 09:04:41 +01:00
misagh
29305dd070 config validation moved to configuration file 2019-03-14 09:01:03 +01:00
misagh
3c99e3b7c7 test adapted to new market refactoring 2019-03-14 09:00:28 +01:00
misagh
9a226ec7e6 conflict with develop resolved 2019-03-14 07:56:21 +01:00
Misagh
2959600f52 Merge pull request #1656 from freqtrade/fix/1633
Default value for minimal_roi
2019-03-14 07:51:07 +01:00
Matthias
ff9231eec4 Format attributes-table 2019-03-14 06:42:27 +01:00
Matthias
6b8f5963a8 Merge pull request #1623 from iuvbio/markets_refactor
Markets refactor
2019-03-14 06:22:18 +01:00
iuvbio
a1841c35ae reset _last_markets_refresh 2019-03-13 20:18:49 +01:00
iuvbio
aa2d747d8f update docs 2019-03-13 20:08:51 +01:00
Misagh
ee613b564c Merge pull request #1657 from freqtrade/fix/1653
send notification when stoploss_on_exchange is hit
2019-03-13 19:52:32 +01:00
Matthias
2bf5a3843d Use close_rate for notification if available 2019-03-13 19:41:58 +01:00
Matthias
29e84c9e88 Merge pull request #1659 from freqtrade/pyup/scheduled-update-2019-03-13
Scheduled daily dependency update on Wednesday
2019-03-13 14:33:10 +01:00
pyup-bot
23666858e2 Update pytest from 4.3.0 to 4.3.1 2019-03-13 13:32:06 +01:00
pyup-bot
5151a4521f Update ccxt from 1.18.358 to 1.18.361 2019-03-13 13:32:04 +01:00
Matthias
6b948cfc7e Don't move notify_sell to rpc_manager - it needs exchange stuff 2019-03-12 22:01:19 +01:00
Matthias
9054165e8a Adjust test, since rpc_message is now called on buy and sel 2019-03-12 21:55:18 +01:00
Matthias
11cc33a982 Refactor notify_sell to rpc_manager
* Call sell_notify also when stoploss_on_exchange is hit

fix #1653
2019-03-12 21:55:00 +01:00
Matthias
e2bcaa4d75 Set Requested_close_rate to stoploss when stoploss_on_exchange was hit 2019-03-12 21:54:52 +01:00
Matthias
94b2d48d02 Add default value for minimal_roi (1000%)
fix #1633
2019-03-12 19:37:58 +01:00
Matthias
0293a61895 Update documentation for minimal_roi, which is not really optional 2019-03-12 19:37:43 +01:00
iuvbio
7ffe65770e fix test 2019-03-12 17:54:16 +01:00
iuvbio
cb9849e192 add markets_refresh_interval to CONF_SCHEMA 2019-03-12 16:54:59 +01:00
iuvbio
299e640170 include markets_refresh_interval in docs 2019-03-12 16:39:13 +01:00
Misagh
954963b40e Merge pull request #1651 from freqtrade/fix/importerror_strats
Catch ModuleNotFoundError when importing external code
2019-03-12 16:37:30 +01:00
iuvbio
779bcdd990 remove reload for async api 2019-03-12 16:35:32 +01:00
iuvbio
0ffefe44a7 reorder vars 2019-03-12 16:31:22 +01:00
iuvbio
deddbda26e delete markets patch from conftest 2019-03-12 16:31:22 +01:00
iuvbio
1a92bf9e8e add test 2019-03-12 16:31:22 +01:00
iuvbio
8741017819 remove get_markets 2019-03-12 16:31:22 +01:00
iuvbio
35c2b961be add config param 2019-03-12 16:31:22 +01:00
iuvbio
0d980134e7 add markets reload func 2019-03-12 16:31:22 +01:00
iuvbio
3ad0686bc7 fix typing 2019-03-12 16:31:22 +01:00
iuvbio
df9410cd15 check if markets were loaded 2019-03-12 16:31:22 +01:00
iuvbio
041e9957dd add reload argument 2019-03-12 16:31:22 +01:00
iuvbio
6b97af4a03 add comment 2019-03-12 16:31:22 +01:00
iuvbio
e234158cc9 update tests 2019-03-12 16:31:22 +01:00
iuvbio
c30fb7f590 return markets as dict 2019-03-12 16:31:22 +01:00
iuvbio
5c840f333f slight change to exception message 2019-03-12 16:31:22 +01:00
iuvbio
b24a22b0b6 use self.markets instead of get_markets 2019-03-12 16:31:22 +01:00
iuvbio
47cc04c0a3 use self.markets instead of _api.markets 2019-03-12 16:31:22 +01:00
iuvbio
ccad883256 adjust get_markets 2019-03-12 16:31:22 +01:00
iuvbio
3a2aa54d2a add markets property 2019-03-12 16:31:22 +01:00
iuvbio
d423f58566 replace fetch_markets 2019-03-12 16:31:22 +01:00
misagh
0bcf50f1b5 added to stoploss doc 2019-03-12 15:48:30 +01:00
misagh
8d5cc42ef5 configuration doc added 2019-03-12 15:46:21 +01:00
misagh
a772ab323e adding the option to resolver 2019-03-12 15:43:53 +01:00
misagh
f55d75e7fc TSL validation tests added 2019-03-12 15:35:44 +01:00
Matthias
5865688c16 Merge pull request #1655 from freqtrade/pyup/scheduled-update-2019-03-12
Scheduled daily dependency update on Tuesday
2019-03-12 14:10:21 +01:00
pyup-bot
3e4c9c8713 Update ccxt from 1.18.357 to 1.18.358 2019-03-12 13:32:05 +01:00
misagh
36e95bc868 unnecessary variable removed 2019-03-12 13:10:59 +01:00
misagh
3e40f5c588 if condition simplified 2019-03-12 13:09:27 +01:00
misagh
643262bc6a add trailing stop loss config validator 2019-03-12 13:03:29 +01:00
misagh
f1f311e456 Merge branch 'develop' into trailing_only_offset 2019-03-12 12:32:10 +01:00
Misagh
c1a22dda46 Merge pull request #1654 from freqtrade/feat/startup_stoploss
Add stoploss to startup messages
2019-03-12 11:48:29 +01:00
Misagh
d14134ddce Merge pull request #1652 from freqtrade/fix/tif-market_order_combo
Fix/tif market order combo
2019-03-12 11:46:15 +01:00
Matthias
48d33b070f Add stoploss to startup messages 2019-03-12 07:06:42 +01:00
Matthias
0eb9dd5fe5 Don't use timeInForce for market orders 2019-03-11 20:30:36 +01:00
Matthias
4705b7da0e Add time_in_force test for sell 2019-03-11 20:30:16 +01:00
Matthias
c0f276a892 Move kraken specific tests to their own file 2019-03-11 20:22:51 +01:00
Matthias
e666c6850e Fix tests so Market orders should not send timeInForce 2019-03-11 20:20:51 +01:00
Matthias
f9aa3c27be Catch ModuleNotFoundError when importing external code 2019-03-11 19:49:03 +01:00
Matthias
5fb8100fc5 Merge pull request #1650 from freqtrade/pyup/scheduled-update-2019-03-11
Scheduled daily dependency update on Monday
2019-03-11 14:20:19 +01:00
hroff-1902
41add9f8ca code cleanup; added message to systemd for reconfiguration 2019-03-11 15:38:00 +03:00
pyup-bot
513b96b61c Update ccxt from 1.18.353 to 1.18.357 2019-03-11 13:32:04 +01:00
hroff-1902
8730852d6e Support for systemd watchdog via sd_notify 2019-03-10 21:04:38 +03:00
misagh
ca496c13b8 TSL only offset test added 2019-03-10 17:11:28 +01:00
misagh
0467004144 added trailing_only_offset_is_reached to full config 2019-03-10 15:54:46 +01:00
Matthias
e14739e102 Merge pull request #1647 from freqtrade/pyup/scheduled-update-2019-03-10
Scheduled daily dependency update on Sunday
2019-03-10 13:54:02 +01:00
pyup-bot
0eaac1cd79 Update sqlalchemy from 1.3.0 to 1.3.1 2019-03-10 13:32:06 +01:00
pyup-bot
5f726d697b Update ccxt from 1.18.352 to 1.18.353 2019-03-10 13:32:05 +01:00
misagh
9c1c962aa7 if condition fixed 2019-03-09 20:30:56 +01:00
misagh
c122eab77b added trailing_only_offset_is_reached option 2019-03-09 20:13:35 +01:00
Misagh
617d2338c4 Merge pull request #1642 from freqtrade/fix/1637
Fix broken dry-mode sells
2019-03-09 19:23:34 +01:00
Misagh
c56f288b56 Merge pull request #1643 from freqtrade/fix/coveralls_multi
Update travis for coveralls
2019-03-09 19:22:20 +01:00
Misagh
51b4d5a57a Merge pull request #1644 from freqtrade/pyup/scheduled-update-2019-03-09
Scheduled daily dependency update on Saturday
2019-03-09 19:19:53 +01:00
pyup-bot
43d30180e8 Update plotly from 3.6.1 to 3.7.0 2019-03-09 13:32:08 +01:00
pyup-bot
3b805813cd Update ccxt from 1.18.347 to 1.18.352 2019-03-09 13:32:07 +01:00
Matthias
21cb4eafe5 Merge pull request #1632 from iuvbio/update_docs
update sql_cheatsheet
2019-03-08 22:19:44 +01:00
Matthias
fa4c8110e7 Rename cheatsheet header 2019-03-08 22:15:03 +01:00
Matthias
25529ad95f use || for coveralls 2019-03-08 21:54:40 +01:00
Matthias
dba30bbfed Update travis for coveralls 2019-03-08 21:37:15 +01:00
Matthias
4cd70138b6 Add test to make sure this ain't reintroduced 2019-03-08 21:26:21 +01:00
Matthias
0a2cacbba8 Fix #1637 2019-03-08 21:17:12 +01:00
Matthias
d213764d19 Merge pull request #1641 from hroff-1902/patch-19
Minor: exchange.sandbox parameter was missing in the docs
2019-03-08 20:39:06 +01:00
hroff-1902
702153d087 exchange.sandbox parameter was missing in the docs 2019-03-08 22:24:55 +03:00
Misagh
8babf0d2b5 Merge pull request #1640 from hroff-1902/patch-18
Minor: typo in doc
2019-03-08 19:31:54 +01:00
hroff-1902
9c1d4183fd typo in doc 2019-03-08 20:18:45 +03:00
Matthias
c4992bd5f3 Merge pull request #1638 from freqtrade/pyup/scheduled-update-2019-03-08
Scheduled daily dependency update on Friday
2019-03-08 14:21:40 +01:00
pyup-bot
2da0d479e7 Update ccxt from 1.18.345 to 1.18.347 2019-03-08 13:33:06 +01:00
Matthias
628d9577a2 Merge pull request #1634 from freqtrade/pyup/scheduled-update-2019-03-07
Scheduled daily dependency update on Thursday
2019-03-07 14:08:36 +01:00
pyup-bot
6b2f4b12fd Update ccxt from 1.18.342 to 1.18.345 2019-03-07 13:33:07 +01:00
Misagh
bc7688a69f Merge pull request #1631 from freqtrade/fix/backtest_sloe
Fix issue that backtest is broken when stoploss_on_exchange is on
2019-03-07 10:05:46 +01:00
iuvbio
7b901e180a update sql_cheatsheet 2019-03-06 21:37:52 +01:00
Matthias
e67ffd2d87 Fix issue that backtest is broken when stoploss_on_exchange is on 2019-03-06 19:55:34 +01:00
Matthias
045de94b49 Merge pull request #1627 from hroff-1902/patch-17
Remove deprecated --dynamic-whitelist from freqtrade.service
2019-03-06 19:09:37 +01:00
hroff-1902
8624d83be0 Remove deprecated --dynamic-whitelist from freqtrade.service 2019-03-06 20:55:40 +03:00
Matthias
0634e135df Merge pull request #1626 from freqtrade/pyup/scheduled-update-2019-03-06
Scheduled daily dependency update on Wednesday
2019-03-06 14:17:38 +01:00
pyup-bot
962cfc5eb9 Update ccxt from 1.18.333 to 1.18.342 2019-03-06 13:33:04 +01:00
Matthias
ca64d8a861 Merge pull request #1620 from hroff-1902/patch-16
Documentation cleanup
2019-03-06 06:17:04 +01:00
hroff-1902
35250eb230 one more typo fixed (by @xmatthias) 2019-03-06 01:21:38 +03:00
Misagh
5dd0a72a52 Merge pull request #1621 from freqtrade/pyup/scheduled-update-2019-03-05
Scheduled daily dependency update on Tuesday
2019-03-05 14:56:05 +01:00
pyup-bot
735e78f01d Update sqlalchemy from 1.2.18 to 1.3.0 2019-03-05 13:33:06 +01:00
pyup-bot
ae7c4c33c0 Update ccxt from 1.18.323 to 1.18.333 2019-03-05 13:33:05 +01:00
hroff-1902
c032dd0f45 new docs/deprecated.md added to the site menu 2019-03-05 14:29:55 +03:00
hroff-1902
ce46555e77 docs/configuration.md reviewed: formatting, wording, grammar, etc 2019-03-05 14:11:40 +03:00
hroff-1902
2f98dd0429 description for --dynamic-whitelist moved to new docs/deprecated.md 2019-03-05 14:09:26 +03:00
hroff-1902
71f5392f89 typo fixed 2019-03-05 12:44:06 +03:00
Misagh
6d63b8e71e Merge pull request #1615 from freqtrade/fix/hyperopt_peram
Update documentation for --customhyperopt
2019-03-05 09:40:05 +01:00
Matthias
f6ca97d1dc Update hyperopt doc to validate backtest results 2019-03-05 06:43:28 +01:00
Matthias
4e50ec81a0 Merge pull request #1618 from hroff-1902/patch-15
minor: doc update index.md
2019-03-05 06:22:36 +01:00
hroff-1902
386abc5eba minor: doc update index.md 2019-03-04 23:44:44 +03:00
Matthias
04ea6dac83 Merge pull request #1617 from freqtrade/pyup/scheduled-update-2019-03-04
Scheduled daily dependency update on Monday
2019-03-04 16:18:20 +01:00
pyup-bot
f16913a76d Update ccxt from 1.18.322 to 1.18.323 2019-03-04 13:32:05 +01:00
Matthias
03ff87d11c Merge pull request #1616 from hroff-1902/patch-14
How to use multiple configuration files
2019-03-04 12:14:26 +01:00
hroff-1902
460e0711c6 How to use multiple configuration files
Description of multiple config command line options added.

Concrete examples to the bot-configuration page (something like "Hiding your key and exchange secret") will follow.

Please review grammar, wording etc.
2019-03-04 11:05:12 +03:00
Matthias
b8eb3ecb1d Update hyperopts documentation to work and match the code 2019-03-04 07:24:49 +01:00
Matthias
2208a21a6c Update help strings 2019-03-04 07:24:41 +01:00
Matthias
2d0aca0d20 Move --customhyperopts to hyperopt section 2019-03-04 07:24:05 +01:00
Matthias
b7a558b951 Merge pull request #1596 from iuvbio/feature/volume-precision-pairlist
Feature/volume precision pairlist
2019-03-03 15:38:31 +01:00
Matthias
3c5deb9aaf Add test for precision_remove ...
BTT should not be in the list when that is enabled.
2019-03-03 15:31:48 +01:00
Matthias
4d64124eef Merge pull request #1613 from freqtrade/pyup/scheduled-update-2019-03-03
Scheduled daily dependency update on Sunday
2019-03-03 15:15:52 +01:00
iuvbio
e2cbb7e7da remove remnants markets and precisionlist 2019-03-03 13:41:51 +01:00
iuvbio
df79098adc update docs 2019-03-03 13:37:54 +01:00
pyup-bot
13ba5ba0db Update ccxt from 1.18.313 to 1.18.322 2019-03-03 13:32:03 +01:00
Misagh
2eb2ace539 Merge pull request #1605 from freqtrade/fix/1604
Add libssl-dev to fix #1604
2019-03-03 12:57:16 +01:00
iuvbio
064f6629ab delete separate pairlist 2019-03-03 00:35:25 +01:00
iuvbio
786244c0d3 Merge branch 'develop' into feature/volume-precision-pairlist 2019-03-02 18:55:40 +01:00
iuvbio
e1ae0d7e90 remove markets changes 2019-03-02 18:53:42 +01:00
iuvbio
c36fa0c7e2 add ticker argumet to get_target_bid 2019-03-02 17:24:48 +01:00
iuvbio
24c587518a add precision_filter 2019-03-02 17:24:28 +01:00
Matthias
4c1f2b2a5b Merge pull request #1612 from freqtrade/pyup/scheduled-update-2019-03-02
Scheduled daily dependency update on Saturday
2019-03-02 16:47:42 +01:00
pyup-bot
6bcfe65877 Update scikit-learn from 0.20.2 to 0.20.3 2019-03-02 13:32:04 +01:00
pyup-bot
28a70eba07 Update ccxt from 1.18.309 to 1.18.313 2019-03-02 13:32:03 +01:00
Matthias
285db2f40b Merge pull request #1611 from freqtrade/pyup/scheduled-update-2019-03-01
Scheduled daily dependency update on Friday
2019-03-01 14:10:20 +01:00
pyup-bot
0fc5445003 Update jsonschema from 3.0.0 to 3.0.1 2019-03-01 13:32:07 +01:00
pyup-bot
e8ea2e6f05 Update ccxt from 1.18.304 to 1.18.309 2019-03-01 13:32:06 +01:00
hroff-1902
b792f00553 exchange cleanup 2019-03-01 02:13:16 +03:00
hroff-1902
4df44d8b32 wallets cleanup 2019-03-01 01:26:29 +03:00
Matthias
58c296c1ff Merge pull request #1610 from freqtrade/pyup/scheduled-update-2019-02-28
Scheduled daily dependency update on Thursday
2019-02-28 16:34:22 +01:00
pyup-bot
13de66d559 Update ccxt from 1.18.297 to 1.18.304 2019-02-28 13:32:06 +01:00
Matthias
e5498ca20f Add libssl-dev to fix #1604 2019-02-27 17:51:00 +01:00
Misagh
0558b203fe Merge pull request #1603 from freqtrade/pyup/scheduled-update-2019-02-27
Scheduled daily dependency update on Wednesday
2019-02-27 14:33:34 +01:00
pyup-bot
38d09f9e78 Update numpy from 1.16.1 to 1.16.2 2019-02-27 13:32:05 +01:00
pyup-bot
768f62a24a Update ccxt from 1.18.296 to 1.18.297 2019-02-27 13:32:04 +01:00
Misagh
7e62a4a79c Merge pull request #1602 from hroff-1902/no-recursion-edge
[Minor] comments: removed mentioning recursion, typos, etc.
2019-02-27 11:50:29 +01:00
hroff-1902
761861f0b7 comments: removed mentioning recursion, typos, etc. 2019-02-27 13:35:06 +03:00
Misagh
4e291795a6 Merge pull request #1601 from hroff-1902/no-recursion-edge
eliminate recursion in Edge
2019-02-27 11:18:23 +01:00
Misagh
7fe9d9520a Merge pull request #1599 from freqtrade/remove_pairurl
Remove pairurl
2019-02-27 10:33:10 +01:00
hroff-1902
4c2961f0d9 eliminate recursion in _detect_next_stop_or_sell_point() 2019-02-27 06:31:27 +03:00
Matthias
ef26484153 Super() should not be called with parameters
source: https://realpython.com/python-super/
2019-02-26 21:01:50 +01:00
Matthias
79aac473b3 Remove market_url from tests 2019-02-26 19:27:28 +01:00
Matthias
5c3177cc79 Adapt documentation to remove market_url 2019-02-26 19:27:28 +01:00
Matthias
6c75b8a36a Remove pair market url 2019-02-26 19:27:28 +01:00
Matthias
ee0e381d65 Merge pull request #1595 from freqtrade/binance_subclass
Create binance Subclass and parametrize exchange-tests
2019-02-26 19:26:23 +01:00
Matthias
8cb7a7e7a5 Merge pull request #1598 from freqtrade/pyup/scheduled-update-2019-02-26
Scheduled daily dependency update on Tuesday
2019-02-26 15:46:02 +01:00
pyup-bot
bcf5b5fdcb Update flake8 from 3.7.6 to 3.7.7 2019-02-26 13:32:04 +01:00
pyup-bot
ef18ddd866 Update ccxt from 1.18.292 to 1.18.296 2019-02-26 13:32:03 +01:00
Misagh
cee4116b80 Merge pull request #1576 from hroff-1902/patch-10
Minor: code cleanup in _process()
2019-02-26 10:17:21 +01:00
Matthias
0c53bd6dd4 Complete refactor, moving query_trades to persistance as get_open_trades 2019-02-25 20:00:17 +01:00
Matthias
aff334fdd6 Merge pull request #1597 from freqtrade/pyup/scheduled-update-2019-02-25
Scheduled daily dependency update on Monday
2019-02-25 15:27:56 +01:00
pyup-bot
185bd1e53c Update ccxt from 1.18.290 to 1.18.292 2019-02-25 13:32:04 +01:00
Matthias
006635003e Fix small typos 2019-02-24 20:18:41 +01:00
Matthias
f2fd5205ef Fix typo 2019-02-24 20:13:38 +01:00
Matthias
31be4d2454 Add parametrized tests 2019-02-24 20:08:27 +01:00
Matthias
5c18346cd5 Add typehint to binance dict 2019-02-24 20:01:20 +01:00
Matthias
e0b634ba3b Parametrize exchanges and test multiple exchanges 2019-02-24 19:59:45 +01:00
Matthias
a05155cb75 Adapt failing test 2019-02-24 19:41:47 +01:00
Matthias
455b168366 add _ft_has to exchangeclass 2019-02-24 19:35:29 +01:00
Matthias
06f486a8eb Add binance exchange subclass 2019-02-24 19:30:05 +01:00
Misagh
42722b2873 Merge pull request #1593 from hroff-1902/patch-13
Edge doc file minor improvements, typos, formatting
2019-02-24 17:35:49 +01:00
Matthias
ecb5137dbe Merge pull request #1594 from freqtrade/pyup/scheduled-update-2019-02-24
Scheduled daily dependency update on Sunday
2019-02-24 13:50:54 +01:00
Matthias
2531961bf8 Merge pull request #1571 from hroff-1902/patch-9
multiple --config options
2019-02-24 13:50:39 +01:00
pyup-bot
417bf2c935 Update jsonschema from 2.6.0 to 3.0.0 2019-02-24 13:32:06 +01:00
pyup-bot
3673dba1e2 Update ccxt from 1.18.287 to 1.18.290 2019-02-24 13:32:05 +01:00
Matthias
9b288c6933 Add test to specifically test for merged dict 2019-02-24 13:29:22 +01:00
hroff-1902
5fac4f7b45 Edge doc file minor improvements, typos, formatting 2019-02-24 13:19:01 +03:00
Matthias
c7b6e19872 Merge pull request #1586 from iuvbio/order_creation
Order creation
2019-02-23 19:23:40 +01:00
Matthias
033e9e09fb Merge pull request #1592 from freqtrade/pyup/scheduled-update-2019-02-23
Scheduled daily dependency update on Saturday
2019-02-23 17:05:36 +01:00
iuvbio
3dcf3f8a82 Merge branch 'develop' into feature/volume-precision-pairlist 2019-02-23 16:28:37 +01:00
iuvbio
403ed48c3e rename _store_dry_order 2019-02-23 16:28:13 +01:00
iuvbio
4d797c9232 Merge branch 'develop' into order_creation 2019-02-23 16:21:52 +01:00
iuvbio
ec6794b9ba fix dry_orders 2019-02-23 16:03:15 +01:00
pyup-bot
634ce87bba Update ccxt from 1.18.281 to 1.18.287 2019-02-23 13:32:04 +01:00
iuvbio
98bca30dfb reorganize imports 2019-02-22 21:16:31 +01:00
iuvbio
cc0fae8e4e change < to <= 2019-02-22 21:13:08 +01:00
iuvbio
8d8da71f20 Merge branch 'develop' into feature/volume-precision-pairlist 2019-02-22 20:31:24 +01:00
iuvbio
9a097214a6 return complete dry_order in buy and sell 2019-02-22 19:22:48 +01:00
Matthias
619945b861 Merge pull request #1591 from hroff-1902/patch-12
minor: formatting math expression in FAQ
2019-02-22 19:08:33 +01:00
iuvbio
71774bce6f Merge branch 'develop' of https://github.com/freqtrade/freqtrade into order_creation 2019-02-22 19:02:31 +01:00
hroff-1902
9c54886f14 minor: formatting math expression in FAQ 2019-02-22 19:33:05 +03:00
Misagh
1252bacb7a Merge pull request #1590 from hroff-1902/patch-12
FAQ updated with question on Edge
2019-02-22 16:56:05 +01:00
hroff-1902
a1b00f9053 Edge question added; minor improvements (sections for Hyperopt and Edge) 2019-02-22 17:37:59 +03:00
Samuel Husso
e4369e06bc Merge pull request #1589 from freqtrade/pyup/scheduled-update-2019-02-22
Scheduled daily dependency update on Friday
2019-02-22 15:27:40 +02:00
pyup-bot
29b8b79732 Update ccxt from 1.18.280 to 1.18.281 2019-02-22 13:30:08 +01:00
Misagh
2c7c19dfb1 Merge pull request #1582 from freqtrade/move/exchange
Refactor exchange importing
2019-02-22 10:24:23 +01:00
iuvbio
b79d967371 add tests, further consolidate orders 2019-02-22 01:48:35 +01:00
iuvbio
69bb6ebaf6 fix comments 2019-02-21 22:43:15 +01:00
Matthias
57b2fb4645 Merge pull request #1584 from freqtrade/pyup/scheduled-update-2019-02-21
Scheduled daily dependency update on Thursday
2019-02-21 19:14:24 +01:00
iuvbio
bf5d2a68f5 Merge branch 'develop' into order_creation 2019-02-21 19:03:29 +01:00
pyup-bot
7738ebbc0f Update ccxt from 1.18.270 to 1.18.280 2019-02-21 13:31:05 +01:00
Matthias
6d7f788989 Merge pull request #1579 from freqtrade/version_bump_dev
Version bump develop to 0.18.2-dev
2019-02-21 12:32:45 +01:00
Matthias
be754244a3 Only resolve exchanges from correct location 2019-02-21 07:07:45 +01:00
Matthias
e0f426d863 Allow import freqtrade.exchange.* 2019-02-21 06:59:52 +01:00
Matthias
e987a915e8 Rename exchange file 2019-02-21 06:56:22 +01:00
Matthias
a79ff1c6c9 Merge pull request #1563 from iuvbio/kraken_support
Kraken support
2019-02-21 06:41:35 +01:00
Matthias
2dcb4134cc Merge branch 'develop' into pr/iuvbio/1563 2019-02-21 06:29:37 +01:00
Matthias
e309f75118 Merge pull request #1581 from hroff-1902/patch-11
Minor changes to exchange
2019-02-21 06:25:47 +01:00
Matthias
643402da1c Merge pull request #1580 from hroff-1902/patch-1
Minor: added amount_reserve_percent into config json-schema
2019-02-21 06:24:16 +01:00
iuvbio
a1d1abfffc Merge branch 'develop' into order_creation 2019-02-21 00:30:46 +01:00
iuvbio
b5758e67f9 order creation cleanup 2019-02-21 00:29:59 +01:00
hroff-1902
2851833726 added _now_is_time_to_refresh() 2019-02-21 01:20:24 +03:00
hroff-1902
c1ef6940b0 removed wrong comment: tuple is not created here 2019-02-21 00:47:18 +03:00
hroff-1902
2aba9c081c fixed typos in comments 2019-02-21 00:46:35 +03:00
hroff-1902
eb21170691 added amount_reserve_percent into config json-schema 2019-02-21 00:26:02 +03:00
Matthias
d9129cb9c5 Develop version bump to 0.18.2-dev 2019-02-20 21:07:54 +01:00
Matthias
4315c157c7 Move exception handling to resolver, add test 2019-02-20 20:13:23 +01:00
hroff-1902
da5bef501e cleanup 2019-02-20 17:55:20 +03:00
hroff-1902
4fbba98168 tests adjusted for multiple --config options 2019-02-20 17:54:20 +03:00
hroff-1902
87c82dea3d support for multiple --config in the download_backtest_data.py utility 2019-02-20 17:00:35 +03:00
hroff-1902
c08a2b6638 help message fixed 2019-02-20 16:23:09 +03:00
hroff-1902
7bc874c7fd comments adjusted 2019-02-20 16:12:17 +03:00
hroff-1902
fac0e4e603 more code cleanup in _process() 2019-02-20 16:01:56 +03:00
hroff-1902
199e3d2234 typo in a comment 2019-02-20 15:13:21 +03:00
hroff-1902
5906d37818 code cleanup in _process() 2019-02-20 15:12:04 +03:00
iuvbio
e495ffec78 align dry_run_orders 2019-02-20 02:38:16 +01:00
iuvbio
84ccb85184 Merge branch 'develop' into feature/volume-precision-pairlist 2019-02-20 01:03:03 +01:00
iuvbio
686949b258 Merge branch 'develop' into kraken_support 2019-02-20 00:52:10 +01:00
iuvbio
3e2f90a32a formatting 2019-02-19 22:27:20 +01:00
iuvbio
bb31e64752 add test_sell_kraken_trading_agreement 2019-02-19 21:56:20 +01:00
iuvbio
481cf02db9 add test and fix exchange_resolver 2019-02-19 19:15:22 +01:00
hroff-1902
2f225e2340 multiple --config options 2019-02-19 15:14:47 +03:00
iuvbio
eed1c2344d delete unnecessary arguments 2019-02-18 01:03:09 +01:00
iuvbio
4241caef95 changes to base and subclass 2019-02-17 23:34:15 +01:00
iuvbio
2103ae5fdf change rateLimit to 1000 2019-02-17 23:26:10 +01:00
iuvbio
6055906eb1 Merge branch 'develop' into feature/volume-precision-pairlist 2019-02-17 16:15:55 +01:00
iuvbio
e98edc1a1a Merge branch 'develop' into kraken_support 2019-02-17 16:15:24 +01:00
iuvbio
62382809b2 Merge branch 'develop' into feature/volume-precision-pairlist 2019-02-17 16:14:20 +01:00
iuvbio
0572336ff7 revert changes to history 2019-02-17 16:12:40 +01:00
iuvbio
d8feceebb5 fix type-hints 2019-02-17 15:54:22 +01:00
iuvbio
da4faacd6b flake8 2019-02-17 15:34:44 +01:00
iuvbio
39c28626aa remove error message to make pytest pass 2019-02-17 15:29:58 +01:00
iuvbio
5e8a7a03c3 correct time_in_force param 2019-02-17 15:26:33 +01:00
iuvbio
dd2522d8d0 Merge branch 'develop' into kraken_support 2019-02-17 15:21:14 +01:00
iuvbio
fe792882b5 load generic class if no subclass exists 2019-02-17 14:42:55 +01:00
iuvbio
d3ead2cd09 exchange import is not needed anymore 2019-02-17 04:25:39 +01:00
iuvbio
c879591f45 add exchange_resolver to resolver init 2019-02-17 04:22:24 +01:00
iuvbio
c315f63e4b use exchange_resolver in freqbot 2019-02-17 04:18:56 +01:00
iuvbio
2fb36b116d change variable names 2019-02-17 04:15:11 +01:00
iuvbio
ca388a9acf create exchange_resolver 2019-02-17 04:01:43 +01:00
iuvbio
32b02c9925 kraken subclass 2019-02-17 04:01:17 +01:00
iuvbio
54d5bce445 undo kraken specific changes 2019-02-17 03:59:40 +01:00
iuvbio
b7afcf3416 add VolumePrecisionPairList 2019-02-16 22:56:04 +01:00
iuvbio
8ed3658447 Merge branch 'develop' into kraken_support 2019-02-15 23:27:41 +01:00
Crypto God
3aa614b983 bump version 2019-02-15 22:51:09 +01:00
Crypto God
3953092edd output error message 2019-02-15 22:50:31 +01:00
Crypto God
ef5a0b9afc add Kraken specifics 2019-02-15 22:50:11 +01:00
154 changed files with 14714 additions and 6684 deletions

17
.dependabot/config.yml Normal file
View File

@@ -0,0 +1,17 @@
version: 1
update_configs:
- package_manager: "python"
directory: "/"
update_schedule: "weekly"
allowed_updates:
- match:
update_type: "all"
target_branch: "develop"
- package_manager: "docker"
directory: "/"
update_schedule: "daily"
allowed_updates:
- match:
update_type: "all"

View File

@@ -5,6 +5,7 @@ If it hasn't been reported, please create a new issue.
## Step 2: Describe your environment
* Operating system: ____
* Python Version: _____ (`python -V`)
* CCXT version: _____ (`pip freeze | grep ccxt`)
* Branch: Master | Develop

10
.gitignore vendored
View File

@@ -6,7 +6,10 @@ config*.json
.hyperopt
logfile.txt
hyperopt_trials.pickle
user_data/
user_data/*
!user_data/notebooks
user_data/notebooks/*
!user_data/notebooks/*example.ipynb
freqtrade-plot.html
freqtrade-profit-plot.html
@@ -80,7 +83,7 @@ docs/_build/
target/
# Jupyter Notebook
.ipynb_checkpoints
*.ipynb_checkpoints
# pyenv
.python-version
@@ -92,3 +95,6 @@ target/
.pytest_cache/
.mypy_cache/
#exceptions
!*.gitkeep

View File

@@ -1,33 +0,0 @@
# autogenerated pyup.io config file
# see https://pyup.io/docs/configuration/ for all available options
# configure updates globally
# default: all
# allowed: all, insecure, False
update: all
# configure dependency pinning globally
# default: True
# allowed: True, False
pin: True
schedule: "every day"
search: False
# Specify requirement files by hand, default is empty
# default: empty
# allowed: list
requirements:
- requirements.txt
- requirements-dev.txt
- requirements-plot.txt
# configure the branch prefix the bot is using
# default: pyup-
branch_prefix: pyup/
# allow to close stale PRs
# default: True
close_prs: True

View File

@@ -10,35 +10,38 @@ services:
env:
global:
- IMAGE_NAME=freqtradeorg/freqtrade
addons:
apt:
packages:
- libelf-dev
- libdw-dev
- binutils-dev
install:
- cd build_helpers && ./install_ta-lib.sh; cd ..
- export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
- pip install --upgrade pytest-random-order
- 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
- pip install -r requirements-dev.txt
- pip install -e .
jobs:
include:
- stage: tests
script:
- pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/
- pytest --random-order --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/
# Allow failure for coveralls
- coveralls || true
name: pytest
- script:
- cp config.json.example config.json
- python freqtrade/main.py --datadir freqtrade/tests/testdata backtesting
- freqtrade --datadir freqtrade/tests/testdata backtesting
name: backtest
- script:
- cp config.json.example config.json
- python freqtrade/main.py --datadir freqtrade/tests/testdata hyperopt -e 5
- freqtrade --datadir freqtrade/tests/testdata hyperopt -e 5
name: hyperopt
- script: flake8 freqtrade
- script: flake8 freqtrade scripts
name: flake8
- script: mypy freqtrade
- script:
# Test Documentation boxes -
# !!! <TYPE>: is not allowed!
- grep -Er '^!{3}\s\S+:' docs/*; test $? -ne 0
name: doc syntax
- script: mypy freqtrade scripts
name: mypy
- stage: docker
@@ -47,13 +50,10 @@ jobs:
- build_helpers/publish_docker.sh
name: "Build and test and push docker image"
after_success:
- coveralls
notifications:
slack:
secure: bKLXmOrx8e2aPZl7W8DA5BdPAXWGpI5UzST33oc1G/thegXcDVmHBTJrBs4sZak6bgAclQQrdZIsRd2eFYzHLalJEaw6pk7hoAw8SvLnZO0ZurWboz7qg2+aZZXfK4eKl/VUe4sM9M4e/qxjkK+yWG7Marg69c4v1ypF7ezUi1fPYILYw8u0paaiX0N5UX8XNlXy+PBlga2MxDjUY70MuajSZhPsY2pDUvYnMY1D/7XN3cFW0g+3O8zXjF0IF4q1Z/1ASQe+eYjKwPQacE+O8KDD+ZJYoTOFBAPllrtpO1jnOPFjNGf3JIbVMZw4bFjIL0mSQaiSUaUErbU3sFZ5Or79rF93XZ81V7uEZ55vD8KMfR2CB1cQJcZcj0v50BxLo0InkFqa0Y8Nra3sbpV4fV5Oe8pDmomPJrNFJnX6ULQhQ1gTCe0M5beKgVms5SITEpt4/Y0CmLUr6iHDT0CUiyMIRWAXdIgbGh1jfaWOMksybeRevlgDsIsNBjXmYI1Sw2ZZR2Eo2u4R6zyfyjOMLwYJ3vgq9IrACv2w5nmf0+oguMWHf6iWi2hiOqhlAN1W74+3HsYQcqnuM3LGOmuCnPprV1oGBqkPXjIFGpy21gNx4vHfO1noLUyJnMnlu2L7SSuN1CdLsnjJ1hVjpJjPfqB4nn8g12x87TqM1bOm+3Q=
cache:
pip: True
directories:
- /usr/local/lib
- $HOME/dependencies

View File

@@ -11,7 +11,7 @@ Few pointers for contributions:
- Create your PR against the `develop` branch, not `master`.
- New features need to contain unit tests and must be PEP8 conformant (max-line-length = 100).
If you are unsure, discuss the feature on our [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE)
If you are unsure, discuss the feature on our [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LWEyODBiNzkzNzcyNzU0MWYyYzE5NjIyOTQxMzBmMGUxOTIzM2YyN2Y4NWY1YTEwZDgwYTRmMzE2NmM5ZmY2MTg)
or in a [issue](https://github.com/freqtrade/freqtrade/issues) before a PR.
## Getting started

View File

@@ -1,7 +1,7 @@
FROM python:3.7.2-slim-stretch
FROM python:3.7.4-slim-stretch
RUN apt-get update \
&& apt-get -y install curl build-essential \
&& apt-get -y install curl build-essential libssl-dev \
&& apt-get clean \
&& pip install --upgrade pip
@@ -16,7 +16,7 @@ RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib*
ENV LD_LIBRARY_PATH /usr/local/lib
# Install dependencies
COPY requirements.txt /freqtrade/
COPY requirements.txt requirements-common.txt /freqtrade/
RUN pip install numpy --no-cache-dir \
&& pip install -r requirements.txt --no-cache-dir

40
Dockerfile.pi Normal file
View File

@@ -0,0 +1,40 @@
FROM balenalib/raspberrypi3-debian:stretch
RUN [ "cross-build-start" ]
RUN apt-get update \
&& apt-get -y install wget curl build-essential libssl-dev libffi-dev \
&& apt-get clean
# Prepare environment
RUN mkdir /freqtrade
WORKDIR /freqtrade
# Install TA-lib
COPY build_helpers/ta-lib-0.4.0-src.tar.gz /freqtrade/
RUN tar -xzf /freqtrade/ta-lib-0.4.0-src.tar.gz \
&& cd /freqtrade/ta-lib/ \
&& ./configure \
&& make \
&& make install \
&& rm /freqtrade/ta-lib-0.4.0-src.tar.gz
ENV LD_LIBRARY_PATH /usr/local/lib
# Install berryconda
RUN wget https://github.com/jjhelmus/berryconda/releases/download/v2.0.0/Berryconda3-2.0.0-Linux-armv7l.sh \
&& bash ./Berryconda3-2.0.0-Linux-armv7l.sh -b \
&& rm Berryconda3-2.0.0-Linux-armv7l.sh
# Install dependencies
COPY requirements-common.txt /freqtrade/
RUN ~/berryconda3/bin/conda install -y numpy pandas scipy \
&& ~/berryconda3/bin/pip install -r requirements-common.txt --no-cache-dir
# Install and execute
COPY . /freqtrade/
RUN ~/berryconda3/bin/pip install -e . --no-cache-dir
RUN [ "cross-build-end" ]
ENTRYPOINT ["/root/berryconda3/bin/python","./freqtrade/main.py"]

View File

@@ -3,4 +3,4 @@ FROM freqtradeorg/freqtrade:develop
RUN apt-get update \
&& apt-get -y install git \
&& apt-get clean \
&& pip install git+https://github.com/berlinguyinca/technical
&& pip install git+https://github.com/freqtrade/technical

View File

@@ -68,39 +68,40 @@ For any other type of installation please refer to [Installation doc](https://ww
### Bot commands
```
usage: main.py [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
[--strategy-path PATH] [--customhyperopt NAME]
[--dynamic-whitelist [INT]] [--db-url PATH]
usage: freqtrade [-h] [-v] [--logfile FILE] [--version] [-c PATH] [-d PATH]
[-s NAME] [--strategy-path PATH] [--dynamic-whitelist [INT]]
[--db-url PATH] [--sd-notify]
{backtesting,edge,hyperopt} ...
Free, open source crypto trading bot
positional arguments:
{backtesting,edge,hyperopt}
backtesting backtesting module
edge edge module
hyperopt hyperopt module
backtesting Backtesting module.
edge Edge module.
hyperopt Hyperopt module.
optional arguments:
-h, --help show this help message and exit
-v, --verbose verbose mode (-vv for more, -vvv to get all messages)
--version show program\'s version number and exit
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified
--version show program's version number and exit
-c PATH, --config PATH
specify configuration file (default: config.json)
Specify configuration file (default: None). Multiple
--config options may be used.
-d PATH, --datadir PATH
path to backtest data
Path to backtest data.
-s NAME, --strategy NAME
specify strategy class name (default: DefaultStrategy)
--strategy-path PATH specify additional strategy lookup path
--customhyperopt NAME
specify hyperopt class name (default:
DefaultHyperOpts)
Specify strategy class name (default:
DefaultStrategy).
--strategy-path PATH Specify additional strategy lookup path.
--dynamic-whitelist [INT]
dynamically generate and update whitelist based on 24h
BaseVolume (default: 20) DEPRECATED.
Dynamically generate and update whitelist based on 24h
BaseVolume (default: 20). DEPRECATED.
--db-url PATH Override trades database URL, this is useful if
dry_run is enabled or in custom deployments (default:
None)
None).
--sd-notify Notify systemd service manager.
```
### Telegram RPC commands
@@ -128,7 +129,6 @@ The project is currently setup in two main branches:
- `master` - This branch contains the latest stable release. The bot 'should' be stable on this branch, and is generally well tested.
- `feat/*` - These are feature branches, which are being worked on heavily. Please don't use these unless you want to test a specific feature.
## A note on Binance
For Binance, please add `"BNB/<STAKE>"` to your blacklist to avoid issues.
@@ -141,7 +141,7 @@ Accounts having BNB accounts use this to pay for fees - if your first trade happ
For any questions not covered by the documentation or for further
information about the bot, we encourage you to join our slack channel.
- [Click here to join Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE).
- [Click here to join Slack channel](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LWEyODBiNzkzNzcyNzU0MWYyYzE5NjIyOTQxMzBmMGUxOTIzM2YyN2Y4NWY1YTEwZDgwYTRmMzE2NmM5ZmY2MTg).
### [Bugs / Issues](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
@@ -172,7 +172,7 @@ to understand the requirements before sending your pull-requests.
Coding is not a neccessity to contribute - maybe start with improving our documentation?
Issues labeled [good first issue](https://github.com/freqtrade/freqtrade/labels/good%20first%20issue) can be good first contributions, and will help get you familiar with the codebase.
**Note** before starting any major new feature work, *please open an issue describing what you are planning to do* or talk to us on [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE). This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it.
**Note** before starting any major new feature work, *please open an issue describing what you are planning to do* or talk to us on [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LWEyODBiNzkzNzcyNzU0MWYyYzE5NjIyOTQxMzBmMGUxOTIzM2YyN2Y4NWY1YTEwZDgwYTRmMzE2NmM5ZmY2MTg). This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it.
**Important:** Always create your PR against the `develop` branch, not `master`.

View File

@@ -1,7 +1,11 @@
#!/usr/bin/env python3
import sys
import warnings
from freqtrade.main import main, set_loggers
set_loggers()
from freqtrade.main import main
warnings.warn(
"Deprecated - To continue to run the bot like this, please run `pip install -e .` again.",
DeprecationWarning)
main(sys.argv[1:])

View File

@@ -1,8 +1,14 @@
if [ ! -f "/usr/local/lib/libta_lib.a" ]; then
if [ -z "$1" ]; then
INSTALL_LOC=/usr/local
else
INSTALL_LOC=${1}
fi
echo "Installing to ${INSTALL_LOC}"
if [ ! -f "${INSTALL_LOC}/lib/libta_lib.a" ]; then
tar zxvf ta-lib-0.4.0-src.tar.gz
cd ta-lib \
&& sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h \
&& ./configure \
&& ./configure --prefix=${INSTALL_LOC}/ \
&& make \
&& which sudo && sudo make install || make install \
&& cd ..

View File

@@ -30,7 +30,8 @@
"secret": "your_exchange_secret",
"ccxt_config": {"enableRateLimit": true},
"ccxt_async_config": {
"enableRateLimit": false
"enableRateLimit": true,
"rateLimit": 500
},
"pair_whitelist": [
"ETH/BTC",

View File

@@ -11,8 +11,8 @@
"sell": 30
},
"bid_strategy": {
"ask_last_balance": 0.0,
"use_order_book": false,
"ask_last_balance": 0.0,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": false,
@@ -30,7 +30,8 @@
"secret": "your_exchange_secret",
"ccxt_config": {"enableRateLimit": true},
"ccxt_async_config": {
"enableRateLimit": false
"enableRateLimit": true,
"rateLimit": 200
},
"pair_whitelist": [
"AST/BTC",

View File

@@ -9,6 +9,7 @@
"trailing_stop": false,
"trailing_stop_positive": 0.005,
"trailing_stop_positive_offset": 0.0051,
"trailing_only_offset_is_reached": false,
"minimal_roi": {
"40": 0.0,
"30": 0.01,
@@ -21,8 +22,8 @@
"sell": 30
},
"bid_strategy": {
"ask_last_balance": 0.0,
"use_order_book": false,
"ask_last_balance": 0.0,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": false,
@@ -38,27 +39,31 @@
"buy": "limit",
"sell": "limit",
"stoploss": "market",
"stoploss_on_exchange": "false",
"stoploss_on_exchange": false,
"stoploss_on_exchange_interval": 60
},
"order_time_in_force": {
"buy": "gtc",
"sell": "gtc",
"sell": "gtc"
},
"pairlist": {
"method": "VolumePairList",
"config": {
"number_assets": 20,
"sort_key": "quoteVolume"
"sort_key": "quoteVolume",
"precision_filter": false
}
},
"exchange": {
"name": "bittrex",
"sandbox": false,
"key": "your_exchange_key",
"secret": "your_exchange_secret",
"password": "",
"ccxt_config": {"enableRateLimit": true},
"ccxt_async_config": {
"enableRateLimit": false,
"rateLimit": 500,
"aiohttp_trust_env": false
},
"pair_whitelist": [
@@ -76,7 +81,8 @@
"pair_blacklist": [
"DOGE/BTC"
],
"outdated_offset": 5
"outdated_offset": 5,
"markets_refresh_interval": 60
},
"edge": {
"enabled": false,
@@ -103,6 +109,13 @@
"token": "your_telegram_token",
"chat_id": "your_telegram_chat_id"
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"username": "freqtrader",
"password": "SuperSecurePassword"
},
"db_url": "sqlite:///tradesv3.sqlite",
"initial_state": "running",
"forcebuy_enable": false,
@@ -110,5 +123,5 @@
"process_throttle_secs": 5
},
"strategy": "DefaultStrategy",
"strategy_path": "/some/folder/"
"strategy_path": "user_data/strategies/"
}

View File

@@ -0,0 +1,70 @@
{
"max_open_trades": 5,
"stake_currency": "EUR",
"stake_amount": 10,
"fiat_display_currency": "EUR",
"ticker_interval" : "5m",
"dry_run": true,
"trailing_stop": false,
"unfilledtimeout": {
"buy": 10,
"sell": 30
},
"bid_strategy": {
"use_order_book": false,
"ask_last_balance": 0.0,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"ask_strategy":{
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 9
},
"exchange": {
"name": "kraken",
"key": "",
"secret": "",
"ccxt_config": {"enableRateLimit": true},
"ccxt_async_config": {
"enableRateLimit": true,
"rateLimit": 1000
},
"pair_whitelist": [
"ETH/EUR",
"BTC/EUR",
"BCH/EUR"
],
"pair_blacklist": [
]
},
"edge": {
"enabled": false,
"process_throttle_secs": 3600,
"calculate_since_number_of_days": 7,
"capital_available_percentage": 0.5,
"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": false,
"token": "your_telegram_token",
"chat_id": "your_telegram_chat_id"
},
"initial_state": "running",
"forcebuy_enable": false,
"internals": {
"process_throttle_secs": 5
}
}

View File

@@ -3,9 +3,43 @@
This page explains how to validate your strategy performance by using
Backtesting.
## Getting data for backtesting and hyperopt
To download data (candles / OHLCV) needed for backtesting and hyperoptimization use the `freqtrade download-data` command.
If no additional parameter is specified, freqtrade will download data for `"1m"` and `"5m"` timeframes.
Exchange and pairs will come from `config.json` (if specified using `-c/--config`). Otherwise `--exchange` becomes mandatory.
Alternatively, a `pairs.json` file can be used.
If you are using Binance for example:
- create a directory `user_data/data/binance` and copy `pairs.json` in that directory.
- update the `pairs.json` to contain the currency pairs you are interested in.
```bash
mkdir -p user_data/data/binance
cp freqtrade/tests/testdata/pairs.json user_data/data/binance
```
Then run:
```bash
freqtrade download-data --exchange binance
```
This will download ticker data for all the currency pairs you defined in `pairs.json`.
- 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 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 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.
## Test your strategy with Backtesting
Now you have good Buy and Sell strategies, you want to test it against
Now you have good Buy and Sell strategies and some historic data, you want to test it against
real data. This is what we call
[backtesting](https://en.wikipedia.org/wiki/Backtesting).
@@ -13,7 +47,7 @@ Backtesting will use the crypto-currencies (pair) from your config file
and load static tickers located in
[/freqtrade/tests/testdata](https://github.com/freqtrade/freqtrade/tree/develop/freqtrade/tests/testdata).
If the 5 min and 1 min ticker for the crypto-currencies to test is not
already in the `testdata` folder, backtesting will download them
already in the `testdata` directory, backtesting will download them
automatically. Testdata files will not be updated until you specify it.
The result of backtesting will confirm you if your bot has better odds of making a profit than a loss.
@@ -24,81 +58,52 @@ The backtesting is very easy with freqtrade.
#### With 5 min tickers (Per default)
```bash
python3 ./freqtrade/main.py backtesting
freqtrade backtesting
```
#### With 1 min tickers
```bash
python3 ./freqtrade/main.py backtesting --ticker-interval 1m
```
#### Update cached pairs with the latest data
```bash
python3 ./freqtrade/main.py backtesting --refresh-pairs-cached
```
#### With live data (do not alter your testdata files)
```bash
python3 ./freqtrade/main.py backtesting --live
freqtrade backtesting --ticker-interval 1m
```
#### Using a different on-disk ticker-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:
```bash
python3 ./freqtrade/main.py backtesting --datadir freqtrade/tests/testdata-20180101
freqtrade backtesting --datadir user_data/data/bittrex-20180101
```
#### With a (custom) strategy file
```bash
python3 ./freqtrade/main.py -s TestStrategy backtesting
freqtrade -s TestStrategy backtesting
```
Where `-s TestStrategy` refers to the class name within the strategy file `test_strategy.py` found in the `freqtrade/user_data/strategies` directory
Where `-s TestStrategy` refers to the class name within the strategy file `test_strategy.py` found in the `freqtrade/user_data/strategies` directory.
#### Comparing multiple Strategies
```bash
freqtrade backtesting --strategy-list TestStrategy1 AwesomeStrategy --ticker-interval 5m
```
Where `TestStrategy1` and `AwesomeStrategy` refer to class names of strategies.
#### Exporting trades to file
```bash
python3 ./freqtrade/main.py backtesting --export trades
freqtrade backtesting --export trades
```
The exported trades can be read using the following code for manual analysis, or can be used by the plotting script `plot_dataframe.py` in the scripts folder.
``` python
import json
from pathlib import Path
import pandas as pd
filename=Path('user_data/backtest_data/backtest-result.json')
with filename.open() as file:
data = json.load(file)
columns = ["pair", "profit", "opents", "closets", "index", "duration",
"open_rate", "close_rate", "open_at_end", "sell_reason"]
df = pd.DataFrame(data, columns=columns)
df['opents'] = pd.to_datetime(df['opents'],
unit='s',
utc=True,
infer_datetime_format=True
)
df['closets'] = pd.to_datetime(df['closets'],
unit='s',
utc=True,
infer_datetime_format=True
)
```
If you have some ideas for interesting / helpful backtest data analysis, feel free to submit a PR so the community can benefit from it.
The exported trades can be used for [further analysis](#further-backtest-result-analysis), or can be used by the plotting script `plot_dataframe.py` in the scripts directory.
#### Exporting trades to file specifying a custom filename
```bash
python3 ./freqtrade/main.py backtesting --export trades --export-filename=backtest_teststrategy.json
freqtrade backtesting --export trades --export-filename=backtest_teststrategy.json
```
#### Running backtest with smaller testset
@@ -109,7 +114,7 @@ you want to use. The last N ticks/timeframes will be used.
Example:
```bash
python3 ./freqtrade/main.py backtesting --timerange=-200
freqtrade backtesting --timerange=-200
```
#### Advanced use of timerange
@@ -129,36 +134,6 @@ The full timerange specification:
- Use tickframes between POSIX timestamps 1527595200 1527618600:
`--timerange=1527595200-1527618600`
#### Downloading new set of ticker data
To download new set of backtesting ticker data, you can use a download script.
If you are using Binance for example:
- create a folder `user_data/data/binance` and copy `pairs.json` in that folder.
- update the `pairs.json` to contain the currency pairs you are interested in.
```bash
mkdir -p user_data/data/binance
cp freqtrade/tests/testdata/pairs.json user_data/data/binance
```
Then run:
```bash
python scripts/download_backtest_data.py --exchange binance
```
This will download ticker data for all the currency pairs you defined in `pairs.json`.
- To use a different folder than the exchange specific default, use `--export user_data/data/some_directory`.
- To change the exchange used to download the tickers, use `--exchange`. Default is `bittrex`.
- To use `pairs.json` from some other folder, use `--pairs-file some_other_dir/pairs.json`.
- To download ticker data for only 10 days, use `--days 10`.
- Use `--timeframes` to specify which tickers to download. Default is `--timeframes 1m 5m` which will download 1-minute and 5-minute tickers.
For help about backtesting usage, please refer to [Backtesting commands](#backtesting-commands).
## Understand the backtesting result
The most important in the backtesting is to understand the result.
@@ -245,6 +220,12 @@ On the other hand, if you set a too high `minimal_roi` like `"0": 0.55`
profit. Hence, keep in mind that your performance is a mix of your
strategies, your configuration, and the crypto-currency you have set up.
### Further backtest-result analysis
To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file).
You can then load the trades to perform further analysis as shown in our [data analysis](data-analysis.md#backtesting) backtesting section.
## Backtesting multiple strategies
To backtest multiple strategies, a list of Strategies can be provided.
@@ -252,13 +233,13 @@ To backtest multiple strategies, a list of Strategies can be provided.
This is limited to 1 ticker-interval per run, however, data is only loaded once from disk so if you have multiple
strategies you'd like to compare, this should give a nice runtime boost.
All listed Strategies need to be in the same folder.
All listed Strategies need to be in the same directory.
``` bash
freqtrade backtesting --timerange 20180401-20180410 --ticker-interval 5m --strategy-list Strategy001 Strategy002 --export trades
```
This will save the results to `user_data/backtest_data/backtest-result-<strategy>.json`, injecting the strategy-name into the target filename.
This will save the results to `user_data/backtest_results/backtest-result-<strategy>.json`, injecting the strategy-name into the target filename.
There will be an additional table comparing win/losses of the different strategies (identical to the "Total" row in the first table).
Detailed output for all strategies one after the other will be available, so make sure to scroll up.

View File

@@ -2,54 +2,117 @@
This page explains the different parameters of the bot and how to run it.
!!! Note
If you've used `setup.sh`, don't forget to activate your virtual environment (`source .env/bin/activate`) before running freqtrade commands.
## Bot commands
```
usage: main.py [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
[--strategy-path PATH] [--customhyperopt NAME]
[--dynamic-whitelist [INT]] [--db-url PATH]
{backtesting,edge,hyperopt} ...
usage: freqtrade [-h] [-v] [--logfile FILE] [-V] [-c PATH] [-d PATH]
[--userdir PATH] [-s NAME] [--strategy-path PATH]
[--db-url PATH] [--sd-notify]
{backtesting,edge,hyperopt,create-userdir,list-exchanges} ...
Free, open source crypto trading bot
positional arguments:
{backtesting,edge,hyperopt}
backtesting backtesting module
edge edge module
hyperopt hyperopt module
{backtesting,edge,hyperopt,create-userdir,list-exchanges}
backtesting Backtesting module.
edge Edge module.
hyperopt Hyperopt module.
create-userdir Create user-data directory.
list-exchanges Print available exchanges.
optional arguments:
-h, --help show this help message and exit
-v, --verbose verbose mode (-vv for more, -vvv to get all messages)
--version show program\'s version number and exit
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified.
-V, --version show program's version number and exit
-c PATH, --config PATH
specify configuration file (default: config.json)
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 backtest data
Path to directory with historical backtesting data.
--userdir PATH, --user-data-dir PATH
Path to userdata directory.
-s NAME, --strategy NAME
specify strategy class name (default: DefaultStrategy)
--strategy-path PATH specify additional strategy lookup path
--customhyperopt NAME
specify hyperopt class name (default:
DefaultHyperOpts)
--dynamic-whitelist [INT]
dynamically generate and update whitelist based on 24h
BaseVolume (default: 20) DEPRECATED.
--db-url PATH Override trades database URL, this is useful if
dry_run is enabled or in custom deployments (default:
None)
Specify strategy class name (default:
`DefaultStrategy`).
--strategy-path PATH Specify additional strategy lookup path.
--db-url PATH Override trades database URL, this is useful in custom
deployments (default: `sqlite:///tradesv3.sqlite` for
Live Run mode, `sqlite://` for Dry Run).
--sd-notify Notify systemd service manager.
```
### How to use a different config file?
### How to specify which configuration file be used?
The bot allows you to select which config file you want to use. Per
default, the bot will load the file `./config.json`
The bot allows you to select which configuration file you want to use by means of
the `-c/--config` command line option:
```bash
python3 ./freqtrade/main.py -c path/far/far/away/config.json
freqtrade -c path/far/far/away/config.json
```
Per default, the bot loads the `config.json` configuration file from the current
working directory.
### How to use multiple configuration files?
The bot allows you to use multiple configuration files by specifying multiple
`-c/--config` options in the command line. Configuration parameters
defined in the latter configuration files override parameters with the same name
defined in the previous configuration files specified in the command line earlier.
For example, you can make a separate configuration file with your key and secrete
for the Exchange you use for trading, specify default configuration file with
empty key and secrete values while running in the Dry Mode (which does not actually
require them):
```bash
freqtrade -c ./config.json
```
and specify both configuration files when running in the normal Live Trade Mode:
```bash
freqtrade -c ./config.json -c path/to/secrets/keys.config.json
```
This could help you hide your private Exchange key and Exchange secrete on you local machine
by setting appropriate file permissions for the file which contains actual secrets and, additionally,
prevent unintended disclosure of sensitive private data when you publish examples
of your configuration in the project issues or in the Internet.
See more details on this technique with examples in the documentation page on
[configuration](configuration.md).
### Where to store custom data
Freqtrade allows the creation of a user-data directory using `freqtrade create-userdir --userdir someDirectory`.
This directory will look as follows:
```
user_data/
├── backtest_results
├── data
├── hyperopts
├── hyperopts_results
├── plot
└── strategies
```
You can add the entry "user_data_dir" setting to your configuration, to always point your bot to this directory.
Alternatively, pass in `--userdir` to every command.
The bot will fail to start if the directory does not exist, but will create necessary subdirectories.
This directory should contain your custom strategies, custom hyperopts and hyperopt loss functions, backtesting historical data (downloaded using either backtesting command or the download script) and plot outputs.
It is recommended to use version control to keep track of changes to your strategies.
### How to use **--strategy**?
This parameter will allow you to load your custom strategy class.
@@ -65,54 +128,29 @@ In `user_data/strategies` you have a file `my_awesome_strategy.py` which has
a strategy class called `AwesomeStrategy` to load it:
```bash
python3 ./freqtrade/main.py --strategy AwesomeStrategy
freqtrade --strategy AwesomeStrategy
```
If the bot does not find your strategy file, it will display in an error
message the reason (File not found, or errors in your code).
Learn more about strategy file in [optimize your bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md).
Learn more about strategy file in
[Strategy Customization](strategy-customization.md).
### How to use **--strategy-path**?
This parameter allows you to add an additional strategy lookup path, which gets
checked before the default locations (The passed path must be a folder!):
checked before the default locations (The passed path must be a directory!):
```bash
python3 ./freqtrade/main.py --strategy AwesomeStrategy --strategy-path /some/folder
freqtrade --strategy AwesomeStrategy --strategy-path /some/directory
```
#### How to install a strategy?
This is very simple. Copy paste your strategy file into the folder
This is very simple. Copy paste your strategy file into the directory
`user_data/strategies` or use `--strategy-path`. And voila, the bot is ready to use it.
### How to use **--dynamic-whitelist**?
!!! danger "DEPRECATED"
Dynamic-whitelist is deprecated. Please move your configurations to the configuration as outlined [here](/configuration/#dynamic-pairlists)
Per default `--dynamic-whitelist` will retrieve the 20 currencies based
on BaseVolume. This value can be changed when you run the script.
**By Default**
Get the 20 currencies based on BaseVolume.
```bash
python3 ./freqtrade/main.py --dynamic-whitelist
```
**Customize the number of currencies to retrieve**
Get the 30 currencies based on BaseVolume.
```bash
python3 ./freqtrade/main.py --dynamic-whitelist 30
```
**Exception**
`--dynamic-whitelist` must be greater than 0. If you enter 0 or a
negative value (e.g -2), `--dynamic-whitelist` will use the default
value (20).
### How to use **--db-url**?
When you run the bot in Dry-run mode, per default no transactions are
@@ -121,7 +159,7 @@ using `--db-url`. This can also be used to specify a custom database
in production mode. Example command:
```bash
python3 ./freqtrade/main.py -c config.json --db-url sqlite:///tradesv3.dry_run.sqlite
freqtrade -c config.json --db-url sqlite:///tradesv3.dry_run.sqlite
```
## Backtesting commands
@@ -129,59 +167,56 @@ python3 ./freqtrade/main.py -c config.json --db-url sqlite:///tradesv3.dry_run.s
Backtesting also uses the config specified via `-c/--config`.
```
usage: main.py backtesting [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE]
[--eps] [--dmmp] [-l] [-r]
usage: freqtrade backtesting [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE]
[--max_open_trades MAX_OPEN_TRADES]
[--stake_amount STAKE_AMOUNT] [-r] [--eps] [--dmmp]
[-l]
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
[--export EXPORT] [--export-filename PATH]
optional arguments:
-h, --help show this help message and exit
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL
specify ticker interval (1m, 5m, 30m, 1h, 1d)
Specify ticker interval (1m, 5m, 30m, 1h, 1d).
--timerange TIMERANGE
specify what timerange of data to use.
Specify what timerange of data to use.
--max_open_trades MAX_OPEN_TRADES
Specify max_open_trades to use.
--stake_amount STAKE_AMOUNT
Specify stake_amount.
-r, --refresh-pairs-cached
Refresh the pairs files in tests/testdata with the
latest data from the exchange. Use it if you want to
run your optimization commands with up-to-date data.
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking)
stacking).
--dmmp, --disable-max-market-positions
Disable applying `max_open_trades` during backtest
(same as setting `max_open_trades` to a very high
number)
-l, --live using live data
-r, --refresh-pairs-cached
refresh the pairs files in tests/testdata with the
latest data from the exchange. Use it if you want to
run your backtesting with up-to-date data.
number).
--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]
Provide a commaseparated list of strategies to
Provide a space-separated list of strategies to
backtest Please note that ticker-interval needs to be
set either in config or via command line. When using
this together with --export trades, the strategy-name
is injected into the filename (so backtest-data.json
becomes backtest-data-DefaultStrategy.json
--export EXPORT export backtest results, argument are: trades Example
--export EXPORT Export backtest results, argument are: trades. Example
--export=trades
--export-filename PATH
Save backtest results to this filename requires
--export to be set as well Example --export-
filename=user_data/backtest_data/backtest_today.json
(default: user_data/backtest_data/backtest-
filename=user_data/backtest_results/backtest_today.json
(default: user_data/backtest_results/backtest-
result.json)
```
### How to use **--refresh-pairs-cached** parameter?
### Getting historic data for backtesting
The first time your run Backtesting, it will take the pairs you have
set in your config file and download data from Bittrex.
If for any reason you want to update your data set, you use
`--refresh-pairs-cached` to force Backtesting to update the data it has.
!!! Note
Use it only if you want to update your data set. You will not be able to come back to the previous version.
To test your strategy with latest data, we recommend continuing using
the parameter `-l` or `--live`.
The first time your run Backtesting, you will need to download some historic data first.
This can be accomplished by using `freqtrade download-data`.
Check the corresponding [help page section](backtesting.md#Getting-data-for-backtesting-and-hyperopt) for more details
## Hyperopt commands
@@ -189,65 +224,106 @@ To optimize your strategy, you can use hyperopt parameter hyperoptimization
to find optimal parameter values for your stategy.
```
usage: freqtrade hyperopt [-h] [-i TICKER_INTERVAL] [--eps] [--dmmp]
[--timerange TIMERANGE] [-e INT]
[-s {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...]]
usage: freqtrade hyperopt [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE]
[--max_open_trades INT]
[--stake_amount STAKE_AMOUNT] [-r]
[--customhyperopt NAME] [--hyperopt-path PATH]
[--eps] [-e INT]
[-s {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]]
[--dmmp] [--print-all] [--no-color] [-j JOBS]
[--random-state INT] [--min-trades INT] [--continue]
[--hyperopt-loss NAME]
optional arguments:
-h, --help show this help message and exit
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL
specify ticker interval (1m, 5m, 30m, 1h, 1d)
Specify ticker interval (`1m`, `5m`, `30m`, `1h`,
`1d`).
--timerange TIMERANGE
Specify what timerange of data to use.
--max_open_trades INT
Specify max_open_trades to use.
--stake_amount STAKE_AMOUNT
Specify stake_amount.
-r, --refresh-pairs-cached
Refresh the pairs files in tests/testdata with the
latest data from the exchange. Use it if you want to
run your optimization commands with up-to-date data.
--customhyperopt NAME
Specify hyperopt class name (default:
`DefaultHyperOpts`).
--hyperopt-path PATH Specify additional lookup path for Hyperopts and
Hyperopt Loss functions.
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking)
stacking).
-e INT, --epochs INT Specify number of epochs (default: 100).
-s {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...], --spaces {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]
Specify which parameters to hyperopt. Space-separated
list. Default: `all`.
--dmmp, --disable-max-market-positions
Disable applying `max_open_trades` during backtest
(same as setting `max_open_trades` to a very high
number)
--timerange TIMERANGE
specify what timerange of data to use.
--hyperopt PATH specify hyperopt file (default:
freqtrade/optimize/default_hyperopt.py)
-e INT, --epochs INT specify number of epochs (default: 100)
-s {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...], --spaces {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...]
Specify which parameters to hyperopt. Space separate
list. Default: all
number).
--print-all Print all results, not only the best ones.
--no-color Disable colorization of hyperopt results. May be
useful if you are redirecting output to a file.
-j JOBS, --job-workers JOBS
The number of concurrently running jobs for
hyperoptimization (hyperopt worker processes). If -1
(default), all CPUs are used, for -2, all CPUs but one
are used, etc. If 1 is given, no parallel computing
code is used at all.
--random-state INT Set random state to some positive integer for
reproducible hyperopt results.
--min-trades INT Set minimal desired number of trades for evaluations
in the hyperopt optimization path (default: 1).
--continue Continue hyperopt from previous runs. By default,
temporary files will be removed and hyperopt will
start from scratch.
--hyperopt-loss NAME Specify the class name of the hyperopt loss function
class (IHyperOptLoss). Different functions can
generate completely different results, since the
target for optimization is different. Built-in
Hyperopt-loss-functions are: DefaultHyperOptLoss,
OnlyProfitHyperOptLoss, SharpeHyperOptLoss.
(default: `DefaultHyperOptLoss`).
```
## Edge commands
To know your trade expectacny and winrate against historical data, you can use Edge.
To know your trade expectancy and winrate against historical data, you can use Edge.
```
usage: main.py edge [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] [-r]
usage: freqtrade edge [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE]
[--max_open_trades MAX_OPEN_TRADES]
[--stake_amount STAKE_AMOUNT] [-r]
[--stoplosses STOPLOSS_RANGE]
optional arguments:
-h, --help show this help message and exit
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL
specify ticker interval (1m, 5m, 30m, 1h, 1d)
Specify ticker interval (1m, 5m, 30m, 1h, 1d).
--timerange TIMERANGE
specify what timerange of data to use.
Specify what timerange of data to use.
--max_open_trades MAX_OPEN_TRADES
Specify max_open_trades to use.
--stake_amount STAKE_AMOUNT
Specify stake_amount.
-r, --refresh-pairs-cached
refresh the pairs files in tests/testdata with the
Refresh the pairs files in tests/testdata with the
latest data from the exchange. Use it if you want to
run your edge with up-to-date data.
run your optimization commands with up-to-date data.
--stoplosses STOPLOSS_RANGE
defines a range of stoploss against which edge will
assess the strategythe format is "min,max,step"
Defines a range of stoploss against which edge will
assess the strategy the format is "min,max,step"
(without any space).example:
--stoplosses=-0.01,-0.1,-0.001
```
To understand edge and how to read the results, please read the [edge documentation](edge.md).
## A parameter missing in the configuration?
All parameters for `main.py`, `backtesting`, `hyperopt` are referenced
in [misc.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/misc.py#L84)
## Next step
The optimal strategy of the bot will change with time depending of the market trends. The next step is to
[optimize your bot](bot-optimization.md).
[Strategy Customization](strategy-customization.md).

View File

@@ -1,31 +1,52 @@
# Configure the bot
This page explains how to configure your `config.json` file.
This page explains how to configure the bot.
## Setup config.json
## The Freqtrade configuration file
We recommend to copy and use the `config.json.example` as a template
The bot uses a set of configuration parameters during its operation that all together conform the bot configuration. It normally reads its configuration from a file (Freqtrade configuration file).
Per default, the bot loads configuration from the `config.json` file located in the current working directory.
You can change the name of the configuration file used by the bot with the `-c/--config` command line option.
In some advanced use cases, multiple configuration files can be specified and used by the bot or the bot can read its configuration parameters from the process standard input stream.
If you used the [Quick start](installation.md/#quick-start) method for installing
the bot, the installation script should have already created the default configuration file (`config.json`) for you.
If default configuration file is not created we recommend you to copy and use the `config.json.example` as a template
for your bot configuration.
The table below will list all configuration parameters.
The Freqtrade configuration file is to be written in the JSON format.
Mandatory Parameters are marked as **Required**.
Additionally to the standard JSON syntax, you may use one-line `// ...` and multi-line `/* ... */` comments in your configuration files and trailing commas in the lists of parameters.
Do not worry if you are not familiar with JSON format -- simply open the configuration file with an editor of your choice, make some changes to the parameters you need, save your changes and, finally, restart the bot or, if it was previously stopped, run it again with the changes you made to the configuration. The bot validates syntax of the configuration file at startup and will warn you if you made any errors editing it.
## Configuration parameters
The table below will list all configuration parameters available.
Mandatory parameters are marked as **Required**.
| Command | Default | Description |
|----------|---------|-------------|
| `max_open_trades` | 3 | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades)
| `stake_currency` | BTC | **Required.** Crypto-currency used for trading.
| `stake_amount` | 0.05 | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance.
| `stake_currency` | BTC | **Required.** Crypto-currency used for trading. [Strategy Override](#parameters-in-the-strategy).
| `stake_amount` | 0.05 | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance. [Strategy Override](#parameters-in-the-strategy).
| `amount_reserve_percent` | 0.05 | Reserve some amount in min pair stake amount. Default is 5%. The bot will reserve `amount_reserve_percent` + stop-loss value when calculating min pair stake amount in order to avoid possible trade refusals.
| `ticker_interval` | [1m, 5m, 30m, 1h, 1d] | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-strategy).
| `ticker_interval` | [1m, 5m, 15m, 30m, 1h, 1d, ...] | The ticker interval to use (1min, 5 min, 15 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-the-strategy).
| `fiat_display_currency` | USD | **Required.** Fiat currency used to show your profits. More information below.
| `dry_run` | true | **Required.** Define if the bot must be in Dry-run or production mode.
| `process_only_new_candles` | false | If set to true indicators are processed only once a new candle arrives. 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-strategy).
| `minimal_roi` | See below | Set the threshold in percent the bot will use to sell a trade. More information below. [Strategy Override](#parameters-in-strategy).
| `stoploss` | -0.10 | Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-strategy).
| `trailing_stop` | false | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-strategy).
| `trailing_stop_positive` | 0 | Changes stop-loss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-strategy).
| `trailing_stop_positive_offset` | 0 | 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-strategy).
| `dry_run_wallet` | 999.9 | Overrides the default amount of 999.9 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason.
| `process_only_new_candles` | false | If set to true indicators are processed only once a new candle arrives. 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).
| `minimal_roi` | See below | Set the threshold in percent the bot will use to sell a trade. More information below. [Strategy Override](#parameters-in-the-strategy).
| `stoploss` | -0.10 | Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
| `trailing_stop` | false | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
| `trailing_stop_positive` | 0 | Changes stop-loss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
| `trailing_stop_positive_offset` | 0 | 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).
| `trailing_only_offset_is_reached` | false | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
| `unfilledtimeout.buy` | 10 | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled.
| `unfilledtimeout.sell` | 10 | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled.
| `bid_strategy.ask_last_balance` | 0.0 | **Required.** Set the bidding price. More information [below](#understand-ask_last_balance).
@@ -36,21 +57,23 @@ Mandatory Parameters are marked as **Required**.
| `ask_strategy.use_order_book` | false | Allows selling of open traded pair using the rates in Order Book Asks.
| `ask_strategy.order_book_min` | 0 | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
| `ask_strategy.order_book_max` | 0 | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
| `order_types` | None | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-strategy).
| `order_time_in_force` | None | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-strategy).
| `exchange.name` | bittrex | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
| `exchange.key` | key | API key to use for the exchange. Only required when you are in production mode.
| `exchange.secret` | secret | API secret to use for the exchange. Only required when you are in production mode.
| `exchange.pair_whitelist` | [] | List of currency to use by the bot. Can be overrided with `--dynamic-whitelist` param.
| `exchange.pair_blacklist` | [] | List of currency the bot must avoid. Useful when using `--dynamic-whitelist` param.
| `exchange.ccxt_rate_limit` | True | DEPRECATED!! Have CCXT handle Exchange rate limits. Depending on the exchange, having this to false can lead to temporary bans from the exchange.
| `order_types` | None | 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).
| `order_time_in_force` | None | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy).
| `exchange.name` | | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
| `exchange.sandbox` | false | 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.
| `exchange.key` | '' | API key to use for the exchange. Only required when you are in production mode.
| `exchange.secret` | '' | API secret to use for the exchange. Only required when you are in production mode.
| `exchange.pair_whitelist` | [] | List of pairs to use by the bot for trading and to check for potential trades during backtesting. Can be overriden by dynamic pairlists (see [below](#dynamic-pairlists)).
| `exchange.pair_blacklist` | [] | List of pairs the bot must absolutely avoid for trading and backtesting. Can be overriden by dynamic pairlists (see [below](#dynamic-pairlists)).
| `exchange.ccxt_config` | None | 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)
| `exchange.ccxt_async_config` | None | 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)
| `exchange.markets_refresh_interval` | 60 | The interval in minutes in which markets are reloaded.
| `edge` | false | Please refer to [edge configuration document](edge.md) for detailed explanation.
| `experimental.use_sell_signal` | false | Use your sell strategy in addition of the `minimal_roi`. [Strategy Override](#parameters-in-strategy).
| `experimental.sell_profit_only` | false | Waits until you have made a positive profit before taking a sell decision. [Strategy Override](#parameters-in-strategy).
| `experimental.ignore_roi_if_buy_signal` | false | Does not sell if the buy-signal is still active. Takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-strategy).
| `pairlist.method` | StaticPairList | Use Static whitelist. [More information below](#dynamic-pairlists).
| `experimental.use_sell_signal` | false | Use your sell strategy in addition of the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy).
| `experimental.sell_profit_only` | false | Waits until you have made a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy).
| `experimental.ignore_roi_if_buy_signal` | false | Does not sell if the buy-signal is still active. Takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy).
| `experimental.block_bad_exchanges` | true | Block exchanges known to not work with freqtrade. Leave on default unless you want to test if that exchange works now.
| `pairlist.method` | StaticPairList | Use static or dynamic volume-based pairlist. [More information below](#dynamic-pairlists).
| `pairlist.config` | None | Additional configuration for dynamic pairlists. [More information below](#dynamic-pairlists).
| `telegram.enabled` | true | **Required.** Enable or not the usage of Telegram.
| `telegram.token` | token | Your Telegram bot token. Only required if `telegram.enabled` is `true`.
@@ -64,16 +87,21 @@ Mandatory Parameters are marked as **Required**.
| `initial_state` | running | Defines the initial application state. More information below.
| `forcebuy_enable` | false | Enables the RPC Commands to force a buy. More information below.
| `strategy` | DefaultStrategy | Defines Strategy class to use.
| `strategy_path` | null | Adds an additional strategy lookup path (must be a folder).
| `strategy_path` | null | Adds an additional strategy lookup path (must be a directory).
| `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second.
| `internals.sd_notify` | false | 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.
| `logfile` | | Specify Logfile. Uses a rolling strategy of 10 files, with 1Mb per file.
| `user_data_dir` | cwd()/user_data | Directory containing user data. Defaults to `./user_data/`.
### Parameters in strategy
### Parameters in the strategy
The following parameters can be set in either configuration or strategy.
Values in the configuration are always overwriting values set in the strategy.
The following parameters can be set in either configuration file or strategy.
Values set in the configuration file always overwrite values set in the strategy.
* `minimal_roi`
* `stake_currency`
* `stake_amount`
* `ticker_interval`
* `minimal_roi`
* `stoploss`
* `trailing_stop`
* `trailing_stop_positive`
@@ -87,7 +115,7 @@ Values in the configuration are always overwriting values set in the strategy.
### Understand stake_amount
`stake_amount` is an amount of crypto-currency your bot will use for each trade.
The `stake_amount` configuration parameter is an amount of crypto-currency your bot will use for each trade.
The minimal value is 0.0005. If there is not enough crypto-currency in
the account an exception is generated.
To allow the bot to trade all the available `stake_currency` in your account set
@@ -104,7 +132,7 @@ currency_balanse / (max_open_trades - current_open_trades)
### Understand minimal_roi
`minimal_roi` is a JSON object where the key is a duration
The `minimal_roi` configuration parameter is a JSON object where the key is a duration
in minutes and the value is the minimum ROI in percent.
See the example below:
@@ -117,89 +145,131 @@ See the example below:
},
```
Most of the strategy files already include the optimal `minimal_roi`
value. This parameter is optional. If you use it, it will take over the
Most of the strategy files already include the optimal `minimal_roi` value.
This parameter can be set in either Strategy or Configuration file. If you use it in the configuration file, it will override the
`minimal_roi` value from the strategy file.
If it is not set in either Strategy or Configuration, a default of 1000% `{"0": 10}` is used, and minimal roi is disabled unless your trade generates 1000% profit.
### Understand stoploss
`stoploss` is loss in percentage that should trigger a sale.
For example value `-0.10` will cause immediate sell if the
profit dips below -10% for a given trade. This parameter is optional.
Most of the strategy files already include the optimal `stoploss`
value. This parameter is optional. If you use it, it will take over the
`stoploss` value from the strategy file.
Go to the [stoploss documentation](stoploss.md) for more details.
### Understand trailing stoploss
Go to the [trailing stoploss Documentation](stoploss.md) for details on trailing stoploss.
Go to the [trailing stoploss Documentation](stoploss.md#trailing-stop-loss) for details on trailing stoploss.
### Understand initial_state
`initial_state` is an optional field that defines the initial application state.
The `initial_state` configuration parameter is an optional field that defines the initial application state.
Possible values are `running` or `stopped`. (default=`running`)
If the value is `stopped` the bot has to be started with `/start` first.
### Understand forcebuy_enable
`forcebuy_enable` enables the usage of forcebuy commands via Telegram.
The `forcebuy_enable` configuration parameter enables the usage of forcebuy commands via Telegram.
This is disabled for security reasons by default, and will show a warning message on startup if enabled.
You send `/forcebuy ETH/BTC` to the bot, who buys the pair and holds it until a regular sell-signal appears (ROI, stoploss, /forcesell).
For example, you can send `/forcebuy ETH/BTC` Telegram command when this feature if enabled to the bot,
who then buys the pair and holds it until a regular sell-signal (ROI, stoploss, /forcesell) appears.
This can be dangerous with some strategies, so use with care.
Can be dangerous with some strategies, so use with care
See [the telegram documentation](telegram-usage.md) for details on usage.
### Understand process_throttle_secs
`process_throttle_secs` is an optional field that defines in seconds how long the bot should wait
The `process_throttle_secs` configuration parameter is an optional field that defines in seconds how long the bot should wait
before asking the strategy if we should buy or a sell an asset. After each wait period, the strategy is asked again for
every opened trade wether or not we should sell, and for all the remaining pairs (either the dynamic list of pairs or
the static list of pairs) if we should buy.
### Understand ask_last_balance
`ask_last_balance` sets the bidding price. Value `0.0` will use `ask` price, `1.0` will
The `ask_last_balance` configuration parameter sets the bidding price. Value `0.0` will use `ask` price, `1.0` will
use the `last` price and values between those interpolate between ask and last
price. Using `ask` price will guarantee quick success in bid, but bot will also
end up paying more then would probably have been necessary.
### Understand order_types
`order_types` contains a dict mapping order-types to market-types as well as stoploss on or off exchange type and stoploss on exchange update interval in seconds. This allows to buy using limit orders, sell using limit-orders, and create stoploss orders using market. It also allows to set the stoploss "on exchange" which means stoploss order would be placed immediately once the buy order is fulfilled. In case stoploss on exchange and `trailing_stop` are both set, then the bot will use `stoploss_on_exchange_interval` to check it periodically and update it if necessary (e.x. in case of trailing stoploss).
This can be set in the configuration or in the strategy. Configuration overwrites strategy configurations.
The `order_types` configuration parameter contains a dict mapping order-types to
market-types as well as stoploss on or off exchange type and stoploss on exchange
update interval in seconds. This allows to buy using limit orders, sell using
limit-orders, and create stoploss orders using market. It also allows to set the
stoploss "on exchange" which means stoploss order would be placed immediately once
the buy order is fulfilled. In case stoploss on exchange and `trailing_stop` are
both set, then the bot will use `stoploss_on_exchange_interval` to check it periodically
and update it if necessary (e.x. in case of trailing stoploss).
This can be set in the configuration file or in the strategy.
Values set in the configuration file overwrites values set in the strategy.
If this is configured, all 4 values (`"buy"`, `"sell"`, `"stoploss"` and `"stoploss_on_exchange"`) need to be present, otherwise the bot warn about it and will fail to start.
The below is the default which is used if this is not configured in either Strategy or configuration.
If this is configured, all 4 values (`buy`, `sell`, `stoploss` and
`stoploss_on_exchange`) need to be present, otherwise the bot will warn about it and fail to start.
The below is the default which is used if this is not configured in either strategy or configuration file.
Syntax for Strategy:
```python
"order_types": {
order_types = {
"buy": "limit",
"sell": "limit",
"stoploss": "market",
"stoploss_on_exchange": False,
"stoploss_on_exchange_interval": 60
},
}
```
Configuration:
```json
"order_types": {
"buy": "limit",
"sell": "limit",
"stoploss": "market",
"stoploss_on_exchange": false,
"stoploss_on_exchange_interval": 60
}
```
!!! Note
Not all exchanges support "market" orders.
The following message will be shown if your exchange does not support market orders: `"Exchange <yourexchange> does not support market orders."`
The following message will be shown if your exchange does not support market orders:
`"Exchange <yourexchange> does not support market orders."`
!!! Note
stoploss on exchange interval is not mandatory. Do not change it's value if you are unsure of what you are doing. For more information about how stoploss works please read [the stoploss documentation](stoploss.md).
Stoploss on exchange interval is not mandatory. Do not change its value if you are
unsure of what you are doing. For more information about how stoploss works please
read [the stoploss documentation](stoploss.md).
!!! Note
In case of stoploss on exchange if the stoploss is cancelled manually then
the bot would recreate one.
### Understand order_time_in_force
`order_time_in_force` defines the policy by which the order is executed on the exchange. Three commonly used time in force are:<br/>
**GTC (Goog Till Canceled):**
This is most of the time the default time in force. It means the order will remain 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.<br/>
The `order_time_in_force` configuration parameter defines the policy by which the order
is executed on the exchange. Three commonly used time in force are:
**GTC (Good Till Canceled):**
This is most of the time the default time in force. It means the order will remain
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):**
It means if the order is not executed immediately AND fully then it is canceled by the exchange.<br/>
It means if the order is not executed immediately AND fully then it is canceled by the exchange.
**IOC (Immediate Or Canceled):**
It is the same as FOK (above) except it can be partially fulfilled. The remaining part is automatically cancelled by the exchange.
<br/>
`order_time_in_force` contains a dict buy and sell time in force policy. This can be set in the configuration or in the strategy. Configuration overwrites strategy configurations.<br/>
possible values are: `gtc` (default), `fok` or `ioc`.<br/>
It is the same as FOK (above) except it can be partially fulfilled. The remaining part
is automatically cancelled by the exchange.
The `order_time_in_force` parameter contains a dict with buy and sell time in force policy values.
This can be set in the configuration file or in the strategy.
Values set in the configuration file overwrites values set in the strategy.
The possible values are: `gtc` (default), `fok` or `ioc`.
``` python
"order_time_in_force": {
"buy": "gtc",
@@ -208,11 +278,12 @@ possible values are: `gtc` (default), `fok` or `ioc`.<br/>
```
!!! Warning
This is an ongoing work. For now it is supported only for binance and only for buy orders. Please don't change the default value unless you know what you are doing.
This is an ongoing work. For now it is supported only for binance and only for buy orders.
Please don't change the default value unless you know what you are doing.
### What values for exchange.name?
### Exchange configuration
Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports 115 cryptocurrency
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.
@@ -224,35 +295,84 @@ The bot was tested with the following exchanges:
Feel free to test other exchanges and submit your PR to improve the bot.
### What values for fiat_display_currency?
#### Sample exchange configuration
A exchange configuration for "binance" would look as follows:
```json
"exchange": {
"name": "binance",
"key": "your_exchange_key",
"secret": "your_exchange_secret",
"ccxt_config": {"enableRateLimit": true},
"ccxt_async_config": {
"enableRateLimit": true,
"rateLimit": 200
},
```
This configuration enables binance, as well as rate limiting to avoid bans from the exchange.
`"rateLimit": 200` defines a wait-event of 0.2s between each call. This can also be completely disabled by setting `"enableRateLimit"` to false.
!!! Note
Optimal settings for rate limiting depend on the exchange and the size of the whitelist, so an ideal parameter will vary on many other settings.
We try to provide sensible defaults per exchange where possible, if you encounter bans please make sure that `"enableRateLimit"` is enabled and increase the `"rateLimit"` parameter step by step.
#### Advanced FreqTrade Exchange configuration
Advanced options can be configured using the `_ft_has_params` setting, which will override Defaults and exchange-specific behaviours.
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):
```json
"exchange": {
"name": "kraken",
"_ft_has_params": {
"order_time_in_force": ["gtc", "fok"],
"ohlcv_candle_limit": 200
}
```
!!! Warning
Please make sure to fully understand the impacts of these settings before modifying them.
### What values can be used for fiat_display_currency?
The `fiat_display_currency` configuration parameter sets the base currency to use for the
conversion from coin to fiat in the bot Telegram reports.
The valid values are:
`fiat_display_currency` set the base currency to use for the conversion from coin to fiat in Telegram.
The valid values are:<br/>
```json
"AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD"
```
In addition to FIAT currencies, a range of cryto currencies are supported.
In addition to fiat currencies, a range of cryto currencies are supported.
The valid values are:
```json
"BTC", "ETH", "XRP", "LTC", "BCH", "USDT"
```
## Switch to dry-run mode
## Switch to Dry-run mode
We recommend starting the bot in dry-run mode to see how your bot will
behave and how is the performance of your strategy. In Dry-run mode the
We recommend starting the bot in the Dry-run mode to see how your bot will
behave and what is the performance of your strategy. In the Dry-run mode the
bot does not engage your money. It only runs a live simulation without
creating trades.
creating trades on the exchange.
1. Edit your `config.json` file
2. Switch dry-run to true and specify db_url for a persistent db
1. Edit your `config.json` configuration file.
2. Switch `dry-run` to `true` and specify `db_url` for a persistence database.
```json
"dry_run": true,
"db_url": "sqlite:///tradesv3.dryrun.sqlite",
```
3. Remove your Exchange API key (change them by fake api credentials)
3. Remove your Exchange API key and secrete (change them by empty values or fake credentials):
```json
"exchange": {
@@ -263,37 +383,45 @@ creating trades.
}
```
Once you will be happy with your bot performance, you can switch it to
production mode.
Once you will be happy with your bot performance running in the Dry-run mode,
you can switch it to production mode.
### Dynamic Pairlists
Dynamic pairlists select pairs for you based on the logic configured.
The bot runs against all pairs (with that stake) on the exchange, and a number of assets (`number_assets`) is selected based on the selected criteria.
The bot runs against all pairs (with that stake) on the exchange, and a number of assets
(`number_assets`) is selected based on the selected criteria.
By default, a Static Pairlist is used (configured as `"pair_whitelist"` under the `"exchange"` section of this configuration).
By default, the `StaticPairList` method is used.
The Pairlist method is configured as `pair_whitelist` parameter under the `exchange`
section of the configuration.
**Available Pairlist methods:**
* `"StaticPairList"`
* uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklist`
* `"VolumePairList"`
* Formerly available as `--dynamic-whitelist [<number_assets>]`
* Selects `number_assets` top pairs based on `sort_key`, which can be one of `askVolume`, `bidVolume` and `quoteVolume`, defaults to `quoteVolume`.
* `StaticPairList`
* It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklist`.
* `VolumePairList`
* It selects `number_assets` top pairs based on `sort_key`, which can be one of
`askVolume`, `bidVolume` and `quoteVolume`, defaults to `quoteVolume`.
* There is a possibility to filter low-value coins that would not allow setting a stop loss
(set `precision_filter` parameter to `true` for this).
Example:
```json
"pairlist": {
"method": "VolumePairList",
"config": {
"number_assets": 20,
"sort_key": "quoteVolume"
"sort_key": "quoteVolume",
"precision_filter": false
}
},
```
## Switch to production mode
In production mode, the bot will engage your money. Be careful a wrong
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.

202
docs/data-analysis.md Normal file
View File

@@ -0,0 +1,202 @@
# Analyzing bot data with Jupyter notebooks
You can analyze the results of backtests and trading history easily using Jupyter notebooks. Sample notebooks are located at `user_data/notebooks/`.
## Pro tips
* See [jupyter.org](https://jupyter.org/documentation) for usage instructions.
* Don't forget to start a Jupyter notebook server from within your conda or venv environment or use [nb_conda_kernels](https://github.com/Anaconda-Platform/nb_conda_kernels)*
* Copy the example notebook before use so your changes don't get clobbered with the next freqtrade update.
## Fine print
Some tasks don't work especially well in notebooks. For example, anything using asynchronous execution is a problem for Jupyter. Also, freqtrade's primary entry point is the shell cli, so using pure python in a notebook bypasses arguments that provide required objects and parameters to helper functions. You may need to set those values or create expected objects manually.
## Recommended workflow
| Task | Tool |
--- | ---
Bot operations | CLI
Repetitive tasks | Shell scripts
Data analysis & visualization | Notebook
1. Use the CLI to
* download historical data
* run a backtest
* run with real-time data
* export results
1. Collect these actions in shell scripts
* save complicated commands with arguments
* execute multi-step operations
* automate testing strategies and preparing data for analysis
1. Use a notebook to
* visualize data
* munge and plot to generate insights
## Example utility snippets
### Change directory to root
Jupyter notebooks execute from the notebook directory. The following snippet searches for the project root, so relative paths remain consistent.
```python
import os
from pathlib import Path
# Change directory
# Modify this cell to insure that the output shows the correct path.
# Define all paths relative to the project root shown in the cell output
project_root = "somedir/freqtrade"
i=0
try:
os.chdirdir(project_root)
assert Path('LICENSE').is_file()
except:
while i<4 and (not Path('LICENSE').is_file()):
os.chdir(Path(Path.cwd(), '../'))
i+=1
project_root = Path.cwd()
print(Path.cwd())
```
## Load existing objects into a Jupyter notebook
These examples assume that you have already generated data using the cli. They will allow you to drill deeper into your results, and perform analysis which otherwise would make the output very difficult to digest due to information overload.
### Load backtest results into a pandas dataframe
```python
from freqtrade.data.btanalysis import load_backtest_data
# Load backtest results
df = load_backtest_data("user_data/backtest_results/backtest-result.json")
# Show value-counts per pair
df.groupby("pair")["sell_reason"].value_counts()
```
### Load live trading results into a pandas dataframe
``` python
from freqtrade.data.btanalysis import load_trades_from_db
# Fetch trades from database
df = load_trades_from_db("sqlite:///tradesv3.sqlite")
# Display results
df.groupby("pair")["sell_reason"].value_counts()
```
### Load multiple configuration files
This option can be useful to inspect the results of passing in multiple configs
``` python
import json
from freqtrade.configuration import Configuration
# Load config from multiple files
config = Configuration.from_files(["config1.json", "config2.json"])
# Show the config in memory
print(json.dumps(config, indent=1))
```
### Load exchange data to a pandas dataframe
This loads candle data to a dataframe
```python
from pathlib import Path
from freqtrade.data.history import load_pair_history
# Load data using values passed to function
ticker_interval = "5m"
data_location = Path('user_data', 'data', 'bitrex')
pair = "BTC_USDT"
candles = load_pair_history(datadir=data_location,
ticker_interval=ticker_interval,
pair=pair)
# Confirm success
print(f"Loaded len(candles) rows of data for {pair} from {data_location}")
candles.head()
```
## Strategy debugging example
Debugging a strategy can be time-consuming. FreqTrade offers helper functions to visualize raw data.
### Define variables used in analyses
You can override strategy settings as demonstrated below.
```python
# Customize these according to your needs.
# Define some constants
ticker_interval = "5m"
# Name of the strategy class
strategy_name = 'TestStrategy'
# Path to user data
user_data_dir = 'user_data'
# Location of the strategy
strategy_location = Path(user_data_dir, 'strategies')
# Location of the data
data_location = Path(user_data_dir, 'data', 'binance')
# Pair to analyze - Only use one pair here
pair = "BTC_USDT"
```
### Load exchange data
```python
from pathlib import Path
from freqtrade.data.history import load_pair_history
# Load data using values set above
candles = load_pair_history(datadir=data_location,
ticker_interval=ticker_interval,
pair=pair)
# Confirm success
print(f"Loaded {len(candles)} rows of data for {pair} from {data_location}")
candles.head()
```
### Load and run strategy
* Rerun each time the strategy file is changed
```python
from freqtrade.resolvers import StrategyResolver
# Load strategy using values set above
strategy = StrategyResolver({'strategy': strategy_name,
'user_data_dir': user_data_dir,
'strategy_path': strategy_location}).strategy
# Generate buy/sell signals using strategy
df = strategy.analyze_ticker(candles, {'pair': pair})
```
### Display the trade details
* Note that using `data.tail()` is preferable to `data.head()` as most indicators have some "startup" data at the top of the dataframe.
* Some possible problems
* Columns with NaN values at the end of the dataframe
* Columns used in `crossed*()` functions with completely different units
* Comparison with full backtest
* having 200 buy signals as output for one pair from `analyze_ticker()` does not necessarily mean that 200 trades will be made during backtesting.
* Assuming you use only one condition such as, `df['rsi'] < 30` as buy condition, this will generate multiple "buy" signals for each pair in sequence (until rsi returns > 29). The bot will only buy on the first of these signals (and also only if a trade-slot ("max_open_trades") is still available), or on one of the middle signals, as soon as a "slot" becomes available.
```python
# Report results
print(f"Generated {df['buy'].sum()} buy signals")
data = df.set_index('date', drop=True)
data.tail()
```
Feel free to submit an issue or Pull Request enhancing this document if you would like to share ideas on how to best analyze the data.

28
docs/deprecated.md Normal file
View File

@@ -0,0 +1,28 @@
# Deprecated features
This page contains description of the command line arguments, configuration parameters
and the bot features that were declared as DEPRECATED by the bot development team
and are no longer supported. Please avoid their usage in your configuration.
## Deprecated
### the `--refresh-pairs-cached` command line option
`--refresh-pairs-cached` in the context of backtesting, hyperopt and edge allows to refresh candle data for backtesting.
Since this leads to much confusion, and slows down backtesting (while not being part of backtesting) this has been singled out
as a seperate freqtrade subcommand `freqtrade download-data`.
This command line option was deprecated in `2019.7-dev` and will be removed after the next release.
## Removed features
### The **--dynamic-whitelist** command line option
This command line option was deprecated in 2018 and removed freqtrade 2019.6-dev (develop branch)
and in freqtrade 2019.7 (master branch).
### the `--live` command line option
`--live` in the context of backtesting allowed to download the latest tick data for backtesting.
Did only download the latest 500 candles, so was ineffective in getting good backtest data.
Removed in 2019-7-dev (develop branch) and in freqtrade 2019-8 (master branch)

View File

@@ -2,7 +2,7 @@
This page is intended for developers of FreqTrade, people who want to contribute to the FreqTrade codebase or documentation, or people who want to understand the source code of the application they're running.
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/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE) where you can ask questions.
All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We [track issues](https://github.com/freqtrade/freqtrade/issues) on [GitHub](https://github.com) and also have a dev channel in [slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LWEyODBiNzkzNzcyNzU0MWYyYzE5NjIyOTQxMzBmMGUxOTIzM2YyN2Y4NWY1YTEwZDgwYTRmMzE2NmM5ZmY2MTg) where you can ask questions.
## Documentation
@@ -12,11 +12,34 @@ Special fields for the documentation (like Note boxes, ...) can be found [here](
## Developer setup
To configure a development environment, use best use the `setup.sh` script and answer "y" when asked "Do you want to install dependencies for dev [y/N]? ".
Alternatively (if your system is not supported by the setup.sh script), follow the manual installation process and run `pip3 install -r requirements-dev.txt`.
To configure a development environment, best use the `setup.sh` script and answer "y" when asked "Do you want to install dependencies for dev [y/N]? ".
Alternatively (if your system is not supported by the setup.sh script), follow the manual installation process and run `pip3 install -e .[all]`.
This will install all required tools for development, including `pytest`, `flake8`, `mypy`, and `coveralls`.
### Tests
New code should be covered by basic unittests. Depending on the complexity of the feature, Reviewers may request more in-depth unittests.
If necessary, the Freqtrade team can assist and give guidance with writing good tests (however please don't expect anyone to write the tests for you).
#### Checking log content in tests
Freqtrade uses 2 main methods to check log content in tests, `log_has()` and `log_has_re()` (to check using regex, in case of dynamic log-messages).
These are available from `conftest.py` and can be imported in any test module.
A sample check looks as follows:
``` python
from freqtrade.tests.conftest import log_has, log_has_re
def test_method_to_test(caplog):
method_to_test()
assert log_has("This event happened", caplog)
# Check regex with trailing number ...
assert log_has_re(r"This dynamic event happened and produced \d+", caplog)
```
## Modules
### Dynamic Pairlist
@@ -81,11 +104,56 @@ Please also run `self._validate_whitelist(pairs)` and to check and remove pairs
This is a simple method used by `VolumePairList` - however serves as a good example.
It implements caching (`@cached(TTLCache(maxsize=1, ttl=1800))`) as well as a configuration option to allow different (but similar) strategies to work with the same PairListProvider.
## 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.
Most exchanges supported by CCXT should work out of the box.
### Stoploss On Exchange
Check if the new exchange supports Stoploss on Exchange orders through their API.
Since CCXT does not provide unification for Stoploss On Exchange yet, we'll need to implement the exchange-specific parameters ourselfs. Best look at `binance.py` for an example implementation of this. You'll need to dig through the documentation of the Exchange's API on how exactly this can be done. [CCXT Issues](https://github.com/ccxt/ccxt/issues) may also provide great help, since others may have implemented something similar for their projects.
### Incomplete candles
While fetching OHLCV data, we're 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.
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
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)
print(df1["date"].tail(1))
print(datetime.utcnow())
```
``` output
19 2019-06-08 00:00:00+00:00
2019-06-09 12:30:27.873327
```
The output will show the last entry from the Exchange as well as the current UTC date.
If the day shows the same day, then the last candle can be assumed as incomplete and should be dropped (leave the setting `"ohlcv_partial_candle"` from the exchange-class untouched / True). Otherwise, set `"ohlcv_partial_candle"` to `False` to not drop Candles (shown in the example above).
## Creating a release
This part of the documentation is aimed at maintainers, and shows how to create a release.
### create release branch
### Create release branch
``` bash
# make sure you're in develop branch
@@ -95,11 +163,14 @@ git checkout develop
git checkout -b new_release
```
* edit `freqtrade/__init__.py` and add the desired version (for example `0.18.0`)
* 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.
* Commit this part
* push that branch to the remote and create a PR
* push that branch to the remote and create a PR against the master branch
### create changelog from git commits
### Create changelog from git commits
!!! Note
Make sure that both master and develop are up-todate!.
``` bash
# Needs to be done before merging / pulling that branch.
@@ -108,10 +179,14 @@ git log --oneline --no-decorate --no-merges master..develop
### Create github release / tag
Once the PR against master is merged (best right after merging):
* Use the button "Draft a new release" in the Github UI (subsection releases)
* Use the version-number specified as tag.
* Use "master" as reference (this step comes after the above PR is merged).
* use the above changelog as release comment (as codeblock)
* Use the above changelog as release comment (as codeblock)
### After-release
* update version in develop to next valid version and postfix that with `-dev` (`0.18.0 -> 0.18.1-dev`)
* Update version in develop by postfixing that with `-dev` (`2019.6 -> 2019.6-dev`).
* Create a PR against develop to update that branch.

208
docs/docker.md Normal file
View File

@@ -0,0 +1,208 @@
# Using FreqTrade with Docker
## Install Docker
Start by downloading and installing Docker CE for your platform:
* [Mac](https://docs.docker.com/docker-for-mac/install/)
* [Windows](https://docs.docker.com/docker-for-windows/install/)
* [Linux](https://docs.docker.com/install/)
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
Pull the image from docker hub.
Branches / tags available can be checked out on [Dockerhub](https://hub.docker.com/r/freqtradeorg/freqtrade/tags/).
```bash
docker pull freqtradeorg/freqtrade:develop
# Optionally tag the repository so the run-commands remain shorter
docker tag freqtradeorg/freqtrade:develop freqtrade
```
To update the image, simply run the above commands again and restart your running container.
Should you require additional libraries, please [build the image yourself](#build-your-own-docker-image).
!!! Note Docker image update frequency
The official docker images with tags `master`, `develop` and `latest` are automatically rebuild once a week to keep the base image uptodate.
In addition to that, every merge to `develop` will trigger a rebuild for `develop` and `latest`.
### Prepare the configuration files
Even though you will use docker, you'll still need some files from the github repository.
#### Clone the git repository
Linux/Mac/Windows with WSL
```bash
git clone https://github.com/freqtrade/freqtrade.git
```
Windows with docker
```bash
git clone --config core.autocrlf=input https://github.com/freqtrade/freqtrade.git
```
#### Copy `config.json.example` to `config.json`
```bash
cd freqtrade
cp -n config.json.example config.json
```
> To understand the configuration options, please refer to the [Bot Configuration](configuration.md) page.
#### Create your database file
Production
```bash
touch tradesv3.sqlite
````
Dry-Run
```bash
touch tradesv3.dryrun.sqlite
```
!!! Note
Make sure to use the path to this file when starting the bot in docker.
### Build your own Docker image
Best start by pulling the official docker image from dockerhub as explained [here](#download-the-official-docker-image) to speed up building.
To add additional libraries to your docker image, best check out [Dockerfile.technical](https://github.com/freqtrade/freqtrade/blob/develop/Dockerfile.technical) which adds the [technical](https://github.com/freqtrade/technical) module to the image.
```bash
docker build -t freqtrade -f Dockerfile.technical .
```
If you are developing using Docker, use `Dockerfile.develop` to build a dev Docker image, which will also set up develop dependencies:
```bash
docker build -f Dockerfile.develop -t freqtrade-dev .
```
!!! Note
For security reasons, your configuration file will not be included in the image, you will need to bind mount it. It is also advised to bind mount an SQLite database file (see the "5. Run a restartable docker image" section) to keep it between updates.
#### Verify the Docker image
After the build process you can verify that the image was created with:
```bash
docker images
```
The output should contain the freqtrade image.
### Run the Docker image
You can run a one-off container that is immediately deleted upon exiting with the following command (`config.json` must be in the current working directory):
```bash
docker run --rm -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
```
!!! Warning
In this example, the database will be created inside the docker instance and will be lost when you will refresh your image.
#### Adjust timezone
By default, the container will use UTC timezone.
Should you find this irritating please add the following to your docker commands:
##### Linux
``` bash
-v /etc/timezone:/etc/timezone:ro
# Complete command:
docker run --rm -v /etc/timezone:/etc/timezone:ro -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
```
##### MacOS
There is known issue in OSX Docker versions after 17.09.1, whereby `/etc/localtime` cannot be shared causing Docker to not start. A work-around for this is to start with the following cmd.
```bash
docker run --rm -e TZ=`ls -la /etc/localtime | cut -d/ -f8-9` -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
```
More information on this docker issue and work-around can be read [here](https://github.com/docker/for-mac/issues/2396).
### Run a restartable docker image
To run a restartable instance in the background (feel free to place your configuration and database files wherever it feels comfortable on your filesystem).
#### Move your config file and database
The following will assume that you place your configuration / database files to `~/.freqtrade`, which is a hidden directory in your home directory. Feel free to use a different directory and replace the directory in the upcomming commands.
```bash
mkdir ~/.freqtrade
mv config.json ~/.freqtrade
mv tradesv3.sqlite ~/.freqtrade
```
#### Run the docker image
```bash
docker run -d \
--name freqtrade \
-v ~/.freqtrade/config.json:/freqtrade/config.json \
-v ~/.freqtrade/user_data/:/freqtrade/user_data \
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
freqtrade --db-url sqlite:///tradesv3.sqlite --strategy MyAwesomeStrategy
```
!!! Note
db-url defaults to `sqlite:///tradesv3.sqlite` but it defaults to `sqlite://` if `dry_run=True` is being used.
To override this behaviour use a custom db-url value: i.e.: `--db-url sqlite:///tradesv3.dryrun.sqlite`
!!! Note
All available bot command line parameters can be added to the end of the `docker run` command.
### Monitor your Docker instance
You can use the following commands to monitor and manage your container:
```bash
docker logs freqtrade
docker logs -f freqtrade
docker restart freqtrade
docker stop freqtrade
docker start freqtrade
```
For more information on how to operate Docker, please refer to the [official Docker documentation](https://docs.docker.com/).
!!! Note
You do not need to rebuild the image for configuration changes, it will suffice to edit `config.json` and restart the container.
### Backtest with docker
The following assumes that the download/setup of the docker image have been completed successfully.
Also, backtest-data should be available at `~/.freqtrade/user_data/`.
```bash
docker run -d \
--name freqtrade \
-v /etc/localtime:/etc/localtime:ro \
-v ~/.freqtrade/config.json:/freqtrade/config.json \
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
-v ~/.freqtrade/user_data/:/freqtrade/user_data/ \
freqtrade --strategy AwsomelyProfitableStrategy backtesting
```
Head over to the [Backtesting Documentation](backtesting.md) for more details.
!!! Note
Additional bot command line parameters can be appended after the image name (`freqtrade` in the above example).

View File

@@ -3,165 +3,213 @@
This page explains how to use Edge Positioning module in your bot in order to enter into a trade only if the trade has a reasonable win rate and risk reward ratio, and consequently adjust your position size and stoploss.
!!! Warning
Edge positioning is not compatible with dynamic whitelist. it overrides dynamic whitelist.
Edge positioning is not compatible with dynamic (volume-based) whitelist.
!!! Note
Edge won't consider anything else than buy/sell/stoploss signals. So trailing stoploss, ROI, and everything else will be ignored in its calculation.
Edge does not consider anything else than buy/sell/stoploss signals. So trailing stoploss, ROI, and everything else are ignored in its calculation.
## Introduction
Trading is all about probability. No one can claim that he has a strategy working all the time. You have to assume that sometimes you lose.<br/><br/>
But it doesn't mean there is no rule, it only means rules should work "most of the time". Let's play a game: we toss a coin, heads: I give you 10$, tails: You give me 10$. Is it an interesting game ? no, it is quite boring, isn't it?<br/><br/>
But let's say the probability that we have heads is 80%, and the probability that we have tails is 20%. Now it is becoming interesting ...
That means 10$ x 80% versus 10$ x 20%. 8$ versus 2$. That means over time you will win 8$ risking only 2$ on each toss of coin.<br/><br/>
Let's complicate it more: you win 80% of the time but only 2$, I win 20% of the time but 8$. The calculation is: 80% * 2$ versus 20% * 8$. It is becoming boring again because overtime you win $1.6$ (80% x 2$) and me $1.6 (20% * 8$) too.<br/><br/>
The question is: How do you calculate that? how do you know if you wanna play?
Trading is all about probability. No one can claim that he has a strategy working all the time. You have to assume that sometimes you lose.
But it doesn't mean there is no rule, it only means rules should work "most of the time". Let's play a game: we toss a coin, heads: I give you 10$, tails: you give me 10$. Is it an interesting game? No, it's quite boring, isn't it?
But let's say the probability that we have heads is 80% (because our coin has the displaced distribution of mass or other defect), and the probability that we have tails is 20%. Now it is becoming interesting...
That means 10$ X 80% versus 10$ X 20%. 8$ versus 2$. That means over time you will win 8$ risking only 2$ on each toss of coin.
Let's complicate it more: you win 80% of the time but only 2$, I win 20% of the time but 8$. The calculation is: 80% X 2$ versus 20% X 8$. It is becoming boring again because overtime you win $1.6$ (80% X 2$) and me $1.6 (20% X 8$) too.
The question is: How do you calculate that? How do you know if you wanna play?
The answer comes to two factors:
- Win Rate
- Risk Reward Ratio
### Win Rate
Means over X trades what is the percentage of winning trades to total number of trades (note that we don't consider how much you gained but only If you won or not).
Win Rate (*W*) is is the mean over some amount of trades (*N*) what is the percentage of winning trades to total number of trades (note that we don't consider how much you gained but only if you won or not).
W = (Number of winning trades) / (Total number of trades) = (Number of winning trades) / N
`W = (Number of winning trades) / (Total number of trades)`
Complementary Loss Rate (*L*) is defined as
L = (Number of losing trades) / (Total number of trades) = (Number of losing trades) / N
or, which is the same, as
L = 1 W
### Risk Reward Ratio
Risk Reward Ratio is a formula used to measure the expected gains of a given investment against the risk of loss. It is basically what you potentially win divided by what you potentially lose:
Risk Reward Ratio (*R*) is a formula used to measure the expected gains of a given investment against the risk of loss. It is basically what you potentially win divided by what you potentially lose:
`R = Profit / Loss`
R = Profit / Loss
Over time, on many trades, you can calculate your risk reward by dividing your average profit on winning trades by your average loss on losing trades:
`Average profit = (Sum of profits) / (Number of winning trades)`
Average profit = (Sum of profits) / (Number of winning trades)
`Average loss = (Sum of losses) / (Number of losing trades)`
Average loss = (Sum of losses) / (Number of losing trades)
`R = (Average profit) / (Average loss)`
R = (Average profit) / (Average loss)
### Expectancy
At this point we can combine *W* and *R* to create an expectancy ratio. This is a simple process of multiplying the risk reward ratio by the percentage of winning trades and subtracting the percentage of losing trades, which is calculated as follows:
At this point we can combine W and R to create an expectancy ratio. This is a simple process of multiplying the risk reward ratio by the percentage of winning trades, and subtracting the percentage of losing trades, which is calculated as follows:
Expectancy Ratio = (Risk Reward Ratio x Win Rate) Loss Rate
Expectancy Ratio = (Risk Reward Ratio X Win Rate) Loss Rate = (R X W) L
So lets say your Win rate is 28% and your Risk Reward Ratio is 5:
`Expectancy = (5 * 0.28) - 0.72 = 0.68`
Expectancy = (5 X 0.28) 0.72 = 0.68
Superficially, this means that on average you expect this strategys trades to return .68 times the size of your losers. This is important for two reasons: First, it may seem obvious, but you know right away that you have a positive return. Second, you now have a number you can compare to other candidate systems to make decisions about which ones you employ.
Superficially, this means that on average you expect this strategys trades to return .68 times the size of your loses. This is important for two reasons: First, it may seem obvious, but you know right away that you have a positive return. Second, you now have a number you can compare to other candidate systems to make decisions about which ones you employ.
It is important to remember that any system with an expectancy greater than 0 is profitable using past data. The key is finding one that will be profitable in the future.
You can also use this number to evaluate the effectiveness of modifications to this system.
You can also use this value to evaluate the effectiveness of modifications to this system.
**NOTICE:** It's important to keep in mind that Edge is testing your expectancy using historical data , there's no guarantee that you will have a similar edge in the future. It's still vital to do this testing in order to build confidence in your methodology, but be wary of "curve-fitting" your approach to the historical data as things are unlikely to play out the exact same way for future trades.
**NOTICE:** It's important to keep in mind that Edge is testing your expectancy using historical data, there's no guarantee that you will have a similar edge in the future. It's still vital to do this testing in order to build confidence in your methodology, but be wary of "curve-fitting" your approach to the historical data as things are unlikely to play out the exact same way for future trades.
## How does it work?
If enabled in config, Edge will go through historical data with a range of stoplosses in order to find buy and sell/stoploss signals. It then calculates win rate and expectancy over X trades for each stoploss. Here is an example:
If enabled in config, Edge will go through historical data with a range of stoplosses in order to find buy and sell/stoploss signals. It then calculates win rate and expectancy over *N* trades for each stoploss. Here is an example:
| Pair | Stoploss | Win Rate | Risk Reward Ratio | Expectancy |
|----------|:-------------:|-------------:|------------------:|-----------:|
| XZC/ETH | -0.03 | 0.52 |1.359670 | 0.228 |
| XZC/ETH | -0.01 | 0.50 |1.176384 | 0.088 |
| XZC/ETH | -0.02 | 0.51 |1.115941 | 0.079 |
| XZC/ETH | -0.03 | 0.52 |1.359670 | 0.228 |
| XZC/ETH | -0.04 | 0.51 |1.234539 | 0.117 |
The goal here is to find the best stoploss for the strategy in order to have the maximum expectancy. In the above example stoploss at 3% leads to the maximum expectancy according to historical data.
Edge then forces stoploss to your strategy dynamically.
Edge module then forces stoploss value it evaluated to your strategy dynamically.
### Position size
Edge dictates the stake amount for each trade to the bot according to the following factors:
Edge also dictates the stake amount for each trade to the bot according to the following factors:
- Allowed capital at risk
- Stoploss
Allowed capital at risk is calculated as follows:
**allowed capital at risk** = **capital_available_percentage** X **allowed risk per trade**
Allowed capital at risk = (Capital available_percentage) X (Allowed risk per trade)
**Stoploss** is calculated as described above against historical data.
Stoploss is calculated as described above against historical data.
Your position size then will be:
**position size** = **allowed capital at risk** / **stoploss**
Position size = (Allowed capital at risk) / Stoploss
Example:<br/>
Let's say the stake currency is ETH and you have 10 ETH on the exchange, your **capital_available_percentage** is 50% and you would allow 1% of risk for each trade. thus your available capital for trading is **10 x 0.5 = 5 ETH** and allowed capital at risk would be **5 x 0.01 = 0.05 ETH**. <br/>
Let's assume Edge has calculated that for **XLM/ETH** market your stoploss should be at 2%. So your position size will be **0.05 / 0.02 = 2.5ETH**.<br/>
Bot takes a position of 2.5ETH on XLM/ETH (call it trade 1). Up next, you receive another buy signal while trade 1 is still open. This time on BTC/ETH market. Edge calculated stoploss for this market at 4%. So your position size would be 0.05 / 0.04 = 1.25ETH (call it trade 2).<br/>
Note that available capital for trading didnt change for trade 2 even if you had already trade 1. The available capital doesnt mean the free amount on your wallet.<br/>
Now you have two trades open. The Bot receives yet another buy signal for another market: **ADA/ETH**. This time the stoploss is calculated at 1%. So your position size is **0.05 / 0.01 = 5ETH**. But there are already 4ETH blocked in two previous trades. So the position size for this third trade would be 1ETH.<br/>
Available capital doesnt change before a position is sold. Lets assume that trade 1 receives a sell signal and it is sold with a profit of 1ETH. Your total capital on exchange would be 11 ETH and the available capital for trading becomes 5.5ETH. <br/>
So the Bot receives another buy signal for trade 4 with a stoploss at 2% then your position size would be **0.055 / 0.02 = 2.75**.
Example:
Let's say the stake currency is ETH and you have 10 ETH on the exchange, your capital available percentage is 50% and you would allow 1% of risk for each trade. thus your available capital for trading is **10 x 0.5 = 5 ETH** and allowed capital at risk would be **5 x 0.01 = 0.05 ETH**.
Let's assume Edge has calculated that for **XLM/ETH** market your stoploss should be at 2%. So your position size will be **0.05 / 0.02 = 2.5 ETH**.
Bot takes a position of 2.5 ETH on XLM/ETH (call it trade 1). Up next, you receive another buy signal while trade 1 is still open. This time on **BTC/ETH** market. Edge calculated stoploss for this market at 4%. So your position size would be 0.05 / 0.04 = 1.25 ETH (call it trade 2).
Note that available capital for trading didnt change for trade 2 even if you had already trade 1. The available capital doesnt mean the free amount on your wallet.
Now you have two trades open. The bot receives yet another buy signal for another market: **ADA/ETH**. This time the stoploss is calculated at 1%. So your position size is **0.05 / 0.01 = 5 ETH**. But there are already 3.75 ETH blocked in two previous trades. So the position size for this third trade would be **5 3.75 = 1.25 ETH**.
Available capital doesnt change before a position is sold. Lets assume that trade 1 receives a sell signal and it is sold with a profit of 1 ETH. Your total capital on exchange would be 11 ETH and the available capital for trading becomes 5.5 ETH.
So the Bot receives another buy signal for trade 4 with a stoploss at 2% then your position size would be **0.055 / 0.02 = 2.75 ETH**.
## Configurations
Edge has following configurations:
Edge module has following configuration options:
#### enabled
If true, then Edge will run periodically.<br/>
(default to false)
If true, then Edge will run periodically.
(defaults to false)
#### process_throttle_secs
How often should Edge run in seconds? <br/>
(default to 3600 so one hour)
How often should Edge run in seconds?
(defaults to 3600 so one hour)
#### calculate_since_number_of_days
Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy
Note that it downloads historical data so increasing this number would lead to slowing down the bot.<br/>
(default to 7)
Note that it downloads historical data so increasing this number would lead to slowing down the bot.
(defaults to 7)
#### capital_available_percentage
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/>
(default to 0.5)
This is the percentage of the total capital on exchange in stake currency.
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.
(defaults to 0.5)
#### allowed_risk
Percentage of allowed risk per trade.<br/>
(default to 0.01 [1%])
Percentage of allowed risk per trade.
(defaults to 0.01 so 1%)
#### stoploss_range_min
Minimum stoploss.<br/>
(default to -0.01)
Minimum stoploss.
(defaults to -0.01)
#### stoploss_range_max
Maximum stoploss.<br/>
(default to -0.10)
Maximum stoploss.
(defaults to -0.10)
#### 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.
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/>
(default to -0.01)
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.
Note than having a smaller step means having a bigger range which could lead to slow calculation.
If you set this parameter to -0.001, you then slow down the Edge calculation by a factor of 10.
(defaults to -0.01)
#### minimum_winrate
It filters pairs which don't have at least minimum_winrate.
This comes handy if you want to be conservative and don't comprise win rate in favor of risk reward ratio.<br/>
(default to 0.60)
It filters out pairs which don't have at least minimum_winrate.
This comes handy if you want to be conservative and don't comprise win rate in favour of risk reward ratio.
(defaults to 0.60)
#### minimum_expectancy
It filters paris which have an expectancy lower than this number .
Having an expectancy of 0.20 means if you put 10$ on a trade you expect a 12$ return.<br/>
(default to 0.20)
It filters out pairs which have the expectancy lower than this number.
Having an expectancy of 0.20 means if you put 10$ on a trade you expect a 12$ return.
(defaults to 0.20)
#### min_trade_number
When calculating W and 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. 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/>
(default to 10, it is highly recommended not to decrease this 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.
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.
(defaults to 10, it is highly recommended not to decrease this number)
#### 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/>
(default to 1 day, 1440 = 60 * 24)
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.
**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.).
(defaults to 1 day, i.e. to 60 * 24 = 1440 minutes)
#### 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/>
(default to false)
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.
(defaults to false)
## Running Edge independently
You can run Edge independently in order to see in details the result. Here is an example:
```bash
python3 ./freqtrade/main.py edge
freqtrade edge
```
An example of its output:
@@ -185,28 +233,30 @@ An example of its output:
| NEBL/BTC | -0.03 | 0.63 | 1.29 | 0.58 | 0.44 | 19 | 59 |
### Update cached pairs with the latest data
```bash
python3 ./freqtrade/main.py edge --refresh-pairs-cached
```
Edge requires historic data the same way as backtesting does.
Please refer to the [download section](backtesting.md#Getting-data-for-backtesting-and-hyperopt) of the documentation for details.
### Precising stoploss range
```bash
python3 ./freqtrade/main.py edge --stoplosses=-0.01,-0.1,-0.001 #min,max,step
freqtrade edge --stoplosses=-0.01,-0.1,-0.001 #min,max,step
```
### Advanced use of timerange
```bash
python3 ./freqtrade/main.py edge --timerange=20181110-20181113
freqtrade edge --timerange=20181110-20181113
```
Doing --timerange=-200 will get the last 200 timeframes from your inputdata. You can also specify specific dates, or a range span indexed by start and stop.
Doing `--timerange=-200` will get the last 200 timeframes from your inputdata. You can also specify specific dates, or a range span indexed by start and stop.
The full timerange specification:
* Use last 123 tickframes of data: --timerange=-123
* Use first 123 tickframes of data: --timerange=123-
* Use tickframes from line 123 through 456: --timerange=123-456
* Use tickframes till 2018/01/31: --timerange=-20180131
* Use tickframes since 2018/01/31: --timerange=20180131-
* Use tickframes since 2018/01/31 till 2018/03/01 : --timerange=20180131-20180301
* Use tickframes between POSIX timestamps 1527595200 1527618600: --timerange=1527595200-1527618600
* Use last 123 tickframes of data: `--timerange=-123`
* Use first 123 tickframes of data: `--timerange=123-`
* Use tickframes from line 123 through 456: `--timerange=123-456`
* Use tickframes till 2018/01/31: `--timerange=-20180131`
* Use tickframes since 2018/01/31: `--timerange=20180131-`
* Use tickframes since 2018/01/31 till 2018/03/01 : `--timerange=20180131-20180301`
* Use tickframes between POSIX timestamps 1527595200 1527618600: `--timerange=1527595200-1527618600`

View File

@@ -1,12 +1,25 @@
# freqtrade FAQ
# Freqtrade FAQ
#### I have waited 5 minutes, why hasn't the bot made any trades yet?!
## Freqtrade common issues
### The bot does not start
Running the bot with `freqtrade --config config.json` does show the output `freqtrade: command not found`.
This could have the following reasons:
* The virtual environment is not active
* run `source .env/bin/activate` to activate the virtual environment
* The installation did not work correctly.
* Please check the [Installation documentation](installation.md).
### I have waited 5 minutes, why hasn't the bot made any trades yet?!
Depending on the buy strategy, the amount of whitelisted coins, the
situation of the market etc, it can take up to hours to find good entry
position for a trade. Be patient!
#### I have made 12 trades already, why is my total profit negative?!
### I have made 12 trades already, why is my total profit negative?!
I understand your disappointment but unfortunately 12 trades is just
not enough to say anything. If you run backtesting, you can see that our
@@ -17,54 +30,82 @@ of course constantly aim to improve the bot but it will _always_ be a
gamble, which should leave you with modest wins on monthly basis but
you can't say much from few trades.
#### Id like to change the stake amount. Can I just stop the bot with
/stop and then change the config.json and run it again?
### Id like to change the stake amount. Can I just stop the bot with /stop and then change the config.json and run it again?
Not quite. Trades are persisted to a database but the configuration is
currently only read when the bot is killed and restarted. `/stop` more
like pauses. You can stop your bot, adjust settings and start it again.
#### I want to improve the bot with a new strategy
### I want to improve the bot with a new strategy
That's great. We have a nice backtesting and hyperoptimizing setup. See
the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-commands).
#### Is there a setting to only SELL the coins being held and not
perform anymore BUYS?
### Is there a setting to only SELL the coins being held and not perform anymore BUYS?
You can use the `/forcesell all` command from Telegram.
### I get the message "RESTRICTED_MARKET"
Currently known to happen for US Bittrex users.
Bittrex split its exchange into US and International versions.
The International version has more pairs available, however the API always returns all pairs, so there is currently no automated way to detect if you're affected by the restriction.
If you have restricted pairs in your whitelist, you'll get a warning message in the log on FreqTrade startup for each restricted pair.
If you're an "International" Customer on the Bittrex exchange, then this warning will probably not impact you.
If you're a US customer, the bot will fail to create orders for these pairs, and you should remove them from your Whitelist.
## Hyperopt module
### How many epoch do I need to get a good Hyperopt result?
Per default Hyperopts without `-e` or `--epochs` parameter will only
run 100 epochs, means 100 evals of your triggers, guards, .... Too few
run 100 epochs, means 100 evals of your triggers, guards, ... Too few
to find a great result (unless if you are very lucky), so you probably
have to run it for 10.000 or more. But it will take an eternity to
compute.
We recommend you to run it at least 10.000 epochs:
```bash
python3 ./freqtrade/main.py hyperopt -e 10000
freqtrade hyperopt -e 10000
```
or if you want intermediate result to see
```bash
for i in {1..100}; do python3 ./freqtrade/main.py hyperopt -e 100; done
for i in {1..100}; do freqtrade hyperopt -e 100; done
```
#### Why it is so long to run hyperopt?
### Why it is so long to run hyperopt?
Finding a great Hyperopt results takes time.
If you wonder why it takes a while to find great hyperopt results
This answer was written during the under the release 0.15.1, when we had
:
This answer was written during the under the release 0.15.1, when we had:
- 8 triggers
- 9 guards: let's say we evaluate even 10 values from each
- 1 stoploss calculation: let's say we want 10 values from that too to
be evaluated
- 1 stoploss calculation: let's say we want 10 values from that too to be evaluated
The following calculation is still very rough and not very precise
but it will give the idea. With only these triggers and guards there is
already 8*10^9*10 evaluations. A roughly total of 80 billion evals.
already 8\*10^9\*10 evaluations. A roughly total of 80 billion evals.
Did you run 100 000 evals? Congrats, you've done roughly 1 / 100 000 th
of the search space.
## Edge module
### Edge implements interesting approach for controlling position size, is there any theory behind it?
The Edge module is mostly a result of brainstorming of [@mishaker](https://github.com/mishaker) and [@creslinux](https://github.com/creslinux) freqtrade team members.
You can find further info on expectancy, winrate, risk management and position size in the following sources:
- https://www.tradeciety.com/ultimate-math-guide-for-traders/
- http://www.vantharp.com/tharp-concepts/expectancy.asp
- https://samuraitradingacademy.com/trading-expectancy/
- https://www.learningmarkets.com/determining-expectancy-in-your-trading/
- http://www.lonestocktrader.com/make-money-trading-positive-expectancy/
- https://www.babypips.com/trading/trade-expectancy-matter

View File

@@ -12,29 +12,34 @@ and still take a long time.
## Prepare Hyperopting
Before we start digging into Hyperopt, we recommend you to take a look at
an example hyperopt file located into [user_data/hyperopts/](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/test_hyperopt.py)
an example hyperopt file located into [user_data/hyperopts/](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt.py)
Configuring hyperopt is similar to writing your own strategy, and many tasks will be similar and a lot of code can be copied across from the strategy.
### Checklist on all tasks / possibilities in hyperopt
Depending on the space you want to optimize, only some of the below are required.
Depending on the space you want to optimize, only some of the below are required:
* fill `populate_indicators` - probably a copy from your strategy
* fill `buy_strategy_generator` - for buy signal optimization
* fill `indicator_space` - for buy signal optimzation
* fill `sell_strategy_generator` - for sell signal optimization
* fill `sell_indicator_space` - for sell signal optimzation
* fill `roi_space` - for ROI optimization
* fill `generate_roi_table` - for ROI optimization (if you need more than 3 entries)
* fill `stoploss_space` - stoploss optimization
* Optional but recommended
* copy `populate_buy_trend` from your strategy - otherwise default-strategy will be used
* copy `populate_sell_trend` from your strategy - otherwise default-strategy will be used
Optional, but recommended:
* copy `populate_buy_trend` from your strategy - otherwise default-strategy will be used
* copy `populate_sell_trend` from your strategy - otherwise default-strategy will be used
Rarely you may also need to override:
* `roi_space` - for custom ROI optimization (if you need the ranges for the ROI parameters in the optimization hyperspace that differ from default)
* `generate_roi_table` - for custom ROI optimization (if you need more than 4 entries in the ROI table)
* `stoploss_space` - for custom stoploss optimization (if you need the range for the stoploss parameter in the optimization hyperspace that differs from default)
### 1. Install a Custom Hyperopt File
Put your hyperopt file into the folder`user_data/hyperopts`.
Put your hyperopt file into the directory `user_data/hyperopts`.
Let assume you want a hyperopt file `awesome_hyperopt.py`:
Copy the file `user_data/hyperopts/sample_hyperopt.py` into `user_data/hyperopts/awesome_hyperopt.py`
@@ -71,6 +76,11 @@ 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
The Strategy exposes the ticker-interval as `self.ticker_interval`. The same value is available as class-attribute `HyperoptName.ticker_interval`.
In the case of the linked sample-value this would be `SampleHyperOpts.ticker_interval`.
## Solving a Mystery
Let's say you are curious: should you use MACD crossings or lower Bollinger
@@ -122,6 +132,7 @@ So let's write the buy strategy using these values:
dataframe['macd'], dataframe['macdsignal']
))
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
@@ -138,21 +149,94 @@ it will end with telling you which paramter combination produced the best profit
The search for best parameters starts with a few random combinations and then uses a
regressor algorithm (currently ExtraTreesRegressor) to quickly find a parameter combination
that minimizes the value of the objective function `calculate_loss` in `hyperopt.py`.
that minimizes the value of the [loss function](#loss-functions).
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
add it to the `populate_indicators()` method in `hyperopt.py`.
## Loss-functions
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.
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.
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)
### 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.
For the sample below, you then need to add the command line parameter `--hyperopt-loss SuperDuperHyperOptLoss` to your hyperopt call so this fuction is being used.
A sample of this can be found below, which is identical to the Default Hyperopt loss implementation. A full sample can be found [user_data/hyperopts/](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/sample_hyperopt_loss.py)
``` python
from freqtrade.optimize.hyperopt import IHyperOptLoss
TARGET_TRADES = 600
EXPECTED_MAX_PROFIT = 3.0
MAX_ACCEPTED_TRADE_DURATION = 300
class SuperDuperHyperOptLoss(IHyperOptLoss):
"""
Defines the default loss function for hyperopt
"""
@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 better results
This is the legacy algorithm (used until now in freqtrade).
Weights are distributed as follows:
* 0.4 to trade duration
* 0.25: Avoiding trade loss
* 1.0 to total profit, compared to the expected value (`EXPECTED_MAX_PROFIT`) defined above
"""
total_profit = results.profit_percent.sum()
trade_duration = results.trade_duration.mean()
trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)
profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT)
duration_loss = 0.4 * min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1)
result = trade_loss + profit_loss + duration_loss
return result
```
Currently, the arguments are:
* `results`: DataFrame containing the result
The following columns are available in results (corresponds to the output-file of backtesting when used with `--export trades`):
`pair, profit_percent, profit_abs, open_time, close_time, open_index, close_index, trade_duration, open_at_end, open_rate, close_rate, sell_reason`
* `trade_count`: Amount of trades (identical to `len(results)`)
* `min_date`: Start date of the hyperopting TimeFrame
* `min_date`: End date of the hyperopting TimeFrame
This function needs to return a floating point number (`float`). Smaller numbers will be interpreted as better results. The parameters and balancing for this is up to you.
!!! Note
This function is called once per iteration - so please make sure to have this as optimized as possible to not slow hyperopt down unnecessarily.
!!! Note
Please keep the arguments `*args` and `**kwargs` in the interface to allow us to extend this interface later.
## Execute Hyperopt
Once you have updated your hyperopt configuration you can run it.
Because hyperopt tries a lot of combinations to find the best parameters it will take time you will have the result (more than 30 mins).
Because hyperopt tries a lot of combinations to find the best parameters it will take time to get a good result. More time usually results in better results.
We strongly recommend to use `screen` or `tmux` to prevent any connection loss.
```bash
python3 ./freqtrade/main.py --hyperopt <hyperoptname> -c config.json hyperopt -e 5000 --spaces all
freqtrade -c config.json hyperopt --customhyperopt <hyperoptname> -e 5000 --spaces all
```
Use `<hyperoptname>` as the name of the custom hyperopt used.
@@ -162,8 +246,11 @@ running at least several thousand evaluations.
The `--spaces all` flag determines that all possible parameters should be optimized. Possibilities are listed below.
!!! Note
By default, hyperopt will erase previous results and start from scratch. Continuation can be archived by using `--continue`.
!!! Warning
When switching parameters or changing configuration options, the file `user_data/hyperopt_results.pickle` should be removed. It's used to be able to continue interrupted calculations, but does not detect changes to settings or the hyperopt file.
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
@@ -173,12 +260,11 @@ use data from directory `user_data/data`.
### Running Hyperopt with Smaller Testset
Use the `--timerange` argument to change how much of the testset
you want to use. The last N ticks/timeframes will be used.
Example:
Use the `--timerange` argument to change how much of the testset you want to use.
For example, to use one month of data, pass the following parameter to the hyperopt call:
```bash
python3 ./freqtrade/main.py hyperopt --timerange -200
freqtrade hyperopt --timerange 20180401-20180501
```
### Running Hyperopt with Smaller Search Space
@@ -191,12 +277,33 @@ new buy strategy you have.
Legal values are:
- `all`: optimize everything
- `buy`: just search for a new buy strategy
- `sell`: just search for a new sell strategy
- `roi`: just optimize the minimal profit table for your strategy
- `stoploss`: search for the best stoploss value
- space-separated list of any of the above values for example `--spaces roi stoploss`
* `all`: optimize everything
* `buy`: just search for a new buy strategy
* `sell`: just search for a new sell strategy
* `roi`: just optimize the minimal profit table for your strategy
* `stoploss`: search for the best stoploss value
* space-separated list of any of the above values for example `--spaces roi stoploss`
### Position stacking and disabling max market positions
In some situations, you may need to run Hyperopt (and Backtesting) with the
`--eps`/`--enable-position-staking` and `--dmmp`/`--disable-max-market-positions` arguments.
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.
The `--eps`/`--enable-position-stacking` argument allows emulation of buying the same pair multiple times,
while `--dmmp`/`--disable-max-market-positions` disables applying `max_open_trades`
during Hyperopt/Backtesting (which is equal to setting `max_open_trades` to a very high
number).
!!! Note
Dry/live runs will **NOT** use position stacking - therefore it does make sense to also validate the strategy without this as it's closer to reality.
You can also enable position stacking in the configuration file by explicitly setting
`"position_stacking"=true`.
## Understand the Hyperopt Result
@@ -205,8 +312,10 @@ Given the following result from hyperopt:
```
Best result:
135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins.
with values:
44/100: 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins. Objective: 1.94367
Buy hyperspace params:
{ 'adx-value': 44,
'rsi-value': 29,
'adx-enabled': False,
@@ -225,7 +334,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
(dataframe['rsi'] < 29.0)
```
@@ -243,27 +352,25 @@ def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
return dataframe
```
By default, hyperopt prints colorized results -- epochs with positive profit are printed in the green color. This highlighting helps you find epochs that can be interesting for later analysis. Epochs with zero total profit or with negative profits (losses) are printed in the normal color. If you do not need colorization of results (for instance, when you are redirecting hyperopt output to a file) you can switch colorization off by specifying the `--no-color` option in the command line.
You can use the `--print-all` command line option if you would like to see all results in the hyperopt output, not only the best ones. When `--print-all` is used, current best results are also colorized by default -- they are printed in bold (bright) style. This can also be switched off with the `--no-color` command line option.
### Understand Hyperopt ROI results
If you are optimizing ROI, you're result will look as follows and include a ROI table.
If you are optimizing ROI (i.e. if optimization search-space contains 'all' or 'roi'), your result will look as follows and include a ROI table:
```
Best result:
135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins.
with values:
44/100: 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins. Objective: 1.94367
Buy hyperspace params:
{ 'adx-value': 44,
'rsi-value': 29,
'adx-enabled': false,
'adx-enabled': False,
'rsi-enabled': True,
'trigger': 'bb_lower',
'roi_t1': 40,
'roi_t2': 57,
'roi_t3': 21,
'roi_p1': 0.03634636907306948,
'roi_p2': 0.055237357937802885,
'roi_p3': 0.015163796015548354,
'stoploss': -0.37996664668703606
}
'trigger': 'bb_lower'}
ROI table:
{ 0: 0.10674752302642071,
21: 0.09158372701087236,
@@ -274,22 +381,54 @@ ROI table:
This would translate to the following ROI table:
``` python
minimal_roi = {
minimal_roi = {
"118": 0,
"78": 0.0363463,
"78": 0.0363,
"21": 0.0915,
"0": 0.106
}
```
### Validate backtest result
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) with the values that can vary in the following ranges:
| # | minutes | ROI percentage |
|---|---|---|
| 1 | always 0 | 0.03...0.31 |
| 2 | 10...40 | 0.02...0.11 |
| 3 | 20...100 | 0.01...0.04 |
| 4 | 30...220 | always 0 |
This structure of the ROI table is sufficient in most cases. Override the `roi_space()` method defining the ranges desired if you need components of the ROI tables to vary in other ranges.
Override the `generate_roi_table()` and `roi_space()` methods and implement your own custom approach for generation of the ROI tables during hyperoptimization in these methods if you need a different structure of the ROI table or other amount of rows (steps) in the ROI tables.
### Understand Hyperopt Stoploss results
If you are optimizing stoploss values (i.e. if optimization search-space contains 'all' or 'stoploss'), your result will look as follows and include stoploss:
```
Best result:
44/100: 135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins. Objective: 1.94367
Buy hyperspace params:
{ 'adx-value': 44,
'rsi-value': 29,
'adx-enabled': False,
'rsi-enabled': True,
'trigger': 'bb_lower'}
Stoploss: -0.37996664668703606
```
If you are optimizing stoploss values, Freqtrade creates the 'stoploss' optimization hyperspace for you. By default, the stoploss values in that hyperspace can vary in the range -0.5...-0.02, which is sufficient in most cases.
Override the `stoploss_space()` method and define the desired range in it if you need stoploss values to vary in other range during hyperoptimization.
### Validate backtesting results
Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected.
To archive the same results (number of trades, ...) than during hyperopt, please use the command line flag `--disable-max-market-positions`.
This setting is the default for hyperopt for speed reasons. You can overwrite this in the configuration by setting `"position_stacking"=false` or by changing the relevant line in your hyperopt file [here](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L283).
!!! Note:
Dry/live runs will **NOT** use position stacking - therefore it does make sense to also validate the strategy without this as it's closer to reality.
To achieve same results (number of trades, their durations, profit, etc.) than during Hyperopt, please use same set of arguments `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
## Next Step

View File

@@ -19,29 +19,31 @@ Freqtrade is a cryptocurrency trading bot written in Python.
Always start by running a trading bot in Dry-run and do not engage money before you understand how it works and what profit/loss you should expect.
We strongly recommend you to have coding and Python knowledge. Do not hesitate to read the source code and understand the mechanism of this bot.
We strongly recommend you to have basic coding skills and Python knowledge. Do not hesitate to read the source code and understand the mechanisms of this bot, algorithms and techniques implemented in it.
## Features
- Based on Python 3.6+: For botting on any operating system - Windows, macOS and Linux
- Persistence: Persistence is achieved through sqlite
- Dry-run: Run the bot without playing money.
- Backtesting: Run a simulation of your buy/sell strategy.
- Strategy Optimization by machine learning: Use machine learning to optimize your buy/sell strategy parameters with real exchange data.
- Edge position sizing Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. Learn more
- Whitelist crypto-currencies: Select which crypto-currency you want to trade or use dynamic whitelists.
- Blacklist crypto-currencies: Select which crypto-currency you want to avoid.
- Manageable via Telegram: Manage the bot with Telegram
- Display profit/loss in fiat: Display your profit/loss in 33 fiat.
- Daily summary of profit/loss: Provide a daily summary of your profit/loss.
- Performance status report: Provide a performance status of your current trades.
- Based on Python 3.6+: For botting on any operating system — Windows, macOS and Linux.
- Persistence: Persistence is achieved through sqlite database.
- Dry-run mode: Run the bot without playing money.
- Backtesting: Run a simulation of your buy/sell strategy with historical data.
- Strategy Optimization by machine learning: Use machine learning to optimize your buy/sell strategy parameters with real exchange data.
- Edge position sizing: Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market.
- Whitelist crypto-currencies: Select which crypto-currency you want to trade or use dynamic whitelists based on market (pair) trade volume.
- Blacklist crypto-currencies: Select which crypto-currency you want to avoid.
- Manageable via Telegram or REST APi: Manage the bot with Telegram or via the builtin REST API.
- Display profit/loss in fiat: Display your profit/loss in any of 33 fiat currencies supported.
- Daily summary of profit/loss: Receive the daily summary of your profit/loss.
- Performance status report: Receive the performance status of your current trades.
## Requirements
### Uptodate clock
The clock must be accurate, syncronized to a NTP server very frequently to avoid problems with communication to the exchanges.
### Up to date clock
The clock on the system running the bot must be accurate, synchronized to a NTP server frequently enough to avoid problems with communication to the exchanges.
### Hardware requirements
To run this bot we recommend you a cloud instance with a minimum of:
- 2GB RAM
@@ -49,19 +51,21 @@ To run this bot we recommend you a cloud instance with a minimum of:
- 2vCPU
### Software requirements
- Python 3.6.x
- pip
- pip (pip3)
- git
- TA-Lib
- virtualenv (Recommended)
- Docker (Recommended)
## Support
Help / Slack
For any questions not covered by the documentation or for further information about the bot, we encourage you to join our slack channel.
Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE) to join Slack channel.
Help / Slack
For any questions not covered by the documentation or for further information about the bot, we encourage you to join our Slack channel.
Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LWEyODBiNzkzNzcyNzU0MWYyYzE5NjIyOTQxMzBmMGUxOTIzM2YyN2Y4NWY1YTEwZDgwYTRmMzE2NmM5ZmY2MTg) to join Slack channel.
## Ready to try?
Begin by reading our installation guide [here](installation).

View File

@@ -1,72 +1,49 @@
# Installation
This page explains how to prepare your environment for running the bot.
## Prerequisite
Before running your bot in production you will need to setup few
external API. In production mode, the bot required valid Bittrex API
credentials and a Telegram bot (optional but recommended).
- [Setup your exchange account](#setup-your-exchange-account)
- [Backtesting commands](#setup-your-telegram-bot)
### Requirements
Click each one for install guide:
* [Python >= 3.6.x](http://docs.python-guide.org/en/latest/starting/installation/)
* [pip](https://pip.pypa.io/en/stable/installing/)
* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) (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
*To be completed, please feel free to complete this section.*
### Setup your Telegram bot
The only things you need is a working Telegram bot and its API token.
Below we explain how to create your Telegram Bot, and how to get your
Telegram user id.
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.
### 1. Create your Telegram bot
**1.1. Start a chat with https://telegram.me/BotFather**
**1.2. Send the message `/newbot`. ** *BotFather response:*
```
Alright, a new bot. How are we going to call it? Please choose a name for your bot.
```
**1.3. Choose the public name of your bot (e.x. `Freqtrade bot`)**
*BotFather response:*
```
Good. Now let's choose a username for your bot. It must end in `bot`. Like this, for example: TetrisBot or tetris_bot.
```
**1.4. Choose the name id of your bot (e.x "`My_own_freqtrade_bot`")**
**1.5. Father bot will return you the token (API key)**<br/>
Copy it and keep it you will use it for the config parameter `token`.
*BotFather response:*
```hl_lines="4"
Done! Congratulations on your new bot. You will find it at t.me/My_own_freqtrade_bot. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.
Use this token to access the HTTP API:
521095879:AAEcEZEL7ADJ56FtG_qD0bQJSKETbXCBCi0
For a description of the Bot API, see this page: https://core.telegram.org/bots/api
```
**1.6. Don't forget to start the conversation with your bot, by clicking /START button**
### 2. Get your user id
**2.1. Talk to https://telegram.me/userinfobot**
**2.2. Get your "Id", you will use it for the config parameter
`chat_id`.**
<hr/>
## Quick start
Freqtrade provides a Linux/MacOS script to install all dependencies and help you to configure the bot.
!!! Note
Python3.6 or higher and the corresponding pip are assumed to be available. The install-script will warn and stop if that's not the case.
```bash
git clone git@github.com:freqtrade/freqtrade.git
cd freqtrade
git checkout develop
./setup.sh --install
```
!!! Note
Windows installation is explained [here](#windows).
<hr/>
## Easy Installation - Linux Script
If you are on Debian, Ubuntu or MacOS a freqtrade provides a script to Install, Update, Configure, and Reset your bot.
If you are on Debian, Ubuntu or MacOS freqtrade provides a script to Install, Update, Configure, and Reset your bot.
```bash
$ ./setup.sh
@@ -81,7 +58,7 @@ usage:
This script will install everything you need to run the bot:
* Mandatory software as: `Python3`, `ta-lib`, `wget`
* Mandatory software as: `ta-lib`
* Setup your virtualenv
* Configure your `config.json` file
@@ -101,212 +78,21 @@ Config parameter is a `config.json` configurator. This script will ask you quest
------
## Automatic Installation - Docker
Start by downloading Docker for your platform:
* [Mac](https://www.docker.com/products/docker#/mac)
* [Windows](https://www.docker.com/products/docker#/windows)
* [Linux](https://www.docker.com/products/docker#/linux)
Once you have Docker installed, simply create the config file (e.g. `config.json`) and then create a Docker image for `freqtrade` using the Dockerfile in this repo.
### 1. Prepare the Bot
**1.1. Clone the git repository**
Linux/Mac/Windows with WSL
```bash
git clone https://github.com/freqtrade/freqtrade.git
```
Windows with docker
```bash
git clone --config core.autocrlf=input https://github.com/freqtrade/freqtrade.git
```
**1.2. (Optional) Checkout the develop branch**
```bash
git checkout develop
```
**1.3. Go into the new directory**
```bash
cd freqtrade
```
**1.4. Copy `config.json.example` to `config.json`**
```bash
cp -n config.json.example config.json
```
> To edit the config please refer to the [Bot Configuration](configuration.md) page.
**1.5. Create your database file *(optional - the bot will create it if it is missing)**
Production
```bash
touch tradesv3.sqlite
````
Dry-Run
```bash
touch tradesv3.dryrun.sqlite
```
### 2. Download or build the docker image
Either use the prebuilt image from docker hub - or build the image yourself if you would like more control on which version is used.
Branches / tags available can be checked out on [Dockerhub](https://hub.docker.com/r/freqtradeorg/freqtrade/tags/).
**2.1. Download the docker image**
Pull the image from docker hub and (optionally) change the name of the image
```bash
docker pull freqtradeorg/freqtrade:develop
# Optionally tag the repository so the run-commands remain shorter
docker tag freqtradeorg/freqtrade:develop freqtrade
```
To update the image, simply run the above commands again and restart your running container.
**2.2. Build the Docker image**
```bash
cd freqtrade
docker build -t freqtrade .
```
If you are developing using Docker, use `Dockerfile.develop` to build a dev Docker image, which will also set up develop dependencies:
```bash
docker build -f ./Dockerfile.develop -t freqtrade-dev .
```
For security reasons, your configuration file will not be included in the image, you will need to bind mount it. It is also advised to bind mount an SQLite database file (see the "5. Run a restartable docker image" section) to keep it between updates.
### 3. Verify the Docker image
After the build process you can verify that the image was created with:
```bash
docker images
```
### 4. Run the Docker image
You can run a one-off container that is immediately deleted upon exiting with the following command (`config.json` must be in the current working directory):
```bash
docker run --rm -v /etc/localtime:/etc/localtime:ro -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
```
There is known issue in OSX Docker versions after 17.09.1, whereby /etc/localtime cannot be shared causing Docker to not start. A work-around for this is to start with the following cmd.
```bash
docker run --rm -e TZ=`ls -la /etc/localtime | cut -d/ -f8-9` -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
```
More information on this docker issue and work-around can be read [here](https://github.com/docker/for-mac/issues/2396).
In this example, the database will be created inside the docker instance and will be lost when you will refresh your image.
### 5. Run a restartable docker image
To run a restartable instance in the background (feel free to place your configuration and database files wherever it feels comfortable on your filesystem).
**5.1. Move your config file and database**
```bash
mkdir ~/.freqtrade
mv config.json ~/.freqtrade
mv tradesv3.sqlite ~/.freqtrade
```
**5.2. Run the docker image**
```bash
docker run -d \
--name freqtrade \
-v /etc/localtime:/etc/localtime:ro \
-v ~/.freqtrade/config.json:/freqtrade/config.json \
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
freqtrade --db-url sqlite:///tradesv3.sqlite
```
!!! Note
db-url defaults to `sqlite:///tradesv3.sqlite` but it defaults to `sqlite://` if `dry_run=True` is being used.
To override this behaviour use a custom db-url value: i.e.: `--db-url sqlite:///tradesv3.dryrun.sqlite`
### 6. Monitor your Docker instance
You can then use the following commands to monitor and manage your container:
```bash
docker logs freqtrade
docker logs -f freqtrade
docker restart freqtrade
docker stop freqtrade
docker start freqtrade
```
For more information on how to operate Docker, please refer to the [official Docker documentation](https://docs.docker.com/).
!!! Note
You do not need to rebuild the image for configuration changes, it will suffice to edit `config.json` and restart the container.
### 7. Backtest with docker
The following assumes that the above steps (1-4) have been completed successfully.
Also, backtest-data should be available at `~/.freqtrade/user_data/`.
```bash
docker run -d \
--name freqtrade \
-v /etc/localtime:/etc/localtime:ro \
-v ~/.freqtrade/config.json:/freqtrade/config.json \
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
-v ~/.freqtrade/user_data/:/freqtrade/user_data/ \
freqtrade --strategy AwsomelyProfitableStrategy backtesting
```
Head over to the [Backtesting Documentation](backtesting.md) for more details.
!!! Note
Additional parameters can be appended after the image name (`freqtrade` in the above example).
------
## Custom Installation
We've included/collected install instructions for Ubuntu 16.04, MacOS, and Windows. These are guidelines and your success may vary with other distros.
OS Specific steps are listed first, the [Common](#common) section below is necessary for all systems.
### Requirements
Click each one for install guide:
* [Python >= 3.6.x](http://docs.python-guide.org/en/latest/starting/installation/)
* [pip](https://pip.pypa.io/en/stable/installing/)
* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) (Recommended)
* [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html)
!!! Note
Python3.6 or higher and the corresponding pip are assumed to be available.
### Linux - Ubuntu 16.04
#### Install Python 3.6, Git, and wget
#### Install necessary dependencies
```bash
sudo add-apt-repository ppa:jonathonf/python-3.6
sudo apt-get update
sudo apt-get install python3.6 python3.6-venv python3.6-dev build-essential autoconf libtool pkg-config make wget git
sudo apt-get install build-essential git
```
#### Raspberry Pi / Raspbian
@@ -315,7 +101,6 @@ Before installing FreqTrade on a Raspberry Pi running the official Raspbian Imag
The following assumes that miniconda3 is installed and available in your environment. Last miniconda3 installation file use python 3.4, we will update to python 3.6 on this installation.
It's recommended to use (mini)conda for this as installation/compilation of `numpy`, `scipy` and `pandas` takes a long time.
If you have installed it from (mini)conda, you can remove `numpy`, `scipy`, and `pandas` from `requirements.txt` before you install it with `pip`.
Additional package to install on your Raspbian, `libffi-dev` required by cryptography (from python-telegram-bot).
@@ -327,18 +112,10 @@ conda activate freqtrade
conda install scipy pandas numpy
sudo apt install libffi-dev
python3 -m pip install -r requirements.txt
python3 -m pip install -r requirements-common.txt
python3 -m pip install -e .
```
### MacOS
#### Install Python 3.6, git and wget
```bash
brew install python3 git wget
```
### Common
#### 1. Install TA-Lib
@@ -379,7 +156,7 @@ git clone https://github.com/freqtrade/freqtrade.git
```
Optionally checkout the stable/master branch:
Optionally checkout the master branch to get the latest stable release:
```bash
git checkout master
@@ -397,9 +174,9 @@ cp config.json.example config.json
#### 5. Install python dependencies
``` bash
pip3 install --upgrade pip
pip3 install -r requirements.txt
pip3 install -e .
python3 -m pip install --upgrade pip
python3 -m pip install -r requirements.txt
python3 -m pip install -e .
```
#### 6. Run the Bot
@@ -407,10 +184,10 @@ pip3 install -e .
If this is the first time you run the bot, ensure you are running it in Dry-run `"dry_run": true,` otherwise it will start to buy and sell coins.
```bash
python3.6 ./freqtrade/main.py -c config.json
freqtrade -c config.json
```
*Note*: If you run the bot on a server, you should consider using [Docker](#automatic-installation---docker) a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout.
*Note*: If you run the bot on a server, you should consider using [Docker](docker.md) or a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout.
#### 7. [Optional] Configure `freqtrade` as a `systemd` service
@@ -428,11 +205,34 @@ For this to be persistent (run when user is logged out) you'll need to enable `l
sudo loginctl enable-linger "$USER"
```
If you run the bot as a service, you can use systemd service manager as a software watchdog monitoring freqtrade bot
state and restarting it in the case of failures. If the `internals.sd_notify` parameter is set to true in the
configuration or the `--sd-notify` command line option is used, the bot will send keep-alive ping messages to systemd
using the sd_notify (systemd notifications) protocol and will also tell systemd its current state (Running or Stopped)
when it changes.
The `freqtrade.service.watchdog` file contains an example of the service unit configuration file which uses systemd
as the watchdog.
!!! Note
The sd_notify communication between the bot and the systemd service manager will not work if the bot runs in a Docker container.
------
## Using Conda
Freqtrade can also be installed using Anaconda (or Miniconda).
``` bash
conda env create -f environment.yml
```
!!! Note
This requires the [ta-lib](#1-install-ta-lib) C-library to be installed first.
## Windows
We recommend that Windows users use [Docker](#docker) as this will work much easier and smoother (also more secure).
We recommend that Windows users use [Docker](docker.md) as this will work much easier and smoother (also more secure).
If that is not possible, try using the Windows Linux subsystem (WSL) - for which the Ubuntu instructions should work.
If that is not available on your system, feel free to try the instructions below, which led to success for some.
@@ -445,8 +245,6 @@ If that is not available on your system, feel free to try the instructions below
git clone https://github.com/freqtrade/freqtrade.git
```
copy paste `config.json` to ``\path\freqtrade-develop\freqtrade`
#### Install ta-lib
Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows).
@@ -476,7 +274,7 @@ error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++
Unfortunately, many packages requiring compilation don't provide a pre-build wheel. It is therefore mandatory to have a C/C++ compiler installed and available for your python environment to use.
The easiest way is to download install Microsoft Visual Studio Community [here](https://visualstudio.microsoft.com/downloads/) and make sure to install "Common Tools for Visual C++" to enable building c code on Windows. Unfortunately, this is a heavy download / dependency (~4Gb) so you might want to consider WSL or docker first.
The easiest way is to download install Microsoft Visual Studio Community [here](https://visualstudio.microsoft.com/downloads/) and make sure to install "Common Tools for Visual C++" to enable building c code on Windows. Unfortunately, this is a heavy download / dependency (~4Gb) so you might want to consider WSL or [docker](docker.md) first.
---

View File

@@ -1,63 +1,75 @@
# Plotting
This page explains how to plot prices, indicator, profits.
This page explains how to plot prices, indicators and profits.
## Installation
Plotting scripts use Plotly library. Install/upgrade it with:
``` bash
pip install -U -r requirements-plot.txt
```
pip install --upgrade plotly
```
At least version 2.3.0 is required.
## Plot price and indicators
Usage for the price plotter:
```
script/plot_dataframe.py [-h] [-p pairs] [--live]
``` bash
python3 script/plot_dataframe.py [-h] [-p pairs]
```
Example
```
python scripts/plot_dataframe.py -p BTC/ETH
``` bash
python3 scripts/plot_dataframe.py -p BTC/ETH
```
The `-p` pairs argument, can be used to specify
pairs you would like to plot.
The `-p` pairs argument can be used to specify pairs you would like to plot.
**Advanced use**
Specify custom indicators.
Use `--indicators1` for the main plot and `--indicators2` for the subplot below (if values are in a different range than prices).
``` bash
python3 scripts/plot_dataframe.py -p BTC/ETH --indicators1 sma,ema --indicators2 macd
```
### Advanced use
To plot multiple pairs, separate them with a comma:
```
python scripts/plot_dataframe.py -p BTC/ETH,XRP/ETH
```
To plot the current live price use the `--live` flag:
```
python scripts/plot_dataframe.py -p BTC/ETH --live
``` bash
python3 scripts/plot_dataframe.py -p BTC/ETH,XRP/ETH
```
To plot a timerange (to zoom in):
``` bash
python3 scripts/plot_dataframe.py -p BTC/ETH --timerange=20180801-20180805
```
python scripts/plot_dataframe.py -p BTC/ETH --timerange=100-200
```
Timerange doesn't work with live data.
To plot trades stored in a database use `--db-url` argument:
```
python scripts/plot_dataframe.py --db-url sqlite:///tradesv3.dry_run.sqlite -p BTC/ETH
``` bash
python3 scripts/plot_dataframe.py --db-url sqlite:///tradesv3.dry_run.sqlite -p BTC/ETH --trade-source DB
```
To plot a test strategy the strategy should have first be backtested.
The results may then be plotted with the -s argument:
To plot trades from a backtesting result, use `--export-filename <filename>`
``` bash
python3 scripts/plot_dataframe.py --export-filename user_data/backtest_results/backtest-result.json -p BTC/ETH
```
python scripts/plot_dataframe.py -s Strategy_Name -p BTC/ETH --datadir user_data/data/<exchange_name>/
To plot a custom strategy the strategy should have first be backtested.
The results may then be plotted with the -s argument:
``` bash
python3 scripts/plot_dataframe.py -s Strategy_Name -p BTC/ETH --datadir user_data/data/<exchange_name>/
```
## Plot profit
The profit plotter show a picture with three plots:
The profit plotter shows a picture with three plots:
1) Average closing price for all pairs
2) The summarized profit made by backtesting.
Note that this is not the real-world profit, but
@@ -67,7 +79,7 @@ The profit plotter show a picture with three plots:
The first graph is good to get a grip of how the overall market
progresses.
The second graph will show how you algorithm works or doesnt.
The second graph will show how your algorithm works or doesn't.
Perhaps you want an algorithm that steadily makes small profits,
or one that acts less seldom, but makes big swings.
@@ -76,13 +88,14 @@ that makes profit spikes.
Usage for the profit plotter:
```
script/plot_profit.py [-h] [-p pair] [--datadir directory] [--ticker_interval num]
``` bash
python3 script/plot_profit.py [-h] [-p pair] [--datadir directory] [--ticker_interval num]
```
The `-p` pair argument, can be used to plot a single pair
Example
```
python3 scripts/plot_profit.py --datadir ../freqtrade/freqtrade/tests/testdata-20171221/ -p BTC_LTC
``` bash
python3 scripts/plot_profit.py --datadir ../freqtrade/freqtrade/tests/testdata-20171221/ -p LTC/BTC
```

View File

@@ -1 +1 @@
mkdocs-material==3.1.0
mkdocs-material==4.4.0

193
docs/rest-api.md Normal file
View File

@@ -0,0 +1,193 @@
# REST API Usage
## Configuration
Enable the rest API by adding the api_server section to your configuration and setting `api_server.enabled` to `true`.
Sample configuration:
``` json
"api_server": {
"enabled": true,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"username": "Freqtrader",
"password": "SuperSecret1!"
},
```
!!! Danger Security warning
By default, the configuration listens on localhost only (so it's not reachable from other systems). We strongly recommend to not expose this API to the internet and choose a strong, unique password, since others will potentially be able to control your bot.
!!! Danger Password selection
Please make sure to select a very strong, unique password to protect your bot from unauthorized access.
You can then access the API by going to `http://127.0.0.1:8080/api/v1/version` to check if the API is running correctly.
To generate a secure password, either use a password manager, or use the below code snipped.
``` python
import secrets
secrets.token_hex()
```
### Configuration with docker
If you run your bot using docker, you'll need to have the bot listen to incomming connections. The security is then handled by docker.
``` json
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8080
},
```
Add the following to your docker command:
``` bash
-p 127.0.0.1:8080:8080
```
A complete sample-command may then look as follows:
```bash
docker run -d \
--name freqtrade \
-v ~/.freqtrade/config.json:/freqtrade/config.json \
-v ~/.freqtrade/user_data/:/freqtrade/user_data \
-v ~/.freqtrade/tradesv3.sqlite:/freqtrade/tradesv3.sqlite \
-p 127.0.0.1:8080:8080 \
freqtrade --db-url sqlite:///tradesv3.sqlite --strategy MyAwesomeStrategy
```
!!! Danger "Security warning"
By using `-p 8080:8080` the API is available to everyone connecting to the server under the correct port, so others may be able to control your bot.
## 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.
``` bash
python3 scripts/rest_client.py <command> [optional parameters]
```
By default, the script assumes `127.0.0.1` (localhost) and port `8080` to be used, however you can specify a configuration file to override this behaviour.
### Minimalistic client config
``` json
{
"api_server": {
"enabled": true,
"listen_ip_address": "0.0.0.0",
"listen_port": 8080
}
}
```
``` bash
python3 scripts/rest_client.py --config rest_config.json <command> [optional parameters]
```
## Available commands
| Command | Default | Description |
|----------|---------|-------------|
| `start` | | Starts the trader
| `stop` | | Stops the trader
| `stopbuy` | | Stops the trader from opening new trades. Gracefully closes open trades according to their rules.
| `reload_conf` | | Reloads the configuration file
| `status` | | Lists all open trades
| `status table` | | List all open trades in a table format
| `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`).
| `forcesell all` | | Instantly sells all open trades (Ignoring `minimum_roi`).
| `forcebuy <pair> [rate]` | | Instantly buys the given pair. Rate is optional. (`forcebuy_enable` must be set to True)
| `performance` | | Show performance of each finished trade grouped by pair
| `balance` | | Show account balance per currency
| `daily <n>` | 7 | Shows profit or loss per day, over the last n days
| `whitelist` | | Show the current whitelist
| `blacklist [pair]` | | Show the current blacklist, or adds a pair to the blacklist.
| `edge` | | Show validated pairs by Edge if it is enabled.
| `version` | | Show version
Possible commands can be listed from the rest-client script using the `help` command.
``` bash
python3 scripts/rest_client.py help
```
``` output
Possible commands:
balance
Get the account balance
:returns: json object
blacklist
Show the current blacklist
:param add: List of coins to add (example: "BNB/BTC")
:returns: json object
count
Returns the amount of open trades
:returns: json object
daily
Returns the amount of open trades
:returns: json object
edge
Returns information about edge
:returns: json object
forcebuy
Buy an asset
:param pair: Pair to buy (ETH/BTC)
:param price: Optional - price to buy
:returns: json object of the trade
forcesell
Force-sell a trade
:param tradeid: Id of the trade (can be received via status command)
:returns: json object
performance
Returns the performance of the different coins
:returns: json object
profit
Returns the profit summary
:returns: json object
reload_conf
Reload configuration
:returns: json object
start
Start the bot if it's in stopped state.
:returns: json object
status
Get the status of open trades
:returns: json object
stop
Stop the bot. Use start to restart
:returns: json object
stopbuy
Stop buying (but handle sells gracefully).
use reload_conf to reset
:returns: json object
version
Returns the version of the bot
:returns: json object containing the version
whitelist
Show the current whitelist
:returns: json object
```

View File

@@ -1,5 +1,5 @@
# SQL Helper
This page constains some help if you want to edit your sqlite db.
This page contains some help if you want to edit your sqlite db.
## Install sqlite3
**Ubuntu/Debian installation**
@@ -44,6 +44,14 @@ CREATE TABLE trades (
open_date DATETIME NOT NULL,
close_date DATETIME,
open_order_id VARCHAR,
stop_loss FLOAT,
initial_stop_loss FLOAT,
stoploss_order_id VARCHAR,
stoploss_last_update DATETIME,
max_rate FLOAT,
sell_reason VARCHAR,
strategy VARCHAR,
ticker_interval INTEGER,
PRIMARY KEY (id),
CHECK (is_open IN (0, 1))
);
@@ -55,38 +63,45 @@ CREATE TABLE trades (
SELECT * FROM trades;
```
## Fix trade still open after a /forcesell
## Fix trade still open after a manual sell on the exchange
!!! Warning
Manually selling a pair on the exchange will not be detected by the bot and it will try to sell anyway. Whenever possible, forcesell <tradeid> should be used to accomplish the same thing.
It is strongly advised to backup your database file before making any manual changes.
!!! Note
This should not be necessary after /forcesell, as forcesell orders are closed automatically by the bot on the next iteration.
```sql
UPDATE trades
SET is_open=0, close_date=<close_date>, close_rate=<close_rate>, close_profit=close_rate/open_rate-1
SET is_open=0, close_date=<close_date>, close_rate=<close_rate>, close_profit=close_rate/open_rate-1, sell_reason=<sell_reason>
WHERE id=<trade_ID_to_update>;
```
**Example:**
##### Example
```sql
UPDATE trades
SET is_open=0, close_date='2017-12-20 03:08:45.103418', close_rate=0.19638016, close_profit=0.0496
SET is_open=0, close_date='2017-12-20 03:08:45.103418', close_rate=0.19638016, close_profit=0.0496, sell_reason='force_sell'
WHERE id=31;
```
## Insert manually a new trade
```sql
INSERT
INTO trades (exchange, pair, is_open, fee_open, fee_close, open_rate, stake_amount, amount, open_date)
VALUES ('BITTREX', 'BTC_<COIN>', 1, 0.0025, 0.0025, <open_rate>, <stake_amount>, <amount>, '<datetime>')
INSERT INTO trades (exchange, pair, is_open, fee_open, fee_close, open_rate, stake_amount, amount, open_date)
VALUES ('bittrex', 'ETH/BTC', 1, 0.0025, 0.0025, <open_rate>, <stake_amount>, <amount>, '<datetime>')
```
**Example:**
##### Example:
```sql
INSERT INTO trades (exchange, pair, is_open, fee_open, fee_close, open_rate, stake_amount, amount, open_date) VALUES ('BITTREX', 'BTC_ETC', 1, 0.0025, 0.0025, 0.00258580, 0.002, 0.7715262081, '2017-11-28 12:44:24.000000')
INSERT INTO trades (exchange, pair, is_open, fee_open, fee_close, open_rate, stake_amount, amount, open_date)
VALUES ('bittrex', 'ETH/BTC', 1, 0.0025, 0.0025, 0.00258580, 0.002, 0.7715262081, '2017-11-28 12:44:24.000000')
```
## Fix wrong fees in the table
If your DB was created before
[PR#200](https://github.com/freqtrade/freqtrade/pull/200) was merged
(before 12/23/17).
If your DB was created before [PR#200](https://github.com/freqtrade/freqtrade/pull/200) was merged (before 12/23/17).
```sql
UPDATE trades SET fee=0.0025 WHERE fee=0.005;

View File

@@ -1,4 +1,13 @@
# Stop Loss support
# Stop Loss
The `stoploss` configuration parameter is loss in percentage that should trigger a sale.
For example, value `-0.10` will cause immediate sell if the profit dips below -10% for a given trade. This parameter is optional.
Most of the strategy files already include the optimal `stoploss`
value. This parameter is optional. If you use it in the configuration file, it will take over the
`stoploss` value from the strategy file.
## Stop Loss support
At this stage the bot contains the following stoploss support modes:
@@ -16,13 +25,12 @@ In case of stoploss on exchange there is another parameter called `stoploss_on_e
!!! Note
Stoploss on exchange is only supported for Binance as of now.
## Static Stop Loss
This is very simple, basically you define a stop loss of x in your strategy file or alternative in the configuration, which
will overwrite the strategy definition. This will basically try to sell your asset, the second the loss exceeds the defined loss.
## Trail Stop Loss
## Trailing Stop Loss
The initial value for this stop loss, is defined in your strategy or configuration. Just as you would define your Stop Loss normally.
To enable this Feauture all you have to do is to define the configuration element:
@@ -55,8 +63,21 @@ Both values can be configured in the main configuration file and requires `"trai
``` json
"trailing_stop_positive": 0.01,
"trailing_stop_positive_offset": 0.011,
"trailing_only_offset_is_reached": false
```
The 0.01 would translate to a 1% stop loss, once you hit 1.1% profit.
You should also make sure to have this value (`trailing_stop_positive_offset`) lower than your minimal ROI, otherwise minimal ROI will apply first and sell your trade.
If `"trailing_only_offset_is_reached": true` then the trailing stoploss is only activated once the offset is reached. Until then, the stoploss remains at the configured`stoploss`.
## Changing stoploss on open trades
A stoploss on an open trade can be changed by changing the value in the configuration or strategy and use the `/reload_conf` command (alternatively, completely stopping and restarting the bot also works).
The new stoploss value will be applied to open trades (and corresponding log-messages will be generated).
### Limitations
Stoploss values cannot be changed if `trailing_stop` is enabled and the stoploss has already been adjusted, or if [Edge](edge.md) is enabled (since Edge would recalculate stoploss based on the current market situation).

View File

@@ -5,8 +5,7 @@ indicators.
## Install a custom strategy file
This is very simple. Copy paste your strategy file into the folder
`user_data/strategies`.
This is very simple. Copy paste your strategy file into the directory `user_data/strategies`.
Let assume you have a class called `AwesomeStrategy` in the file `awesome-strategy.py`:
@@ -14,7 +13,7 @@ Let assume you have a class called `AwesomeStrategy` in the file `awesome-strate
2. Start the bot with the param `--strategy AwesomeStrategy` (the parameter is the class name)
```bash
python3 ./freqtrade/main.py --strategy AwesomeStrategy
freqtrade --strategy AwesomeStrategy
```
## Change your strategy
@@ -22,7 +21,7 @@ python3 ./freqtrade/main.py --strategy AwesomeStrategy
The bot includes a default strategy file. However, we recommend you to
use your own file to not have to lose your parameters every time the default
strategy file will be updated on Github. Put your custom strategy file
into the folder `user_data/strategies`.
into the directory `user_data/strategies`.
Best copy the test-strategy and modify this copy to avoid having bot-updates override your changes.
`cp user_data/strategies/test_strategy.py user_data/strategies/awesome-strategy.py`
@@ -41,18 +40,24 @@ The bot also include a sample strategy called `TestStrategy` you can update: `us
You can test it with the parameter: `--strategy TestStrategy`
```bash
python3 ./freqtrade/main.py --strategy AwesomeStrategy
freqtrade --strategy AwesomeStrategy
```
**For the following section we will use the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py)
file as reference.**
!!! Note: Strategies and Backtesting
!!! Note Strategies and Backtesting
To avoid problems and unexpected differences between Backtesting and dry/live modes, please be aware
that during backtesting the full time-interval is passed to the `populate_*()` methods at once.
It is therefore best to use vectorized operations (across the whole dataframe, not loops) and
avoid index referencing (`df.iloc[-1]`), but instead use `df.shift()` to get to the previous candle.
!!! Warning Using future data
Since backtesting passes the full time interval to the `populate_*()` methods, the strategy author
needs to take care to avoid having the strategy utilize data from the future.
Samples for usage of future data are `dataframe.shift(-1)`, `dataframe.resample("1h")` (this uses the left border of the interval, so moves data from an hour to the start of the hour).
They all use data which is not available during regular operations, so these strategies will perform well during backtesting, but will fail / perform badly in dry-runs.
### Customize Indicators
Buy and sell strategies need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file.
@@ -212,9 +217,12 @@ stoploss = -0.10
```
This would signify a stoploss of -10%.
For the full documentation on stoploss features, look at the dedicated [stoploss page](stoploss.md).
If your exchange supports it, it's recommended to also set `"stoploss_on_exchange"` in the order dict, so your stoploss is on the exchange and cannot be missed for network-problems (or other problems).
For more information on order_types please look [here](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md#understand-order_types).
For more information on order_types please look [here](configuration.md#understand-order_types).
### Ticker interval
@@ -250,48 +258,59 @@ class Awesomestrategy(IStrategy):
self.cust_info[metadata["pair"]["crosstime"] = 1
```
!!! Warning:
!!! Warning
The data is not persisted after a bot-restart (or config-reload). Also, the amount of data should be kept smallish (no DataFrames and such), otherwise the bot will start to consume a lot of memory and eventually run out of memory and crash.
!!! Note:
!!! Note
If the data is pair-specific, make sure to use pair as one of the keys in the dictionary.
### Additional data (DataProvider)
The strategy provides access to the `DataProvider`. This allows you to get additional data to use in your strategy.
!!!Note:
The DataProvier is currently not available during backtesting / hyperopt, but this is planned for the future.
All methods return `None` in case of failure (do not raise an exception).
Please always check if the `DataProvider` is available to avoid failures during backtesting.
Please always check the mode of operation to select the correct method to get data (samples see below).
#### Possible options for DataProvider
- `available_pairs` - Property with tuples listing cached pairs with their intervals. (pair, interval)
- `ohlcv(pair, ticker_interval)` - Currently cached ticker data for all pairs in the whitelist, returns DataFrame or empty DataFrame
- `historic_ohlcv(pair, ticker_interval)` - Data stored on disk
- `available_pairs` - Property with tuples listing cached pairs with their intervals (pair, interval).
- `ohlcv(pair, ticker_interval)` - Currently cached ticker data for the pair, returns DataFrame or empty DataFrame.
- `historic_ohlcv(pair, ticker_interval)` - Returns historical data stored on disk.
- `get_pair_dataframe(pair, ticker_interval)` - This is a universal method, which returns either historical data (for backtesting) or cached live data (for the Dry-Run and Live-Run modes).
- `runmode` - Property containing the current runmode.
#### ohlcv / historic_ohlcv
#### Example: fetch live ohlcv / historic data for the first informative pair
``` python
if self.dp:
if dp.runmode == 'live':
if ('ETH/BTC', ticker_interval) in self.dp.available_pairs:
data_eth = self.dp.ohlcv(pair='ETH/BTC',
ticker_interval=ticker_interval)
else:
# Get historic ohlcv data (cached on disk).
history_eth = self.dp.historic_ohlcv(pair='ETH/BTC',
ticker_interval='1h')
inf_pair, inf_timeframe = self.informative_pairs()[0]
informative = self.dp.get_pair_dataframe(pair=inf_pair,
ticker_interval=inf_timeframe)
```
!!! Warning: Warning about backtesting
Be carefull when using dataprovider in backtesting. `historic_ohlcv()` provides the full time-range in one go,
!!! Warning Warning about backtesting
Be carefull when using dataprovider in backtesting. `historic_ohlcv()` (and `get_pair_dataframe()`
for the backtesting runmode) provides the full time-range in one go,
so please be aware of it and make sure to not "look into the future" to avoid surprises when running in dry/live mode).
!!! Warning Warning in hyperopt
This option cannot currently be used during hyperopt.
#### Orderbook
``` python
if self.dp:
if self.dp.runmode 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]
```
!!! Warning
The order book is not part of the historic data which means backtesting and hyperopt will not work if this
method is used.
#### Available Pairs
``` python
@@ -317,7 +336,7 @@ def informative_pairs(self):
]
```
!!! Warning:
!!! Warning
As these pairs will be refreshed as part of the regular whitelist refresh, it's best to keep this list short.
All intervals and all pairs can be specified as long as they are available (and active) on the used exchange.
It is however better to use resampling to longer time-intervals when possible
@@ -327,7 +346,7 @@ def informative_pairs(self):
The strategy provides access to the `Wallets` object. This contains the current balances on the exchange.
!!!NOTE:
!!! Note
Wallets is not available during backtesting / hyperopt.
Please always check if `Wallets` is available to avoid failures during backtesting.
@@ -345,6 +364,30 @@ if self.wallets:
- `get_used(asset)` - currently tied up balance (open orders)
- `get_total(asset)` - total available balance - sum of the 2 above
### Print created dataframe
To inspect the created dataframe, you can issue a print-statement in either `populate_buy_trend()` or `populate_sell_trend()`.
You may also want to print the pair so it's clear what data is currently shown.
``` python
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe.loc[
(
#>> whatever condition<<<
),
'buy'] = 1
# Print the Analyzed pair
print(f"result for {metadata['pair']}")
# Inspect the last 5 rows
print(dataframe.tail())
return dataframe
```
Printing more than a few rows is also possible (simply use `print(dataframe)` instead of `print(dataframe.tail())`), however not recommended, as that will be very verbose (~500 lines per pair every 5 seconds).
### Where is the default strategy?
The default buy strategy is located in the file
@@ -352,10 +395,10 @@ The default buy strategy is located in the file
### Specify custom strategy location
If you want to use a strategy from a different folder you can pass `--strategy-path`
If you want to use a strategy from a different directory you can pass `--strategy-path`
```bash
python3 ./freqtrade/main.py --strategy AwesomeStrategy --strategy-path /some/folder
freqtrade --strategy AwesomeStrategy --strategy-path /some/directory
```
### Further strategy ideas
@@ -364,7 +407,7 @@ To get additional Ideas for strategies, head over to our [strategy repository](h
Feel free to use any of them as inspiration for your own strategies.
We're happy to accept Pull Requests containing new Strategies to that repo.
We also got a *strategy-sharing* channel in our [Slack community](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE) which is a great place to get and/or share ideas.
We also got a *strategy-sharing* channel in our [Slack community](https://join.slack.com/t/highfrequencybot/shared_invite/enQtNjU5ODcwNjI1MDU3LWEyODBiNzkzNzcyNzU0MWYyYzE5NjIyOTQxMzBmMGUxOTIzM2YyN2Y4NWY1YTEwZDgwYTRmMzE2NmM5ZmY2MTg) which is a great place to get and/or share ideas.
## Next step

View File

@@ -1,13 +1,48 @@
# Telegram usage
This page explains how to command your bot with Telegram.
## Setup your Telegram bot
## Prerequisite
To control your bot with Telegram, you need first to
[set up a Telegram bot](installation.md)
and add your Telegram API keys into your config file.
Below we explain how to create your Telegram Bot, and how to get your
Telegram user id.
### 1. Create your Telegram bot
Start a chat with the [Telegram BotFather](https://telegram.me/BotFather)
Send the message `/newbot`.
*BotFather response:*
> Alright, a new bot. How are we going to call it? Please choose a name for your bot.
Choose the public name of your bot (e.x. `Freqtrade bot`)
*BotFather response:*
> Good. Now let's choose a username for your bot. It must end in `bot`. Like this, for example: TetrisBot or tetris_bot.
Choose the name id of your bot and send it to the BotFather (e.g. "`My_own_freqtrade_bot`")
*BotFather response:*
> Done! Congratulations on your new bot. You will find it at `t.me/yourbots_name_bot`. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.
> Use this token to access the HTTP API: `22222222:APITOKEN`
> For a description of the Bot API, see this page: https://core.telegram.org/bots/api Father bot will return you the token (API key)
Copy the API Token (`22222222:APITOKEN` in the above example) and keep use it for the config parameter `token`.
Don't forget to start the conversation with your bot, by clicking `/START` button
### 2. Get your user id
Talk to the [userinfobot](https://telegram.me/userinfobot)
Get your "Id", you will use it for the config parameter `chat_id`.
## Telegram commands
Per default, the Telegram bot shows predefined commands. Some commands
are only available by sending them to the bot. The table below list the
official commands. You can ask at any moment for help with `/help`.
@@ -16,6 +51,7 @@ official commands. You can ask at any moment for help with `/help`.
|----------|---------|-------------|
| `/start` | | Starts the trader
| `/stop` | | Stops the trader
| `/stopbuy` | | Stops the trader from opening new trades. Gracefully closes open trades according to their rules.
| `/reload_conf` | | Reloads the configuration file
| `/status` | | Lists all open trades
| `/status table` | | List all open trades in a table format
@@ -27,6 +63,9 @@ official commands. You can ask at any moment for help with `/help`.
| `/performance` | | Show performance of each finished trade grouped by pair
| `/balance` | | Show account balance per currency
| `/daily <n>` | 7 | Shows profit or loss per day, over the last n days
| `/whitelist` | | Show the current whitelist
| `/blacklist [pair]` | | Show the current blacklist, or adds a pair to the blacklist.
| `/edge` | | Show validated pairs by Edge if it is enabled.
| `/help` | | Show help message
| `/version` | | Show version
@@ -43,22 +82,34 @@ Below, example of Telegram message you will receive for each command.
> `Stopping trader ...`
> **Status:** `stopped`
## /status
### /stopbuy
> **status:** `Setting max_open_trades to 0. Run /reload_conf to reset.`
Prevents the bot from opening new trades by temporarily setting "max_open_trades" to 0. Open trades will be handled via their regular rules (ROI / Sell-signal, stoploss, ...).
After this, give the bot time to close off open trades (can be checked via `/status table`).
Once all positions are sold, run `/stop` to completely stop the bot.
`/reload_conf` resets "max_open_trades" to the value set in the configuration and resets this command.
!!! warning
The stop-buy signal is ONLY active while the bot is running, and is not persisted anyway, so restarting the bot will cause this to reset.
### /status
For each open trade, the bot will send you the following message.
> **Trade ID:** `123`
> **Trade ID:** `123` `(since 1 days ago)`
> **Current Pair:** CVC/BTC
> **Open Since:** `1 days ago`
> **Amount:** `26.64180098`
> **Open Rate:** `0.00007489`
> **Close Rate:** `None`
> **Current Rate:** `0.00007489`
> **Close Profit:** `None`
> **Current Profit:** `12.95%`
> **Open Order:** `None`
> **Stoploss:** `0.00007389 (-0.02%)`
## /status table
### /status table
Return the status of all open trades in a table format.
```
@@ -68,7 +119,7 @@ Return the status of all open trades in a table format.
123 CVC/BTC 1 h 12.95%
```
## /count
### /count
Return the number of trades used and available.
```
@@ -77,7 +128,7 @@ current max
2 10
```
## /profit
### /profit
Return a summary of your profit/loss and performance.
@@ -94,17 +145,19 @@ Return a summary of your profit/loss and performance.
> **Avg. Duration:** `2:33:45`
> **Best Performing:** `PAY/BTC: 50.23%`
## /forcesell <trade_id>
### /forcesell <trade_id>
> **BITTREX:** Selling BTC/LTC with limit `0.01650000 (profit: ~-4.07%, -0.00008168)`
## /forcebuy <pair>
### /forcebuy <pair>
> **BITTREX**: Buying ETH/BTC with limit `0.03400000` (`1.000000 ETH`, `225.290 USD`)
> **BITTREX:** Buying ETH/BTC with limit `0.03400000` (`1.000000 ETH`, `225.290 USD`)
Note that for this to work, `forcebuy_enable` needs to be set to true.
## /performance
[More details](configuration.md/#understand-forcebuy_enable)
### /performance
Return the performance of each crypto-currency the bot has sold.
> Performance:
@@ -115,7 +168,7 @@ Return the performance of each crypto-currency the bot has sold.
> 5. `STORJ/BTC 27.24%`
> ...
## /balance
### /balance
Return the balance of all crypto-currency your have on the exchange.
@@ -129,7 +182,7 @@ Return the balance of all crypto-currency your have on the exchange.
> **Balance:** 86.64180098
> **Pending:** 0.0
## /daily <n>
### /daily <n>
Per default `/daily` will return the 7 last days.
The example below if for `/daily 3`:
@@ -143,6 +196,38 @@ Day Profit BTC Profit USD
2018-01-01 0.00269130 BTC 34.986 USD
```
## /version
### /whitelist
Shows the current whitelist
> Using whitelist `StaticPairList` with 22 pairs
> `IOTA/BTC, NEO/BTC, TRX/BTC, VET/BTC, ADA/BTC, ETC/BTC, NCASH/BTC, DASH/BTC, XRP/BTC, XVG/BTC, EOS/BTC, LTC/BTC, OMG/BTC, BTG/BTC, LSK/BTC, ZEC/BTC, HOT/BTC, IOTX/BTC, XMR/BTC, AST/BTC, XLM/BTC, NANO/BTC`
### /blacklist [pair]
Shows the current blacklist.
If Pair is set, then this pair will be added to the pairlist.
Also supports multiple pairs, seperated by a space.
Use `/reload_conf` to reset the blacklist.
> Using blacklist `StaticPairList` with 2 pairs
>`DODGE/BTC`, `HOT/BTC`.
### /edge
Shows pairs validated by Edge along with their corresponding winrate, expectancy and stoploss values.
> **Edge only validated following pairs:**
```
Pair Winrate Expectancy Stoploss
-------- --------- ------------ ----------
DOCK/ETH 0.522727 0.881821 -0.03
PHX/ETH 0.677419 0.560488 -0.03
HOT/ETH 0.733333 0.490492 -0.03
HC/ETH 0.588235 0.280988 -0.02
ARDR/ETH 0.366667 0.143059 -0.01
```
### /version
> **Version:** `0.14.3`

View File

@@ -1,7 +1,5 @@
# Webhook usage
This page explains how to configure your bot to talk to webhooks.
## Configuration
Enable webhooks by adding a webhook-section to your configuration file, and setting `webhook.enabled` to `true`.
@@ -39,34 +37,32 @@ Different payloads can be configured for different events. Not all fields are ne
The fields in `webhook.webhookbuy` are filled when the bot executes a buy. Parameters are filled using string.format.
Possible parameters are:
* exchange
* pair
* market_url
* limit
* stake_amount
* stake_amount_fiat
* stake_currency
* fiat_currency
* `exchange`
* `pair`
* `limit`
* `stake_amount`
* `stake_currency`
* `fiat_currency`
* `order_type`
### Webhooksell
The fields in `webhook.webhooksell` are filled when the bot sells a trade. Parameters are filled using string.format.
Possible parameters are:
* exchange
* pair
* gain
* market_url
* limit
* amount
* open_rate
* current_rate
* profit_amount
* profit_percent
* profit_fiat
* stake_currency
* fiat_currency
* sell_reason
* `exchange`
* `pair`
* `gain`
* `limit`
* `amount`
* `open_rate`
* `current_rate`
* `profit_amount`
* `profit_percent`
* `stake_currency`
* `fiat_currency`
* `sell_reason`
* `order_type`
### Webhookstatus

59
environment.yml Normal file
View File

@@ -0,0 +1,59 @@
name: freqtrade
channels:
- defaults
- conda-forge
dependencies:
# Required for app
- python>=3.6
- pip
- wheel
- numpy
- pandas
- scipy
- SQLAlchemy
- scikit-learn
- arrow
- requests
- urllib3
- wrapt
- joblib
- jsonschema
- tabulate
- python-rapidjson
- filelock
- flask
- python-dotenv
- cachetools
- scikit-optimize
- python-telegram-bot
# Optional for plotting
- plotly
# Optional for development
- flake8
- pytest
- pytest-mock
- pytest-asyncio
- pytest-cov
- coveralls
- mypy
# Useful for jupyter
- jupyter
- ipykernel
- isort
- yapf
- pip:
# Required for app
- cython
- coinmarketcap
- ccxt
- TA-Lib
- py_find_1st
- sdnotify
# Optional for develpment
- flake8-tidy-imports
- flake8-type-annotations
- pytest-random-order
- -e .

View File

@@ -6,7 +6,7 @@ After=network.target
# Set WorkingDirectory and ExecStart to your file paths accordingly
# NOTE: %h will be resolved to /home/<username>
WorkingDirectory=%h/freqtrade
ExecStart=/usr/bin/freqtrade --dynamic-whitelist 40
ExecStart=/usr/bin/freqtrade
Restart=on-failure
[Install]

View File

@@ -0,0 +1,30 @@
[Unit]
Description=Freqtrade Daemon
After=network.target
[Service]
# Set WorkingDirectory and ExecStart to your file paths accordingly
# NOTE: %h will be resolved to /home/<username>
WorkingDirectory=%h/freqtrade
ExecStart=/usr/bin/freqtrade --sd-notify
Restart=always
#Restart=on-failure
# Note that we use Type=notify here
Type=notify
# Currently required if Type=notify
NotifyAccess=all
StartLimitInterval=1min
StartLimitBurst=5
TimeoutStartSec=1min
# Use here (process_throttle_secs * 2) or longer time interval
WatchdogSec=20
[Install]
WantedBy=default.target

View File

@@ -1,15 +1,15 @@
""" FreqTrade bot """
__version__ = '0.18.1'
__version__ = '2019.8-1'
class DependencyException(BaseException):
class DependencyException(Exception):
"""
Indicates that a assumed dependency is not met.
Indicates that an assumed dependency is not met.
This could happen when there is currently not enough money on the account.
"""
class OperationalException(BaseException):
class OperationalException(Exception):
"""
Requires manual intervention.
This happens when an exchange returns an unexpected error during runtime
@@ -17,7 +17,15 @@ class OperationalException(BaseException):
"""
class TemporaryError(BaseException):
class InvalidOrderException(Exception):
"""
This is returned when the order is not valid. Example:
If stoploss on exchange order is hit, then trying to cancel the order
should return this exception.
"""
class TemporaryError(Exception):
"""
Temporary network or exchange related error.
This could happen when an exchange is congested, unavailable, or the user

View File

@@ -6,10 +6,7 @@ To launch Freqtrade as a module
> python -m freqtrade (with Python >= 3.6)
"""
import sys
from freqtrade import main
if __name__ == '__main__':
main.set_loggers()
main.main(sys.argv[1:])
main.main()

View File

@@ -1,423 +0,0 @@
"""
This module contains the argument manager class
"""
import argparse
import os
import re
from typing import List, NamedTuple, Optional
import arrow
from freqtrade import __version__, constants
class TimeRange(NamedTuple):
"""
NamedTuple Defining timerange inputs.
[start/stop]type defines if [start/stop]ts shall be used.
if *type is none, don't use corresponding startvalue.
"""
starttype: Optional[str] = None
stoptype: Optional[str] = None
startts: int = 0
stopts: int = 0
class Arguments(object):
"""
Arguments Class. Manage the arguments received by the cli
"""
def __init__(self, args: List[str], description: str) -> None:
self.args = args
self.parsed_arg: Optional[argparse.Namespace] = None
self.parser = argparse.ArgumentParser(description=description)
def _load_args(self) -> None:
self.common_args_parser()
self._build_subcommands()
def get_parsed_arg(self) -> argparse.Namespace:
"""
Return the list of arguments
:return: List[str] List of arguments
"""
if self.parsed_arg is None:
self._load_args()
self.parsed_arg = self.parse_args()
return self.parsed_arg
def parse_args(self) -> argparse.Namespace:
"""
Parses given arguments and returns an argparse Namespace instance.
"""
parsed_arg = self.parser.parse_args(self.args)
return parsed_arg
def common_args_parser(self) -> None:
"""
Parses given common arguments and returns them as a parsed object.
"""
self.parser.add_argument(
'-v', '--verbose',
help='verbose mode (-vv for more, -vvv to get all messages)',
action='count',
dest='loglevel',
default=0,
)
self.parser.add_argument(
'--version',
action='version',
version=f'%(prog)s {__version__}'
)
self.parser.add_argument(
'-c', '--config',
help='specify configuration file (default: %(default)s)',
dest='config',
default='config.json',
type=str,
metavar='PATH',
)
self.parser.add_argument(
'-d', '--datadir',
help='path to backtest data',
dest='datadir',
default=None,
type=str,
metavar='PATH',
)
self.parser.add_argument(
'-s', '--strategy',
help='specify strategy class name (default: %(default)s)',
dest='strategy',
default='DefaultStrategy',
type=str,
metavar='NAME',
)
self.parser.add_argument(
'--strategy-path',
help='specify additional strategy lookup path',
dest='strategy_path',
type=str,
metavar='PATH',
)
self.parser.add_argument(
'--customhyperopt',
help='specify hyperopt class name (default: %(default)s)',
dest='hyperopt',
default=constants.DEFAULT_HYPEROPT,
type=str,
metavar='NAME',
)
self.parser.add_argument(
'--dynamic-whitelist',
help='dynamically generate and update whitelist'
' based on 24h BaseVolume (default: %(const)s)'
' DEPRECATED.',
dest='dynamic_whitelist',
const=constants.DYNAMIC_WHITELIST,
type=int,
metavar='INT',
nargs='?',
)
self.parser.add_argument(
'--db-url',
help='Override trades database URL, this is useful if dry_run is enabled'
' or in custom deployments (default: %(default)s)',
dest='db_url',
type=str,
metavar='PATH',
)
@staticmethod
def backtesting_options(parser: argparse.ArgumentParser) -> None:
"""
Parses given arguments for Backtesting scripts.
"""
parser.add_argument(
'--eps', '--enable-position-stacking',
help='Allow buying the same pair multiple times (position stacking)',
action='store_true',
dest='position_stacking',
default=False
)
parser.add_argument(
'--dmmp', '--disable-max-market-positions',
help='Disable applying `max_open_trades` during backtest '
'(same as setting `max_open_trades` to a very high number)',
action='store_false',
dest='use_max_market_positions',
default=True
)
parser.add_argument(
'-l', '--live',
help='using live data',
action='store_true',
dest='live',
)
parser.add_argument(
'-r', '--refresh-pairs-cached',
help='refresh the pairs files in tests/testdata with the latest data from the '
'exchange. Use it if you want to run your backtesting with up-to-date data.',
action='store_true',
dest='refresh_pairs',
)
parser.add_argument(
'--strategy-list',
help='Provide a commaseparated list of strategies to backtest '
'Please note that ticker-interval needs to be set either in config '
'or via command line. When using this together with --export trades, '
'the strategy-name is injected into the filename '
'(so backtest-data.json becomes backtest-data-DefaultStrategy.json',
nargs='+',
dest='strategy_list',
)
parser.add_argument(
'--export',
help='export backtest results, argument are: trades\
Example --export=trades',
type=str,
default=None,
dest='export',
)
parser.add_argument(
'--export-filename',
help='Save backtest results to this filename \
requires --export to be set as well\
Example --export-filename=user_data/backtest_data/backtest_today.json\
(default: %(default)s)',
type=str,
default=os.path.join('user_data', 'backtest_data', 'backtest-result.json'),
dest='exportfilename',
metavar='PATH',
)
@staticmethod
def edge_options(parser: argparse.ArgumentParser) -> None:
"""
Parses given arguments for Backtesting scripts.
"""
parser.add_argument(
'-r', '--refresh-pairs-cached',
help='refresh the pairs files in tests/testdata with the latest data from the '
'exchange. Use it if you want to run your edge with up-to-date data.',
action='store_true',
dest='refresh_pairs',
)
parser.add_argument(
'--stoplosses',
help='defines a range of stoploss against which edge will assess the strategy '
'the format is "min,max,step" (without any space).'
'example: --stoplosses=-0.01,-0.1,-0.001',
type=str,
dest='stoploss_range',
)
@staticmethod
def optimizer_shared_options(parser: argparse.ArgumentParser) -> None:
"""
Parses given common arguments for Backtesting and Hyperopt scripts.
:param parser:
:return:
"""
parser.add_argument(
'-i', '--ticker-interval',
help='specify ticker interval (1m, 5m, 30m, 1h, 1d)',
dest='ticker_interval',
type=str,
)
parser.add_argument(
'--timerange',
help='specify what timerange of data to use.',
default=None,
type=str,
dest='timerange',
)
@staticmethod
def hyperopt_options(parser: argparse.ArgumentParser) -> None:
"""
Parses given arguments for Hyperopt scripts.
"""
parser.add_argument(
'--eps', '--enable-position-stacking',
help='Allow buying the same pair multiple times (position stacking)',
action='store_true',
dest='position_stacking',
default=False
)
parser.add_argument(
'--dmmp', '--disable-max-market-positions',
help='Disable applying `max_open_trades` during backtest '
'(same as setting `max_open_trades` to a very high number)',
action='store_false',
dest='use_max_market_positions',
default=True
)
parser.add_argument(
'-e', '--epochs',
help='specify number of epochs (default: %(default)d)',
dest='epochs',
default=constants.HYPEROPT_EPOCH,
type=int,
metavar='INT',
)
parser.add_argument(
'-s', '--spaces',
help='Specify which parameters to hyperopt. Space separate list. \
Default: %(default)s',
choices=['all', 'buy', 'sell', 'roi', 'stoploss'],
default='all',
nargs='+',
dest='spaces',
)
def _build_subcommands(self) -> None:
"""
Builds and attaches all subcommands
:return: None
"""
from freqtrade.optimize import backtesting, hyperopt, edge_cli
subparsers = self.parser.add_subparsers(dest='subparser')
# Add backtesting subcommand
backtesting_cmd = subparsers.add_parser('backtesting', help='backtesting module')
backtesting_cmd.set_defaults(func=backtesting.start)
self.optimizer_shared_options(backtesting_cmd)
self.backtesting_options(backtesting_cmd)
# Add edge subcommand
edge_cmd = subparsers.add_parser('edge', help='edge module')
edge_cmd.set_defaults(func=edge_cli.start)
self.optimizer_shared_options(edge_cmd)
self.edge_options(edge_cmd)
# Add hyperopt subcommand
hyperopt_cmd = subparsers.add_parser('hyperopt', help='hyperopt module')
hyperopt_cmd.set_defaults(func=hyperopt.start)
self.optimizer_shared_options(hyperopt_cmd)
self.hyperopt_options(hyperopt_cmd)
@staticmethod
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
:return: Start and End range period
"""
if text is None:
return TimeRange(None, None, 0, 0)
syntax = [(r'^-(\d{8})$', (None, 'date')),
(r'^(\d{8})-$', ('date', None)),
(r'^(\d{8})-(\d{8})$', ('date', 'date')),
(r'^-(\d{10})$', (None, 'date')),
(r'^(\d{10})-$', ('date', None)),
(r'^(\d{10})-(\d{10})$', ('date', 'date')),
(r'^(-\d+)$', (None, 'line')),
(r'^(\d+)-$', ('line', None)),
(r'^(\d+)-(\d+)$', ('index', 'index'))]
for rex, stype in syntax:
# Apply the regular expression to text
match = re.match(rex, text)
if match: # Regex has matched
rvals = match.groups()
index = 0
start: int = 0
stop: int = 0
if stype[0]:
starts = rvals[index]
if stype[0] == 'date' and len(starts) == 8:
start = arrow.get(starts, 'YYYYMMDD').timestamp
else:
start = int(starts)
index += 1
if stype[1]:
stops = rvals[index]
if stype[1] == 'date' and len(stops) == 8:
stop = arrow.get(stops, 'YYYYMMDD').timestamp
else:
stop = int(stops)
return TimeRange(stype[0], stype[1], start, stop)
raise Exception('Incorrect syntax for timerange "%s"' % text)
def scripts_options(self) -> None:
"""
Parses given arguments for scripts.
"""
self.parser.add_argument(
'-p', '--pairs',
help='Show profits for only this pairs. Pairs are comma-separated.',
dest='pairs',
default=None
)
def testdata_dl_options(self) -> None:
"""
Parses given arguments for testdata download
"""
self.parser.add_argument(
'--pairs-file',
help='File containing a list of pairs to download',
dest='pairs_file',
default=None,
metavar='PATH',
)
self.parser.add_argument(
'--export',
help='Export files to given dir',
dest='export',
default=None,
metavar='PATH',
)
self.parser.add_argument(
'-c', '--config',
help='specify configuration file, used for additional exchange parameters',
dest='config',
default=None,
type=str,
metavar='PATH',
)
self.parser.add_argument(
'--days',
help='Download data for number of days',
dest='days',
type=int,
metavar='INT',
default=None
)
self.parser.add_argument(
'--exchange',
help='Exchange name (default: %(default)s). Only valid if no config is provided',
dest='exchange',
type=str,
default='bittrex'
)
self.parser.add_argument(
'-t', '--timeframes',
help='Specify which tickers to download. Space separated list. \
Default: %(default)s',
choices=['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h',
'6h', '8h', '12h', '1d', '3d', '1w'],
default=['1m', '5m'],
nargs='+',
dest='timeframes',
)
self.parser.add_argument(
'--erase',
help='Clean all existing data for the selected exchange/pairs/timeframes',
dest='erase',
action='store_true'
)

View File

@@ -1,334 +0,0 @@
"""
This module contains the configuration class
"""
import json
import logging
import os
from argparse import Namespace
from typing import Any, Dict, Optional
import ccxt
from jsonschema import Draft4Validator, validate
from jsonschema.exceptions import ValidationError, best_match
from freqtrade import OperationalException, constants
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
def set_loggers(log_level: int = 0) -> None:
"""
Set the logger level for Third party libs
:return: None
"""
logging.getLogger('requests').setLevel(logging.INFO if log_level <= 1 else logging.DEBUG)
logging.getLogger("urllib3").setLevel(logging.INFO if log_level <= 1 else logging.DEBUG)
logging.getLogger('ccxt.base.exchange').setLevel(
logging.INFO if log_level <= 2 else logging.DEBUG)
logging.getLogger('telegram').setLevel(logging.INFO)
class Configuration(object):
"""
Class to read and init the bot configuration
Reuse this class for the bot, backtesting, hyperopt and every script that required configuration
"""
def __init__(self, args: Namespace, runmode: RunMode = None) -> None:
self.args = args
self.config: Optional[Dict[str, Any]] = None
self.runmode = runmode
def load_config(self) -> Dict[str, Any]:
"""
Extract information for sys.argv and load the bot configuration
:return: Configuration dictionary
"""
logger.info('Using config: %s ...', self.args.config)
config = self._load_config_file(self.args.config)
# Set strategy if not specified in config and or if it's non default
if self.args.strategy != constants.DEFAULT_STRATEGY or not config.get('strategy'):
config.update({'strategy': self.args.strategy})
if self.args.strategy_path:
config.update({'strategy_path': self.args.strategy_path})
# Add the hyperopt file to use
config.update({'hyperopt': self.args.hyperopt})
# Load Common configuration
config = self._load_common_config(config)
# Load Backtesting
config = self._load_backtesting_config(config)
# Load Edge
config = self._load_edge_config(config)
# Load Hyperopt
config = self._load_hyperopt_config(config)
# Set runmode
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
config.update({'runmode': self.runmode})
return config
def _load_config_file(self, path: str) -> Dict[str, Any]:
"""
Loads a config file from the given path
:param path: path as str
:return: configuration as dictionary
"""
try:
with open(path) as file:
conf = json.load(file)
except FileNotFoundError:
raise OperationalException(
f'Config file "{path}" not found!'
' Please create a config file or check whether it exists.')
if 'internals' not in conf:
conf['internals'] = {}
logger.info('Validating configuration ...')
return self._validate_config(conf)
def _load_common_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
"""
Extract information for sys.argv and load common configuration
:return: configuration as dictionary
"""
# Log level
if 'loglevel' in self.args and self.args.loglevel:
config.update({'verbosity': self.args.loglevel})
else:
config.update({'verbosity': 0})
logging.basicConfig(
level=logging.INFO if config['verbosity'] < 1 else logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
)
set_loggers(config['verbosity'])
logger.info('Verbosity set to %s', config['verbosity'])
# Add dynamic_whitelist if found
if 'dynamic_whitelist' in self.args and self.args.dynamic_whitelist:
# Update to volumePairList (the previous default)
config['pairlist'] = {'method': 'VolumePairList',
'config': {'number_assets': self.args.dynamic_whitelist}
}
logger.warning(
'Parameter --dynamic-whitelist has been deprecated, '
'and will be completely replaced by the whitelist dict in the future. '
'For now: using dynamically generated whitelist based on VolumePairList. '
'(not applicable with Backtesting and Hyperopt)'
)
if self.args.db_url and self.args.db_url != constants.DEFAULT_DB_PROD_URL:
config.update({'db_url': self.args.db_url})
logger.info('Parameter --db-url detected ...')
if config.get('dry_run', False):
logger.info('Dry run is enabled')
if config.get('db_url') in [None, constants.DEFAULT_DB_PROD_URL]:
# Default to in-memory db for dry_run if not specified
config['db_url'] = constants.DEFAULT_DB_DRYRUN_URL
else:
if not config.get('db_url', None):
config['db_url'] = constants.DEFAULT_DB_PROD_URL
logger.info('Dry run is disabled')
if config.get('forcebuy_enable', False):
logger.warning('`forcebuy` RPC message enabled.')
# Setting max_open_trades to infinite if -1
if config.get('max_open_trades') == -1:
config['max_open_trades'] = float('inf')
logger.info(f'Using DB: "{config["db_url"]}"')
# Check if the exchange set by the user is supported
self.check_exchange(config)
return config
def _create_datadir(self, config: Dict[str, Any], datadir: Optional[str] = None) -> str:
if not datadir:
# set datadir
exchange_name = config.get('exchange', {}).get('name').lower()
datadir = os.path.join('user_data', 'data', exchange_name)
if not os.path.isdir(datadir):
os.makedirs(datadir)
logger.info(f'Created data directory: {datadir}')
return datadir
def _load_backtesting_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
"""
Extract information for sys.argv and load Backtesting configuration
:return: configuration as dictionary
"""
# If -i/--ticker-interval is used we override the configuration parameter
# (that will override the strategy configuration)
if 'ticker_interval' in self.args and self.args.ticker_interval:
config.update({'ticker_interval': self.args.ticker_interval})
logger.info('Parameter -i/--ticker-interval detected ...')
logger.info('Using ticker_interval: %s ...', config.get('ticker_interval'))
# If -l/--live is used we add it to the configuration
if 'live' in self.args and self.args.live:
config.update({'live': True})
logger.info('Parameter -l/--live detected ...')
# If --enable-position-stacking is used we add it to the configuration
if 'position_stacking' in self.args and self.args.position_stacking:
config.update({'position_stacking': True})
logger.info('Parameter --enable-position-stacking detected ...')
# If --disable-max-market-positions is used we add it to the configuration
if 'use_max_market_positions' in self.args and not self.args.use_max_market_positions:
config.update({'use_max_market_positions': False})
logger.info('Parameter --disable-max-market-positions detected ...')
logger.info('max_open_trades set to unlimited ...')
else:
logger.info('Using max_open_trades: %s ...', config.get('max_open_trades'))
# If --timerange is used we add it to the configuration
if 'timerange' in self.args and self.args.timerange:
config.update({'timerange': self.args.timerange})
logger.info('Parameter --timerange detected: %s ...', self.args.timerange)
# If --datadir is used we add it to the configuration
if 'datadir' in self.args and self.args.datadir:
config.update({'datadir': self._create_datadir(config, self.args.datadir)})
else:
config.update({'datadir': self._create_datadir(config, None)})
logger.info('Using data folder: %s ...', config.get('datadir'))
# If -r/--refresh-pairs-cached is used we add it to the configuration
if 'refresh_pairs' in self.args and self.args.refresh_pairs:
config.update({'refresh_pairs': True})
logger.info('Parameter -r/--refresh-pairs-cached detected ...')
if 'strategy_list' in self.args and self.args.strategy_list:
config.update({'strategy_list': self.args.strategy_list})
logger.info('Using strategy list of %s Strategies', len(self.args.strategy_list))
if 'ticker_interval' in self.args and self.args.ticker_interval:
config.update({'ticker_interval': self.args.ticker_interval})
logger.info('Overriding ticker interval with Command line argument')
# If --export is used we add it to the configuration
if 'export' in self.args and self.args.export:
config.update({'export': self.args.export})
logger.info('Parameter --export detected: %s ...', self.args.export)
# If --export-filename is used we add it to the configuration
if 'export' in config and 'exportfilename' in self.args and self.args.exportfilename:
config.update({'exportfilename': self.args.exportfilename})
logger.info('Storing backtest results to %s ...', self.args.exportfilename)
return config
def _load_edge_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
"""
Extract information for sys.argv and load Edge configuration
:return: configuration as dictionary
"""
# If --timerange is used we add it to the configuration
if 'timerange' in self.args and self.args.timerange:
config.update({'timerange': self.args.timerange})
logger.info('Parameter --timerange detected: %s ...', self.args.timerange)
# If --timerange is used we add it to the configuration
if 'stoploss_range' in self.args and self.args.stoploss_range:
txt_range = eval(self.args.stoploss_range)
config['edge'].update({'stoploss_range_min': txt_range[0]})
config['edge'].update({'stoploss_range_max': txt_range[1]})
config['edge'].update({'stoploss_range_step': txt_range[2]})
logger.info('Parameter --stoplosses detected: %s ...', self.args.stoploss_range)
# If -r/--refresh-pairs-cached is used we add it to the configuration
if 'refresh_pairs' in self.args and self.args.refresh_pairs:
config.update({'refresh_pairs': True})
logger.info('Parameter -r/--refresh-pairs-cached detected ...')
return config
def _load_hyperopt_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
"""
Extract information for sys.argv and load Hyperopt configuration
:return: configuration as dictionary
"""
# If --epochs is used we add it to the configuration
if 'epochs' in self.args and self.args.epochs:
config.update({'epochs': self.args.epochs})
logger.info('Parameter --epochs detected ...')
logger.info('Will run Hyperopt with for %s epochs ...', config.get('epochs'))
# If --spaces is used we add it to the configuration
if 'spaces' in self.args and self.args.spaces:
config.update({'spaces': self.args.spaces})
logger.info('Parameter -s/--spaces detected: %s', config.get('spaces'))
return config
def _validate_config(self, conf: Dict[str, Any]) -> Dict[str, Any]:
"""
Validate the configuration follow the Config Schema
:param conf: Config in JSON format
:return: Returns the config if valid, otherwise throw an exception
"""
try:
validate(conf, constants.CONF_SCHEMA, Draft4Validator)
return conf
except ValidationError as exception:
logger.critical(
'Invalid configuration. See config.json.example. Reason: %s',
exception
)
raise ValidationError(
best_match(Draft4Validator(constants.CONF_SCHEMA).iter_errors(conf)).message
)
def get_config(self) -> Dict[str, Any]:
"""
Return the config. Use this method to get the bot config
:return: Dict: Bot config
"""
if self.config is None:
self.config = self.load_config()
return self.config
def check_exchange(self, config: Dict[str, Any]) -> bool:
"""
Check if the exchange name in the config file is supported by Freqtrade
:return: True or raised an exception if the exchange if not supported
"""
exchange = config.get('exchange', {}).get('name').lower()
if exchange not in ccxt.exchanges:
exception_msg = f'Exchange "{exchange}" not supported.\n' \
f'The following exchanges are supported: {", ".join(ccxt.exchanges)}'
logger.critical(exception_msg)
raise OperationalException(
exception_msg
)
# Depreciation warning
if 'ccxt_rate_limit' in config.get('exchange', {}):
logger.warning("`ccxt_rate_limit` has been deprecated in favor of "
"`ccxt_config` and `ccxt_async_config` and will be removed "
"in a future version.")
logger.debug('Exchange "%s" supported', exchange)
return True

View File

@@ -0,0 +1,4 @@
from freqtrade.configuration.arguments import Arguments # noqa: F401
from freqtrade.configuration.timerange import TimeRange # noqa: F401
from freqtrade.configuration.configuration import Configuration # noqa: F401
from freqtrade.configuration.config_validation import validate_config_consistency # noqa: F401

View File

@@ -0,0 +1,141 @@
"""
This module contains the argument manager class
"""
import argparse
from typing import List, Optional
from freqtrade.configuration.cli_options import AVAILABLE_CLI_OPTIONS
from freqtrade import constants
ARGS_COMMON = ["verbosity", "logfile", "version", "config", "datadir", "user_data_dir"]
ARGS_STRATEGY = ["strategy", "strategy_path"]
ARGS_MAIN = ARGS_COMMON + ARGS_STRATEGY + ["db_url", "sd_notify"]
ARGS_COMMON_OPTIMIZE = ["ticker_interval", "timerange",
"max_open_trades", "stake_amount", "refresh_pairs"]
ARGS_BACKTEST = ARGS_COMMON_OPTIMIZE + ["position_stacking", "use_max_market_positions",
"strategy_list", "export", "exportfilename"]
ARGS_HYPEROPT = ARGS_COMMON_OPTIMIZE + ["hyperopt", "hyperopt_path",
"position_stacking", "epochs", "spaces",
"use_max_market_positions", "print_all",
"print_colorized", "print_json", "hyperopt_jobs",
"hyperopt_random_state", "hyperopt_min_trades",
"hyperopt_continue", "hyperopt_loss"]
ARGS_EDGE = ARGS_COMMON_OPTIMIZE + ["stoploss_range"]
ARGS_LIST_EXCHANGES = ["print_one_column"]
ARGS_CREATE_USERDIR = ["user_data_dir"]
ARGS_DOWNLOAD_DATA = ["pairs", "pairs_file", "days", "exchange", "timeframes", "erase"]
ARGS_PLOT_DATAFRAME = (ARGS_COMMON + ARGS_STRATEGY +
["pairs", "indicators1", "indicators2", "plot_limit", "db_url",
"trade_source", "export", "exportfilename", "timerange",
"refresh_pairs"])
ARGS_PLOT_PROFIT = (ARGS_COMMON + ARGS_STRATEGY +
["pairs", "timerange", "export", "exportfilename", "db_url", "trade_source"])
NO_CONF_REQURIED = ["start_download_data"]
class Arguments(object):
"""
Arguments Class. Manage the arguments received by the cli
"""
def __init__(self, args: Optional[List[str]], description: str,
no_default_config: bool = False) -> None:
self.args = args
self._parsed_arg: Optional[argparse.Namespace] = None
self.parser = argparse.ArgumentParser(description=description)
self._no_default_config = no_default_config
def _load_args(self) -> None:
self._build_args(optionlist=ARGS_MAIN)
self._build_subcommands()
def get_parsed_arg(self) -> argparse.Namespace:
"""
Return the list of arguments
:return: List[str] List of arguments
"""
if self._parsed_arg is None:
self._load_args()
self._parsed_arg = self._parse_args()
return self._parsed_arg
def _parse_args(self) -> argparse.Namespace:
"""
Parses given arguments and returns an argparse Namespace instance.
"""
parsed_arg = self.parser.parse_args(self.args)
# 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 (not self._no_default_config and parsed_arg.config is None
and not (hasattr(parsed_arg, 'func')
and parsed_arg.func.__name__ in NO_CONF_REQURIED)):
parsed_arg.config = [constants.DEFAULT_CONFIG]
return parsed_arg
def _build_args(self, optionlist, parser=None):
parser = parser or self.parser
for val in optionlist:
opt = AVAILABLE_CLI_OPTIONS[val]
parser.add_argument(*opt.cli, dest=val, **opt.kwargs)
def _build_subcommands(self) -> None:
"""
Builds and attaches all subcommands.
:return: None
"""
from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge
from freqtrade.utils import start_create_userdir, start_download_data, start_list_exchanges
subparsers = self.parser.add_subparsers(dest='subparser')
# Add backtesting subcommand
backtesting_cmd = subparsers.add_parser('backtesting', help='Backtesting module.')
backtesting_cmd.set_defaults(func=start_backtesting)
self._build_args(optionlist=ARGS_BACKTEST, parser=backtesting_cmd)
# Add edge subcommand
edge_cmd = subparsers.add_parser('edge', help='Edge module.')
edge_cmd.set_defaults(func=start_edge)
self._build_args(optionlist=ARGS_EDGE, parser=edge_cmd)
# Add hyperopt subcommand
hyperopt_cmd = subparsers.add_parser('hyperopt', help='Hyperopt module.')
hyperopt_cmd.set_defaults(func=start_hyperopt)
self._build_args(optionlist=ARGS_HYPEROPT, parser=hyperopt_cmd)
create_userdir_cmd = subparsers.add_parser('create-userdir',
help="Create user-data directory.")
create_userdir_cmd.set_defaults(func=start_create_userdir)
self._build_args(optionlist=ARGS_CREATE_USERDIR, parser=create_userdir_cmd)
# Add list-exchanges subcommand
list_exchanges_cmd = subparsers.add_parser(
'list-exchanges',
help='Print available exchanges.'
)
list_exchanges_cmd.set_defaults(func=start_list_exchanges)
self._build_args(optionlist=ARGS_LIST_EXCHANGES, parser=list_exchanges_cmd)
# Add download-data subcommand
download_data_cmd = subparsers.add_parser(
'download-data',
help='Download backtesting data.'
)
download_data_cmd.set_defaults(func=start_download_data)
self._build_args(optionlist=ARGS_DOWNLOAD_DATA, parser=download_data_cmd)

View File

@@ -0,0 +1,47 @@
import logging
from typing import Any, Dict
from freqtrade import OperationalException
from freqtrade.exchange import (available_exchanges, get_exchange_bad_reason,
is_exchange_available, is_exchange_bad,
is_exchange_officially_supported)
logger = logging.getLogger(__name__)
def check_exchange(config: Dict[str, Any], check_for_bad: bool = True) -> bool:
"""
Check if the exchange name in the config file is supported by Freqtrade
:param check_for_bad: if True, check the exchange against the list of known 'bad'
exchanges
:return: False if exchange is 'bad', i.e. is known to work with the bot with
critical issues or does not work at all, crashes, etc. True otherwise.
raises an exception if the exchange if not supported by ccxt
and thus is not known for the Freqtrade at all.
"""
logger.info("Checking exchange...")
exchange = config.get('exchange', {}).get('name').lower()
if not is_exchange_available(exchange):
raise OperationalException(
f'Exchange "{exchange}" is not supported by ccxt '
f'and therefore not available for the bot.\n'
f'The following exchanges are supported by ccxt: '
f'{", ".join(available_exchanges())}'
)
if check_for_bad and is_exchange_bad(exchange):
raise OperationalException(f'Exchange "{exchange}" is known to not work with the bot yet. '
f'Reason: {get_exchange_bad_reason(exchange)}')
if is_exchange_officially_supported(exchange):
logger.info(f'Exchange "{exchange}" is officially supported '
f'by the Freqtrade development team.')
else:
logger.warning(f'Exchange "{exchange}" is supported by ccxt '
f'and therefore available for the bot but not officially supported '
f'by the Freqtrade development team. '
f'It may work flawlessly (please report back) or have serious issues. '
f'Use it at your own discretion.')
return True

View File

@@ -0,0 +1,319 @@
"""
Definition of cli arguments used in arguments.py
"""
import argparse
import os
from freqtrade import __version__, constants
def check_int_positive(value: str) -> int:
try:
uint = int(value)
if uint <= 0:
raise ValueError
except ValueError:
raise argparse.ArgumentTypeError(
f"{value} is invalid for this parameter, should be a positive integer value"
)
return uint
class Arg:
# Optional CLI arguments
def __init__(self, *args, **kwargs):
self.cli = args
self.kwargs = kwargs
# List of available command line options
AVAILABLE_CLI_OPTIONS = {
# Common options
"verbosity": Arg(
'-v', '--verbose',
help='Verbose mode (-vv for more, -vvv to get all messages).',
action='count',
default=0,
),
"logfile": Arg(
'--logfile',
help='Log to the file specified.',
metavar='FILE',
),
"version": Arg(
'-V', '--version',
action='version',
version=f'%(prog)s {__version__}',
),
"config": Arg(
'-c', '--config',
help=f'Specify configuration file (default: `{constants.DEFAULT_CONFIG}`). '
f'Multiple --config options may be used. '
f'Can be set to `-` to read config from stdin.',
action='append',
metavar='PATH',
),
"datadir": Arg(
'-d', '--datadir',
help='Path to directory with historical backtesting data.',
metavar='PATH',
),
"user_data_dir": Arg(
'--userdir', '--user-data-dir',
help='Path to userdata directory.',
metavar='PATH',
),
# Main options
"strategy": Arg(
'-s', '--strategy',
help='Specify strategy class name (default: `%(default)s`).',
metavar='NAME',
default='DefaultStrategy',
),
"strategy_path": Arg(
'--strategy-path',
help='Specify additional strategy lookup path.',
metavar='PATH',
),
"db_url": Arg(
'--db-url',
help=f'Override trades database URL, this is useful in custom deployments '
f'(default: `{constants.DEFAULT_DB_PROD_URL}` for Live Run mode, '
f'`{constants.DEFAULT_DB_DRYRUN_URL}` for Dry Run).',
metavar='PATH',
),
"sd_notify": Arg(
'--sd-notify',
help='Notify systemd service manager.',
action='store_true',
),
# Optimize common
"ticker_interval": Arg(
'-i', '--ticker-interval',
help='Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`).',
),
"timerange": Arg(
'--timerange',
help='Specify what timerange of data to use.',
),
"max_open_trades": Arg(
'--max_open_trades',
help='Specify max_open_trades to use.',
type=int,
metavar='INT',
),
"stake_amount": Arg(
'--stake_amount',
help='Specify stake_amount.',
type=float,
),
"refresh_pairs": Arg(
'-r', '--refresh-pairs-cached',
help='Refresh the pairs files in tests/testdata with the latest data from the '
'exchange. Use it if you want to run your optimization commands with '
'up-to-date data.',
action='store_true',
),
# Backtesting
"position_stacking": Arg(
'--eps', '--enable-position-stacking',
help='Allow buying the same pair multiple times (position stacking).',
action='store_true',
default=False,
),
"use_max_market_positions": Arg(
'--dmmp', '--disable-max-market-positions',
help='Disable applying `max_open_trades` during backtest '
'(same as setting `max_open_trades` to a very high number).',
action='store_false',
default=True,
),
"strategy_list": Arg(
'--strategy-list',
help='Provide a space-separated list of strategies to backtest. '
'Please note that ticker-interval needs to be set either in config '
'or via command line. When using this together with `--export trades`, '
'the strategy-name is injected into the filename '
'(so `backtest-data.json` becomes `backtest-data-DefaultStrategy.json`',
nargs='+',
),
"export": Arg(
'--export',
help='Export backtest results, argument are: trades. '
'Example: `--export=trades`',
),
"exportfilename": Arg(
'--export-filename',
help='Save backtest results to the file with this filename (default: `%(default)s`). '
'Requires `--export` to be set as well. '
'Example: `--export-filename=user_data/backtest_results/backtest_today.json`',
metavar='PATH',
default=os.path.join('user_data', 'backtest_results',
'backtest-result.json'),
),
# Edge
"stoploss_range": Arg(
'--stoplosses',
help='Defines a range of stoploss values against which edge will assess the strategy. '
'The format is "min,max,step" (without any space). '
'Example: `--stoplosses=-0.01,-0.1,-0.001`',
),
# Hyperopt
"hyperopt": Arg(
'--customhyperopt',
help='Specify hyperopt class name (default: `%(default)s`).',
metavar='NAME',
default=constants.DEFAULT_HYPEROPT,
),
"hyperopt_path": Arg(
'--hyperopt-path',
help='Specify additional lookup path for Hyperopts and Hyperopt Loss functions.',
metavar='PATH',
),
"epochs": Arg(
'-e', '--epochs',
help='Specify number of epochs (default: %(default)d).',
type=check_int_positive,
metavar='INT',
default=constants.HYPEROPT_EPOCH,
),
"spaces": Arg(
'-s', '--spaces',
help='Specify which parameters to hyperopt. Space-separated list. '
'Default: `%(default)s`.',
choices=['all', 'buy', 'sell', 'roi', 'stoploss'],
nargs='+',
default='all',
),
"print_all": Arg(
'--print-all',
help='Print all results, not only the best ones.',
action='store_true',
default=False,
),
"print_colorized": Arg(
'--no-color',
help='Disable colorization of hyperopt results. May be useful if you are '
'redirecting output to a file.',
action='store_false',
default=True,
),
"print_json": Arg(
'--print-json',
help='Print best result detailization in JSON format.',
action='store_true',
default=False,
),
"hyperopt_jobs": Arg(
'-j', '--job-workers',
help='The number of concurrently running jobs for hyperoptimization '
'(hyperopt worker processes). '
'If -1 (default), all CPUs are used, for -2, all CPUs but one are used, etc. '
'If 1 is given, no parallel computing code is used at all.',
type=int,
metavar='JOBS',
default=-1,
),
"hyperopt_random_state": Arg(
'--random-state',
help='Set random state to some positive integer for reproducible hyperopt results.',
type=check_int_positive,
metavar='INT',
),
"hyperopt_min_trades": Arg(
'--min-trades',
help="Set minimal desired number of trades for evaluations in the hyperopt "
"optimization path (default: 1).",
type=check_int_positive,
metavar='INT',
default=1,
),
"hyperopt_continue": Arg(
"--continue",
help="Continue hyperopt from previous runs. "
"By default, temporary files will be removed and hyperopt will start from scratch.",
default=False,
action='store_true',
),
"hyperopt_loss": Arg(
'--hyperopt-loss',
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.'
'(default: `%(default)s`).',
metavar='NAME',
default=constants.DEFAULT_HYPEROPT_LOSS,
),
# List exchanges
"print_one_column": Arg(
'-1', '--one-column',
help='Print exchanges in one column.',
action='store_true',
),
# Script options
"pairs": Arg(
'-p', '--pairs',
help='Show profits for only these pairs. Pairs are space-separated.',
nargs='+',
),
# Download data
"pairs_file": Arg(
'--pairs-file',
help='File containing a list of pairs to download.',
metavar='FILE',
),
"days": Arg(
'--days',
help='Download data for given number of days.',
type=check_int_positive,
metavar='INT',
),
"exchange": Arg(
'--exchange',
help=f'Exchange name (default: `{constants.DEFAULT_EXCHANGE}`). '
f'Only valid if no config is provided.',
),
"timeframes": Arg(
'-t', '--timeframes',
help=f'Specify which tickers to download. Space-separated list. '
f'Default: `1m 5m`.',
choices=['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h',
'6h', '8h', '12h', '1d', '3d', '1w'],
default=['1m', '5m'],
nargs='+',
),
"erase": Arg(
'--erase',
help='Clean all existing data for the selected exchange/pairs/timeframes.',
action='store_true',
),
# Plot dataframe
"indicators1": Arg(
'--indicators1',
help='Set indicators from your strategy you want in the first row of the graph. '
'Comma-separated list. Example: `ema3,ema5`. Default: `%(default)s`.',
default='sma,ema3,ema5',
),
"indicators2": Arg(
'--indicators2',
help='Set indicators from your strategy you want in the third row of the graph. '
'Comma-separated list. Example: `fastd,fastk`. Default: `%(default)s`.',
default='macd,macdsignal',
),
"plot_limit": Arg(
'--plot-limit',
help='Specify tick limit for plotting. Notice: too high values cause huge files. '
'Default: %(default)s.',
type=check_int_positive,
metavar='INT',
default=750,
),
"trade_source": Arg(
'--trade-source',
help='Specify the source for trades (Can be DB or file (backtest file)) '
'Default: %(default)s',
choices=["DB", "file"],
default="file",
),
}

View File

@@ -0,0 +1,113 @@
import logging
from typing import Any, Dict
from jsonschema import Draft4Validator, validators
from jsonschema.exceptions import ValidationError, best_match
from freqtrade import constants, OperationalException
logger = logging.getLogger(__name__)
def _extend_validator(validator_class):
"""
Extended validator for the Freqtrade configuration JSON Schema.
Currently it only handles defaults for subschemas.
"""
validate_properties = validator_class.VALIDATORS['properties']
def set_defaults(validator, properties, instance, schema):
for prop, subschema in properties.items():
if 'default' in subschema:
instance.setdefault(prop, subschema['default'])
for error in validate_properties(
validator, properties, instance, schema,
):
yield error
return validators.extend(
validator_class, {'properties': set_defaults}
)
FreqtradeValidator = _extend_validator(Draft4Validator)
def validate_config_schema(conf: Dict[str, Any]) -> Dict[str, Any]:
"""
Validate the configuration follow the Config Schema
:param conf: Config in JSON format
:return: Returns the config if valid, otherwise throw an exception
"""
try:
FreqtradeValidator(constants.CONF_SCHEMA).validate(conf)
return conf
except ValidationError as e:
logger.critical(
f"Invalid configuration. See config.json.example. Reason: {e}"
)
raise ValidationError(
best_match(Draft4Validator(constants.CONF_SCHEMA).iter_errors(conf)).message
)
def validate_config_consistency(conf: Dict[str, Any]) -> None:
"""
Validate the configuration consistency.
Should be ran after loading both configuration and strategy,
since strategies can set certain configuration settings too.
:param conf: Config in JSON format
:return: Returns None if everything is ok, otherwise throw an OperationalException
"""
# validating trailing stoploss
_validate_trailing_stoploss(conf)
_validate_edge(conf)
def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:
if conf.get('stoploss') == 0.0:
raise OperationalException(
'The config stoploss needs to be different from 0 to avoid problems with sell orders.'
)
# Skip if trailing stoploss is not activated
if not conf.get('trailing_stop', False):
return
tsl_positive = float(conf.get('trailing_stop_positive', 0))
tsl_offset = float(conf.get('trailing_stop_positive_offset', 0))
tsl_only_offset = conf.get('trailing_only_offset_is_reached', False)
if tsl_only_offset:
if tsl_positive == 0.0:
raise OperationalException(
'The config trailing_only_offset_is_reached needs '
'trailing_stop_positive_offset to be more than 0 in your config.')
if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive:
raise OperationalException(
'The config trailing_stop_positive_offset needs '
'to be greater than trailing_stop_positive in your config.')
# Fetch again without default
if 'trailing_stop_positive' in conf and float(conf['trailing_stop_positive']) == 0.0:
raise OperationalException(
'The config trailing_stop_positive needs to be different from 0 '
'to avoid problems with sell orders.'
)
def _validate_edge(conf: Dict[str, Any]) -> None:
"""
Edge and Dynamic whitelist should not both be enabled, since edge overrides dynamic whitelists.
"""
if not conf.get('edge', {}).get('enabled'):
return
if conf.get('pairlist', {}).get('method') == 'VolumePairList':
raise OperationalException(
"Edge and VolumePairList are incompatible, "
"Edge will override whatever pairs VolumePairlist selects."
)

View File

@@ -0,0 +1,371 @@
"""
This module contains the configuration class
"""
import logging
import warnings
from argparse import Namespace
from copy import deepcopy
from pathlib import Path
from typing import Any, Callable, Dict, List, Optional
from freqtrade import OperationalException, constants
from freqtrade.configuration.check_exchange import check_exchange
from freqtrade.configuration.config_validation import (
validate_config_consistency, validate_config_schema)
from freqtrade.configuration.directory_operations import (create_datadir,
create_userdata_dir)
from freqtrade.configuration.load_config import load_config_file
from freqtrade.loggers import setup_logging
from freqtrade.misc import deep_merge_dicts, json_load
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
class Configuration(object):
"""
Class to read and init the bot configuration
Reuse this class for the bot, backtesting, hyperopt and every script that required configuration
"""
def __init__(self, args: Namespace, runmode: RunMode = None) -> None:
self.args = args
self.config: Optional[Dict[str, Any]] = None
self.runmode = runmode
def get_config(self) -> Dict[str, Any]:
"""
Return the config. Use this method to get the bot config
:return: Dict: Bot config
"""
if self.config is None:
self.config = self.load_config()
return self.config
@staticmethod
def from_files(files: List[str]) -> Dict[str, Any]:
"""
Iterate through the config files passed in, loading all of them
and merging their contents.
Files are loaded in sequence, parameters in later configuration files
override the same parameter from an earlier file (last definition wins).
:param files: List of file paths
:return: configuration dictionary
"""
# Keep this method as staticmethod, so it can be used from interactive environments
config: Dict[str, Any] = {}
if not files:
return deepcopy(constants.MINIMAL_CONFIG)
# We expect here a list of config filenames
for path in files:
logger.info(f'Using config: {path} ...')
# Merge config options, overwriting old values
config = deep_merge_dicts(load_config_file(path), config)
# Normalize config
if 'internals' not in config:
config['internals'] = {}
# validate configuration before returning
logger.info('Validating configuration ...')
validate_config_schema(config)
return config
def load_config(self) -> Dict[str, Any]:
"""
Extract information for sys.argv and load the bot configuration
:return: Configuration dictionary
"""
# Load all configs
config: Dict[str, Any] = Configuration.from_files(self.args.config)
self._process_common_options(config)
self._process_optimize_options(config)
self._process_plot_options(config)
self._process_runmode(config)
# Check if the exchange set by the user is supported
check_exchange(config, config.get('experimental', {}).get('block_bad_exchanges', True))
self._resolve_pairs_list(config)
validate_config_consistency(config)
return config
def _process_logging_options(self, config: Dict[str, Any]) -> None:
"""
Extract information for sys.argv and load logging configuration:
the -v/--verbose, --logfile options
"""
# Log level
if 'verbosity' in self.args and self.args.verbosity:
config.update({'verbosity': self.args.verbosity})
else:
config.update({'verbosity': 0})
if 'logfile' in self.args and self.args.logfile:
config.update({'logfile': self.args.logfile})
setup_logging(config)
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.strategy != constants.DEFAULT_STRATEGY or not config.get('strategy'):
config.update({'strategy': self.args.strategy})
self._args_to_config(config, argname='strategy_path',
logstring='Using additional Strategy lookup path: {}')
if ('db_url' in self.args and self.args.db_url and
self.args.db_url != constants.DEFAULT_DB_PROD_URL):
config.update({'db_url': self.args.db_url})
logger.info('Parameter --db-url detected ...')
if config.get('dry_run', False):
logger.info('Dry run is enabled')
if config.get('db_url') in [None, constants.DEFAULT_DB_PROD_URL]:
# Default to in-memory db for dry_run if not specified
config['db_url'] = constants.DEFAULT_DB_DRYRUN_URL
else:
if not config.get('db_url', None):
config['db_url'] = constants.DEFAULT_DB_PROD_URL
logger.info('Dry run is disabled')
logger.info(f'Using DB: "{config["db_url"]}"')
if config.get('forcebuy_enable', False):
logger.warning('`forcebuy` RPC message enabled.')
# Setting max_open_trades to infinite if -1
if config.get('max_open_trades') == -1:
config['max_open_trades'] = float('inf')
# Support for sd_notify
if 'sd_notify' in self.args and self.args.sd_notify:
config['internals'].update({'sd_notify': True})
def _process_datadir_options(self, config: Dict[str, Any]) -> None:
"""
Extract information for sys.argv and load directory configurations
--user-data, --datadir
"""
# Check exchange parameter here - otherwise `datadir` might be wrong.
if "exchange" in self.args and self.args.exchange:
config['exchange']['name'] = self.args.exchange
logger.info(f"Using exchange {config['exchange']['name']}")
if 'user_data_dir' in self.args and self.args.user_data_dir:
config.update({'user_data_dir': self.args.user_data_dir})
elif 'user_data_dir' not in config:
# Default to cwd/user_data (legacy option ...)
config.update({'user_data_dir': str(Path.cwd() / "user_data")})
# reset to user_data_dir so this contains the absolute path.
config['user_data_dir'] = create_userdata_dir(config['user_data_dir'], create_dir=False)
logger.info('Using user-data directory: %s ...', config['user_data_dir'])
if 'datadir' in self.args and self.args.datadir:
config.update({'datadir': create_datadir(config, self.args.datadir)})
else:
config.update({'datadir': create_datadir(config, None)})
logger.info('Using data directory: %s ...', config.get('datadir'))
def _process_optimize_options(self, config: Dict[str, Any]) -> None:
# This will override the strategy configuration
self._args_to_config(config, argname='ticker_interval',
logstring='Parameter -i/--ticker-interval detected ... '
'Using ticker_interval: {} ...')
self._args_to_config(config, argname='position_stacking',
logstring='Parameter --enable-position-stacking detected ...')
if 'use_max_market_positions' in self.args and not self.args.use_max_market_positions:
config.update({'use_max_market_positions': False})
logger.info('Parameter --disable-max-market-positions detected ...')
logger.info('max_open_trades set to unlimited ...')
elif 'max_open_trades' in self.args and self.args.max_open_trades:
config.update({'max_open_trades': self.args.max_open_trades})
logger.info('Parameter --max_open_trades detected, '
'overriding max_open_trades to: %s ...', config.get('max_open_trades'))
else:
logger.info('Using max_open_trades: %s ...', config.get('max_open_trades'))
self._args_to_config(config, argname='stake_amount',
logstring='Parameter --stake_amount detected, '
'overriding stake_amount to: {} ...')
self._args_to_config(config, argname='timerange',
logstring='Parameter --timerange detected: {} ...')
self._process_datadir_options(config)
self._args_to_config(config, argname='refresh_pairs',
logstring='Parameter -r/--refresh-pairs-cached detected ...',
deprecated_msg='-r/--refresh-pairs-cached will be removed soon.')
self._args_to_config(config, argname='strategy_list',
logstring='Using strategy list of {} Strategies', logfun=len)
self._args_to_config(config, argname='ticker_interval',
logstring='Overriding ticker interval with Command line argument')
self._args_to_config(config, argname='export',
logstring='Parameter --export detected: {} ...')
self._args_to_config(config, argname='exportfilename',
logstring='Storing backtest results to {} ...')
# Edge section:
if 'stoploss_range' in self.args and self.args.stoploss_range:
txt_range = eval(self.args.stoploss_range)
config['edge'].update({'stoploss_range_min': txt_range[0]})
config['edge'].update({'stoploss_range_max': txt_range[1]})
config['edge'].update({'stoploss_range_step': txt_range[2]})
logger.info('Parameter --stoplosses detected: %s ...', self.args.stoploss_range)
# Hyperopt section
self._args_to_config(config, argname='hyperopt',
logstring='Using Hyperopt file {}')
self._args_to_config(config, argname='hyperopt_path',
logstring='Using additional Hyperopt lookup path: {}')
self._args_to_config(config, argname='epochs',
logstring='Parameter --epochs detected ... '
'Will run Hyperopt with for {} epochs ...'
)
self._args_to_config(config, argname='spaces',
logstring='Parameter -s/--spaces detected: {}')
self._args_to_config(config, argname='print_all',
logstring='Parameter --print-all detected ...')
if 'print_colorized' in self.args and not self.args.print_colorized:
logger.info('Parameter --no-color detected ...')
config.update({'print_colorized': False})
else:
config.update({'print_colorized': True})
self._args_to_config(config, argname='print_json',
logstring='Parameter --print-json detected ...')
self._args_to_config(config, argname='hyperopt_jobs',
logstring='Parameter -j/--job-workers detected: {}')
self._args_to_config(config, argname='hyperopt_random_state',
logstring='Parameter --random-state detected: {}')
self._args_to_config(config, argname='hyperopt_min_trades',
logstring='Parameter --min-trades detected: {}')
self._args_to_config(config, argname='hyperopt_continue',
logstring='Hyperopt continue: {}')
self._args_to_config(config, argname='hyperopt_loss',
logstring='Using loss function: {}')
def _process_plot_options(self, config: Dict[str, Any]) -> None:
self._args_to_config(config, argname='pairs',
logstring='Using pairs {}')
self._args_to_config(config, argname='indicators1',
logstring='Using indicators1: {}')
self._args_to_config(config, argname='indicators2',
logstring='Using indicators2: {}')
self._args_to_config(config, argname='plot_limit',
logstring='Limiting plot to: {}')
self._args_to_config(config, argname='trade_source',
logstring='Using trades from: {}')
self._args_to_config(config, argname='erase',
logstring='Erase detected. Deleting existing data.')
self._args_to_config(config, argname='timeframes',
logstring='timeframes --timeframes: {}')
self._args_to_config(config, argname='days',
logstring='Detected --days: {}')
def _process_runmode(self, config: Dict[str, Any]) -> None:
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}.")
config.update({'runmode': self.runmode})
def _args_to_config(self, config: Dict[str, Any], argname: str,
logstring: str, logfun: Optional[Callable] = None,
deprecated_msg: Optional[str] = None) -> None:
"""
:param config: Configuration dictionary
:param argname: Argumentname in self.args - will be copied to config dict.
:param logstring: Logging String
:param logfun: logfun is applied to the configuration entry before passing
that entry to the log string using .format().
sample: logfun=len (prints the length of the found
configuration instead of the content)
"""
if argname in self.args and getattr(self.args, argname):
config.update({argname: getattr(self.args, argname)})
if logfun:
logger.info(logstring.format(logfun(config[argname])))
else:
logger.info(logstring.format(config[argname]))
if deprecated_msg:
warnings.warn(f"DEPRECATED: {deprecated_msg}", DeprecationWarning)
def _resolve_pairs_list(self, config: Dict[str, Any]) -> None:
"""
Helper for download script.
Takes first found:
* -p (pairs argument)
* --pairs-file
* whitelist from config
"""
if "pairs" in config:
return
if "pairs_file" in self.args and self.args.pairs_file:
pairs_file = Path(self.args.pairs_file)
logger.info(f'Reading pairs file "{pairs_file}".')
# Download pairs from the pairs file if no config is specified
# or if pairs file is specified explicitely
if not pairs_file.exists():
raise OperationalException(f'No pairs file found with path "{pairs_file}".')
with pairs_file.open('r') as f:
config['pairs'] = json_load(f)
config['pairs'].sort()
return
if "config" in self.args and self.args.config:
logger.info("Using pairlist from configuration.")
config['pairs'] = config.get('exchange', {}).get('pair_whitelist')
else:
# Fall back to /dl_path/pairs.json
pairs_file = Path(config['datadir']) / "pairs.json"
if pairs_file.exists():
with pairs_file.open('r') as f:
config['pairs'] = json_load(f)
if 'pairs' in config:
config['pairs'].sort()

View File

@@ -0,0 +1,50 @@
import logging
from typing import Any, Dict, Optional
from pathlib import Path
from freqtrade import OperationalException
logger = logging.getLogger(__name__)
def create_datadir(config: Dict[str, Any], datadir: Optional[str] = None) -> str:
folder = Path(datadir) if datadir else Path(f"{config['user_data_dir']}/data")
if not datadir:
# set datadir
exchange_name = config.get('exchange', {}).get('name').lower()
folder = folder.joinpath(exchange_name)
if not folder.is_dir():
folder.mkdir(parents=True)
logger.info(f'Created data directory: {datadir}')
return str(folder)
def create_userdata_dir(directory: str, create_dir=False) -> Path:
"""
Create userdata directory structure.
if create_dir is True, then the parent-directory will be created if it does not exist.
Sub-directories will always be created if the parent directory exists.
Raises OperationalException if given a non-existing directory.
:param directory: Directory to check
:param create_dir: Create directory if it does not exist.
:return: Path object containing the directory
"""
sub_dirs = ["backtest_results", "data", "hyperopts", "hyperopt_results", "plot", "strategies", ]
folder = Path(directory)
if not folder.is_dir():
if create_dir:
folder.mkdir(parents=True)
logger.info(f'Created user-data directory: {folder}')
else:
raise OperationalException(
f"Directory `{folder}` does not exist. "
"Please use `freqtrade create-userdir` to create a user directory")
# Create required subdirectories
for f in sub_dirs:
subfolder = folder / f
if not subfolder.is_dir():
subfolder.mkdir(parents=False)
return folder

View File

@@ -0,0 +1,33 @@
"""
This module contain functions to load the configuration file
"""
import rapidjson
import logging
import sys
from typing import Any, Dict
from freqtrade import OperationalException
logger = logging.getLogger(__name__)
CONFIG_PARSE_MODE = rapidjson.PM_COMMENTS | rapidjson.PM_TRAILING_COMMAS
def load_config_file(path: str) -> Dict[str, Any]:
"""
Loads a config file from the given path
:param path: path as str
:return: configuration as dictionary
"""
try:
# Read config from stdin if requested in the options
with open(path) if path != '-' else sys.stdin as file:
config = rapidjson.load(file, parse_mode=CONFIG_PARSE_MODE)
except FileNotFoundError:
raise OperationalException(
f'Config file "{path}" not found!'
' Please create a config file or check whether it exists.')
return config

View File

@@ -0,0 +1,70 @@
"""
This module contains the argument manager class
"""
import re
from typing import Optional
import arrow
class TimeRange():
"""
object defining timerange inputs.
[start/stop]type defines if [start/stop]ts shall be used.
if *type is None, don't use corresponding startvalue.
"""
def __init__(self, starttype: Optional[str] = None, stoptype: Optional[str] = None,
startts: int = 0, stopts: int = 0):
self.starttype: Optional[str] = starttype
self.stoptype: Optional[str] = stoptype
self.startts: int = startts
self.stopts: int = stopts
def __eq__(self, other):
"""Override the default Equals behavior"""
return (self.starttype == other.starttype and self.stoptype == other.stoptype
and self.startts == other.startts and self.stopts == other.stopts)
@staticmethod
def parse_timerange(text: Optional[str]):
"""
Parse the value of the argument --timerange to determine what is the range desired
:param text: value from --timerange
:return: Start and End range period
"""
if text is None:
return TimeRange(None, None, 0, 0)
syntax = [(r'^-(\d{8})$', (None, 'date')),
(r'^(\d{8})-$', ('date', None)),
(r'^(\d{8})-(\d{8})$', ('date', 'date')),
(r'^-(\d{10})$', (None, 'date')),
(r'^(\d{10})-$', ('date', None)),
(r'^(\d{10})-(\d{10})$', ('date', 'date')),
(r'^(-\d+)$', (None, 'line')),
(r'^(\d+)-$', ('line', None)),
(r'^(\d+)-(\d+)$', ('index', 'index'))]
for rex, stype in syntax:
# Apply the regular expression to text
match = re.match(rex, text)
if match: # Regex has matched
rvals = match.groups()
index = 0
start: int = 0
stop: int = 0
if stype[0]:
starts = rvals[index]
if stype[0] == 'date' and len(starts) == 8:
start = arrow.get(starts, 'YYYYMMDD').timestamp
else:
start = int(starts)
index += 1
if stype[1]:
stops = rvals[index]
if stype[1] == 'date' and len(stops) == 8:
stop = arrow.get(stops, 'YYYYMMDD').timestamp
else:
stop = int(stops)
return TimeRange(stype[0], stype[1], start, stop)
raise Exception('Incorrect syntax for timerange "%s"' % text)

View File

@@ -3,13 +3,15 @@
"""
bot constants
"""
DYNAMIC_WHITELIST = 20 # pairs
DEFAULT_CONFIG = 'config.json'
DEFAULT_EXCHANGE = 'bittrex'
PROCESS_THROTTLE_SECS = 5 # sec
TICKER_INTERVAL = 5 # min
DEFAULT_TICKER_INTERVAL = 5 # min
HYPEROPT_EPOCH = 100 # epochs
RETRY_TIMEOUT = 30 # sec
DEFAULT_STRATEGY = 'DefaultStrategy'
DEFAULT_HYPEROPT = 'DefaultHyperOpts'
DEFAULT_HYPEROPT_LOSS = 'DefaultHyperOptLoss'
DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite'
DEFAULT_DB_DRYRUN_URL = 'sqlite://'
UNLIMITED_STAKE_AMOUNT = 'unlimited'
@@ -19,23 +21,13 @@ REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']
ORDERTYPE_POSSIBILITIES = ['limit', 'market']
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList']
DRY_RUN_WALLET = 999.9
TICKER_INTERVAL_MINUTES = {
'1m': 1,
'3m': 3,
'5m': 5,
'15m': 15,
'30m': 30,
'1h': 60,
'2h': 120,
'4h': 240,
'6h': 360,
'8h': 480,
'12h': 720,
'1d': 1440,
'3d': 4320,
'1w': 10080,
}
TICKER_INTERVALS = [
'1m', '3m', '5m', '15m', '30m',
'1h', '2h', '4h', '6h', '8h', '12h',
'1d', '3d', '1w',
]
SUPPORTED_FIAT = [
"AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK",
@@ -45,12 +37,26 @@ SUPPORTED_FIAT = [
"BTC", "XBT", "ETH", "XRP", "LTC", "BCH", "USDT"
]
MINIMAL_CONFIG = {
'stake_currency': '',
'dry_run': True,
'exchange': {
'name': '',
'key': '',
'secret': '',
'pair_whitelist': [],
'ccxt_async_config': {
'enableRateLimit': True,
}
}
}
# Required json-schema for user specified config
CONF_SCHEMA = {
'type': 'object',
'properties': {
'max_open_trades': {'type': 'integer', 'minimum': -1},
'ticker_interval': {'type': 'string', 'enum': list(TICKER_INTERVAL_MINUTES.keys())},
'ticker_interval': {'type': 'string', 'enum': TICKER_INTERVALS},
'stake_currency': {'type': 'string', 'enum': ['BTC', 'XBT', 'ETH', 'USDT', 'EUR', 'USD']},
'stake_amount': {
"type": ["number", "string"],
@@ -59,6 +65,7 @@ CONF_SCHEMA = {
},
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
'dry_run': {'type': 'boolean'},
'dry_run_wallet': {'type': 'number'},
'process_only_new_candles': {'type': 'boolean'},
'minimal_roi': {
'type': 'object',
@@ -67,10 +74,12 @@ CONF_SCHEMA = {
},
'minProperties': 1
},
'amount_reserve_percent': {'type': 'number', 'minimum': 0.0, 'maximum': 0.5},
'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True},
'trailing_stop': {'type': 'boolean'},
'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1},
'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1},
'trailing_only_offset_is_reached': {'type': 'boolean'},
'unfilledtimeout': {
'type': 'object',
'properties': {
@@ -162,6 +171,21 @@ CONF_SCHEMA = {
'webhookstatus': {'type': 'object'},
},
},
'api_server': {
'type': 'object',
'properties': {
'enabled': {'type': 'boolean'},
'listen_ip_address': {'format': 'ipv4'},
'listen_port': {
'type': 'integer',
"minimum": 1024,
"maximum": 65535
},
'username': {'type': 'string'},
'password': {'type': 'string'},
},
'required': ['enabled', 'listen_ip_address', 'listen_port', 'username', 'password']
},
'db_url': {'type': 'string'},
'initial_state': {'type': 'string', 'enum': ['running', 'stopped']},
'forcebuy_enable': {'type': 'boolean'},
@@ -169,7 +193,8 @@ CONF_SCHEMA = {
'type': 'object',
'properties': {
'process_throttle_secs': {'type': 'number'},
'interval': {'type': 'integer'}
'interval': {'type': 'integer'},
'sd_notify': {'type': 'boolean'},
}
}
},
@@ -178,10 +203,10 @@ CONF_SCHEMA = {
'type': 'object',
'properties': {
'name': {'type': 'string'},
'sandbox': {'type': 'boolean'},
'key': {'type': 'string'},
'secret': {'type': 'string'},
'password': {'type': 'string'},
'sandbox': {'type': 'boolean', 'default': False},
'key': {'type': 'string', 'default': ''},
'secret': {'type': 'string', 'default': ''},
'password': {'type': 'string', 'default': ''},
'uid': {'type': 'string'},
'pair_whitelist': {
'type': 'array',
@@ -200,10 +225,11 @@ CONF_SCHEMA = {
'uniqueItems': True
},
'outdated_offset': {'type': 'integer', 'minimum': 1},
'markets_refresh_interval': {'type': 'integer'},
'ccxt_config': {'type': 'object'},
'ccxt_async_config': {'type': 'object'}
},
'required': ['name', 'key', 'secret', 'pair_whitelist']
'required': ['name', 'pair_whitelist']
},
'edge': {
'type': 'object',

View File

@@ -0,0 +1,165 @@
"""
Helpers when analyzing backtest data
"""
import logging
from pathlib import Path
from typing import Dict
import numpy as np
import pandas as pd
import pytz
from freqtrade import persistence
from freqtrade.misc import json_load
from freqtrade.persistence import Trade
logger = logging.getLogger(__name__)
# must align with columns in backtest.py
BT_DATA_COLUMNS = ["pair", "profitperc", "open_time", "close_time", "index", "duration",
"open_rate", "close_rate", "open_at_end", "sell_reason"]
def load_backtest_data(filename) -> pd.DataFrame:
"""
Load backtest data file.
:param filename: pathlib.Path object, or string pointing to the file.
:return: a dataframe with the analysis results
"""
if isinstance(filename, str):
filename = Path(filename)
if not filename.is_file():
raise ValueError(f"File {filename} does not exist.")
with filename.open() as file:
data = json_load(file)
df = pd.DataFrame(data, columns=BT_DATA_COLUMNS)
df['open_time'] = pd.to_datetime(df['open_time'],
unit='s',
utc=True,
infer_datetime_format=True
)
df['close_time'] = pd.to_datetime(df['close_time'],
unit='s',
utc=True,
infer_datetime_format=True
)
df['profitabs'] = df['close_rate'] - df['open_rate']
df = df.sort_values("open_time").reset_index(drop=True)
return df
def evaluate_result_multi(results: pd.DataFrame, freq: str, max_open_trades: int) -> pd.DataFrame:
"""
Find overlapping trades by expanding each trade once per period it was open
and then counting overlaps
:param results: Results Dataframe - can be loaded
:param freq: Frequency used for the backtest
:param max_open_trades: parameter max_open_trades used during backtest run
:return: dataframe with open-counts per time-period in freq
"""
dates = [pd.Series(pd.date_range(row[1].open_time, row[1].close_time, freq=freq))
for row in results[['open_time', 'close_time']].iterrows()]
deltas = [len(x) for x in dates]
dates = pd.Series(pd.concat(dates).values, name='date')
df2 = pd.DataFrame(np.repeat(results.values, deltas, axis=0), columns=results.columns)
df2 = pd.concat([dates, df2], axis=1)
df2 = df2.set_index('date')
df_final = df2.resample(freq)[['pair']].count()
return df_final[df_final['pair'] > max_open_trades]
def load_trades_from_db(db_url: str) -> pd.DataFrame:
"""
Load trades from a DB (using dburl)
:param db_url: Sqlite url (default format sqlite:///tradesv3.dry-run.sqlite)
:return: Dataframe containing Trades
"""
trades: pd.DataFrame = pd.DataFrame([], columns=BT_DATA_COLUMNS)
persistence.init(db_url, clean_open_orders=False)
columns = ["pair", "open_time", "close_time", "profit", "profitperc",
"open_rate", "close_rate", "amount", "duration", "sell_reason",
"fee_open", "fee_close", "open_rate_requested", "close_rate_requested",
"stake_amount", "max_rate", "min_rate", "id", "exchange",
"stop_loss", "initial_stop_loss", "strategy", "ticker_interval"]
trades = pd.DataFrame([(t.pair,
t.open_date.replace(tzinfo=pytz.UTC),
t.close_date.replace(tzinfo=pytz.UTC) if t.close_date else None,
t.calc_profit(), t.calc_profit_percent(),
t.open_rate, t.close_rate, t.amount,
(t.close_date.timestamp() - t.open_date.timestamp()
if t.close_date else None),
t.sell_reason,
t.fee_open, t.fee_close,
t.open_rate_requested,
t.close_rate_requested,
t.stake_amount,
t.max_rate,
t.min_rate,
t.id, t.exchange,
t.stop_loss, t.initial_stop_loss,
t.strategy, t.ticker_interval
)
for t in Trade.query.all()],
columns=columns)
return trades
def load_trades(config) -> pd.DataFrame:
"""
Based on configuration option "trade_source":
* loads data from DB (using `db_url`)
* loads data from backtestfile (using `exportfilename`)
"""
if config["trade_source"] == "DB":
return load_trades_from_db(config["db_url"])
elif config["trade_source"] == "file":
return load_backtest_data(Path(config["exportfilename"]))
def extract_trades_of_period(dataframe: pd.DataFrame, trades: pd.DataFrame) -> pd.DataFrame:
"""
Compare trades and backtested pair DataFrames to get trades performed on backtested period
:return: the DataFrame of a trades of period
"""
trades = trades.loc[(trades['open_time'] >= dataframe.iloc[0]['date']) &
(trades['close_time'] <= dataframe.iloc[-1]['date'])]
return trades
def combine_tickers_with_mean(tickers: Dict[str, pd.DataFrame], column: str = "close"):
"""
Combine multiple dataframes "column"
:param tickers: 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['mean'] = df_comb.mean(axis=1)
return df_comb
def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str) -> pd.DataFrame:
"""
Adds a column `col_name` with the cumulative profit for the given trades array.
:param df: DataFrame with date index
:param trades: DataFrame containing trades (requires columns close_time and profitperc)
:return: Returns df with one additional column, col_name, containing the cumulative profit.
"""
df[col_name] = trades.set_index('close_time')['profitperc'].cumsum()
# Set first value to 0
df.loc[df.iloc[0].name, col_name] = 0
# FFill to get continuous
df[col_name] = df[col_name].ffill()
return df

View File

@@ -2,22 +2,25 @@
Functions to convert data from one format to another
"""
import logging
import pandas as pd
from pandas import DataFrame, to_datetime
from freqtrade.constants import TICKER_INTERVAL_MINUTES
logger = logging.getLogger(__name__)
def parse_ticker_dataframe(ticker: list, ticker_interval: str,
fill_missing: bool = True) -> DataFrame:
def parse_ticker_dataframe(ticker: list, ticker_interval: 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
:param ticker_interval: ticker_interval (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
"""
logger.debug("Parsing tickerlist to dataframe")
@@ -43,21 +46,25 @@ def parse_ticker_dataframe(ticker: list, ticker_interval: str,
'close': 'last',
'volume': 'max',
})
frame.drop(frame.tail(1).index, inplace=True) # eliminate partial candle
# eliminate partial candle
if drop_incomplete:
frame.drop(frame.tail(1).index, inplace=True)
logger.debug('Dropping last candle')
if fill_missing:
return ohlcv_fill_up_missing_data(frame, ticker_interval)
return ohlcv_fill_up_missing_data(frame, ticker_interval, pair)
else:
return frame
def ohlcv_fill_up_missing_data(dataframe: DataFrame, ticker_interval: str) -> DataFrame:
def ohlcv_fill_up_missing_data(dataframe: DataFrame, ticker_interval: str, pair: str) -> DataFrame:
"""
Fills up missing data with 0 volume rows,
using the previous close as price for "open", "high" "low" and "close", volume is set to 0
"""
from freqtrade.exchange import timeframe_to_minutes
ohlc_dict = {
'open': 'first',
'high': 'max',
@@ -65,9 +72,9 @@ def ohlcv_fill_up_missing_data(dataframe: DataFrame, ticker_interval: str) -> Da
'close': 'last',
'volume': 'sum'
}
tick_mins = TICKER_INTERVAL_MINUTES[ticker_interval]
ticker_minutes = timeframe_to_minutes(ticker_interval)
# Resample to create "NAN" values
df = dataframe.resample(f'{tick_mins}min', on='date').agg(ohlc_dict)
df = dataframe.resample(f'{ticker_minutes}min', on='date').agg(ohlc_dict)
# Forwardfill close for missing columns
df['close'] = df['close'].fillna(method='ffill')
@@ -78,7 +85,10 @@ def ohlcv_fill_up_missing_data(dataframe: DataFrame, ticker_interval: str) -> Da
'low': df['close'],
})
df.reset_index(inplace=True)
logger.debug(f"Missing data fillup: before: {len(dataframe)} - after: {len(df)}")
len_before = len(dataframe)
len_after = len(df)
if len_before != len_after:
logger.info(f"Missing data fillup for {pair}: before: {len_before} - after: {len_after}")
return df

View File

@@ -17,7 +17,7 @@ from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
class DataProvider(object):
class DataProvider():
def __init__(self, config: dict, exchange: Exchange) -> None:
self._config = config
@@ -37,43 +37,56 @@ class DataProvider(object):
@property
def available_pairs(self) -> List[Tuple[str, str]]:
"""
Return a list of tuples containing pair, tick_interval for which data is currently cached.
Return a list of tuples containing pair, ticker_interval for which data is currently cached.
Should be whitelist + open trades.
"""
return list(self._exchange._klines.keys())
def ohlcv(self, pair: str, tick_interval: str = None, copy: bool = True) -> DataFrame:
def ohlcv(self, pair: str, ticker_interval: str = None, copy: bool = True) -> DataFrame:
"""
get ohlcv data for the given pair as DataFrame
Please check `available_pairs` to verify which pairs are currently cached.
Get 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 tick_interval: ticker_interval to get pair for
:param copy: copy dataframe before returning.
Use false only for RO operations (where the dataframe is not modified)
:param ticker_interval: ticker interval 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)
"""
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
if tick_interval:
pairtick = (pair, tick_interval)
else:
pairtick = (pair, self._config['ticker_interval'])
return self._exchange.klines(pairtick, copy=copy)
return self._exchange.klines((pair, ticker_interval or self._config['ticker_interval']),
copy=copy)
else:
return DataFrame()
def historic_ohlcv(self, pair: str, ticker_interval: str) -> DataFrame:
def historic_ohlcv(self, pair: str, ticker_interval: str = None) -> DataFrame:
"""
get stored historic ohlcv data
Get stored historic ohlcv data
:param pair: pair to get the data for
:param tick_interval: ticker_interval to get pair for
:param ticker_interval: ticker interval to get data for
"""
return load_pair_history(pair=pair,
ticker_interval=ticker_interval,
ticker_interval=ticker_interval or self._config['ticker_interval'],
refresh_pairs=False,
datadir=Path(self._config['datadir']) if self._config.get(
'datadir') else None
)
def get_pair_dataframe(self, pair: str, ticker_interval: str = None) -> DataFrame:
"""
Return pair ohlcv data, either live or cached historical -- depending
on the runmode.
:param pair: pair to get the data for
:param ticker_interval: ticker interval to get data for
"""
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
# Get live ohlcv data.
data = self.ohlcv(pair=pair, ticker_interval=ticker_interval)
else:
# Get historic ohlcv data (cached on disk).
data = self.historic_ohlcv(pair=pair, ticker_interval=ticker_interval)
if len(data) == 0:
logger.warning(f"No data found for ({pair}, {ticker_interval}).")
return data
def ticker(self, pair: str):
"""
Return last ticker data
@@ -81,12 +94,14 @@ class DataProvider(object):
# TODO: Implement me
pass
def orderbook(self, pair: str, max: int):
def orderbook(self, pair: str, maximum: int):
"""
return latest orderbook data
:param pair: pair to get the data for
:param maximum: Maximum number of orderbook entries to query
:return: dict including bids/asks with a total of `maximum` entries.
"""
# TODO: Implement me
pass
return self._exchange.get_order_book(pair, maximum)
@property
def runmode(self) -> RunMode:

View File

@@ -1,21 +1,24 @@
"""
Handle historic data (ohlcv).
includes:
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 datetime import datetime
from pathlib import Path
from typing import Optional, List, Dict, Tuple, Any
from typing import Any, Dict, List, Optional, Tuple
import arrow
from pandas import DataFrame
from freqtrade import misc, constants, OperationalException
from freqtrade import OperationalException, misc
from freqtrade.configuration import TimeRange
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.exchange import Exchange
from freqtrade.arguments import TimeRange
from freqtrade.exchange import Exchange, timeframe_to_minutes
logger = logging.getLogger(__name__)
@@ -40,7 +43,7 @@ def trim_tickerlist(tickerlist: List[Dict], timerange: TimeRange) -> List[Dict]:
start_index += 1
if timerange.stoptype == 'line':
start_index = len(tickerlist) + timerange.stopts
start_index = max(len(tickerlist) + timerange.stopts, 0)
if timerange.stoptype == 'index':
stop_index = timerange.stopts
elif timerange.stoptype == 'date':
@@ -54,52 +57,60 @@ def trim_tickerlist(tickerlist: List[Dict], timerange: TimeRange) -> List[Dict]:
return tickerlist[start_index:stop_index]
def load_tickerdata_file(
datadir: Optional[Path], pair: str,
ticker_interval: str,
def load_tickerdata_file(datadir: Optional[Path], pair: str, ticker_interval: str,
timerange: Optional[TimeRange] = None) -> Optional[list]:
"""
Load a pair from file, either .json.gz or .json
:return tickerlist or None if unsuccesful
:return: tickerlist or None if unsuccesful
"""
path = make_testdata_path(datadir)
pair_s = pair.replace('/', '_')
file = path.joinpath(f'{pair_s}-{ticker_interval}.json')
pairdata = misc.file_load_json(file)
filename = pair_data_filename(datadir, pair, ticker_interval)
pairdata = misc.file_load_json(filename)
if not pairdata:
return None
return []
if timerange:
pairdata = trim_tickerlist(pairdata, timerange)
return pairdata
def store_tickerdata_file(datadir: Optional[Path], pair: str,
ticker_interval: str, data: list, is_zip: bool = False):
"""
Stores tickerdata to file
"""
filename = pair_data_filename(datadir, pair, ticker_interval)
misc.file_dump_json(filename, data, is_zip=is_zip)
def load_pair_history(pair: str,
ticker_interval: str,
datadir: Optional[Path],
timerange: TimeRange = TimeRange(None, None, 0, 0),
refresh_pairs: bool = False,
exchange: Optional[Exchange] = None,
fill_up_missing: bool = True
fill_up_missing: bool = True,
drop_incomplete: bool = True
) -> DataFrame:
"""
Loads cached ticker history for the given pair.
:param pair: Pair to load data for
:param ticker_interval: Ticker-interval (e.g. "5m")
:param datadir: Path to the data storage location.
:param timerange: Limit data to be loaded to this timerange
:param refresh_pairs: Refresh pairs from exchange.
(Note: Requires exchange to be passed as well.)
:param exchange: Exchange object (needed when using "refresh_pairs")
:param fill_up_missing: Fill missing values with "No action"-candles
:param drop_incomplete: Drop last candle assuming it may be incomplete.
:return: DataFrame with ohlcv data
"""
# If the user force the refresh of pairs
# The user forced the refresh of pairs
if refresh_pairs:
if not exchange:
raise OperationalException("Exchange needs to be initialized when "
"calling load_data with refresh_pairs=True")
logger.info('Download data for pair and store them in %s', datadir)
download_pair_history(datadir=datadir,
exchange=exchange,
pair=pair,
tick_interval=ticker_interval,
ticker_interval=ticker_interval,
timerange=timerange)
pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
@@ -112,11 +123,15 @@ def load_pair_history(pair: str,
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'))
return parse_ticker_dataframe(pairdata, ticker_interval, fill_up_missing)
return parse_ticker_dataframe(pairdata, ticker_interval, pair=pair,
fill_missing=fill_up_missing,
drop_incomplete=drop_incomplete)
else:
logger.warning('No data for pair: "%s", Interval: %s. '
'Use --refresh-pairs-cached to download the data',
pair, ticker_interval)
logger.warning(
f'No history data for pair: "{pair}", interval: {ticker_interval}. '
'Use --refresh-pairs-cached option or `freqtrade download-data` '
'script to download the data'
)
return None
@@ -126,12 +141,25 @@ def load_data(datadir: Optional[Path],
refresh_pairs: bool = False,
exchange: Optional[Exchange] = None,
timerange: TimeRange = TimeRange(None, None, 0, 0),
fill_up_missing: bool = True) -> Dict[str, DataFrame]:
fill_up_missing: bool = True,
live: bool = False
) -> Dict[str, DataFrame]:
"""
Loads ticker history data for a list of pairs the given parameters
:return: dict(<pair>:<tickerlist>)
"""
result = {}
result: Dict[str, DataFrame] = {}
if live:
if exchange:
logger.info('Live: Downloading data for all defined pairs ...')
exchange.refresh_latest_ohlcv([(pair, ticker_interval) for pair in pairs])
result = {key[0]: value for key, value in exchange._klines.items() if value is not None}
else:
raise OperationalException(
"Exchange needs to be initialized when using live data."
)
else:
logger.info('Using local backtesting data ...')
for pair in pairs:
hist = load_pair_history(pair=pair, ticker_interval=ticker_interval,
@@ -149,11 +177,21 @@ def make_testdata_path(datadir: Optional[Path]) -> Path:
return datadir or (Path(__file__).parent.parent / "tests" / "testdata").resolve()
def load_cached_data_for_updating(filename: Path, tick_interval: str,
def pair_data_filename(datadir: Optional[Path], pair: str, ticker_interval: str) -> Path:
path = make_testdata_path(datadir)
pair_s = pair.replace("/", "_")
filename = path.joinpath(f'{pair_s}-{ticker_interval}.json')
return filename
def load_cached_data_for_updating(datadir: Optional[Path], pair: str, ticker_interval: str,
timerange: Optional[TimeRange]) -> Tuple[List[Any],
Optional[int]]:
"""
Load cached data and choose what part of the data should be updated
Load cached data to download more data.
If timerange is passed in, checks wether data from an before the stored data will be downloaded.
If that's the case than what's available should be completely overwritten.
Only used by download_pair_history().
"""
since_ms = None
@@ -163,13 +201,12 @@ def load_cached_data_for_updating(filename: Path, tick_interval: str,
if timerange.starttype == 'date':
since_ms = timerange.startts * 1000
elif timerange.stoptype == 'line':
num_minutes = timerange.stopts * constants.TICKER_INTERVAL_MINUTES[tick_interval]
num_minutes = timerange.stopts * timeframe_to_minutes(ticker_interval)
since_ms = arrow.utcnow().shift(minutes=num_minutes).timestamp * 1000
# read the cached file
if filename.is_file():
with open(filename, "rt") as file:
data = misc.json_load(file)
# Intentionally don't pass timerange in - since we need to load the full dataset.
data = load_tickerdata_file(datadir, pair, ticker_interval)
# remove the last item, could be incomplete candle
if data:
data.pop()
@@ -188,9 +225,9 @@ def load_cached_data_for_updating(filename: Path, tick_interval: str,
def download_pair_history(datadir: Optional[Path],
exchange: Exchange,
exchange: Optional[Exchange],
pair: str,
tick_interval: str = '5m',
ticker_interval: str = '5m',
timerange: Optional[TimeRange] = None) -> bool:
"""
Download the latest ticker intervals from the exchange for the pair passed in parameters
@@ -199,37 +236,81 @@ def download_pair_history(datadir: Optional[Path],
the full data will be redownloaded
Based on @Rybolov work: https://github.com/rybolov/freqtrade-data
:param pair: pair to download
:param tick_interval: ticker interval
:param ticker_interval: ticker interval
:param timerange: range of time to download
:return: bool with success state
"""
if not exchange:
raise OperationalException(
"Exchange needs to be initialized when downloading pair history data"
)
try:
path = make_testdata_path(datadir)
filepair = pair.replace("/", "_")
filename = path.joinpath(f'{filepair}-{tick_interval}.json')
logger.info(
f'Download history data for pair: "{pair}", interval: {ticker_interval} '
f'and store in {datadir}.'
)
logger.info('Download the pair: "%s", Interval: %s', pair, tick_interval)
data, since_ms = load_cached_data_for_updating(filename, tick_interval, timerange)
data, since_ms = load_cached_data_for_updating(datadir, pair, ticker_interval, timerange)
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')
# Default since_ms to 30 days if nothing is given
new_data = exchange.get_history(pair=pair, tick_interval=tick_interval,
new_data = exchange.get_historic_ohlcv(pair=pair, ticker_interval=ticker_interval,
since_ms=since_ms if since_ms
else
int(arrow.utcnow().shift(days=-30).float_timestamp) * 1000)
int(arrow.utcnow().shift(
days=-30).float_timestamp) * 1000)
data.extend(new_data)
logger.debug("New Start: %s", misc.format_ms_time(data[0][0]))
logger.debug("New End: %s", misc.format_ms_time(data[-1][0]))
misc.file_dump_json(filename, data)
store_tickerdata_file(datadir, pair, ticker_interval, data=data)
return True
except BaseException:
logger.info('Failed to download the pair: "%s", Interval: %s',
pair, tick_interval)
except Exception as e:
logger.error(
f'Failed to download history data for pair: "{pair}", interval: {ticker_interval}. '
f'Error: {e}'
)
return False
def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]:
"""
Get the maximum timeframe for the given backtest data
:param data: dictionary with preprocessed backtesting data
:return: tuple containing min_date, max_date
"""
timeframe = [
(arrow.get(frame['date'].min()), arrow.get(frame['date'].max()))
for frame in data.values()
]
return min(timeframe, key=operator.itemgetter(0))[0], \
max(timeframe, key=operator.itemgetter(1))[1]
def validate_backtest_data(data: DataFrame, pair: str, min_date: datetime,
max_date: datetime, ticker_interval_mins: int) -> bool:
"""
Validates preprocessed backtesting data for missing values and shows warnings about it that.
:param data: preprocessed backtesting data (as DataFrame)
:param pair: pair used for log output.
:param min_date: start-date of the data
:param max_date: end-date of the data
:param ticker_interval_mins: ticker interval in minutes
"""
# total difference in minutes / interval-minutes
expected_frames = int((max_date - min_date).total_seconds() // 60 // ticker_interval_mins)
found_missing = False
dflen = len(data)
if dflen < expected_frames:
found_missing = True
logger.warning("%s has missing frames: expected %s, got %s, that's %s missing values",
pair, expected_frames, dflen, expected_frames - dflen)
return found_missing

View File

@@ -10,10 +10,8 @@ import utils_find_1st as utf1st
from pandas import DataFrame
from freqtrade import constants, OperationalException
from freqtrade.arguments import Arguments
from freqtrade.arguments import TimeRange
from freqtrade.configuration import TimeRange
from freqtrade.data import history
from freqtrade.optimize import get_timeframe
from freqtrade.strategy.interface import SellType
@@ -47,11 +45,6 @@ class Edge():
self.config = config
self.exchange = exchange
self.strategy = strategy
self.ticker_interval = self.strategy.ticker_interval
self.tickerdata_to_dataframe = self.strategy.tickerdata_to_dataframe
self.get_timeframe = get_timeframe
self.advise_sell = self.strategy.advise_sell
self.advise_buy = self.strategy.advise_buy
self.edge_config = self.config.get('edge', {})
self._cached_pairs: Dict[str, Any] = {} # Keeps a list of pairs
@@ -82,7 +75,7 @@ class Edge():
self._stoploss_range_step
)
self._timerange: TimeRange = Arguments.parse_timerange("%s-" % arrow.now().shift(
self._timerange: TimeRange = TimeRange.parse_timerange("%s-" % arrow.now().shift(
days=-1 * self._since_number_of_days).format('YYYYMMDD'))
self.fee = self.exchange.get_fee()
@@ -102,7 +95,7 @@ class Edge():
data = history.load_data(
datadir=Path(self.config['datadir']) if self.config.get('datadir') else None,
pairs=pairs,
ticker_interval=self.ticker_interval,
ticker_interval=self.strategy.ticker_interval,
refresh_pairs=self._refresh_pairs,
exchange=self.exchange,
timerange=self._timerange
@@ -114,10 +107,10 @@ class Edge():
logger.critical("No data found. Edge is stopped ...")
return False
preprocessed = self.tickerdata_to_dataframe(data)
preprocessed = self.strategy.tickerdata_to_dataframe(data)
# Print timeframe
min_date, max_date = self.get_timeframe(preprocessed)
min_date, max_date = history.get_timeframe(preprocessed)
logger.info(
'Measuring data from %s up to %s (%s days) ...',
min_date.isoformat(),
@@ -132,13 +125,14 @@ class Edge():
pair_data = pair_data.sort_values(by=['date'])
pair_data = pair_data.reset_index(drop=True)
ticker_data = self.advise_sell(
self.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy()
ticker_data = 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)
# If no trade found then exit
if len(trades) == 0:
logger.info("No trades found.")
return False
# Fill missing, calculable columns, profit, duration , abs etc.
@@ -203,6 +197,22 @@ class Edge():
return self._final_pairs
def accepted_pairs(self) -> list:
"""
return a list of accepted pairs along with their winrate, expectancy and stoploss
"""
final = []
for pair, info in self._cached_pairs.items():
if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \
info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)):
final.append({
'Pair': pair,
'Winrate': info.winrate,
'Expectancy': info.expectancy,
'Stoploss': info.stoploss,
})
return final
def _fill_calculable_fields(self, result: DataFrame) -> DataFrame:
"""
The result frame contains a number of columns that are calculable
@@ -351,26 +361,31 @@ class Edge():
return result
def _detect_next_stop_or_sell_point(self, buy_column, sell_column, date_column,
ohlc_columns, stoploss, pair, start_point=0):
ohlc_columns, stoploss, pair):
"""
Iterate through ohlc_columns recursively in order to find the next trade
Iterate through ohlc_columns in order to find the next trade
Next trade opens from the first buy signal noticed to
The sell or stoploss signal after it.
It then calls itself cutting OHLC, buy_column, sell_colum and date_column
Cut from (the exit trade index) + 1
It then cuts OHLC, buy_column, sell_column and date_column.
Cut from (the exit trade index) + 1.
Author: https://github.com/mishaker
"""
result: list = []
start_point = 0
while True:
open_trade_index = utf1st.find_1st(buy_column, 1, utf1st.cmp_equal)
# return empty if we don't find trade entry (i.e. buy==1) or
# we find a buy but at the of array
# Return empty if we don't find trade entry (i.e. buy==1) or
# we find a buy but at the end of array
if open_trade_index == -1 or open_trade_index == len(buy_column) - 1:
return []
break
else:
open_trade_index += 1 # when a buy signal is seen,
# When a buy signal is seen,
# 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]
@@ -395,19 +410,19 @@ class Edge():
# It is not interesting for Edge to consider it so we simply ignore the trade
# And stop iterating there is no more entry
if stop_index == sell_index == float('inf'):
return []
break
if stop_index <= sell_index:
exit_index = open_trade_index + stop_index
exit_type = SellType.STOP_LOSS
exit_price = stop_price
elif stop_index > sell_index:
# if exit is SELL then we exit at the next candle
# If exit is SELL then we exit at the next candle
exit_index = open_trade_index + sell_index + 1
# check if we have the next candle
# Check if we have the next candle
if len(ohlc_columns) - 1 < exit_index:
return []
break
exit_type = SellType.SELL_SIGNAL
exit_price = ohlc_columns[exit_index, 0]
@@ -428,14 +443,11 @@ class Edge():
result.append(trade)
# Calling again the same function recursively but giving
# it a view of exit_index till the end of array
return result + self._detect_next_stop_or_sell_point(
buy_column[exit_index:],
sell_column[exit_index:],
date_column[exit_index:],
ohlc_columns[exit_index:],
stoploss,
pair,
(start_point + exit_index)
)
# Giving a view of exit_index till the end of array
buy_column = buy_column[exit_index:]
sell_column = sell_column[exit_index:]
date_column = date_column[exit_index:]
ohlc_columns = ohlc_columns[exit_index:]
start_point += exit_index
return result

View File

@@ -1,730 +1,13 @@
# pragma pylint: disable=W0603
""" Cryptocurrency Exchanges support """
import logging
import inspect
from random import randint
from typing import List, Dict, Tuple, Any, Optional
from datetime import datetime
from math import floor, ceil
import arrow
import asyncio
import ccxt
import ccxt.async_support as ccxt_async
from pandas import DataFrame
from freqtrade import constants, OperationalException, DependencyException, TemporaryError
from freqtrade.data.converter import parse_ticker_dataframe
logger = logging.getLogger(__name__)
API_RETRY_COUNT = 4
# Urls to exchange markets, insert quote and base with .format()
_EXCHANGE_URLS = {
ccxt.bittrex.__name__: '/Market/Index?MarketName={quote}-{base}',
ccxt.binance.__name__: '/tradeDetail.html?symbol={base}_{quote}'
}
def retrier_async(f):
async def wrapper(*args, **kwargs):
count = kwargs.pop('count', API_RETRY_COUNT)
try:
return await f(*args, **kwargs)
except (TemporaryError, DependencyException) as ex:
logger.warning('%s() returned exception: "%s"', f.__name__, ex)
if count > 0:
count -= 1
kwargs.update({'count': count})
logger.warning('retrying %s() still for %s times', f.__name__, count)
return await wrapper(*args, **kwargs)
else:
logger.warning('Giving up retrying: %s()', f.__name__)
raise ex
return wrapper
def retrier(f):
def wrapper(*args, **kwargs):
count = kwargs.pop('count', API_RETRY_COUNT)
try:
return f(*args, **kwargs)
except (TemporaryError, DependencyException) as ex:
logger.warning('%s() returned exception: "%s"', f.__name__, ex)
if count > 0:
count -= 1
kwargs.update({'count': count})
logger.warning('retrying %s() still for %s times', f.__name__, count)
return wrapper(*args, **kwargs)
else:
logger.warning('Giving up retrying: %s()', f.__name__)
raise ex
return wrapper
class Exchange(object):
_conf: Dict = {}
def __init__(self, config: dict) -> None:
"""
Initializes this module with the given config,
it does basic validation whether the specified
exchange and pairs are valid.
:return: None
"""
self._conf.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] = {}
# Holds candles
self._klines: Dict[Tuple[str, str], DataFrame] = {}
# Holds all open sell orders for dry_run
self._dry_run_open_orders: Dict[str, Any] = {}
if config['dry_run']:
logger.info('Instance is running with dry_run enabled')
exchange_config = config['exchange']
self._api: ccxt.Exchange = self._init_ccxt(
exchange_config, ccxt_kwargs=exchange_config.get('ccxt_config'))
self._api_async: ccxt_async.Exchange = self._init_ccxt(
exchange_config, ccxt_async, ccxt_kwargs=exchange_config.get('ccxt_async_config'))
logger.info('Using Exchange "%s"', self.name)
self.markets = self._load_markets()
# Check if all pairs are available
self.validate_pairs(config['exchange']['pair_whitelist'])
self.validate_ordertypes(config.get('order_types', {}))
self.validate_order_time_in_force(config.get('order_time_in_force', {}))
if config.get('ticker_interval'):
# Check if timeframe is available
self.validate_timeframes(config['ticker_interval'])
def __del__(self):
"""
Destructor - clean up async stuff
"""
logger.debug("Exchange object destroyed, closing async loop")
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,
ccxt_kwargs: dict = None) -> ccxt.Exchange:
"""
Initialize ccxt with given config and return valid
ccxt instance.
"""
# Find matching class for the given exchange name
name = exchange_config['name']
if name not in ccxt_module.exchanges:
raise OperationalException(f'Exchange {name} is not supported')
ex_config = {
'apiKey': exchange_config.get('key'),
'secret': exchange_config.get('secret'),
'password': exchange_config.get('password'),
'uid': exchange_config.get('uid', ''),
'enableRateLimit': exchange_config.get('ccxt_rate_limit', True)
}
if ccxt_kwargs:
logger.info('Applying additional ccxt config: %s', ccxt_kwargs)
ex_config.update(ccxt_kwargs)
try:
api = getattr(ccxt_module, name.lower())(ex_config)
except (KeyError, AttributeError):
raise OperationalException(f'Exchange {name} is not supported')
self.set_sandbox(api, exchange_config, name)
return api
@property
def name(self) -> str:
"""exchange Name (from ccxt)"""
return self._api.name
@property
def id(self) -> str:
"""exchange ccxt id"""
return self._api.id
def klines(self, pair_interval: Tuple[str, str], copy=True) -> DataFrame:
# create key tuple
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):
if exchange_config.get('sandbox'):
if api.urls.get('test'):
api.urls['api'] = api.urls['test']
logger.info("Enabled Sandbox API on %s", name)
else:
logger.warning(name, "No Sandbox URL in CCXT, exiting. "
"Please check your config.json")
raise OperationalException(f'Exchange {name} does not provide a sandbox api')
def _load_async_markets(self) -> None:
try:
if self._api_async:
asyncio.get_event_loop().run_until_complete(self._api_async.load_markets())
except ccxt.BaseError as e:
logger.warning('Could not load async markets. Reason: %s', e)
return
def _load_markets(self) -> Dict[str, Any]:
""" Initialize markets both sync and async """
try:
markets = self._api.load_markets()
self._load_async_markets()
return markets
except ccxt.BaseError as e:
logger.warning('Unable to initialize markets. Reason: %s', e)
return {}
def validate_pairs(self, pairs: List[str]) -> None:
"""
Checks if all given pairs are tradable on the current exchange.
Raises OperationalException if one pair is not available.
:param pairs: list of pairs
:return: None
"""
if not self.markets:
logger.warning('Unable to validate pairs (assuming they are correct).')
# return
stake_cur = self._conf['stake_currency']
for pair in pairs:
# Note: ccxt has BaseCurrency/QuoteCurrency format for pairs
# TODO: add a support for having coins in BTC/USDT format
if not pair.endswith(stake_cur):
raise OperationalException(
f'Pair {pair} not compatible with stake_currency: {stake_cur}')
if self.markets and pair not in self.markets:
raise OperationalException(
f'Pair {pair} is not available at {self.name}'
f'Please remove {pair} from your whitelist.')
def validate_timeframes(self, timeframe: List[str]) -> None:
"""
Checks if ticker interval from config is a supported timeframe on the exchange
"""
timeframes = self._api.timeframes
if timeframe not in timeframes:
raise OperationalException(
f'Invalid ticker {timeframe}, this Exchange supports {timeframes}')
def validate_ordertypes(self, order_types: Dict) -> None:
"""
Checks if order-types configured in strategy/config are supported
"""
if any(v == 'market' for k, v in order_types.items()):
if not self.exchange_has('createMarketOrder'):
raise OperationalException(
f'Exchange {self.name} does not support market orders.')
if order_types.get('stoploss_on_exchange'):
if self.name != 'Binance':
raise OperationalException(
'On exchange stoploss is not supported for %s.' % self.name
)
def validate_order_time_in_force(self, order_time_in_force: Dict) -> None:
"""
Checks if order time in force configured in strategy/config are supported
"""
if any(v != 'gtc' for k, v in order_time_in_force.items()):
if self.name != 'Binance':
raise OperationalException(
f'Time in force policies are not supporetd for {self.name} yet.')
def exchange_has(self, endpoint: str) -> bool:
"""
Checks if exchange implements a specific API endpoint.
Wrapper around ccxt 'has' attribute
:param endpoint: Name of endpoint (e.g. 'fetchOHLCV', 'fetchTickers')
:return: bool
"""
return endpoint in self._api.has and self._api.has[endpoint]
def symbol_amount_prec(self, pair, amount: float):
'''
Returns the amount to buy or sell to a precision the Exchange accepts
Rounded down
'''
if self._api.markets[pair]['precision']['amount']:
symbol_prec = self._api.markets[pair]['precision']['amount']
big_amount = amount * pow(10, symbol_prec)
amount = floor(big_amount) / pow(10, symbol_prec)
return amount
def symbol_price_prec(self, pair, price: float):
'''
Returns the price buying or selling with to the precision the Exchange accepts
Rounds up
'''
if self._api.markets[pair]['precision']['price']:
symbol_prec = self._api.markets[pair]['precision']['price']
big_price = price * pow(10, symbol_prec)
price = ceil(big_price) / pow(10, symbol_prec)
return price
def buy(self, pair: str, ordertype: str, amount: float,
rate: float, time_in_force) -> Dict:
if self._conf['dry_run']:
order_id = f'dry_run_buy_{randint(0, 10**6)}'
self._dry_run_open_orders[order_id] = {
'pair': pair,
'price': rate,
'amount': amount,
'type': ordertype,
'side': 'buy',
'remaining': 0.0,
'datetime': arrow.utcnow().isoformat(),
'status': 'closed',
'fee': None
}
return {'id': order_id}
try:
# Set the precision for amount and price(rate) as accepted by the exchange
amount = self.symbol_amount_prec(pair, amount)
rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None
if time_in_force == 'gtc':
return self._api.create_order(pair, ordertype, 'buy', amount, rate)
else:
return self._api.create_order(pair, ordertype, 'buy',
amount, rate, {'timeInForce': time_in_force})
except ccxt.InsufficientFunds as e:
raise DependencyException(
f'Insufficient funds to create limit buy order on market {pair}.'
f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not create limit buy order on market {pair}.'
f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place buy order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
def sell(self, pair: str, ordertype: str, amount: float,
rate: float, time_in_force='gtc') -> Dict:
if self._conf['dry_run']:
order_id = f'dry_run_sell_{randint(0, 10**6)}'
self._dry_run_open_orders[order_id] = {
'pair': pair,
'price': rate,
'amount': amount,
'type': ordertype,
'side': 'sell',
'remaining': 0.0,
'datetime': arrow.utcnow().isoformat(),
'status': 'closed'
}
return {'id': order_id}
try:
# Set the precision for amount and price(rate) as accepted by the exchange
amount = self.symbol_amount_prec(pair, amount)
rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None
if time_in_force == 'gtc':
return self._api.create_order(pair, ordertype, 'sell', amount, rate)
else:
return self._api.create_order(pair, ordertype, 'sell',
amount, rate, {'timeInForce': time_in_force})
except ccxt.InsufficientFunds as e:
raise DependencyException(
f'Insufficient funds to create limit sell order on market {pair}.'
f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not create limit sell order on market {pair}.'
f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict:
"""
creates a stoploss limit order.
NOTICE: it is not supported by all exchanges. only binance is tested for now.
"""
# Set the precision for amount and price(rate) as accepted by the exchange
amount = self.symbol_amount_prec(pair, amount)
rate = self.symbol_price_prec(pair, rate)
stop_price = self.symbol_price_prec(pair, stop_price)
# Ensure rate is less than stop price
if stop_price <= rate:
raise OperationalException(
'In stoploss limit order, stop price should be more than limit price')
if self._conf['dry_run']:
order_id = f'dry_run_buy_{randint(0, 10**6)}'
self._dry_run_open_orders[order_id] = {
'info': {},
'id': order_id,
'pair': pair,
'price': stop_price,
'amount': amount,
'type': 'stop_loss_limit',
'side': 'sell',
'remaining': amount,
'datetime': arrow.utcnow().isoformat(),
'status': 'open',
'fee': None
}
return self._dry_run_open_orders[order_id]
try:
order = self._api.create_order(pair, 'stop_loss_limit', 'sell',
amount, rate, {'stopPrice': stop_price})
logger.info('stoploss limit order added for %s. '
'stop price: %s. limit: %s' % (pair, stop_price, rate))
return order
except ccxt.InsufficientFunds as e:
raise DependencyException(
f'Insufficient funds to place stoploss limit order on market {pair}. '
f'Tried to put a stoploss amount {amount} with '
f'stop {stop_price} and limit {rate} (total {rate*amount}).'
f'Message: {e}')
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not place stoploss limit order on market {pair}.'
f'Tried to place stoploss amount {amount} with '
f'stop {stop_price} and limit {rate} (total {rate*amount}).'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place stoploss limit order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_balance(self, currency: str) -> float:
if self._conf['dry_run']:
return 999.9
# ccxt exception is already handled by get_balances
balances = self.get_balances()
balance = balances.get(currency)
if balance is None:
raise TemporaryError(
f'Could not get {currency} balance due to malformed exchange response: {balances}')
return balance['free']
@retrier
def get_balances(self) -> dict:
if self._conf['dry_run']:
return {}
try:
balances = self._api.fetch_balance()
# Remove additional info from ccxt results
balances.pop("info", None)
balances.pop("free", None)
balances.pop("total", None)
balances.pop("used", None)
return balances
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get balance due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_tickers(self) -> Dict:
try:
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'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load tickers due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_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:
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}')
except ccxt.BaseError as e:
raise OperationalException(e)
else:
logger.info("returning cached ticker-data for %s", pair)
return self._cached_ticker[pair]
def get_history(self, pair: str, tick_interval: str,
since_ms: int) -> List:
"""
Gets candle history using asyncio and returns the list of candles.
Handles all async doing.
"""
return asyncio.get_event_loop().run_until_complete(
self._async_get_history(pair=pair, tick_interval=tick_interval,
since_ms=since_ms))
async def _async_get_history(self, pair: str,
tick_interval: str,
since_ms: int) -> List:
# Assume exchange returns 500 candles
_LIMIT = 500
one_call = constants.TICKER_INTERVAL_MINUTES[tick_interval] * 60 * _LIMIT * 1000
logger.debug("one_call: %s", one_call)
input_coroutines = [self._async_get_candle_history(
pair, tick_interval, since) for since in
range(since_ms, arrow.utcnow().timestamp * 1000, one_call)]
tickers = await asyncio.gather(*input_coroutines, return_exceptions=True)
# Combine tickers
data: List = []
for p, ticker_interval, ticker in tickers:
if p == pair:
data.extend(ticker)
# 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))
return data
def refresh_latest_ohlcv(self, pair_list: List[Tuple[str, str]]) -> List[Tuple[str, List]]:
"""
Refresh in-memory ohlcv asyncronously and set `_klines` with the result
"""
logger.debug("Refreshing ohlcv data for %d pairs", len(pair_list))
input_coroutines = []
# Gather corotines to run
for pair, ticker_interval in set(pair_list):
# Calculating ticker interval in second
interval_in_sec = constants.TICKER_INTERVAL_MINUTES[ticker_interval] * 60
if not ((self._pairs_last_refresh_time.get((pair, ticker_interval), 0)
+ interval_in_sec) >= arrow.utcnow().timestamp
and (pair, ticker_interval) in self._klines):
input_coroutines.append(self._async_get_candle_history(pair, ticker_interval))
else:
logger.debug("Using cached ohlcv data for %s, %s ...", pair, ticker_interval)
tickers = asyncio.get_event_loop().run_until_complete(
asyncio.gather(*input_coroutines, return_exceptions=True))
# handle caching
for res in tickers:
if isinstance(res, Exception):
logger.warning("Async code raised an exception: %s", res.__class__.__name__)
continue
pair = res[0]
tick_interval = res[1]
ticks = res[2]
# keeping last candle time as last refreshed time of the pair
if ticks:
self._pairs_last_refresh_time[(pair, tick_interval)] = ticks[-1][0] // 1000
# keeping parsed dataframe in cache
self._klines[(pair, tick_interval)] = parse_ticker_dataframe(
ticks, tick_interval, fill_missing=True)
return tickers
@retrier_async
async def _async_get_candle_history(self, pair: str, tick_interval: str,
since_ms: Optional[int] = None) -> Tuple[str, str, List]:
"""
Asyncronously gets candle histories using fetch_ohlcv
returns tuple: (pair, tick_interval, ohlcv_list)
"""
try:
# fetch ohlcv asynchronously
logger.debug("fetching %s, %s since %s ...", pair, tick_interval, since_ms)
data = await self._api_async.fetch_ohlcv(pair, timeframe=tick_interval,
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)
# Only sort if necessary to save computing time
try:
if data and data[0][0] > data[-1][0]:
data = sorted(data, key=lambda x: x[0])
except IndexError:
logger.exception("Error loading %s. Result was %s.", pair, data)
return pair, tick_interval, []
logger.debug("done fetching %s, %s ...", pair, tick_interval)
return pair, tick_interval, data
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {self._api.name} does not support fetching historical candlestick data.'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(f'Could not fetch ticker data. Msg: {e}')
@retrier
def cancel_order(self, order_id: str, pair: str) -> None:
if self._conf['dry_run']:
return
try:
return self._api.cancel_order(order_id, pair)
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not cancel order. Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not cancel order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_order(self, order_id: str, pair: str) -> Dict:
if self._conf['dry_run']:
order = self._dry_run_open_orders[order_id]
order.update({
'id': order_id
})
return order
try:
return self._api.fetch_order(order_id, pair)
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not get order. Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_order_book(self, pair: str, limit: int = 100) -> dict:
"""
get order book level 2 from exchange
Notes:
20180619: bittrex doesnt support limits -.-
20180619: binance support limits but only on specific range
"""
try:
if self._api.name == 'Binance':
limit_range = [5, 10, 20, 50, 100, 500, 1000]
# get next-higher step in the limit_range list
limit = min(list(filter(lambda x: limit <= x, limit_range)))
# above script works like loop below (but with slightly better performance):
# for limitx in limit_range:
# if limit <= limitx:
# limit = limitx
# break
return self._api.fetch_l2_order_book(pair, limit)
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {self._api.name} does not support fetching order book.'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get order book due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> List:
if self._conf['dry_run']:
return []
if not self.exchange_has('fetchMyTrades'):
return []
try:
# Allow 5s offset to catch slight time offsets (discovered in #1185)
my_trades = self._api.fetch_my_trades(pair, since.timestamp() - 5)
matched_trades = [trade for trade in my_trades if trade['order'] == order_id]
return matched_trades
except ccxt.NetworkError as e:
raise TemporaryError(
f'Could not get trades due to networking error. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
def get_pair_detail_url(self, pair: str) -> str:
try:
url_base = self._api.urls.get('www')
base, quote = pair.split('/')
return url_base + _EXCHANGE_URLS[self._api.id].format(base=base, quote=quote)
except KeyError:
logger.warning('Could not get exchange url for %s', self.name)
return ""
@retrier
def get_markets(self) -> List[dict]:
try:
return self._api.fetch_markets()
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load markets due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_fee(self, symbol='ETH/BTC', type='', side='', amount=1,
price=1, taker_or_maker='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:
self._api.load_markets()
return self._api.calculate_fee(symbol=symbol, type=type, side=side, amount=amount,
price=price, takerOrMaker=taker_or_maker)['rate']
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get fee info due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
from freqtrade.exchange.exchange import Exchange # noqa: F401
from freqtrade.exchange.exchange import (get_exchange_bad_reason, # noqa: F401
is_exchange_bad,
is_exchange_available,
is_exchange_officially_supported,
available_exchanges)
from freqtrade.exchange.exchange import (timeframe_to_seconds, # noqa: F401
timeframe_to_minutes,
timeframe_to_msecs,
timeframe_to_next_date,
timeframe_to_prev_date)
from freqtrade.exchange.kraken import Kraken # noqa: F401
from freqtrade.exchange.binance import Binance # noqa: F401

View File

@@ -0,0 +1,27 @@
""" Binance exchange subclass """
import logging
from typing import Dict
from freqtrade.exchange import Exchange
logger = logging.getLogger(__name__)
class Binance(Exchange):
_ft_has: Dict = {
"stoploss_on_exchange": True,
"order_time_in_force": ['gtc', 'fok', 'ioc'],
}
def get_order_book(self, pair: str, limit: int = 100) -> dict:
"""
get order book level 2 from exchange
20180619: binance support limits but only on specific range
"""
limit_range = [5, 10, 20, 50, 100, 500, 1000]
# get next-higher step in the limit_range list
limit = min(list(filter(lambda x: limit <= x, limit_range)))
return super().get_order_book(pair, limit)

View File

@@ -0,0 +1,847 @@
# pragma pylint: disable=W0603
"""
Cryptocurrency Exchanges support
"""
import asyncio
import inspect
import logging
from copy import deepcopy
from datetime import datetime, timezone
from math import ceil, floor
from random import randint
from typing import Any, Dict, List, Optional, Tuple
import arrow
import ccxt
import ccxt.async_support as ccxt_async
from pandas import DataFrame
from freqtrade import (DependencyException, InvalidOrderException,
OperationalException, TemporaryError, constants)
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.misc import deep_merge_dicts
logger = logging.getLogger(__name__)
API_RETRY_COUNT = 4
BAD_EXCHANGES = {
"bitmex": "Various reasons",
"bitstamp": "Does not provide history. "
"Details in https://github.com/freqtrade/freqtrade/issues/1983",
}
def retrier_async(f):
async def wrapper(*args, **kwargs):
count = kwargs.pop('count', API_RETRY_COUNT)
try:
return await f(*args, **kwargs)
except (TemporaryError, DependencyException) as ex:
logger.warning('%s() returned exception: "%s"', f.__name__, ex)
if count > 0:
count -= 1
kwargs.update({'count': count})
logger.warning('retrying %s() still for %s times', f.__name__, count)
return await wrapper(*args, **kwargs)
else:
logger.warning('Giving up retrying: %s()', f.__name__)
raise ex
return wrapper
def retrier(f):
def wrapper(*args, **kwargs):
count = kwargs.pop('count', API_RETRY_COUNT)
try:
return f(*args, **kwargs)
except (TemporaryError, DependencyException) as ex:
logger.warning('%s() returned exception: "%s"', f.__name__, ex)
if count > 0:
count -= 1
kwargs.update({'count': count})
logger.warning('retrying %s() still for %s times', f.__name__, count)
return wrapper(*args, **kwargs)
else:
logger.warning('Giving up retrying: %s()', f.__name__)
raise ex
return wrapper
class Exchange(object):
_config: Dict = {}
_params: Dict = {}
# Dict to specify which options each exchange implements
# This defines defaults, which can be selectively overridden by subclasses using _ft_has
# or by specifying them in the configuration.
_ft_has_default: Dict = {
"stoploss_on_exchange": False,
"order_time_in_force": ["gtc"],
"ohlcv_candle_limit": 500,
"ohlcv_partial_candle": True,
}
_ft_has: Dict = {}
def __init__(self, config: dict) -> None:
"""
Initializes this module with the given config,
it does basic validation whether the specified exchange and pairs are valid.
:return: None
"""
self._api: ccxt.Exchange = None
self._api_async: ccxt_async.Exchange = None
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
self._last_markets_refresh: int = 0
# Holds candles
self._klines: Dict[Tuple[str, str], DataFrame] = {}
# Holds all open sell orders for dry_run
self._dry_run_open_orders: Dict[str, Any] = {}
if config['dry_run']:
logger.info('Instance is running with dry_run enabled')
exchange_config = config['exchange']
# Deep merge ft_has with default ft_has options
self._ft_has = deep_merge_dicts(self._ft_has, deepcopy(self._ft_has_default))
if exchange_config.get("_ft_has_params"):
self._ft_has = deep_merge_dicts(exchange_config.get("_ft_has_params"),
self._ft_has)
logger.info("Overriding exchange._ft_has with config params, result: %s", self._ft_has)
# Assign this directly for easy access
self._ohlcv_candle_limit = self._ft_has['ohlcv_candle_limit']
self._ohlcv_partial_candle = self._ft_has['ohlcv_partial_candle']
# Initialize ccxt objects
self._api = self._init_ccxt(
exchange_config, ccxt_kwargs=exchange_config.get('ccxt_config'))
self._api_async = self._init_ccxt(
exchange_config, ccxt_async, ccxt_kwargs=exchange_config.get('ccxt_async_config'))
logger.info('Using Exchange "%s"', self.name)
# Converts the interval provided in minutes in config to seconds
self.markets_refresh_interval: int = exchange_config.get(
"markets_refresh_interval", 60) * 60
# Initial markets load
self._load_markets()
# Check if all pairs are available
self.validate_pairs(config['exchange']['pair_whitelist'])
self.validate_ordertypes(config.get('order_types', {}))
self.validate_order_time_in_force(config.get('order_time_in_force', {}))
if config.get('ticker_interval'):
# Check if timeframe is available
self.validate_timeframes(config['ticker_interval'])
def __del__(self):
"""
Destructor - clean up async stuff
"""
logger.debug("Exchange object destroyed, closing async loop")
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,
ccxt_kwargs: dict = None) -> ccxt.Exchange:
"""
Initialize ccxt with given config and return valid
ccxt instance.
"""
# Find matching class for the given exchange name
name = exchange_config['name']
if not is_exchange_available(name, ccxt_module):
raise OperationalException(f'Exchange {name} is not supported by ccxt')
ex_config = {
'apiKey': exchange_config.get('key'),
'secret': exchange_config.get('secret'),
'password': exchange_config.get('password'),
'uid': exchange_config.get('uid', ''),
}
if ccxt_kwargs:
logger.info('Applying additional ccxt config: %s', ccxt_kwargs)
ex_config.update(ccxt_kwargs)
try:
api = getattr(ccxt_module, name.lower())(ex_config)
except (KeyError, AttributeError) as e:
raise OperationalException(f'Exchange {name} is not supported') from e
except ccxt.BaseError as e:
raise OperationalException(f"Initialization of ccxt failed. Reason: {e}") from e
self.set_sandbox(api, exchange_config, name)
return api
@property
def name(self) -> str:
"""exchange Name (from ccxt)"""
return self._api.name
@property
def id(self) -> str:
"""exchange ccxt id"""
return self._api.id
@property
def markets(self) -> Dict:
"""exchange ccxt markets"""
if not self._api.markets:
logger.warning("Markets were not loaded. Loading them now..")
self._load_markets()
return self._api.markets
def klines(self, pair_interval: Tuple[str, str], copy=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):
if exchange_config.get('sandbox'):
if api.urls.get('test'):
api.urls['api'] = api.urls['test']
logger.info("Enabled Sandbox API on %s", name)
else:
logger.warning(name, "No Sandbox URL in CCXT, exiting. "
"Please check your config.json")
raise OperationalException(f'Exchange {name} does not provide a sandbox api')
def _load_async_markets(self, reload=False) -> None:
try:
if self._api_async:
asyncio.get_event_loop().run_until_complete(
self._api_async.load_markets(reload=reload))
except ccxt.BaseError as e:
logger.warning('Could not load async markets. Reason: %s', e)
return
def _load_markets(self) -> None:
""" Initialize markets both sync and async """
try:
self._api.load_markets()
self._load_async_markets()
self._last_markets_refresh = arrow.utcnow().timestamp
except ccxt.BaseError as e:
logger.warning('Unable to initialize markets. Reason: %s', e)
def _reload_markets(self) -> None:
"""Reload markets both sync and async, if refresh interval has passed"""
# Check whether markets have to be reloaded
if (self._last_markets_refresh > 0) and (
self._last_markets_refresh + self.markets_refresh_interval
> arrow.utcnow().timestamp):
return None
logger.debug("Performing scheduled market reload..")
try:
self._api.load_markets(reload=True)
self._last_markets_refresh = arrow.utcnow().timestamp
except ccxt.BaseError:
logger.exception("Could not reload markets.")
def validate_pairs(self, pairs: List[str]) -> None:
"""
Checks if all given pairs are tradable on the current exchange.
Raises OperationalException if one pair is not available.
:param pairs: list of pairs
:return: None
"""
if not self.markets:
logger.warning('Unable to validate pairs (assuming they are correct).')
return
for pair in pairs:
# Note: ccxt has BaseCurrency/QuoteCurrency format for pairs
# TODO: add a support for having coins in BTC/USDT format
if self.markets and pair not in self.markets:
raise OperationalException(
f'Pair {pair} is not available on {self.name}. '
f'Please remove {pair} from your whitelist.')
elif self.markets[pair].get('info', {}).get('IsRestricted', False):
# Warn users about restricted pairs in whitelist.
# We cannot determine reliably if Users are affected.
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.")
def get_valid_pair_combination(self, curr_1, curr_2) -> str:
"""
Get valid pair combination of curr_1 and curr_2 by trying both combinations.
"""
for pair in [f"{curr_1}/{curr_2}", f"{curr_2}/{curr_1}"]:
if pair in self.markets and self.markets[pair].get('active'):
return pair
raise DependencyException(f"Could not combine {curr_1} and {curr_2} to get a valid pair.")
def validate_timeframes(self, timeframe: List[str]) -> None:
"""
Checks if ticker interval 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
# has no fetchOHLCV method.
# Therefore we also show that.
raise OperationalException(
f"The ccxt library does not provide the list of timeframes "
f"for the exchange \"{self.name}\" and this exchange "
f"is therefore not supported. ccxt fetchOHLCV: {self.exchange_has('fetchOHLCV')}")
timeframes = self._api.timeframes
if timeframe not in timeframes:
raise OperationalException(
f'Invalid ticker {timeframe}, this Exchange supports {timeframes}')
def validate_ordertypes(self, order_types: Dict) -> None:
"""
Checks if order-types configured in strategy/config are supported
"""
if any(v == 'market' for k, v in order_types.items()):
if not self.exchange_has('createMarketOrder'):
raise OperationalException(
f'Exchange {self.name} does not support market orders.')
if (order_types.get("stoploss_on_exchange")
and not self._ft_has.get("stoploss_on_exchange", False)):
raise OperationalException(
'On exchange stoploss is not supported for %s.' % self.name
)
def validate_order_time_in_force(self, order_time_in_force: Dict) -> None:
"""
Checks if order time in force configured in strategy/config are supported
"""
if any(v not in self._ft_has["order_time_in_force"]
for k, v in order_time_in_force.items()):
raise OperationalException(
f'Time in force policies are not supported for {self.name} yet.')
def exchange_has(self, endpoint: str) -> bool:
"""
Checks if exchange implements a specific API endpoint.
Wrapper around ccxt 'has' attribute
:param endpoint: Name of endpoint (e.g. 'fetchOHLCV', 'fetchTickers')
:return: bool
"""
return endpoint in self._api.has and self._api.has[endpoint]
def symbol_amount_prec(self, pair, amount: float):
'''
Returns the amount to buy or sell to a precision the Exchange accepts
Rounded down
'''
if self.markets[pair]['precision']['amount']:
symbol_prec = self.markets[pair]['precision']['amount']
big_amount = amount * pow(10, symbol_prec)
amount = floor(big_amount) / pow(10, symbol_prec)
return amount
def symbol_price_prec(self, pair, price: float):
'''
Returns the price buying or selling with to the precision the Exchange accepts
Rounds up
'''
if self.markets[pair]['precision']['price']:
symbol_prec = self.markets[pair]['precision']['price']
big_price = price * pow(10, symbol_prec)
price = ceil(big_price) / pow(10, symbol_prec)
return price
def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float,
rate: float, params: Dict = {}) -> Dict[str, Any]:
order_id = f'dry_run_{side}_{randint(0, 10**6)}'
dry_order = { # TODO: additional entry should be added for stoploss limit
"id": order_id,
'pair': pair,
'price': rate,
'amount': amount,
"cost": amount * rate,
'type': ordertype,
'side': side,
'remaining': amount,
'datetime': arrow.utcnow().isoformat(),
'status': "closed" if ordertype == "market" else "open",
'fee': None,
"info": {}
}
self._store_dry_order(dry_order)
return dry_order
def _store_dry_order(self, dry_order: Dict) -> None:
closed_order = dry_order.copy()
if closed_order["type"] in ["market", "limit"]:
closed_order.update({
"status": "closed",
"filled": closed_order["amount"],
"remaining": 0
})
self._dry_run_open_orders[closed_order["id"]] = closed_order
def create_order(self, pair: str, ordertype: str, side: str, amount: float,
rate: float, params: Dict = {}) -> Dict:
try:
# Set the precision for amount and price(rate) as accepted by the exchange
amount = self.symbol_amount_prec(pair, amount)
needs_price = (ordertype != 'market'
or self._api.options.get("createMarketBuyOrderRequiresPrice", False))
rate = self.symbol_price_prec(pair, rate) if needs_price else None
return self._api.create_order(pair, ordertype, side,
amount, rate, params)
except ccxt.InsufficientFunds as e:
raise DependencyException(
f'Insufficient funds to create {ordertype} {side} order on market {pair}.'
f'Tried to {side} amount {amount} at rate {rate}.'
f'Message: {e}') from e
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not create {ordertype} {side} order on market {pair}.'
f'Tried to {side} amount {amount} at rate {rate}.'
f'Message: {e}') from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place {side} order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
def buy(self, pair: str, ordertype: str, amount: float,
rate: float, time_in_force) -> Dict:
if self._config['dry_run']:
dry_order = self.dry_run_order(pair, ordertype, "buy", amount, rate)
return dry_order
params = self._params.copy()
if time_in_force != 'gtc' and ordertype != 'market':
params.update({'timeInForce': time_in_force})
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:
if self._config['dry_run']:
dry_order = self.dry_run_order(pair, ordertype, "sell", amount, rate)
return dry_order
params = self._params.copy()
if time_in_force != 'gtc' and ordertype != 'market':
params.update({'timeInForce': time_in_force})
return self.create_order(pair, ordertype, 'sell', amount, rate, params)
def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict:
"""
creates a stoploss limit order.
NOTICE: it is not supported by all exchanges. only binance is tested for now.
TODO: implementation maybe needs to be moved to the binance subclass
"""
ordertype = "stop_loss_limit"
stop_price = self.symbol_price_prec(pair, stop_price)
# Ensure rate is less than stop price
if stop_price <= rate:
raise OperationalException(
'In stoploss limit order, stop price should be more than limit price')
if self._config['dry_run']:
dry_order = self.dry_run_order(
pair, ordertype, "sell", amount, stop_price)
return dry_order
params = self._params.copy()
params.update({'stopPrice': stop_price})
order = self.create_order(pair, ordertype, 'sell', amount, rate, params)
logger.info('stoploss limit order added for %s. '
'stop price: %s. limit: %s', pair, stop_price, rate)
return order
@retrier
def get_balance(self, currency: str) -> float:
if self._config['dry_run']:
return constants.DRY_RUN_WALLET
# ccxt exception is already handled by get_balances
balances = self.get_balances()
balance = balances.get(currency)
if balance is None:
raise TemporaryError(
f'Could not get {currency} balance due to malformed exchange response: {balances}')
return balance['free']
@retrier
def get_balances(self) -> dict:
if self._config['dry_run']:
return {}
try:
balances = self._api.fetch_balance()
# Remove additional info from ccxt results
balances.pop("info", None)
balances.pop("free", None)
balances.pop("total", None)
balances.pop("used", None)
return balances
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get balance due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
@retrier
def get_tickers(self) -> Dict:
try:
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'Message: {e}') from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load tickers due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
@retrier
def get_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 get_historic_ohlcv(self, pair: str, ticker_interval: 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.
:param pair: Pair to download
:param ticker_interval: Interval to get
:param since_ms: Timestamp in milliseconds to get history from
:returns List of tickers
"""
return asyncio.get_event_loop().run_until_complete(
self._async_get_historic_ohlcv(pair=pair, ticker_interval=ticker_interval,
since_ms=since_ms))
async def _async_get_historic_ohlcv(self, pair: str,
ticker_interval: str,
since_ms: int) -> List:
one_call = timeframe_to_msecs(ticker_interval) * self._ohlcv_candle_limit
logger.debug(
"one_call: %s msecs (%s)",
one_call,
arrow.utcnow().shift(seconds=one_call // 1000).humanize(only_distance=True)
)
input_coroutines = [self._async_get_candle_history(
pair, ticker_interval, since) for since in
range(since_ms, arrow.utcnow().timestamp * 1000, one_call)]
tickers = await asyncio.gather(*input_coroutines, return_exceptions=True)
# Combine tickers
data: List = []
for p, ticker_interval, ticker in tickers:
if p == pair:
data.extend(ticker)
# 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))
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
Loops asynchronously over pair_list and downloads all pairs async (semi-parallel).
:param pair_list: List of 2 element tuples containing pair, interval to refresh
:return: Returns a List of ticker-dataframes.
"""
logger.debug("Refreshing ohlcv data for %d pairs", len(pair_list))
input_coroutines = []
# Gather coroutines to run
for pair, ticker_interval in set(pair_list):
if (not ((pair, ticker_interval) in self._klines)
or self._now_is_time_to_refresh(pair, ticker_interval)):
input_coroutines.append(self._async_get_candle_history(pair, ticker_interval))
else:
logger.debug(
"Using cached ohlcv data for pair %s, interval %s ...",
pair, ticker_interval
)
tickers = asyncio.get_event_loop().run_until_complete(
asyncio.gather(*input_coroutines, return_exceptions=True))
# handle caching
for res in tickers:
if isinstance(res, Exception):
logger.warning("Async code raised an exception: %s", res.__class__.__name__)
continue
pair = res[0]
ticker_interval = res[1]
ticks = res[2]
# keeping last candle time as last refreshed time of the pair
if ticks:
self._pairs_last_refresh_time[(pair, ticker_interval)] = ticks[-1][0] // 1000
# keeping parsed dataframe in cache
self._klines[(pair, ticker_interval)] = parse_ticker_dataframe(
ticks, ticker_interval, pair=pair, fill_missing=True,
drop_incomplete=self._ohlcv_partial_candle)
return tickers
def _now_is_time_to_refresh(self, pair: str, ticker_interval: str) -> bool:
# Calculating ticker interval in seconds
interval_in_sec = timeframe_to_seconds(ticker_interval)
return not ((self._pairs_last_refresh_time.get((pair, ticker_interval), 0)
+ interval_in_sec) >= arrow.utcnow().timestamp)
@retrier_async
async def _async_get_candle_history(self, pair: str, ticker_interval: str,
since_ms: Optional[int] = None) -> Tuple[str, str, List]:
"""
Asynchronously gets candle histories using fetch_ohlcv
returns tuple: (pair, ticker_interval, ohlcv_list)
"""
try:
# 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...",
pair, ticker_interval, since_ms, s
)
data = await self._api_async.fetch_ohlcv(pair, timeframe=ticker_interval,
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)
# Only sort if necessary to save computing time
try:
if data and data[0][0] > data[-1][0]:
data = sorted(data, key=lambda x: x[0])
except IndexError:
logger.exception("Error loading %s. Result was %s.", pair, data)
return pair, ticker_interval, []
logger.debug("Done fetching pair %s, interval %s ...", pair, ticker_interval)
return pair, ticker_interval, data
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {self._api.name} does not support fetching historical candlestick data.'
f'Message: {e}') from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(f'Could not load ticker history 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
@retrier
def cancel_order(self, order_id: str, pair: str) -> None:
if self._config['dry_run']:
return
try:
return self._api.cancel_order(order_id, pair)
except ccxt.InvalidOrder as e:
raise InvalidOrderException(
f'Could not cancel order. Message: {e}') from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not cancel order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
@retrier
def get_order(self, order_id: str, pair: str) -> Dict:
if self._config['dry_run']:
try:
order = self._dry_run_open_orders[order_id]
return order
except KeyError as e:
# Gracefully handle errors with dry-run orders.
raise InvalidOrderException(
f'Tried to get an invalid dry-run-order (id: {order_id}). Message: {e}') from e
try:
return self._api.fetch_order(order_id, pair)
except ccxt.InvalidOrder as e:
raise InvalidOrderException(
f'Tried to get an invalid order (id: {order_id}). Message: {e}') from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get order due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
@retrier
def get_order_book(self, pair: str, limit: int = 100) -> dict:
"""
get order book level 2 from exchange
Notes:
20180619: bittrex doesnt support limits -.-
"""
try:
return self._api.fetch_l2_order_book(pair, limit)
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {self._api.name} does not support fetching order book.'
f'Message: {e}') from e
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get order book due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
@retrier
def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> List:
if self._config['dry_run']:
return []
if not self.exchange_has('fetchMyTrades'):
return []
try:
# Allow 5s offset to catch slight time offsets (discovered in #1185)
# since needs to be int in milliseconds
my_trades = self._api.fetch_my_trades(pair, int((since.timestamp() - 5) * 1000))
matched_trades = [trade for trade in my_trades if trade['order'] == order_id]
return matched_trades
except ccxt.NetworkError as e:
raise TemporaryError(
f'Could not get trades due to networking error. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
@retrier
def get_fee(self, symbol='ETH/BTC', type='', side='', amount=1,
price=1, taker_or_maker='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:
self._api.load_markets()
return self._api.calculate_fee(symbol=symbol, type=type, side=side, amount=amount,
price=price, takerOrMaker=taker_or_maker)['rate']
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get fee info due to {e.__class__.__name__}. Message: {e}') from e
except ccxt.BaseError as e:
raise OperationalException(e) from e
def is_exchange_bad(exchange_name: str) -> bool:
return exchange_name in BAD_EXCHANGES
def get_exchange_bad_reason(exchange_name: str) -> str:
return BAD_EXCHANGES.get(exchange_name, "")
def is_exchange_available(exchange_name: str, ccxt_module=None) -> bool:
return exchange_name in available_exchanges(ccxt_module)
def is_exchange_officially_supported(exchange_name: str) -> bool:
return exchange_name in ['bittrex', 'binance']
def available_exchanges(ccxt_module=None) -> List[str]:
return ccxt_module.exchanges if ccxt_module is not None else ccxt.exchanges
def timeframe_to_seconds(ticker_interval: str) -> int:
"""
Translates the timeframe interval value written in the human readable
form ('1m', '5m', '1h', '1d', '1w', etc.) to the number
of seconds for one timeframe interval.
"""
return ccxt.Exchange.parse_timeframe(ticker_interval)
def timeframe_to_minutes(ticker_interval: str) -> int:
"""
Same as timeframe_to_seconds, but returns minutes.
"""
return ccxt.Exchange.parse_timeframe(ticker_interval) // 60
def timeframe_to_msecs(ticker_interval: str) -> int:
"""
Same as timeframe_to_seconds, but returns milliseconds.
"""
return ccxt.Exchange.parse_timeframe(ticker_interval) * 1000
def timeframe_to_prev_date(timeframe: str, date: datetime = None) -> datetime:
"""
Use Timeframe and determine last possible candle.
:param timeframe: timeframe in string format (e.g. "5m")
:param date: date to use. Defaults to utcnow()
:returns: date of previous candle (with utc timezone)
"""
if not date:
date = datetime.now(timezone.utc)
timeframe_secs = timeframe_to_seconds(timeframe)
# Get offset based on timerame_secs
offset = date.timestamp() % timeframe_secs
# Subtract seconds passed since last offset
new_timestamp = date.timestamp() - offset
return datetime.fromtimestamp(new_timestamp, tz=timezone.utc)
def timeframe_to_next_date(timeframe: str, date: datetime = None) -> datetime:
"""
Use Timeframe and determine next candle.
:param timeframe: timeframe in string format (e.g. "5m")
:param date: date to use. Defaults to utcnow()
:returns: date of next candle (with utc timezone)
"""
prevdate = timeframe_to_prev_date(timeframe, date)
timeframe_secs = timeframe_to_seconds(timeframe)
# Add one interval to previous candle
new_timestamp = prevdate.timestamp() + timeframe_secs
return datetime.fromtimestamp(new_timestamp, tz=timezone.utc)

View File

@@ -0,0 +1,12 @@
""" Kraken exchange subclass """
import logging
from typing import Dict
from freqtrade.exchange import Exchange
logger = logging.getLogger(__name__)
class Kraken(Exchange):
_params: Dict = {"trading_agreement": "agree"}

View File

@@ -4,24 +4,24 @@ Freqtrade is the main module of this bot. It contains the class Freqtrade()
import copy
import logging
import time
import traceback
from datetime import datetime
from typing import Any, Callable, Dict, List, Optional
from typing import Any, Dict, List, Optional, Tuple
import arrow
from requests.exceptions import RequestException
from freqtrade import (DependencyException, OperationalException,
TemporaryError, __version__, constants, persistence)
from freqtrade import (DependencyException, OperationalException, InvalidOrderException,
__version__, constants, persistence)
from freqtrade.data.converter import order_book_to_dataframe
from freqtrade.data.dataprovider import DataProvider
from freqtrade.edge import Edge
from freqtrade.exchange import Exchange
from freqtrade.configuration import validate_config_consistency
from freqtrade.exchange import timeframe_to_minutes, timeframe_to_next_date
from freqtrade.persistence import Trade
from freqtrade.rpc import RPCManager, RPCMessageType
from freqtrade.resolvers import StrategyResolver, PairListResolver
from freqtrade.state import State
from freqtrade.resolvers import ExchangeResolver, StrategyResolver, PairListResolver
from freqtrade.state import State, RunMode
from freqtrade.strategy.interface import SellType, IStrategy
from freqtrade.wallets import Wallets
@@ -42,21 +42,24 @@ class FreqtradeBot(object):
to get the config dict.
"""
logger.info(
'Starting freqtrade %s',
__version__,
)
logger.info('Starting freqtrade %s', __version__)
# Init bot states
# Init bot state
self.state = State.STOPPED
# Init objects
self.config = config
self.strategy: IStrategy = StrategyResolver(self.config).strategy
# Check config consistency here since strategies can set certain options
validate_config_consistency(config)
self.rpc: RPCManager = RPCManager(self)
self.exchange = Exchange(self.config)
self.wallets = Wallets(self.exchange)
self.exchange = ExchangeResolver(self.config['exchange']['name'], self.config).exchange
self.wallets = Wallets(self.config, self.exchange)
self.dataprovider = DataProvider(self.config, self.exchange)
# Attach Dataprovider to Strategy baseclass
@@ -72,24 +75,19 @@ class FreqtradeBot(object):
self.config.get('edge', {}).get('enabled', False) else None
self.active_pair_whitelist: List[str] = self.config['exchange']['pair_whitelist']
self._init_modules()
def _init_modules(self) -> None:
"""
Initializes all modules and updates the config
:return: None
"""
# Initialize all modules
persistence.init(self.config.get('db_url', None),
clean_open_orders=self.config.get('dry_run', False))
persistence.init(self.config)
# Set initial application state
# Stoploss on exchange does not make sense, therefore we need to disable that.
if (self.dataprovider.runmode == RunMode.DRY_RUN and
self.strategy.order_types.get('stoploss_on_exchange', False)):
logger.info("Disabling stoploss_on_exchange during dry-run.")
self.strategy.order_types['stoploss_on_exchange'] = False
config['order_types']['stoploss_on_exchange'] = False
# Set initial bot state from config
initial_state = self.config.get('initial_state')
if initial_state:
self.state = State[initial_state.upper()]
else:
self.state = State.STOPPED
self.state = State[initial_state.upper()] if initial_state else State.STOPPED
def cleanup(self) -> None:
"""
@@ -97,114 +95,76 @@ class FreqtradeBot(object):
:return: None
"""
logger.info('Cleaning up modules ...')
self.rpc.cleanup()
persistence.cleanup()
def worker(self, old_state: State = None) -> State:
def startup(self) -> None:
"""
Trading routine that must be run at each loop
:param old_state: the previous service state from the previous call
:return: current service state
Called on startup and after reloading the bot - triggers notifications and
performs startup tasks
"""
# Log state transition
state = self.state
if state != old_state:
self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'{state.name.lower()}'
})
logger.info('Changing state to: %s', state.name)
if state == State.RUNNING:
self.rpc.startup_messages(self.config, self.pairlists)
if not self.edge:
# Adjust stoploss if it was changed
Trade.stoploss_reinitialization(self.strategy.stoploss)
if state == State.STOPPED:
time.sleep(1)
elif state == State.RUNNING:
min_secs = self.config.get('internals', {}).get(
'process_throttle_secs',
constants.PROCESS_THROTTLE_SECS
)
self._throttle(func=self._process,
min_secs=min_secs)
return state
def _throttle(self, func: Callable[..., Any], min_secs: float, *args, **kwargs) -> Any:
"""
Throttles the given callable that it
takes at least `min_secs` to finish execution.
:param func: Any callable
:param min_secs: minimum execution time in seconds
:return: Any
"""
start = time.time()
result = func(*args, **kwargs)
end = time.time()
duration = max(min_secs - (end - start), 0.0)
logger.debug('Throttling %s for %.2f seconds', func.__name__, duration)
time.sleep(duration)
return result
def _process(self) -> bool:
def process(self) -> None:
"""
Queries the persistence layer for open trades and handles them,
otherwise a new trade is created.
:return: True if one or more trades has been created or closed, False otherwise
"""
state_changed = False
try:
# Check whether markets have to be reloaded
self.exchange._reload_markets()
# Refresh whitelist
self.pairlists.refresh_pairlist()
self.active_pair_whitelist = self.pairlists.whitelist
# Calculating Edge positiong
# Calculating Edge positioning
if self.edge:
self.edge.calculate()
self.active_pair_whitelist = self.edge.adjust(self.active_pair_whitelist)
# Query trades from persistence layer
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
trades = Trade.get_open_trades()
# Extend active-pair whitelist with pairs from open trades
# ensures that tickers are downloaded for open trades
self.active_pair_whitelist.extend([trade.pair for trade in trades
if trade.pair not in self.active_pair_whitelist])
# It ensures that tickers are downloaded for open trades
self._extend_whitelist_with_trades(self.active_pair_whitelist, trades)
# Create pair-whitelist tuple with (pair, ticker_interval)
pair_whitelist_tuple = [(pair, self.config['ticker_interval'])
for pair in self.active_pair_whitelist]
# Refreshing candles
self.dataprovider.refresh(pair_whitelist_tuple,
self.dataprovider.refresh(self._create_pair_whitelist(self.active_pair_whitelist),
self.strategy.informative_pairs())
# First process current opened trades
for trade in trades:
state_changed |= self.process_maybe_execute_sell(trade)
self.process_maybe_execute_sell(trade)
# Then looking for buy opportunities
if len(trades) < self.config['max_open_trades']:
state_changed = self.process_maybe_execute_buy()
self.process_maybe_execute_buy()
if 'unfilledtimeout' in self.config:
# Check and handle any timed out open orders
self.check_handle_timedout()
Trade.session.flush()
except TemporaryError as error:
logger.warning(f"Error: {error}, retrying in {constants.RETRY_TIMEOUT} seconds...")
time.sleep(constants.RETRY_TIMEOUT)
except OperationalException:
tb = traceback.format_exc()
hint = 'Issue `/start` if you think it is safe to restart.'
self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'OperationalException:\n```\n{tb}```{hint}'
})
logger.exception('OperationalException. Stopping trader ...')
self.state = State.STOPPED
return state_changed
def _extend_whitelist_with_trades(self, whitelist: List[str], trades: List[Any]):
"""
Extend whitelist with pairs from open trades
"""
whitelist.extend([trade.pair for trade in trades if trade.pair not in whitelist])
def get_target_bid(self, pair: str) -> float:
def _create_pair_whitelist(self, pairs: List[str]) -> List[Tuple[str, str]]:
"""
Create pair-whitelist tuple with (pair, ticker_interval)
"""
return [(pair, self.config['ticker_interval']) for pair in pairs]
def get_target_bid(self, pair: str, tick: Dict = None) -> float:
"""
Calculates bid target between current ask price and last price
:return: float: Price
@@ -221,8 +181,11 @@ class FreqtradeBot(object):
logger.info('...top %s order book buy rate %0.8f', order_book_top, order_book_rate)
used_rate = order_book_rate
else:
if not tick:
logger.info('Using Last Ask / Last Price')
ticker = self.exchange.get_ticker(pair)
else:
ticker = tick
if ticker['ask'] < ticker['last']:
ticker_rate = ticker['ask']
else:
@@ -248,31 +211,29 @@ class FreqtradeBot(object):
else:
stake_amount = self.config['stake_amount']
avaliable_amount = self.wallets.get_free(self.config['stake_currency'])
available_amount = self.wallets.get_free(self.config['stake_currency'])
if stake_amount == constants.UNLIMITED_STAKE_AMOUNT:
open_trades = len(Trade.query.filter(Trade.is_open.is_(True)).all())
open_trades = len(Trade.get_open_trades())
if open_trades >= self.config['max_open_trades']:
logger.warning('Can\'t open a new trade: max number of trades is reached')
return None
return avaliable_amount / (self.config['max_open_trades'] - open_trades)
return available_amount / (self.config['max_open_trades'] - open_trades)
# Check if stake_amount is fulfilled
if avaliable_amount < stake_amount:
if available_amount < stake_amount:
raise DependencyException(
f"Available balance({avaliable_amount} {self.config['stake_currency']}) is "
f"Available balance({available_amount} {self.config['stake_currency']}) is "
f"lower than stake amount({stake_amount} {self.config['stake_currency']})"
)
return stake_amount
def _get_min_pair_stake_amount(self, pair: str, price: float) -> Optional[float]:
markets = self.exchange.get_markets()
markets = [m for m in markets if m['symbol'] == pair]
if not markets:
raise ValueError(f'Can\'t get market information for symbol {pair}')
market = markets[0]
try:
market = self.exchange.markets[pair]
except KeyError:
raise ValueError(f"Can't get market information for symbol {pair}")
if 'limits' not in market:
return None
@@ -299,33 +260,43 @@ class FreqtradeBot(object):
amount_reserve_percent = max(amount_reserve_percent, 0.5)
return min(min_stake_amounts) / amount_reserve_percent
def create_trade(self) -> bool:
def create_trades(self) -> bool:
"""
Checks the implemented trading indicator(s) for a randomly picked pair,
if one pair triggers the buy_signal a new trade record gets created
:return: True if a trade object has been created and persisted, False otherwise
Checks the implemented trading strategy for buy-signals, using the active pair whitelist.
If a pair triggers the buy_signal a new trade record gets created.
Checks pairs as long as the open trade count is below `max_open_trades`.
:return: True if at least one trade has been created.
"""
interval = self.strategy.ticker_interval
whitelist = copy.deepcopy(self.active_pair_whitelist)
if not whitelist:
logger.warning("Whitelist is empty.")
return False
# Remove currently opened and latest pairs from whitelist
for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
for trade in Trade.get_open_trades():
if trade.pair in whitelist:
whitelist.remove(trade.pair)
logger.debug('Ignoring %s in pair whitelist', trade.pair)
if not whitelist:
raise DependencyException('No currency pairs in whitelist')
logger.info("No currency pair in whitelist, but checking to sell open trades.")
return False
buycount = 0
# running get_signal on historical data fetched
for _pair in whitelist:
if self.strategy.is_pair_locked(_pair):
logger.info(f"Pair {_pair} is currently locked.")
continue
(buy, sell) = self.strategy.get_signal(
_pair, interval, self.dataprovider.ohlcv(_pair, self.strategy.ticker_interval))
if buy and not sell:
if buy and not sell and len(Trade.get_open_trades()) < self.config['max_open_trades']:
stake_amount = self._get_trade_stake_amount(_pair)
if not stake_amount:
return False
continue
logger.info(f"Buy signal found: about create a new trade with stake_amount: "
f"{stake_amount} ...")
@@ -335,12 +306,13 @@ class FreqtradeBot(object):
if (bidstrat_check_depth_of_market.get('enabled', False)) and\
(bidstrat_check_depth_of_market.get('bids_to_ask_delta', 0) > 0):
if self._check_depth_of_market_buy(_pair, bidstrat_check_depth_of_market):
return self.execute_buy(_pair, stake_amount)
buycount += self.execute_buy(_pair, stake_amount)
else:
return False
return self.execute_buy(_pair, stake_amount)
continue
return False
buycount += self.execute_buy(_pair, stake_amount)
return buycount > 0
def _check_depth_of_market_buy(self, pair: str, conf: Dict) -> bool:
"""
@@ -366,7 +338,6 @@ class FreqtradeBot(object):
:return: None
"""
pair_s = pair.replace('_', '/')
pair_url = self.exchange.get_pair_detail_url(pair)
stake_currency = self.config['stake_currency']
fiat_currency = self.config.get('fiat_display_currency', None)
time_in_force = self.strategy.order_time_in_force['buy']
@@ -386,8 +357,8 @@ class FreqtradeBot(object):
return False
amount = stake_amount / buy_limit_requested
order = self.exchange.buy(pair=pair, ordertype=self.strategy.order_types['buy'],
order_type = self.strategy.order_types['buy']
order = self.exchange.buy(pair=pair, ordertype=order_type,
amount=amount, rate=buy_limit_requested,
time_in_force=time_in_force)
order_id = order['id']
@@ -397,7 +368,6 @@ class FreqtradeBot(object):
buy_limit_filled_price = buy_limit_requested
if order_status == 'expired' or order_status == 'rejected':
order_type = self.strategy.order_types['buy']
order_tif = self.strategy.order_time_in_force['buy']
# return false if the order is not filled
@@ -425,14 +395,13 @@ class FreqtradeBot(object):
stake_amount = order['cost']
amount = order['amount']
buy_limit_filled_price = order['price']
order_id = None
self.rpc.send_msg({
'type': RPCMessageType.BUY_NOTIFICATION,
'exchange': self.exchange.name.capitalize(),
'pair': pair_s,
'market_url': pair_url,
'limit': buy_limit_filled_price,
'order_type': order_type,
'stake_amount': stake_amount,
'stake_currency': stake_currency,
'fiat_currency': fiat_currency
@@ -452,9 +421,13 @@ class FreqtradeBot(object):
exchange=self.exchange.id,
open_order_id=order_id,
strategy=self.strategy.get_strategy_name(),
ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']]
ticker_interval=timeframe_to_minutes(self.config['ticker_interval'])
)
# Update fees if order is closed
if order_status == 'closed':
self.update_trade_state(trade, order)
Trade.session.add(trade)
Trade.session.flush()
@@ -463,21 +436,17 @@ class FreqtradeBot(object):
return True
def process_maybe_execute_buy(self) -> bool:
def process_maybe_execute_buy(self) -> None:
"""
Tries to execute a buy trade in a safe way
:return: True if executed
"""
try:
# Create entity and execute trade
if self.create_trade():
return True
logger.info('Found no buy signals for whitelisted currencies. Trying again..')
return False
if not self.create_trades():
logger.info('Found no buy signals for whitelisted currencies. Trying again...')
except DependencyException as exception:
logger.warning('Unable to create trade: %s', exception)
return False
def process_maybe_execute_sell(self, trade: Trade) -> bool:
"""
@@ -485,23 +454,7 @@ class FreqtradeBot(object):
:return: True if executed
"""
try:
# Get order details for actual price per unit
if trade.open_order_id:
# Update trade with order values
logger.info('Found open order for %s', trade)
order = self.exchange.get_order(trade.open_order_id, trade.pair)
# Try update amount (binance-fix)
try:
new_amount = self.get_real_amount(trade, order)
if order['amount'] != new_amount:
order['amount'] = new_amount
# Fee was applied, so set to 0
trade.fee_open = 0
except OperationalException as exception:
logger.warning("Could not update trade amount: %s", exception)
trade.update(order)
self.update_trade_state(trade)
if self.strategy.order_types.get('stoploss_on_exchange') and trade.is_open:
result = self.handle_stoploss_on_exchange(trade)
@@ -526,7 +479,7 @@ class FreqtradeBot(object):
def get_real_amount(self, trade: Trade, order: Dict) -> float:
"""
Get real amount for the trade
Necessary for self.exchanges which charge fees in base currency (e.g. binance)
Necessary for exchanges which charge fees in base currency (e.g. binance)
"""
order_amount = order['amount']
# Only run for closed orders
@@ -534,8 +487,11 @@ class FreqtradeBot(object):
return order_amount
# use fee from order-dict if possible
if 'fee' in order and order['fee'] and (order['fee'].keys() >= {'currency', 'cost'}):
if trade.pair.startswith(order['fee']['currency']):
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'])):
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)
@@ -552,9 +508,12 @@ class FreqtradeBot(object):
fee_abs = 0
for exectrade in trades:
amount += exectrade['amount']
if "fee" in exectrade and (exectrade['fee'].keys() >= {'currency', 'cost'}):
if ("fee" in exectrade and exectrade['fee'] is not None and
(exectrade['fee'].keys() >= {'currency', 'cost'})):
# only applies if fee is in quote currency!
if trade.pair.startswith(exectrade['fee']['currency']):
if (exectrade['fee']['currency'] is not None and
exectrade['fee']['cost'] is not None and
trade.pair.startswith(exectrade['fee']['currency'])):
fee_abs += exectrade['fee']['cost']
if amount != order_amount:
@@ -566,6 +525,55 @@ class FreqtradeBot(object):
f"(from {order_amount} to {real_amount}) from Trades")
return real_amount
def update_trade_state(self, trade, action_order: dict = None):
"""
Checks trades with open orders and updates the amount if necessary
"""
# Get order details for actual price per unit
if trade.open_order_id:
# Update trade with order values
logger.info('Found open order for %s', trade)
try:
order = action_order or self.exchange.get_order(trade.open_order_id, trade.pair)
except InvalidOrderException as exception:
logger.warning('Unable to fetch order %s: %s', trade.open_order_id, exception)
return
# Try update amount (binance-fix)
try:
new_amount = self.get_real_amount(trade, order)
if order['amount'] != new_amount:
order['amount'] = new_amount
# Fee was applied, so set to 0
trade.fee_open = 0
except OperationalException as exception:
logger.warning("Could not update trade amount: %s", exception)
trade.update(order)
# Updating wallets when order is closed
if not trade.is_open:
self.wallets.update()
def get_sell_rate(self, pair: str, refresh: bool) -> float:
"""
Get sell rate - either using get-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 get_ticker)
or remain static in any other case since it's not updating.
: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')
order_book = self.exchange.get_order_book(pair, 1)
rate = order_book['bids'][0][0]
else:
rate = self.exchange.get_ticker(pair, refresh)['bid']
return rate
def handle_trade(self, trade: Trade) -> bool:
"""
Sells the current pair if the threshold is reached and updates the trade record.
@@ -597,13 +605,13 @@ class FreqtradeBot(object):
logger.info(' order book asks top %s: %0.8f', i, order_book_rate)
sell_rate = order_book_rate
if self.check_sell(trade, sell_rate, buy, sell):
if self._check_and_execute_sell(trade, sell_rate, buy, sell):
return True
else:
logger.debug('checking sell')
sell_rate = self.exchange.get_ticker(trade.pair)['bid']
if self.check_sell(trade, sell_rate, buy, sell):
sell_rate = self.get_sell_rate(trade.pair, True)
if self._check_and_execute_sell(trade, sell_rate, buy, sell):
return True
logger.debug('Found no sell signal for %s.', trade)
@@ -616,11 +624,25 @@ class FreqtradeBot(object):
is enabled.
"""
result = False
logger.debug('Handling stoploss on exchange %s ...', trade)
# If trade is open and the buy order is fulfilled but there is no stoploss,
# then we add a stoploss on exchange
if not trade.open_order_id and not trade.stoploss_order_id:
stoploss_order = None
try:
# First we check if there is already a stoploss on exchange
stoploss_order = self.exchange.get_order(trade.stoploss_order_id, trade.pair) \
if trade.stoploss_order_id else None
except InvalidOrderException as exception:
logger.warning('Unable to fetch stoploss order: %s', exception)
# If trade open order id does not exist: buy order is fulfilled
buy_order_fulfilled = not trade.open_order_id
# Limit price threshold: As limit price should always be below price
limit_price_pct = 0.99
# If buy order is fulfilled but there is no stoploss, we add a stoploss on exchange
if (buy_order_fulfilled and not stoploss_order):
if self.edge:
stoploss = self.edge.stoploss(pair=trade.pair)
else:
@@ -629,31 +651,52 @@ class FreqtradeBot(object):
stop_price = trade.open_rate * (1 + stoploss)
# limit price should be less than stop price.
# 0.99 is arbitrary here.
limit_price = stop_price * 0.99
limit_price = stop_price * limit_price_pct
try:
stoploss_order_id = self.exchange.stoploss_limit(
pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price
)['id']
trade.stoploss_order_id = str(stoploss_order_id)
trade.stoploss_last_update = datetime.now()
return False
# Or the trade open and there is already a stoploss on exchange.
# so we check if it is hit ...
elif trade.stoploss_order_id:
logger.debug('Handling stoploss on exchange %s ...', trade)
order = self.exchange.get_order(trade.stoploss_order_id, trade.pair)
if order['status'] == 'closed':
except DependencyException as exception:
trade.stoploss_order_id = None
logger.warning('Unable to place a stoploss order on exchange: %s', exception)
# If stoploss order is canceled for some reason we add it
if stoploss_order and stoploss_order['status'] == 'canceled':
try:
stoploss_order_id = self.exchange.stoploss_limit(
pair=trade.pair, amount=trade.amount,
stop_price=trade.stop_loss, rate=trade.stop_loss * limit_price_pct
)['id']
trade.stoploss_order_id = str(stoploss_order_id)
return False
except DependencyException as exception:
trade.stoploss_order_id = None
logger.warning('Stoploss order was cancelled, '
'but unable to recreate one: %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(order)
result = True
elif self.config.get('trailing_stop', False):
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)
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
# in which case we cancel stoploss order and put another one with new
# value immediately
self.handle_trailing_stoploss_on_exchange(trade, order)
self.handle_trailing_stoploss_on_exchange(trade, stoploss_order)
return result
return False
def handle_trailing_stoploss_on_exchange(self, trade: Trade, order):
"""
@@ -669,23 +712,35 @@ class FreqtradeBot(object):
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 '
'in order to add another one ...')
if self.exchange.cancel_order(order['id'], trade.pair):
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)
except InvalidOrderException:
logger.exception(f"Could not cancel stoploss order {order['id']} "
f"for pair {trade.pair}")
try:
# creating the new one
stoploss_order_id = self.exchange.stoploss_limit(
pair=trade.pair, amount=trade.amount,
stop_price=trade.stop_loss, rate=trade.stop_loss * 0.99
)['id']
trade.stoploss_order_id = str(stoploss_order_id)
except DependencyException:
trade.stoploss_order_id = None
logger.exception(f"Could not create trailing stoploss order "
f"for pair {trade.pair}.")
def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool:
if self.edge:
stoploss = self.edge.stoploss(trade.pair)
def _check_and_execute_sell(self, trade: Trade, sell_rate: float,
buy: bool, sell: bool) -> bool:
"""
Check and execute sell
"""
should_sell = self.strategy.should_sell(
trade, sell_rate, datetime.utcnow(), buy, sell, force_stoploss=stoploss)
else:
should_sell = self.strategy.should_sell(trade, sell_rate, datetime.utcnow(), buy, sell)
trade, sell_rate, datetime.utcnow(), buy, sell,
force_stoploss=self.edge.stoploss(trade.pair) if self.edge else 0
)
if should_sell.sell_flag:
self.execute_sell(trade, sell_rate, should_sell.sell_type)
@@ -713,7 +768,7 @@ class FreqtradeBot(object):
if not trade.open_order_id:
continue
order = self.exchange.get_order(trade.open_order_id, trade.pair)
except (RequestException, DependencyException):
except (RequestException, DependencyException, InvalidOrderException):
logger.info(
'Cannot query order for %s due to %s',
trade,
@@ -823,23 +878,40 @@ class FreqtradeBot(object):
# First cancelling stoploss on exchange ...
if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id:
try:
self.exchange.cancel_order(trade.stoploss_order_id, trade.pair)
except InvalidOrderException:
logger.exception(f"Could not cancel stoploss order {trade.stoploss_order_id}")
# Execute sell and update trade record
order_id = self.exchange.sell(pair=str(trade.pair),
order = self.exchange.sell(pair=str(trade.pair),
ordertype=self.strategy.order_types[sell_type],
amount=trade.amount, rate=limit,
time_in_force=self.strategy.order_time_in_force['sell']
)['id']
)
trade.open_order_id = order_id
trade.open_order_id = order['id']
trade.close_rate_requested = limit
trade.sell_reason = sell_reason.value
# In case of market sell orders the order can be closed immediately
if order.get('status', 'unknown') == 'closed':
trade.update(order)
Trade.session.flush()
profit_trade = trade.calc_profit(rate=limit)
current_rate = self.exchange.get_ticker(trade.pair)['bid']
profit_percent = trade.calc_profit_percent(limit)
pair_url = self.exchange.get_pair_detail_url(trade.pair)
# 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)
def _notify_sell(self, trade: Trade):
"""
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.
current_rate = self.get_sell_rate(trade.pair, False)
profit_percent = trade.calc_profit_percent(profit_rate)
gain = "profit" if profit_percent > 0 else "loss"
msg = {
@@ -847,14 +919,14 @@ class FreqtradeBot(object):
'exchange': trade.exchange.capitalize(),
'pair': trade.pair,
'gain': gain,
'market_url': pair_url,
'limit': limit,
'limit': trade.close_rate_requested,
'order_type': self.strategy.order_types['sell'],
'amount': trade.amount,
'open_rate': trade.open_rate,
'current_rate': current_rate,
'profit_amount': profit_trade,
'profit_percent': profit_percent,
'sell_reason': sell_reason.value
'sell_reason': trade.sell_reason
}
# For regular case, when the configuration exists
@@ -868,4 +940,3 @@ class FreqtradeBot(object):
# Send the message
self.rpc.send_msg(msg)
Trade.session.flush()

50
freqtrade/loggers.py Normal file
View File

@@ -0,0 +1,50 @@
import logging
import sys
from logging.handlers import RotatingFileHandler
from typing import Any, Dict, List
logger = logging.getLogger(__name__)
def _set_loggers(verbosity: int = 0) -> None:
"""
Set the logging level for third party libraries
:return: None
"""
logging.getLogger('requests').setLevel(
logging.INFO if verbosity <= 1 else logging.DEBUG
)
logging.getLogger("urllib3").setLevel(
logging.INFO if verbosity <= 1 else logging.DEBUG
)
logging.getLogger('ccxt.base.exchange').setLevel(
logging.INFO if verbosity <= 2 else logging.DEBUG
)
logging.getLogger('telegram').setLevel(logging.INFO)
def setup_logging(config: Dict[str, Any]) -> None:
"""
Process -v/--verbose, --logfile options
"""
# Log level
verbosity = config['verbosity']
# Log to stdout, not stderr
log_handlers: List[logging.Handler] = [logging.StreamHandler(sys.stdout)]
if config.get('logfile'):
log_handlers.append(RotatingFileHandler(config['logfile'],
maxBytes=1024 * 1024, # 1Mb
backupCount=10))
logging.basicConfig(
level=logging.INFO if verbosity < 1 else logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=log_handlers
)
_set_loggers(verbosity)
logger.info('Verbosity set to %s', verbosity)

View File

@@ -3,87 +3,66 @@
Main Freqtrade bot script.
Read the documentation to know what cli arguments you need.
"""
import logging
import sys
# check min. python version
if sys.version_info < (3, 6):
sys.exit("Freqtrade requires Python version >= 3.6")
# flake8: noqa E402
import logging
from argparse import Namespace
from typing import List
from typing import Any, List
from freqtrade import OperationalException
from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration, set_loggers
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.state import State
from freqtrade.rpc import RPCMessageType
from freqtrade.configuration import Arguments
from freqtrade.worker import Worker
logger = logging.getLogger('freqtrade')
def main(sysargv: List[str]) -> None:
def main(sysargv: List[str] = None) -> None:
"""
This function will initiate the bot and start the trading loop.
:return: None
"""
return_code: Any = 1
worker = None
try:
arguments = Arguments(
sysargv,
'Free, open source crypto trading bot'
)
args = arguments.get_parsed_arg()
args: Namespace = arguments.get_parsed_arg()
# A subcommand has been issued.
# Means if Backtesting or Hyperopt have been called we exit the bot
if hasattr(args, 'func'):
args.func(args)
return
freqtrade = None
return_code = 1
try:
# Load and validate configuration
config = Configuration(args, None).get_config()
# Init the bot
freqtrade = FreqtradeBot(config)
state = None
while True:
state = freqtrade.worker(old_state=state)
if state == State.RELOAD_CONF:
freqtrade = reconfigure(freqtrade, args)
# TODO: fetch return_code as returned by the command function here
return_code = 0
else:
# Load and run worker
worker = Worker(args)
worker.run()
except SystemExit as e:
return_code = e
except KeyboardInterrupt:
logger.info('SIGINT received, aborting ...')
return_code = 0
except OperationalException as e:
logger.error(str(e))
return_code = 2
except BaseException:
except Exception:
logger.exception('Fatal exception!')
finally:
if freqtrade:
freqtrade.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': 'process died'
})
freqtrade.cleanup()
if worker:
worker.exit()
sys.exit(return_code)
def reconfigure(freqtrade: FreqtradeBot, args: Namespace) -> FreqtradeBot:
"""
Cleans up current instance, reloads the configuration and returns the new instance
"""
# Clean up current modules
freqtrade.cleanup()
# Create new instance
freqtrade = FreqtradeBot(Configuration(args, None).get_config())
freqtrade.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': 'config reloaded'
})
return freqtrade
if __name__ == '__main__':
set_loggers()
main(sys.argv[1:])
main()

View File

@@ -1,15 +1,14 @@
"""
Various tool function for Freqtrade and scripts
"""
import gzip
import logging
import re
from datetime import datetime
from typing import Dict
from pathlib import Path
from typing.io import IO
import numpy as np
from pandas import DataFrame
import rapidjson
logger = logging.getLogger(__name__)
@@ -41,25 +40,7 @@ def datesarray_to_datetimearray(dates: np.ndarray) -> np.ndarray:
return dates.dt.to_pydatetime()
def common_datearray(dfs: Dict[str, DataFrame]) -> np.ndarray:
"""
Return dates from Dataframe
:param dfs: Dict with format pair: pair_data
:return: List of dates
"""
alldates = {}
for pair, pair_data in dfs.items():
dates = datesarray_to_datetimearray(pair_data['date'])
for date in dates:
alldates[date] = 1
lst = []
for date, _ in alldates.items():
lst.append(date)
arr = np.array(lst)
return np.sort(arr, axis=0)
def file_dump_json(filename, data, is_zip=False) -> None:
def file_dump_json(filename: Path, data, is_zip=False) -> None:
"""
Dump JSON data into a file
:param filename: file to create
@@ -69,8 +50,8 @@ def file_dump_json(filename, data, is_zip=False) -> None:
logger.info(f'dumping json to "{filename}"')
if is_zip:
if not filename.endswith('.gz'):
filename = filename + '.gz'
if filename.suffix != '.gz':
filename = filename.with_suffix('.gz')
with gzip.open(filename, 'w') as fp:
rapidjson.dump(data, fp, default=str, number_mode=rapidjson.NM_NATIVE)
else:
@@ -80,7 +61,7 @@ def file_dump_json(filename, data, is_zip=False) -> None:
logger.debug(f'done json to "{filename}"')
def json_load(datafile):
def json_load(datafile: IO):
"""
load data with rapidjson
Use this to have a consistent experience,
@@ -113,3 +94,23 @@ def format_ms_time(date: int) -> str:
: epoch-string in ms
"""
return datetime.fromtimestamp(date/1000.0).strftime('%Y-%m-%dT%H:%M:%S')
def deep_merge_dicts(source, destination):
"""
Values from Source override destination, destination is returned (and modified!!)
Sample:
>>> a = { 'first' : { 'rows' : { 'pass' : 'dog', 'number' : '1' } } }
>>> b = { 'first' : { 'rows' : { 'fail' : 'cat', 'number' : '5' } } }
>>> merge(b, a) == { 'first' : { 'rows' : { 'pass' : 'dog', 'fail' : 'cat', 'number' : '5' } } }
True
"""
for key, value in source.items():
if isinstance(value, dict):
# get node or create one
node = destination.setdefault(key, {})
deep_merge_dicts(value, node)
else:
destination[key] = value
return destination

View File

@@ -1,49 +1,111 @@
# pragma pylint: disable=missing-docstring
import logging
from datetime import datetime
from typing import Dict, Tuple
import operator
from argparse import Namespace
from typing import Any, Dict
import arrow
from pandas import DataFrame
from filelock import FileLock, Timeout
from freqtrade import DependencyException, constants
from freqtrade.state import RunMode
from freqtrade.utils import setup_utils_configuration
from freqtrade.optimize.default_hyperopt import DefaultHyperOpts # noqa: F401
logger = logging.getLogger(__name__)
def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]:
def setup_configuration(args: Namespace, method: RunMode) -> Dict[str, Any]:
"""
Get the maximum timeframe for the given backtest data
:param data: dictionary with preprocessed backtesting data
:return: tuple containing min_date, max_date
Prepare the configuration for the Hyperopt module
:param args: Cli args from Arguments()
:return: Configuration
"""
timeframe = [
(arrow.get(frame['date'].min()), arrow.get(frame['date'].max()))
for frame in data.values()
]
return min(timeframe, key=operator.itemgetter(0))[0], \
max(timeframe, key=operator.itemgetter(1))[1]
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)
if method == RunMode.HYPEROPT:
# Special cases for Hyperopt
if config.get('strategy') and config.get('strategy') != 'DefaultStrategy':
logger.error("Please don't use --strategy for hyperopt.")
logger.error(
"Read the documentation at "
"https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md "
"to understand how to configure hyperopt.")
raise DependencyException("--strategy configured but not supported for hyperopt")
return config
def validate_backtest_data(data: Dict[str, DataFrame], min_date: datetime,
max_date: datetime, ticker_interval_mins: int) -> bool:
def start_backtesting(args: Namespace) -> None:
"""
Validates preprocessed backtesting data for missing values and shows warnings about it that.
Start Backtesting script
:param args: Cli args from Arguments()
:return: None
"""
# Import here to avoid loading backtesting module when it's not used
from freqtrade.optimize.backtesting import Backtesting
:param data: dictionary with preprocessed backtesting data
:param min_date: start-date of the data
:param max_date: end-date of the data
:param ticker_interval_mins: ticker interval in minutes
# Initialize configuration
config = setup_configuration(args, RunMode.BACKTEST)
logger.info('Starting freqtrade in Backtesting mode')
# Initialize backtesting object
backtesting = Backtesting(config)
backtesting.start()
def start_hyperopt(args: Namespace) -> None:
"""
# total difference in minutes / interval-minutes
expected_frames = int((max_date - min_date).total_seconds() // 60 // ticker_interval_mins)
found_missing = False
for pair, df in data.items():
dflen = len(df)
if dflen < expected_frames:
found_missing = True
logger.warning("%s has missing frames: expected %s, got %s, that's %s missing values",
pair, expected_frames, dflen, expected_frames - dflen)
return found_missing
Start hyperopt script
:param args: Cli args from Arguments()
:return: None
"""
# Import here to avoid loading hyperopt module when it's not used
from freqtrade.optimize.hyperopt import Hyperopt
# Initialize configuration
config = setup_configuration(args, RunMode.HYPEROPT)
logger.info('Starting freqtrade in Hyperopt mode')
lock = FileLock(Hyperopt.get_lock_filename(config))
try:
with lock.acquire(timeout=1):
# Remove noisy log messages
logging.getLogger('hyperopt.tpe').setLevel(logging.WARNING)
logging.getLogger('filelock').setLevel(logging.WARNING)
# Initialize backtesting object
hyperopt = Hyperopt(config)
hyperopt.start()
except Timeout:
logger.info("Another running instance of freqtrade Hyperopt detected.")
logger.info("Simultaneous execution of multiple Hyperopt commands is not supported. "
"Hyperopt module is resource hungry. Please run your Hyperopts sequentially "
"or on separate machines.")
logger.info("Quitting now.")
# TODO: return False here in order to help freqtrade to exit
# with non-zero exit code...
# Same in Edge and Backtesting start() functions.
def start_edge(args: Namespace) -> None:
"""
Start Edge script
:param args: Cli args from Arguments()
:return: None
"""
from freqtrade.optimize.edge_cli import EdgeCli
# Initialize configuration
config = setup_configuration(args, RunMode.EDGE)
logger.info('Starting freqtrade in Edge mode')
# Initialize Edge object
edge_cli = EdgeCli(config)
edge_cli.start()

View File

@@ -4,26 +4,24 @@
This module contains the backtesting logic
"""
import logging
from argparse import Namespace
from copy import deepcopy
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, Dict, List, NamedTuple, Optional
from pandas import DataFrame
from tabulate import tabulate
from freqtrade import optimize
from freqtrade import DependencyException, constants
from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration
from freqtrade.exchange import Exchange
from freqtrade import OperationalException
from freqtrade.configuration import TimeRange
from freqtrade.data import history
from freqtrade.data.dataprovider import DataProvider
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.misc import file_dump_json
from freqtrade.persistence import Trade
from freqtrade.resolvers import StrategyResolver
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.state import RunMode
from freqtrade.strategy.interface import SellType, IStrategy
from freqtrade.strategy.interface import IStrategy, SellType
from tabulate import tabulate
logger = logging.getLogger(__name__)
@@ -65,34 +63,44 @@ class Backtesting(object):
self.config['exchange']['uid'] = ''
self.config['dry_run'] = True
self.strategylist: List[IStrategy] = []
self.exchange = ExchangeResolver(self.config['exchange']['name'], self.config).exchange
self.fee = self.exchange.get_fee()
if self.config.get('runmode') != RunMode.HYPEROPT:
self.dataprovider = DataProvider(self.config, self.exchange)
IStrategy.dp = self.dataprovider
if self.config.get('strategy_list', None):
# Force one interval
self.ticker_interval = str(self.config.get('ticker_interval'))
self.ticker_interval_mins = constants.TICKER_INTERVAL_MINUTES[self.ticker_interval]
for strat in list(self.config['strategy_list']):
stratconf = deepcopy(self.config)
stratconf['strategy'] = strat
self.strategylist.append(StrategyResolver(stratconf).strategy)
else:
# only one strategy
# No strategy list specified, only one strategy
self.strategylist.append(StrategyResolver(self.config).strategy)
# Load one strategy
self._set_strategy(self.strategylist[0])
self.exchange = Exchange(self.config)
self.fee = self.exchange.get_fee()
# Load one (first) strategy
self._set_strategy(self.strategylist[0])
def _set_strategy(self, strategy):
"""
Load strategy into backtesting
"""
self.strategy = strategy
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`")
self.ticker_interval = self.config.get('ticker_interval')
self.ticker_interval_mins = constants.TICKER_INTERVAL_MINUTES[self.ticker_interval]
self.tickerdata_to_dataframe = strategy.tickerdata_to_dataframe
self.ticker_interval_mins = timeframe_to_minutes(self.ticker_interval)
self.advise_buy = strategy.advise_buy
self.advise_sell = strategy.advise_sell
# Set stoploss_on_exchange to false for backtesting,
# since a "perfect" stoploss-sell is assumed anyway
# And the regular "stoploss" function would not apply to that case
self.strategy.order_types['stoploss_on_exchange'] = False
def _generate_text_table(self, data: Dict[str, Dict], results: DataFrame,
skip_nan: bool = False) -> str:
@@ -182,7 +190,7 @@ class Backtesting(object):
return tabulate(tabular_data, headers=headers, # type: ignore
floatfmt=floatfmt, tablefmt="pipe")
def _store_backtest_result(self, recordfilename: str, results: DataFrame,
def _store_backtest_result(self, recordfilename: Path, results: DataFrame,
strategyname: Optional[str] = None) -> None:
records = [(t.pair, t.profit_percent, t.open_time.timestamp(),
@@ -193,18 +201,43 @@ class Backtesting(object):
if records:
if strategyname:
# Inject strategyname to filename
recname = Path(recordfilename)
recordfilename = str(Path.joinpath(
recname.parent, f'{recname.stem}-{strategyname}').with_suffix(recname.suffix))
logger.info('Dumping backtest results to %s', recordfilename)
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]:
"""
Helper function to convert a processed tickerlist into a list 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
for pair, pair_data in processed.items():
pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run
ticker_data = self.advise_sell(
self.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)
ticker_data.drop(ticker_data.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
def _get_sell_trade_entry(
self, pair: str, buy_row: DataFrame,
partial_ticker: List, trade_count_lock: Dict, args: Dict) -> Optional[BacktestResult]:
partial_ticker: List, trade_count_lock: Dict,
stake_amount: float, max_open_trades: int) -> Optional[BacktestResult]:
stake_amount = args['stake_amount']
max_open_trades = args.get('max_open_trades', 0)
trade = Trade(
open_rate=buy_row.open,
open_date=buy_row.date,
@@ -220,26 +253,23 @@ class Backtesting(object):
# Increase trade_count_lock for every iteration
trade_count_lock[sell_row.date] = trade_count_lock.get(sell_row.date, 0) + 1
buy_signal = sell_row.buy
sell = self.strategy.should_sell(trade, sell_row.open, sell_row.date, buy_signal,
sell = self.strategy.should_sell(trade, sell_row.open, sell_row.date, sell_row.buy,
sell_row.sell, low=sell_row.low, high=sell_row.high)
if sell.sell_flag:
trade_dur = int((sell_row.date - buy_row.date).total_seconds() // 60)
# Special handling if high or low hit STOP_LOSS or ROI
if sell.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
# Set close_rate to stoploss
closerate = trade.stop_loss
elif sell.sell_type == (SellType.ROI):
# get next entry in min_roi > to trade duration
# Interface.py skips on trade_duration <= duration
roi_entry = max(list(filter(lambda x: trade_dur >= x,
self.strategy.minimal_roi.keys())))
roi = self.strategy.minimal_roi[roi_entry]
roi = self.strategy.min_roi_reached_entry(trade_dur)
if roi is not None:
# - (Expected abs profit + open_rate + open_fee) / (fee_close -1)
closerate = - (trade.open_rate * roi + trade.open_rate *
(1 + trade.fee_open)) / (trade.fee_close - 1)
else:
# This should not be reached...
closerate = sell_row.open
else:
closerate = sell_row.open
@@ -293,74 +323,75 @@ class Backtesting(object):
position_stacking: do we allow position stacking? (default: False)
:return: DataFrame
"""
headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high']
# Arguments are long and noisy, so this is commented out.
# Uncomment if you need to debug the backtest() method.
# logger.debug(f"Start backtest, args: {args}")
processed = args['processed']
stake_amount = args['stake_amount']
max_open_trades = args.get('max_open_trades', 0)
position_stacking = args.get('position_stacking', False)
start_date = args['start_date']
end_date = args['end_date']
trades = []
trade_count_lock: Dict = {}
ticker: Dict = {}
pairs = []
# Create ticker dict
for pair, pair_data in processed.items():
pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run
ticker_data = self.advise_sell(
self.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)
ticker_data.drop(ticker_data.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()]
pairs.append(pair)
# Dict of ticker-lists for performance (looping lists is a lot faster than dataframes)
ticker: Dict = self._get_ticker_list(processed)
lock_pair_until: Dict = {}
# Indexes per pair, so some pairs are allowed to have a missing start.
indexes: Dict = {}
tmp = start_date + timedelta(minutes=self.ticker_interval_mins)
index = 0
# Loop timerange and test per pair
# Loop timerange and get candle for each pair at that point in time
while tmp < end_date:
# print(f"time: {tmp}")
for i, pair in enumerate(ticker):
if pair not in indexes:
indexes[pair] = 0
try:
row = ticker[pair][index]
row = ticker[pair][indexes[pair]]
except IndexError:
# missing Data for one pair ...
# Warnings for this are shown by `validate_backtest_data`
# missing Data for one pair at the end.
# Warnings for this are shown during data loading
continue
# Waits until the time-counter reaches the start of the data for this pair.
if row.date > tmp.datetime:
continue
indexes[pair] += 1
if row.buy == 0 or row.sell == 1:
continue # skip rows where no buy signal or that would immediately sell off
if not position_stacking:
if pair in lock_pair_until and row.date <= lock_pair_until[pair]:
if (not position_stacking and pair in lock_pair_until
and row.date <= lock_pair_until[pair]):
# without positionstacking, we can only have one open trade per pair.
continue
if max_open_trades > 0:
# Check if max_open_trades has already been reached for the given date
if not trade_count_lock.get(row.date, 0) < max_open_trades:
continue
trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1
trade_entry = self._get_sell_trade_entry(pair, row, ticker[pair][index + 1:],
trade_count_lock, args)
# 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_count_lock, stake_amount,
max_open_trades)
if trade_entry:
lock_pair_until[pair] = trade_entry.close_time
trades.append(trade_entry)
else:
# Set lock_pair_until to end of testing period if trade could not be closed
# This happens only if the buy-signal was with the last candle
lock_pair_until[pair] = end_date
lock_pair_until[pair] = end_date.datetime
# Move time one configured time_interval ahead.
tmp += timedelta(minutes=self.ticker_interval_mins)
index += 1
return DataFrame.from_records(trades, columns=BacktestResult._fields)
def start(self) -> None:
@@ -373,15 +404,7 @@ class Backtesting(object):
logger.info('Using stake_currency: %s ...', self.config['stake_currency'])
logger.info('Using stake_amount: %s ...', self.config['stake_amount'])
if self.config.get('live'):
logger.info('Downloading data for all pairs in whitelist ...')
self.exchange.refresh_latest_ohlcv([(pair, self.ticker_interval) for pair in pairs])
data = {key[0]: value for key, value in self.exchange._klines.items()}
else:
logger.info('Using local backtesting data (using whitelist in given config) ...')
timerange = Arguments.parse_timerange(None if self.config.get(
timerange = TimeRange.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange')))
data = history.load_data(
datadir=Path(self.config['datadir']) if self.config.get('datadir') else None,
@@ -389,7 +412,7 @@ class Backtesting(object):
ticker_interval=self.ticker_interval,
refresh_pairs=self.config.get('refresh_pairs', False),
exchange=self.exchange,
timerange=timerange
timerange=timerange,
)
if not data:
@@ -403,20 +426,19 @@ class Backtesting(object):
max_open_trades = 0
all_results = {}
for strat in self.strategylist:
logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())
self._set_strategy(strat)
min_date, max_date = history.get_timeframe(data)
min_date, max_date = optimize.get_timeframe(data)
# Validate dataframe for missing values (mainly at start and end, as fillup is called)
optimize.validate_backtest_data(data, min_date, max_date,
constants.TICKER_INTERVAL_MINUTES[self.ticker_interval])
logger.info(
'Measuring data from %s up to %s (%s days)..',
'Backtesting with data from %s up to %s (%s days)..',
min_date.isoformat(),
max_date.isoformat(),
(max_date - min_date).days
)
for strat in self.strategylist:
logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())
self._set_strategy(strat)
# need to reprocess data every time to populate signals
preprocessed = self.strategy.tickerdata_to_dataframe(data)
@@ -435,7 +457,7 @@ class Backtesting(object):
for strategy, results in all_results.items():
if self.config.get('export', False):
self._store_backtest_result(self.config['exportfilename'], results,
self._store_backtest_result(Path(self.config['exportfilename']), results,
strategy if len(self.strategylist) > 1 else None)
print(f"Result for strategy {strategy}")
@@ -453,38 +475,3 @@ class Backtesting(object):
print(' Strategy Summary '.center(133, '='))
print(self._generate_text_table_strategy(all_results))
print('\nFor more details, please look at the detail tables above')
def setup_configuration(args: Namespace) -> Dict[str, Any]:
"""
Prepare the configuration for the backtesting
:param args: Cli args from Arguments()
:return: Configuration
"""
configuration = Configuration(args, RunMode.BACKTEST)
config = configuration.get_config()
# Ensure we do not use Exchange credentials
config['exchange']['key'] = ''
config['exchange']['secret'] = ''
if config['stake_amount'] == constants.UNLIMITED_STAKE_AMOUNT:
raise DependencyException('stake amount could not be "%s" for backtesting' %
constants.UNLIMITED_STAKE_AMOUNT)
return config
def start(args: Namespace) -> None:
"""
Start Backtesting script
:param args: Cli args from Arguments()
:return: None
"""
# Initialize configuration
config = setup_configuration(args)
logger.info('Starting freqtrade in Backtesting mode')
# Initialize backtesting object
backtesting = Backtesting(config)
backtesting.start()

View File

@@ -1,52 +1,61 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
from functools import reduce
from typing import Any, Callable, Dict, List
import talib.abstract as ta
from pandas import DataFrame
from typing import Dict, Any, Callable, List
from functools import reduce
from skopt.space import Categorical, Dimension, Integer, Real
from skopt.space import Categorical, Dimension, Integer
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.optimize.hyperopt_interface import IHyperOpt
class_name = 'DefaultHyperOpts'
class DefaultHyperOpts(IHyperOpt):
"""
Default hyperopt provided by freqtrade bot.
You can override it with your own hyperopt
Default hyperopt provided by the Freqtrade bot.
You can override it with your own Hyperopt
"""
@staticmethod
def populate_indicators(dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Add several indicators needed for buy and sell strategies defined below.
"""
# ADX
dataframe['adx'] = ta.ADX(dataframe)
# MACD
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
# MFI
dataframe['mfi'] = ta.MFI(dataframe)
# RSI
dataframe['rsi'] = ta.RSI(dataframe)
# Stochastic Fast
stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd']
# Minus-DI
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# Bollinger bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_upperband'] = bollinger['upper']
# SAR
dataframe['sar'] = ta.SAR(dataframe)
return dataframe
@staticmethod
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Define the buy strategy parameters to be used by hyperopt
Define the buy strategy parameters to be used by Hyperopt.
"""
def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Buy strategy Hyperopt will build and use
Buy strategy Hyperopt will build and use.
"""
conditions = []
# GUARDS AND TRENDS
if 'mfi-enabled' in params and params['mfi-enabled']:
conditions.append(dataframe['mfi'] < params['mfi-value'])
@@ -70,6 +79,7 @@ class DefaultHyperOpts(IHyperOpt):
dataframe['close'], dataframe['sar']
))
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
@@ -81,7 +91,7 @@ class DefaultHyperOpts(IHyperOpt):
@staticmethod
def indicator_space() -> List[Dimension]:
"""
Define your Hyperopt space for searching strategy parameters
Define your Hyperopt space for searching buy strategy parameters.
"""
return [
Integer(10, 25, name='mfi-value'),
@@ -98,14 +108,14 @@ class DefaultHyperOpts(IHyperOpt):
@staticmethod
def sell_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Define the sell strategy parameters to be used by hyperopt
Define the sell strategy parameters to be used by Hyperopt.
"""
def populate_sell_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Sell strategy Hyperopt will build and use
Sell strategy Hyperopt will build and use.
"""
# print(params)
conditions = []
# GUARDS AND TRENDS
if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']:
conditions.append(dataframe['mfi'] > params['sell-mfi-value'])
@@ -129,6 +139,7 @@ class DefaultHyperOpts(IHyperOpt):
dataframe['sar'], dataframe['close']
))
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'sell'] = 1
@@ -140,7 +151,7 @@ class DefaultHyperOpts(IHyperOpt):
@staticmethod
def sell_indicator_space() -> List[Dimension]:
"""
Define your Hyperopt space for searching sell strategy parameters
Define your Hyperopt space for searching sell strategy parameters.
"""
return [
Integer(75, 100, name='sell-mfi-value'),
@@ -156,47 +167,11 @@ class DefaultHyperOpts(IHyperOpt):
'sell-sar_reversal'], name='sell-trigger')
]
@staticmethod
def generate_roi_table(params: Dict) -> Dict[int, float]:
"""
Generate the ROI table that will be used by Hyperopt
"""
roi_table = {}
roi_table[0] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
roi_table[params['roi_t3']] = params['roi_p1'] + params['roi_p2']
roi_table[params['roi_t3'] + params['roi_t2']] = params['roi_p1']
roi_table[params['roi_t3'] + params['roi_t2'] + params['roi_t1']] = 0
return roi_table
@staticmethod
def stoploss_space() -> List[Dimension]:
"""
Stoploss Value to search
"""
return [
Real(-0.5, -0.02, name='stoploss'),
]
@staticmethod
def roi_space() -> List[Dimension]:
"""
Values to search for each ROI steps
"""
return [
Integer(10, 120, name='roi_t1'),
Integer(10, 60, name='roi_t2'),
Integer(10, 40, name='roi_t3'),
Real(0.01, 0.04, name='roi_p1'),
Real(0.01, 0.07, name='roi_p2'),
Real(0.01, 0.20, name='roi_p3'),
]
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators. Should be a copy of from strategy
must align to populate_indicators in this file
Only used when --spaces does not include buy
Based on TA indicators. Should be a copy of same method from strategy.
Must align to populate_indicators in this file.
Only used when --spaces does not include buy space.
"""
dataframe.loc[
(
@@ -211,9 +186,9 @@ class DefaultHyperOpts(IHyperOpt):
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators. Should be a copy of from strategy
must align to populate_indicators in this file
Only used when --spaces does not include sell
Based on TA indicators. Should be a copy of same method from strategy.
Must align to populate_indicators in this file.
Only used when --spaces does not include sell space.
"""
dataframe.loc[
(
@@ -223,4 +198,5 @@ class DefaultHyperOpts(IHyperOpt):
(dataframe['fastd'] > 54)
),
'sell'] = 1
return dataframe

View File

@@ -0,0 +1,52 @@
"""
DefaultHyperOptLoss
This module defines the default HyperoptLoss class which is being used for
Hyperoptimization.
"""
from math import exp
from pandas import DataFrame
from freqtrade.optimize.hyperopt import IHyperOptLoss
# Set TARGET_TRADES to suit your number concurrent trades so its realistic
# to the number of days
TARGET_TRADES = 600
# This is assumed to be expected avg profit * expected trade count.
# For example, for 0.35% avg per trade (or 0.0035 as ratio) and 1100 trades,
# expected max profit = 3.85
# Check that the reported Σ% values do not exceed this!
# Note, this is ratio. 3.85 stated above means 385Σ%.
EXPECTED_MAX_PROFIT = 3.0
# Max average trade duration in minutes.
# If eval ends with higher value, we consider it a failed eval.
MAX_ACCEPTED_TRADE_DURATION = 300
class DefaultHyperOptLoss(IHyperOptLoss):
"""
Defines the default loss function for hyperopt
"""
@staticmethod
def hyperopt_loss_function(results: DataFrame, trade_count: int,
*args, **kwargs) -> float:
"""
Objective function, returns smaller number for better results
This is the Default algorithm
Weights are distributed as follows:
* 0.4 to trade duration
* 0.25: Avoiding trade loss
* 1.0 to total profit, compared to the expected value (`EXPECTED_MAX_PROFIT`) defined above
"""
total_profit = results.profit_percent.sum()
trade_duration = results.trade_duration.mean()
trade_loss = 1 - 0.25 * exp(-(trade_count - TARGET_TRADES) ** 2 / 10 ** 5.8)
profit_loss = max(0, 1 - total_profit / EXPECTED_MAX_PROFIT)
duration_loss = 0.4 * min(trade_duration / MAX_ACCEPTED_TRADE_DURATION, 1)
result = trade_loss + profit_loss + duration_loss
return result

View File

@@ -4,16 +4,14 @@
This module contains the edge backtesting interface
"""
import logging
from argparse import Namespace
from typing import Dict, Any
from tabulate import tabulate
from freqtrade import constants
from freqtrade.edge import Edge
from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration
from freqtrade.configuration import TimeRange
from freqtrade.exchange import Exchange
from freqtrade.resolvers import StrategyResolver
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@@ -35,6 +33,7 @@ class EdgeCli(object):
self.config['exchange']['secret'] = ''
self.config['exchange']['password'] = ''
self.config['exchange']['uid'] = ''
self.config['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
self.config['dry_run'] = True
self.exchange = Exchange(self.config)
self.strategy = StrategyResolver(self.config).strategy
@@ -42,7 +41,7 @@ class EdgeCli(object):
self.edge = Edge(config, self.exchange, self.strategy)
self.edge._refresh_pairs = self.config.get('refresh_pairs', False)
self.timerange = Arguments.parse_timerange(None if self.config.get(
self.timerange = TimeRange.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange')))
self.edge._timerange = self.timerange
@@ -73,37 +72,7 @@ class EdgeCli(object):
floatfmt=floatfmt, tablefmt="pipe")
def start(self) -> None:
self.edge.calculate()
print('') # blank like for readability
result = self.edge.calculate()
if result:
print('') # blank line for readability
print(self._generate_edge_table(self.edge._cached_pairs))
def setup_configuration(args: Namespace) -> Dict[str, Any]:
"""
Prepare the configuration for edge backtesting
:param args: Cli args from Arguments()
:return: Configuration
"""
configuration = Configuration(args, RunMode.EDGECLI)
config = configuration.get_config()
# Ensure we do not use Exchange credentials
config['exchange']['key'] = ''
config['exchange']['secret'] = ''
return config
def start(args: Namespace) -> None:
"""
Start Edge script
:param args: Cli args from Arguments()
:return: None
"""
# Initialize configuration
config = setup_configuration(args)
logger.info('Starting freqtrade in Edge mode')
# Initialize Edge object
edge_cli = EdgeCli(config)
edge_cli.start()

View File

@@ -5,36 +5,39 @@ This module contains the hyperopt logic
"""
import logging
import multiprocessing
import os
import sys
from argparse import Namespace
from math import exp
from collections import OrderedDict
from operator import itemgetter
from pathlib import Path
from pprint import pprint
from typing import Any, Dict, List
from typing import Any, Dict, List, Optional
from joblib import Parallel, delayed, dump, load, wrap_non_picklable_objects
import rapidjson
from colorama import init as colorama_init
from colorama import Fore, Style
from joblib import Parallel, delayed, dump, load, wrap_non_picklable_objects, cpu_count
from pandas import DataFrame
from skopt import Optimizer
from skopt.space import Dimension
from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration
from freqtrade.data.history import load_data
from freqtrade.optimize import get_timeframe
from freqtrade.configuration import TimeRange
from freqtrade.data.history import load_data, get_timeframe
from freqtrade.optimize.backtesting import Backtesting
from freqtrade.state import RunMode
from freqtrade.resolvers import HyperOptResolver
# Import IHyperOptLoss to allow users import from this file
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss # noqa: F4
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver, HyperOptLossResolver
logger = logging.getLogger(__name__)
INITIAL_POINTS = 30
MAX_LOSS = 100000 # just a big enough number to be bad result in loss optimization
TICKERDATA_PICKLE = os.path.join('user_data', 'hyperopt_tickerdata.pkl')
class Hyperopt(Backtesting):
class Hyperopt:
"""
Hyperopt class, this class contains all the logic to run a hyperopt simulation
@@ -43,29 +46,66 @@ class Hyperopt(Backtesting):
hyperopt.start()
"""
def __init__(self, config: Dict[str, Any]) -> None:
super().__init__(config)
self.config = config
self.backtesting = Backtesting(self.config)
self.custom_hyperopt = HyperOptResolver(self.config).hyperopt
# set TARGET_TRADES to suit your number concurrent trades so its realistic
# to the number of days
self.target_trades = 600
self.total_tries = config.get('epochs', 0)
self.custom_hyperoptloss = HyperOptLossResolver(self.config).hyperoptloss
self.calculate_loss = self.custom_hyperoptloss.hyperopt_loss_function
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.total_epochs = config.get('epochs', 0)
self.current_best_loss = 100
# max average trade duration in minutes
# if eval ends with higher value, we consider it a failed eval
self.max_accepted_trade_duration = 300
# this is expexted avg profit * expected trade count
# for example 3.5%, 1100 trades, self.expected_max_profit = 3.85
# check that the reported Σ% values do not exceed this!
self.expected_max_profit = 3.0
if not self.config.get('hyperopt_continue'):
self.clean_hyperopt()
else:
logger.info("Continuing on previous hyperopt results.")
# Previous evaluations
self.trials_file = os.path.join('user_data', 'hyperopt_results.pickle')
self.trials: List = []
# Populate functions here (hasattr is slow so should not be run during "regular" operations)
if hasattr(self.custom_hyperopt, 'populate_buy_trend'):
self.backtesting.advise_buy = self.custom_hyperopt.populate_buy_trend # type: ignore
if hasattr(self.custom_hyperopt, 'populate_sell_trend'):
self.backtesting.advise_sell = 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):
self.max_open_trades = self.config['max_open_trades']
else:
logger.debug('Ignoring max_open_trades (--disable-max-market-positions was used) ...')
self.max_open_trades = 0
self.position_stacking = self.config.get('position_stacking', False),
if self.has_space('sell'):
# Make sure experimental is enabled
if 'experimental' not in self.config:
self.config['experimental'] = {}
self.config['experimental']['use_sell_signal'] = True
@staticmethod
def get_lock_filename(config) -> str:
return str(config['user_data_dir'] / 'hyperopt.lock')
def clean_hyperopt(self):
"""
Remove hyperopt pickle files to restart hyperopt.
"""
for f in [self.tickerdata_pickle, self.trials_file]:
p = Path(f)
if p.is_file():
logger.info(f"Removing `{p}`.")
p.unlink()
def get_args(self, params):
dimensions = self.hyperopt_space()
# Ensure the number of dimensions match
@@ -93,7 +133,7 @@ class Hyperopt(Backtesting):
"""
logger.info('Reading Trials from \'%s\'', self.trials_file)
trials = load(self.trials_file)
os.remove(self.trials_file)
self.trials_file.unlink()
return trials
def log_trials_result(self) -> None:
@@ -102,140 +142,190 @@ class Hyperopt(Backtesting):
"""
results = sorted(self.trials, key=itemgetter('loss'))
best_result = results[0]
logger.info(
'Best result:\n%s\nwith values:\n',
best_result['result']
params = best_result['params']
log_str = self.format_results_logstring(best_result)
print(f"\nBest result:\n\n{log_str}\n")
if self.config.get('print_json'):
result_dict: Dict = {}
if self.has_space('buy') or self.has_space('sell'):
result_dict['params'] = {}
if self.has_space('buy'):
result_dict['params'].update({p.name: params.get(p.name)
for p in self.hyperopt_space('buy')})
if self.has_space('sell'):
result_dict['params'].update({p.name: params.get(p.name)
for p in self.hyperopt_space('sell')})
if self.has_space('roi'):
# Convert keys in min_roi dict to strings because
# rapidjson cannot dump dicts with integer keys...
# OrderedDict is used to keep the numeric order of the items
# in the dict.
result_dict['minimal_roi'] = OrderedDict(
(str(k), v) for k, v in self.custom_hyperopt.generate_roi_table(params).items()
)
pprint(best_result['params'], indent=4)
if 'roi_t1' in best_result['params']:
logger.info('ROI table:')
pprint(self.custom_hyperopt.generate_roi_table(best_result['params']), indent=4)
if self.has_space('stoploss'):
result_dict['stoploss'] = params.get('stoploss')
print(rapidjson.dumps(result_dict, default=str, number_mode=rapidjson.NM_NATIVE))
else:
if self.has_space('buy'):
print('Buy hyperspace params:')
pprint({p.name: params.get(p.name) for p in self.hyperopt_space('buy')},
indent=4)
if self.has_space('sell'):
print('Sell hyperspace params:')
pprint({p.name: params.get(p.name) for p in self.hyperopt_space('sell')},
indent=4)
if self.has_space('roi'):
print("ROI table:")
pprint(self.custom_hyperopt.generate_roi_table(params), indent=4)
if self.has_space('stoploss'):
print(f"Stoploss: {params.get('stoploss')}")
def log_results(self, results) -> None:
"""
Log results if it is better than any previous evaluation
"""
if results['loss'] < self.current_best_loss:
current = results['current_tries']
total = results['total_tries']
res = results['result']
loss = results['loss']
print_all = self.config.get('print_all', False)
is_best_loss = results['loss'] < self.current_best_loss
if print_all or is_best_loss:
if is_best_loss:
self.current_best_loss = results['loss']
log_msg = f'\n{current:5d}/{total}: {res}. Loss {loss:.5f}'
print(log_msg)
log_str = self.format_results_logstring(results)
# Colorize output
if self.config.get('print_colorized', False):
if results['total_profit'] > 0:
log_str = Fore.GREEN + log_str
if print_all and is_best_loss:
log_str = Style.BRIGHT + log_str
if print_all:
print(log_str)
else:
print('\n' + log_str)
else:
print('.', end='')
sys.stdout.flush()
def calculate_loss(self, total_profit: float, trade_count: int, trade_duration: float) -> float:
"""
Objective function, returns smaller number for more optimal results
"""
trade_loss = 1 - 0.25 * exp(-(trade_count - self.target_trades) ** 2 / 10 ** 5.8)
profit_loss = max(0, 1 - total_profit / self.expected_max_profit)
duration_loss = 0.4 * min(trade_duration / self.max_accepted_trade_duration, 1)
result = trade_loss + profit_loss + duration_loss
return result
def format_results_logstring(self, results) -> str:
# Output human-friendly index here (starting from 1)
current = results['current_epoch'] + 1
total = self.total_epochs
res = results['results_explanation']
loss = results['loss']
log_str = f'{current:5d}/{total}: {res} Objective: {loss:.5f}'
log_str = f'*{log_str}' if results['is_initial_point'] else f' {log_str}'
return log_str
def has_space(self, space: str) -> bool:
"""
Tell if a space value is contained in the configuration
"""
if space in self.config['spaces'] or 'all' in self.config['spaces']:
return True
return False
return any(s in self.config['spaces'] for s in [space, 'all'])
def hyperopt_space(self) -> List[Dimension]:
def hyperopt_space(self, space: Optional[str] = None) -> List[Dimension]:
"""
Return the space to use during Hyperopt
Return the dimensions in the hyperoptimization space.
:param space: Defines hyperspace to return dimensions for.
If None, then the self.has_space() will be used to return dimensions
for all hyperspaces used.
"""
spaces: List[Dimension] = []
if self.has_space('buy'):
if space == 'buy' or (space is None and self.has_space('buy')):
logger.debug("Hyperopt has 'buy' space")
spaces += self.custom_hyperopt.indicator_space()
if self.has_space('sell'):
if space == 'sell' or (space is None and self.has_space('sell')):
logger.debug("Hyperopt has 'sell' space")
spaces += self.custom_hyperopt.sell_indicator_space()
# Make sure experimental is enabled
if 'experimental' not in self.config:
self.config['experimental'] = {}
self.config['experimental']['use_sell_signal'] = True
if self.has_space('roi'):
if space == 'roi' or (space is None and self.has_space('roi')):
logger.debug("Hyperopt has 'roi' space")
spaces += self.custom_hyperopt.roi_space()
if self.has_space('stoploss'):
if space == 'stoploss' or (space is None and self.has_space('stoploss')):
logger.debug("Hyperopt has 'stoploss' space")
spaces += self.custom_hyperopt.stoploss_space()
return spaces
def generate_optimizer(self, _params: Dict) -> Dict:
"""
Used Optimize function. Called once per epoch to optimize whatever is configured.
Keep this function as optimized as possible!
"""
params = self.get_args(_params)
if self.has_space('roi'):
self.strategy.minimal_roi = self.custom_hyperopt.generate_roi_table(params)
self.backtesting.strategy.minimal_roi = self.custom_hyperopt.generate_roi_table(params)
if self.has_space('buy'):
self.advise_buy = self.custom_hyperopt.buy_strategy_generator(params)
elif hasattr(self.custom_hyperopt, 'populate_buy_trend'):
self.advise_buy = self.custom_hyperopt.populate_buy_trend # type: ignore
self.backtesting.advise_buy = self.custom_hyperopt.buy_strategy_generator(params)
if self.has_space('sell'):
self.advise_sell = self.custom_hyperopt.sell_strategy_generator(params)
elif hasattr(self.custom_hyperopt, 'populate_sell_trend'):
self.advise_sell = self.custom_hyperopt.populate_sell_trend # type: ignore
self.backtesting.advise_sell = self.custom_hyperopt.sell_strategy_generator(params)
if self.has_space('stoploss'):
self.strategy.stoploss = params['stoploss']
self.backtesting.strategy.stoploss = params['stoploss']
processed = load(self.tickerdata_pickle)
processed = load(TICKERDATA_PICKLE)
min_date, max_date = get_timeframe(processed)
results = self.backtest(
results = self.backtesting.backtest(
{
'stake_amount': self.config['stake_amount'],
'processed': processed,
'position_stacking': self.config.get('position_stacking', True),
'max_open_trades': self.max_open_trades,
'position_stacking': self.position_stacking,
'start_date': min_date,
'end_date': max_date,
}
)
result_explanation = self.format_results(results)
results_explanation = self.format_results(results)
total_profit = results.profit_percent.sum()
trade_count = len(results.index)
trade_duration = results.trade_duration.mean()
total_profit = results.profit_abs.sum()
if trade_count == 0:
# If this evaluation contains too short amount of trades to be
# interesting -- consider it as 'bad' (assigned max. loss value)
# in order to cast this hyperspace point away from optimization
# path. We do not want to optimize 'hodl' strategies.
if trade_count < self.config['hyperopt_min_trades']:
return {
'loss': MAX_LOSS,
'params': params,
'result': result_explanation,
'results_explanation': results_explanation,
'total_profit': total_profit,
}
loss = self.calculate_loss(total_profit, trade_count, trade_duration)
loss = self.calculate_loss(results=results, trade_count=trade_count,
min_date=min_date.datetime, max_date=max_date.datetime)
return {
'loss': loss,
'params': params,
'result': result_explanation,
'results_explanation': results_explanation,
'total_profit': total_profit,
}
def format_results(self, results: DataFrame) -> str:
"""
Return the format result in a string
Return the formatted results explanation in a string
"""
trades = len(results.index)
avg_profit = results.profit_percent.mean() * 100.0
total_profit = results.profit_abs.sum()
stake_cur = self.config['stake_currency']
profit = results.profit_percent.sum()
profit = results.profit_percent.sum() * 100.0
duration = results.trade_duration.mean()
return (f'{trades:6d} trades. Avg profit {avg_profit: 5.2f}%. '
f'Total profit {total_profit: 11.8f} {stake_cur} '
f'({profit:.4f}Σ%). Avg duration {duration:5.1f} mins.')
f'({profit: 7.2f}Σ%). Avg duration {duration:5.1f} mins.')
def get_optimizer(self, cpu_count) -> Optimizer:
return Optimizer(
self.hyperopt_space(),
base_estimator="ET",
acq_optimizer="auto",
n_initial_points=30,
acq_optimizer_kwargs={'n_jobs': cpu_count}
n_initial_points=INITIAL_POINTS,
acq_optimizer_kwargs={'n_jobs': cpu_count},
random_state=self.config.get('hyperopt_random_state', None)
)
def run_optimizer_parallel(self, parallel, asked) -> List:
@@ -244,7 +334,7 @@ class Hyperopt(Backtesting):
def load_previous_results(self):
""" read trials file if we have one """
if os.path.exists(self.trials_file) and os.path.getsize(self.trials_file) > 0:
if self.trials_file.is_file() and self.trials_file.stat().st_size > 0:
self.trials = self.read_trials()
logger.info(
'Loaded %d previous evaluations from disk.',
@@ -252,75 +342,71 @@ class Hyperopt(Backtesting):
)
def start(self) -> None:
timerange = Arguments.parse_timerange(None if self.config.get(
timerange = TimeRange.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange')))
data = load_data(
datadir=Path(self.config['datadir']) if self.config.get('datadir') else None,
pairs=self.config['exchange']['pair_whitelist'],
ticker_interval=self.ticker_interval,
ticker_interval=self.backtesting.ticker_interval,
refresh_pairs=self.config.get('refresh_pairs', False),
exchange=self.backtesting.exchange,
timerange=timerange
)
if self.has_space('buy') or self.has_space('sell'):
self.strategy.advise_indicators = \
if not data:
logger.critical("No data found. Terminating.")
return
min_date, max_date = get_timeframe(data)
logger.info(
'Hyperopting with data from %s up to %s (%s days)..',
min_date.isoformat(),
max_date.isoformat(),
(max_date - min_date).days
)
self.backtesting.strategy.advise_indicators = \
self.custom_hyperopt.populate_indicators # type: ignore
dump(self.strategy.tickerdata_to_dataframe(data), TICKERDATA_PICKLE)
self.exchange = None # type: ignore
preprocessed = self.backtesting.strategy.tickerdata_to_dataframe(data)
dump(preprocessed, self.tickerdata_pickle)
# We don't need exchange instance anymore while running hyperopt
self.backtesting.exchange = None # type: ignore
self.load_previous_results()
cpus = multiprocessing.cpu_count()
cpus = cpu_count()
logger.info(f'Found {cpus} CPU cores. Let\'s make them scream!')
config_jobs = self.config.get('hyperopt_jobs', -1)
logger.info(f'Number of parallel jobs set as: {config_jobs}')
opt = self.get_optimizer(config_jobs)
if self.config.get('print_colorized', False):
colorama_init(autoreset=True)
opt = self.get_optimizer(cpus)
EVALS = max(self.total_tries // cpus, 1)
try:
with Parallel(n_jobs=cpus) as parallel:
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)
for i in range(EVALS):
asked = opt.ask(n_points=cpus)
asked = opt.ask(n_points=jobs)
f_val = self.run_optimizer_parallel(parallel, asked)
opt.tell(asked, [i['loss'] for i in f_val])
self.trials += f_val
for j in range(cpus):
self.log_results({
'loss': f_val[j]['loss'],
'current_tries': i * cpus + j,
'total_tries': self.total_tries,
'result': f_val[j]['result'],
})
opt.tell(asked, [v['loss'] for v in f_val])
for j in range(jobs):
current = i * jobs + j
val = f_val[j]
val['current_epoch'] = current
val['is_initial_point'] = current < INITIAL_POINTS
self.log_results(val)
self.trials.append(val)
logger.debug(f"Optimizer epoch evaluated: {val}")
except KeyboardInterrupt:
print('User interrupted..')
self.save_trials()
self.log_trials_result()
def start(args: Namespace) -> None:
"""
Start Backtesting script
:param args: Cli args from Arguments()
:return: None
"""
# Remove noisy log messages
logging.getLogger('hyperopt.tpe').setLevel(logging.WARNING)
# Initialize configuration
# Monkey patch the configuration with hyperopt_conf.py
configuration = Configuration(args, RunMode.HYPEROPT)
logger.info('Starting freqtrade in Hyperopt mode')
config = configuration.load_config()
config['exchange']['key'] = ''
config['exchange']['secret'] = ''
if config.get('strategy') and config.get('strategy') != 'DefaultStrategy':
logger.error("Please don't use --strategy for hyperopt.")
logger.error(
"Read the documentation at "
"https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md "
"to understand how to configure hyperopt.")
raise ValueError("--strategy configured but not supported for hyperopt")
# Initialize backtesting object
hyperopt = Hyperopt(config)
hyperopt.start()

View File

@@ -7,7 +7,7 @@ from abc import ABC, abstractmethod
from typing import Dict, Any, Callable, List
from pandas import DataFrame
from skopt.space import Dimension
from skopt.space import Dimension, Integer, Real
class IHyperOpt(ABC):
@@ -20,61 +20,86 @@ class IHyperOpt(ABC):
stoploss -> float: optimal stoploss designed for the strategy
ticker_interval -> int: value of the ticker interval to use for the strategy
"""
ticker_interval: str
@staticmethod
@abstractmethod
def populate_indicators(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()
:return: a Dataframe with all mandatory indicators for the strategies
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().
:return: A Dataframe with all mandatory indicators for the strategies.
"""
@staticmethod
@abstractmethod
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Create a buy strategy generator
Create a buy strategy generator.
"""
@staticmethod
@abstractmethod
def sell_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Create a sell strategy generator
Create a sell strategy generator.
"""
@staticmethod
@abstractmethod
def indicator_space() -> List[Dimension]:
"""
Create an indicator space
Create an indicator space.
"""
@staticmethod
@abstractmethod
def sell_indicator_space() -> List[Dimension]:
"""
Create a sell indicator space
Create a sell indicator space.
"""
@staticmethod
@abstractmethod
def generate_roi_table(params: Dict) -> Dict[int, float]:
"""
Create an roi table
Create a ROI table.
Generates the ROI table that will be used by Hyperopt.
You may override it in your custom Hyperopt class.
"""
roi_table = {}
roi_table[0] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
roi_table[params['roi_t3']] = params['roi_p1'] + params['roi_p2']
roi_table[params['roi_t3'] + params['roi_t2']] = params['roi_p1']
roi_table[params['roi_t3'] + params['roi_t2'] + params['roi_t1']] = 0
return roi_table
@staticmethod
@abstractmethod
def stoploss_space() -> List[Dimension]:
"""
Create a stoploss space
Create a stoploss space.
Defines range of stoploss values to search.
You may override it in your custom Hyperopt class.
"""
return [
Real(-0.5, -0.02, name='stoploss'),
]
@staticmethod
@abstractmethod
def roi_space() -> List[Dimension]:
"""
Create a roi space
Create a ROI space.
Defines values to search for each ROI steps.
You may override it in your custom Hyperopt class.
"""
return [
Integer(10, 120, name='roi_t1'),
Integer(10, 60, name='roi_t2'),
Integer(10, 40, name='roi_t3'),
Real(0.01, 0.04, name='roi_p1'),
Real(0.01, 0.07, name='roi_p2'),
Real(0.01, 0.20, name='roi_p3'),
]

View File

@@ -0,0 +1,25 @@
"""
IHyperOptLoss interface
This module defines the interface for the loss-function for hyperopts
"""
from abc import ABC, abstractmethod
from datetime import datetime
from pandas import DataFrame
class IHyperOptLoss(ABC):
"""
Interface for freqtrade hyperopts Loss functions.
Defines the custom loss function (`hyperopt_loss_function()` which is evaluated every epoch.)
"""
ticker_interval: str
@staticmethod
@abstractmethod
def hyperopt_loss_function(results: DataFrame, trade_count: int,
min_date: datetime, max_date: datetime, *args, **kwargs) -> float:
"""
Objective function, returns smaller number for better results
"""

View File

@@ -0,0 +1,38 @@
"""
OnlyProfitHyperOptLoss
This module defines the alternative HyperOptLoss class which can be used for
Hyperoptimization.
"""
from pandas import DataFrame
from freqtrade.optimize.hyperopt import IHyperOptLoss
# This is assumed to be expected avg profit * expected trade count.
# For example, for 0.35% avg per trade (or 0.0035 as ratio) and 1100 trades,
# expected max profit = 3.85
#
# Note, this is ratio. 3.85 stated above means 385Σ%, 3.0 means 300Σ%.
#
# In this implementation it's only used in calculation of the resulting value
# of the objective function as a normalization coefficient and does not
# represent any limit for profits as in the Freqtrade legacy default loss function.
EXPECTED_MAX_PROFIT = 3.0
class OnlyProfitHyperOptLoss(IHyperOptLoss):
"""
Defines the loss function for hyperopt.
This implementation takes only profit into account.
"""
@staticmethod
def hyperopt_loss_function(results: DataFrame, trade_count: int,
*args, **kwargs) -> float:
"""
Objective function, returns smaller number for better results.
"""
total_profit = results.profit_percent.sum()
return 1 - total_profit / EXPECTED_MAX_PROFIT

View File

@@ -0,0 +1,45 @@
"""
SharpeHyperOptLoss
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 SharpeHyperOptLoss(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.
"""
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
if (np.std(total_profit) != 0.):
sharp_ratio = expected_yearly_return / np.std(total_profit) * 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)
return -sharp_ratio

View File

@@ -60,32 +60,27 @@ class IPairList(ABC):
def _validate_whitelist(self, whitelist: List[str]) -> List[str]:
"""
Check available markets and remove pair from whitelist if necessary
:param whitelist: the sorted list (based on BaseVolume) of pairs the user might want to
trade
:return: the list of pairs the user wants to trade without the one unavailable or
:param whitelist: the sorted list of pairs the user might want to trade
:return: the list of pairs the user wants to trade without those unavailable or
black_listed
"""
sanitized_whitelist = whitelist
markets = self._freqtrade.exchange.get_markets()
markets = self._freqtrade.exchange.markets
# Filter to markets in stake currency
markets = [m for m in markets if m['quote'] == self._config['stake_currency']]
known_pairs = set()
for market in markets:
pair = market['symbol']
# pair is not int the generated dynamic market, or in the blacklist ... ignore it
if pair not in whitelist or pair in self.blacklist:
sanitized_whitelist = set()
for pair in whitelist:
# pair is not in the generated dynamic market, or in the blacklist ... ignore it
if (pair in self.blacklist or pair not in markets
or not pair.endswith(self._config['stake_currency'])):
logger.warning(f"Pair {pair} is not compatible with exchange "
f"{self._freqtrade.exchange.name} or contained in "
f"your blacklist. Removing it from whitelist..")
continue
# else the pair is valid
known_pairs.add(pair)
# Market is not active
# Check if market is active
market = markets[pair]
if not market['active']:
sanitized_whitelist.remove(pair)
logger.info(
'Ignoring %s from whitelist. Market is not active.',
pair
)
logger.info(f"Ignoring {pair} from whitelist. Market is not active.")
continue
sanitized_whitelist.add(pair)
# We need to remove pairs that are unknown
return [x for x in sanitized_whitelist if x in known_pairs]
return list(sanitized_whitelist)

View File

@@ -1,5 +1,5 @@
"""
Static List provider
Volume PairList provider
Provides lists as configured in config.json
@@ -26,6 +26,7 @@ class VolumePairList(IPairList):
'for "pairlist.config.number_assets"')
self._number_pairs = self._whitelistconf['number_assets']
self._sort_key = self._whitelistconf.get('sort_key', 'quoteVolume')
self._precision_filter = self._whitelistconf.get('precision_filter', False)
if not self._freqtrade.exchange.exchange_has('fetchTickers'):
raise OperationalException(
@@ -52,9 +53,8 @@ class VolumePairList(IPairList):
-> Please overwrite in subclasses
"""
# Generate dynamic whitelist
pairs = self._gen_pair_whitelist(self._config['stake_currency'], self._sort_key)
# Validate whitelist to only have active market pairs
self._whitelist = self._validate_whitelist(pairs)[:self._number_pairs]
self._whitelist = self._gen_pair_whitelist(
self._config['stake_currency'], self._sort_key)[:self._number_pairs]
@cached(TTLCache(maxsize=1, ttl=1800))
def _gen_pair_whitelist(self, base_currency: str, key: str) -> List[str]:
@@ -68,8 +68,29 @@ class VolumePairList(IPairList):
tickers = self._freqtrade.exchange.get_tickers()
# check length so that we make sure that '/' is actually in the string
tickers = [v for k, v in tickers.items()
if len(k.split('/')) == 2 and k.split('/')[1] == base_currency]
if (len(k.split('/')) == 2 and k.split('/')[1] == base_currency
and v[key] is not None)]
sorted_tickers = sorted(tickers, reverse=True, key=lambda t: t[key])
pairs = [s['symbol'] for s in sorted_tickers]
# Validate whitelist to only have active market pairs
valid_pairs = self._validate_whitelist([s['symbol'] for s in sorted_tickers])
valid_tickers = [t for t in sorted_tickers if t["symbol"] in valid_pairs]
if self._freqtrade.strategy.stoploss is not None and self._precision_filter:
stop_prices = [self._freqtrade.get_target_bid(t["symbol"], t)
* (1 - abs(self._freqtrade.strategy.stoploss)) for t in valid_tickers]
rates = [sp * 0.99 for sp in stop_prices]
logger.debug("\n".join([f"{sp} : {r}" for sp, r in zip(stop_prices[:10], rates[:10])]))
for i, t in enumerate(valid_tickers):
sp = self._freqtrade.exchange.symbol_price_prec(t["symbol"], stop_prices[i])
r = self._freqtrade.exchange.symbol_price_prec(t["symbol"], rates[i])
logger.debug(f"{t['symbol']} - {sp} : {r}")
if sp <= r:
logger.info(f"Removed {t['symbol']} from whitelist, "
f"because stop price {sp} would be <= stop limit {r}")
valid_tickers.remove(t)
pairs = [s['symbol'] for s in valid_tickers]
logger.info(f"Searching pairs: {self._whitelist}")
return pairs

View File

@@ -5,7 +5,7 @@ This module contains the class to persist trades into SQLite
import logging
from datetime import datetime
from decimal import Decimal
from typing import Any, Dict, Optional
from typing import Any, Dict, List, Optional
import arrow
from sqlalchemy import (Boolean, Column, DateTime, Float, Integer, String,
@@ -25,15 +25,16 @@ _DECL_BASE: Any = declarative_base()
_SQL_DOCS_URL = 'http://docs.sqlalchemy.org/en/latest/core/engines.html#database-urls'
def init(config: Dict) -> None:
def init(db_url: str, clean_open_orders: bool = False) -> None:
"""
Initializes this module with the given config,
registers all known command handlers
and starts polling for message updates
:param config: config to use
:param db_url: Database to use
:param clean_open_orders: Remove open orders from the database.
Useful for dry-run or if all orders have been reset on the exchange.
:return: None
"""
db_url = config.get('db_url', None)
kwargs = {}
# Take care of thread ownership if in-memory db
@@ -57,7 +58,7 @@ def init(config: Dict) -> None:
check_migrate(engine)
# Clean dry_run DB if the db is not in-memory
if config.get('dry_run', False) and db_url != 'sqlite://':
if clean_open_orders and db_url != 'sqlite://':
clean_dry_run_db()
@@ -83,7 +84,7 @@ def check_migrate(engine) -> None:
logger.debug(f'trying {table_back_name}')
# Check for latest column
if not has_column(cols, 'stoploss_last_update'):
if not has_column(cols, 'stop_loss_pct'):
logger.info(f'Running database migration - backup available as {table_back_name}')
fee_open = get_column_def(cols, 'fee_open', 'fee')
@@ -91,10 +92,13 @@ def check_migrate(engine) -> None:
open_rate_requested = get_column_def(cols, 'open_rate_requested', 'null')
close_rate_requested = get_column_def(cols, 'close_rate_requested', 'null')
stop_loss = get_column_def(cols, 'stop_loss', '0.0')
stop_loss_pct = get_column_def(cols, 'stop_loss_pct', 'null')
initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0')
initial_stop_loss_pct = get_column_def(cols, 'initial_stop_loss_pct', 'null')
stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null')
stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null')
max_rate = get_column_def(cols, 'max_rate', '0.0')
min_rate = get_column_def(cols, 'min_rate', 'null')
sell_reason = get_column_def(cols, 'sell_reason', 'null')
strategy = get_column_def(cols, 'strategy', 'null')
ticker_interval = get_column_def(cols, 'ticker_interval', 'null')
@@ -112,8 +116,9 @@ def check_migrate(engine) -> None:
(id, exchange, pair, is_open, fee_open, fee_close, open_rate,
open_rate_requested, close_rate, close_rate_requested, close_profit,
stake_amount, amount, open_date, close_date, open_order_id,
stop_loss, initial_stop_loss, stoploss_order_id, stoploss_last_update,
max_rate, sell_reason, strategy,
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
)
select id, lower(exchange),
@@ -128,9 +133,11 @@ def check_migrate(engine) -> None:
open_rate, {open_rate_requested} open_rate_requested, close_rate,
{close_rate_requested} close_rate_requested, close_profit,
stake_amount, amount, open_date, close_date, open_order_id,
{stop_loss} stop_loss, {initial_stop_loss} initial_stop_loss,
{stop_loss} stop_loss, {stop_loss_pct} stop_loss_pct,
{initial_stop_loss} initial_stop_loss,
{initial_stop_loss_pct} initial_stop_loss_pct,
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
{max_rate} max_rate, {sell_reason} sell_reason,
{max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason,
{strategy} strategy, {ticker_interval} ticker_interval
from {table_back_name}
""")
@@ -183,14 +190,20 @@ class Trade(_DECL_BASE):
open_order_id = Column(String)
# absolute value of the stop loss
stop_loss = Column(Float, nullable=True, default=0.0)
# percentage value of the stop loss
stop_loss_pct = Column(Float, nullable=True)
# absolute value of the initial stop loss
initial_stop_loss = Column(Float, nullable=True, default=0.0)
# percentage value of the initial stop loss
initial_stop_loss_pct = Column(Float, nullable=True)
# stoploss order id which is on exchange
stoploss_order_id = Column(String, nullable=True, index=True)
# last update time of the stoploss order on exchange
stoploss_last_update = Column(DateTime, nullable=True)
# absolute value of the highest reached price
max_rate = Column(Float, nullable=True, default=0.0)
# Lowest price reached
min_rate = Column(Float, nullable=True)
sell_reason = Column(String, nullable=True)
strategy = Column(String, nullable=True)
ticker_interval = Column(Integer, nullable=True)
@@ -201,8 +214,42 @@ class Trade(_DECL_BASE):
return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, '
f'open_rate={self.open_rate:.8f}, open_since={open_since})')
def to_json(self) -> Dict[str, Any]:
return {
'trade_id': self.id,
'pair': self.pair,
'open_date_hum': arrow.get(self.open_date).humanize(),
'open_date': self.open_date.strftime("%Y-%m-%d %H:%M:%S"),
'close_date_hum': (arrow.get(self.close_date).humanize()
if self.close_date else None),
'close_date': (self.close_date.strftime("%Y-%m-%d %H:%M:%S")
if self.close_date else None),
'open_rate': self.open_rate,
'close_rate': self.close_rate,
'amount': round(self.amount, 8),
'stake_amount': round(self.stake_amount, 8),
'stop_loss': self.stop_loss,
'stop_loss_pct': (self.stop_loss_pct * 100) if self.stop_loss_pct else None,
'initial_stop_loss': self.initial_stop_loss,
'initial_stop_loss_pct': (self.initial_stop_loss_pct * 100
if self.initial_stop_loss_pct else None),
}
def adjust_min_max_rates(self, current_price: float):
"""
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):
"""this adjusts the stop loss to it's most recently observed setting"""
"""
This adjusts the stop loss to it's most recently observed setting
:param current_price: Current rate the asset is traded
:param stoploss: Stoploss as factor (sample -0.05 -> -5% below current price).
:param initial: Called to initiate stop_loss.
Skips everything if self.stop_loss is already set.
"""
if initial and not (self.stop_loss is None or self.stop_loss == 0):
# Don't modify if called with initial and nothing to do
@@ -210,24 +257,20 @@ class Trade(_DECL_BASE):
new_loss = float(current_price * (1 - abs(stoploss)))
# keeping track of the highest observed rate for this trade
if self.max_rate is None:
self.max_rate = current_price
else:
if current_price > self.max_rate:
self.max_rate = current_price
# no stop loss assigned yet
if not self.stop_loss:
logger.debug("assigning new stop loss")
self.stop_loss = new_loss
self.stop_loss_pct = -1 * abs(stoploss)
self.initial_stop_loss = new_loss
self.initial_stop_loss_pct = -1 * abs(stoploss)
self.stoploss_last_update = datetime.utcnow()
# evaluate if the stop loss needs to be updated
else:
if new_loss > self.stop_loss: # stop losses only walk up, never down!
self.stop_loss = new_loss
self.stop_loss_pct = -1 * abs(stoploss)
self.stoploss_last_update = datetime.utcnow()
logger.debug("adjusted stop loss")
else:
@@ -266,6 +309,7 @@ class Trade(_DECL_BASE):
logger.info('%s_SELL has been fulfilled for %s.', order_type.upper(), self)
elif order_type == 'stop_loss_limit':
self.stoploss_order_id = None
self.close_rate_requested = self.stop_loss
logger.info('STOP_LOSS_LIMIT is hit for %s.', self)
self.close(order['average'])
else:
@@ -371,3 +415,29 @@ class Trade(_DECL_BASE):
.filter(Trade.is_open.is_(True))\
.scalar()
return total_open_stake_amount or 0
@staticmethod
def get_open_trades() -> List[Any]:
"""
Query trades from persistence layer
"""
return Trade.query.filter(Trade.is_open.is_(True)).all()
@staticmethod
def stoploss_reinitialization(desired_stoploss):
"""
Adjust initial Stoploss to desired stoploss for all open trades.
"""
for trade in Trade.get_open_trades():
logger.info("Found open trade: %s", trade)
# skip case if trailing-stop changed the stoploss already.
if (trade.stop_loss == trade.initial_stop_loss
and trade.initial_stop_loss_pct != desired_stoploss):
# Stoploss value got changed
logger.info(f"Stoploss for {trade} needs adjustment.")
# Force reset of stoploss
trade.stop_loss = None
trade.adjust_stop_loss(trade.open_rate, desired_stoploss)
logger.info(f"new stoploss: {trade.stop_loss}, ")

View File

323
freqtrade/plot/plotting.py Normal file
View File

@@ -0,0 +1,323 @@
import logging
from pathlib import Path
from typing import Dict, List, Optional
import pandas as pd
from freqtrade.configuration import TimeRange
from freqtrade.data import history
from freqtrade.data.btanalysis import (combine_tickers_with_mean,
create_cum_profit, load_trades)
from freqtrade.exchange import Exchange
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
logger = logging.getLogger(__name__)
try:
from plotly.subplots import make_subplots
from plotly.offline import plot
import plotly.graph_objects as go
except ImportError:
logger.exception("Module plotly not found \n Please install using `pip install plotly`")
exit(1)
def init_plotscript(config):
"""
Initialize objects needed for plotting
:return: Dict with tickers, trades, pairs and strategy
"""
exchange: Optional[Exchange] = None
# Exchange is only needed when downloading data!
if config.get("refresh_pairs", False):
exchange = ExchangeResolver(config.get('exchange', {}).get('name'),
config).exchange
strategy = StrategyResolver(config).strategy
if "pairs" in config:
pairs = config["pairs"]
else:
pairs = config["exchange"]["pair_whitelist"]
# Set timerange to use
timerange = TimeRange.parse_timerange(config.get("timerange"))
tickers = history.load_data(
datadir=Path(str(config.get("datadir"))),
pairs=pairs,
ticker_interval=config['ticker_interval'],
refresh_pairs=config.get('refresh_pairs', False),
timerange=timerange,
exchange=exchange,
)
trades = load_trades(config)
return {"tickers": tickers,
"trades": trades,
"pairs": pairs,
"strategy": strategy,
}
def add_indicators(fig, row, indicators: List[str], data: pd.DataFrame) -> make_subplots:
"""
Generator all the indicator selected by the user for a specific row
:param fig: Plot figure to append to
:param row: row number for this plot
:param indicators: List of indicators present in the dataframe
:param data: candlestick DataFrame
"""
for indicator in indicators:
if indicator in data:
# TODO: Figure out why scattergl causes problems
scattergl = go.Scatter(
x=data['date'],
y=data[indicator].values,
mode='lines',
name=indicator
)
fig.add_trace(scattergl, row, 1)
else:
logger.info(
'Indicator "%s" ignored. Reason: This indicator is not found '
'in your strategy.',
indicator
)
return fig
def add_profit(fig, row, data: pd.DataFrame, column: str, name: str) -> make_subplots:
"""
Add profit-plot
:param fig: Plot figure to append to
:param row: row number for this plot
:param data: candlestick DataFrame
:param column: Column to use for plot
:param name: Name to use
:return: fig with added profit plot
"""
profit = go.Scattergl(
x=data.index,
y=data[column],
name=name,
)
fig.add_trace(profit, row, 1)
return fig
def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
"""
Add trades to "fig"
"""
# Trades can be empty
if trades is not None and len(trades) > 0:
trade_buys = go.Scatter(
x=trades["open_time"],
y=trades["open_rate"],
mode='markers',
name='trade_buy',
marker=dict(
symbol='square-open',
size=11,
line=dict(width=2),
color='green'
)
)
# Create description for sell summarizing the trade
desc = trades.apply(lambda row: f"{round(row['profitperc'], 3)}%, {row['sell_reason']}, "
f"{row['duration']}min",
axis=1)
trade_sells = go.Scatter(
x=trades["close_time"],
y=trades["close_rate"],
text=desc,
mode='markers',
name='trade_sell',
marker=dict(
symbol='square-open',
size=11,
line=dict(width=2),
color='red'
)
)
fig.add_trace(trade_buys, 1, 1)
fig.add_trace(trade_sells, 1, 1)
else:
logger.warning("No trades found.")
return fig
def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFrame = None,
indicators1: List[str] = [],
indicators2: List[str] = [],) -> go.Figure:
"""
Generate the graph from the data generated by Backtesting or from DB
Volume will always be ploted in row2, so Row 1 and 3 are to our disposal for custom indicators
:param pair: Pair to Display on the graph
:param data: OHLCV DataFrame containing indicators and buy/sell signals
:param trades: All trades created
:param indicators1: List containing Main plot indicators
:param indicators2: List containing Sub plot indicators
:return: None
"""
# Define the graph
fig = make_subplots(
rows=3,
cols=1,
shared_xaxes=True,
row_width=[1, 1, 4],
vertical_spacing=0.0001,
)
fig['layout'].update(title=pair)
fig['layout']['yaxis1'].update(title='Price')
fig['layout']['yaxis2'].update(title='Volume')
fig['layout']['yaxis3'].update(title='Other')
fig['layout']['xaxis']['rangeslider'].update(visible=False)
# Common information
candles = go.Candlestick(
x=data.date,
open=data.open,
high=data.high,
low=data.low,
close=data.close,
name='Price'
)
fig.add_trace(candles, 1, 1)
if 'buy' in data.columns:
df_buy = data[data['buy'] == 1]
if len(df_buy) > 0:
buys = go.Scatter(
x=df_buy.date,
y=df_buy.close,
mode='markers',
name='buy',
marker=dict(
symbol='triangle-up-dot',
size=9,
line=dict(width=1),
color='green',
)
)
fig.add_trace(buys, 1, 1)
else:
logger.warning("No buy-signals found.")
if 'sell' in data.columns:
df_sell = data[data['sell'] == 1]
if len(df_sell) > 0:
sells = go.Scatter(
x=df_sell.date,
y=df_sell.close,
mode='markers',
name='sell',
marker=dict(
symbol='triangle-down-dot',
size=9,
line=dict(width=1),
color='red',
)
)
fig.add_trace(sells, 1, 1)
else:
logger.warning("No sell-signals found.")
if 'bb_lowerband' in data and 'bb_upperband' in data:
bb_lower = go.Scattergl(
x=data.date,
y=data.bb_lowerband,
name='BB lower',
line={'color': 'rgba(255,255,255,0)'},
)
bb_upper = go.Scattergl(
x=data.date,
y=data.bb_upperband,
name='BB upper',
fill="tonexty",
fillcolor="rgba(0,176,246,0.2)",
line={'color': 'rgba(255,255,255,0)'},
)
fig.add_trace(bb_lower, 1, 1)
fig.add_trace(bb_upper, 1, 1)
# Add indicators to main plot
fig = add_indicators(fig=fig, row=1, indicators=indicators1, data=data)
fig = plot_trades(fig, trades)
# Volume goes to row 2
volume = go.Bar(
x=data['date'],
y=data['volume'],
name='Volume'
)
fig.add_trace(volume, 2, 1)
# Add indicators to seperate row
fig = add_indicators(fig=fig, row=3, indicators=indicators2, data=data)
return fig
def generate_profit_graph(pairs: str, tickers: Dict[str, pd.DataFrame],
trades: pd.DataFrame) -> go.Figure:
# Combine close-values for all pairs, rename columns to "pair"
df_comb = combine_tickers_with_mean(tickers, "close")
# Add combined cumulative profit
df_comb = create_cum_profit(df_comb, trades, 'cum_profit')
# Plot the pairs average close prices, and total profit growth
avgclose = go.Scattergl(
x=df_comb.index,
y=df_comb['mean'],
name='Avg close price',
)
fig = make_subplots(rows=3, cols=1, shared_xaxes=True, row_width=[1, 1, 1])
fig['layout'].update(title="Profit plot")
fig.add_trace(avgclose, 1, 1)
fig = add_profit(fig, 2, df_comb, 'cum_profit', 'Profit')
for pair in pairs:
profit_col = f'cum_profit_{pair}'
df_comb = create_cum_profit(df_comb, trades[trades['pair'] == pair], profit_col)
fig = add_profit(fig, 3, df_comb, profit_col, f"Profit {pair}")
return fig
def generate_plot_filename(pair, ticker_interval) -> str:
"""
Generate filenames per pair/ticker_interval to be used for storing plots
"""
pair_name = pair.replace("/", "_")
file_name = 'freqtrade-plot-' + pair_name + '-' + ticker_interval + '.html'
logger.info('Generate plot file for %s', pair)
return file_name
def store_plot_file(fig, filename: str, directory: Path, auto_open: bool = False) -> None:
"""
Generate a plot html file from pre populated fig plotly object
:param fig: Plotly Figure to plot
:param pair: Pair to plot (used as filename and Plot title)
:param ticker_interval: Used as part of the filename
:return: None
"""
directory.mkdir(parents=True, exist_ok=True)
_filename = directory.joinpath(filename)
plot(fig, filename=str(_filename),
auto_open=auto_open)
logger.info(f"Stored plot as {_filename}")

View File

@@ -1,4 +1,6 @@
from freqtrade.resolvers.iresolver import IResolver # noqa: F401
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver # noqa: F401
from freqtrade.resolvers.exchange_resolver import ExchangeResolver # noqa: F401
# Don't import HyperoptResolver to avoid loading the whole Optimize tree
# from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver # noqa: F401
from freqtrade.resolvers.pairlist_resolver import PairListResolver # noqa: F401
from freqtrade.resolvers.strategy_resolver import StrategyResolver # noqa: F401

View File

@@ -0,0 +1,57 @@
"""
This module loads custom exchanges
"""
import logging
from freqtrade.exchange import Exchange
import freqtrade.exchange as exchanges
from freqtrade.resolvers import IResolver
logger = logging.getLogger(__name__)
class ExchangeResolver(IResolver):
"""
This class contains all the logic to load a custom exchange class
"""
__slots__ = ['exchange']
def __init__(self, exchange_name: str, config: dict) -> None:
"""
Load the custom class from config parameter
:param config: configuration dictionary
"""
exchange_name = exchange_name.title()
try:
self.exchange = self._load_exchange(exchange_name, kwargs={'config': config})
except ImportError:
logger.info(
f"No {exchange_name} specific subclass found. Using the generic class instead.")
if not hasattr(self, "exchange"):
self.exchange = Exchange(config)
def _load_exchange(
self, exchange_name: str, kwargs: dict) -> Exchange:
"""
Loads the specified exchange.
Only checks for exchanges exported in freqtrade.exchanges
:param exchange_name: name of the module to import
:return: Exchange instance or None
"""
try:
ex_class = getattr(exchanges, exchange_name)
exchange = ex_class(kwargs['config'])
if exchange:
logger.info(f"Using resolved exchange '{exchange_name}'...")
return exchange
except AttributeError:
# Pass and raise ImportError instead
pass
raise ImportError(
f"Impossible to load Exchange '{exchange_name}'. This class does not exist "
"or contains Python code errors."
)

View File

@@ -7,8 +7,10 @@ import logging
from pathlib import Path
from typing import Optional, Dict
from freqtrade.constants import DEFAULT_HYPEROPT
from freqtrade import OperationalException
from freqtrade.constants import DEFAULT_HYPEROPT, DEFAULT_HYPEROPT_LOSS
from freqtrade.optimize.hyperopt_interface import IHyperOpt
from freqtrade.optimize.hyperopt_loss_interface import IHyperOptLoss
from freqtrade.resolvers import IResolver
logger = logging.getLogger(__name__)
@@ -21,16 +23,19 @@ class HyperOptResolver(IResolver):
__slots__ = ['hyperopt']
def __init__(self, config: Optional[Dict] = None) -> None:
def __init__(self, config: Dict) -> None:
"""
Load the custom class from config parameter
:param config: configuration dictionary or None
:param config: configuration dictionary
"""
config = config or {}
# Verify the hyperopt is in the configuration, otherwise fallback to the default hyperopt
hyperopt_name = config.get('hyperopt') or DEFAULT_HYPEROPT
self.hyperopt = self._load_hyperopt(hyperopt_name, extra_dir=config.get('hyperopt_path'))
self.hyperopt = self._load_hyperopt(hyperopt_name, config,
extra_dir=config.get('hyperopt_path'))
# Assign ticker_interval to be used in hyperopt
self.hyperopt.__class__.ticker_interval = str(config['ticker_interval'])
if not hasattr(self.hyperopt, 'populate_buy_trend'):
logger.warning("Custom Hyperopt does not provide populate_buy_trend. "
@@ -40,35 +45,88 @@ class HyperOptResolver(IResolver):
"Using populate_sell_trend from DefaultStrategy.")
def _load_hyperopt(
self, hyperopt_name: str, extra_dir: Optional[str] = None) -> IHyperOpt:
self, hyperopt_name: str, config: Dict, extra_dir: Optional[str] = None) -> IHyperOpt:
"""
Search and loads the specified hyperopt.
:param hyperopt_name: name of the module to import
:param config: configuration dictionary
:param extra_dir: additional directory to search for the given hyperopt
:return: HyperOpt instance or None
"""
current_path = Path(__file__).parent.parent.joinpath('optimize').resolve()
abs_paths = [
current_path.parent.parent.joinpath('user_data/hyperopts'),
config['user_data_dir'].joinpath('hyperopts'),
current_path,
]
if extra_dir:
# Add extra hyperopt directory on top of search paths
abs_paths.insert(0, Path(extra_dir))
abs_paths.insert(0, Path(extra_dir).resolve())
for _path in abs_paths:
try:
hyperopt = self._search_object(directory=_path, object_type=IHyperOpt,
hyperopt = self._load_object(paths=abs_paths, object_type=IHyperOpt,
object_name=hyperopt_name)
if hyperopt:
logger.info('Using resolved hyperopt %s from \'%s\'', hyperopt_name, _path)
return hyperopt
except FileNotFoundError:
logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd()))
raise ImportError(
"Impossible to load Hyperopt '{}'. This class does not exist"
" or contains Python code errors".format(hyperopt_name)
raise OperationalException(
f"Impossible to load Hyperopt '{hyperopt_name}'. This class does not exist "
"or contains Python code errors."
)
class HyperOptLossResolver(IResolver):
"""
This class contains all the logic to load custom hyperopt loss class
"""
__slots__ = ['hyperoptloss']
def __init__(self, config: Dict = None) -> None:
"""
Load the custom class from config parameter
:param config: configuration dictionary or None
"""
config = config or {}
# Verify the hyperopt is in the configuration, otherwise fallback to the default hyperopt
hyperopt_name = config.get('hyperopt_loss') or DEFAULT_HYPEROPT_LOSS
self.hyperoptloss = self._load_hyperoptloss(
hyperopt_name, config, extra_dir=config.get('hyperopt_path'))
# Assign ticker_interval to be used in hyperopt
self.hyperoptloss.__class__.ticker_interval = str(config['ticker_interval'])
if not hasattr(self.hyperoptloss, 'hyperopt_loss_function'):
raise OperationalException(
f"Found hyperopt {hyperopt_name} does not implement `hyperopt_loss_function`.")
def _load_hyperoptloss(
self, hyper_loss_name: str, config: Dict,
extra_dir: Optional[str] = None) -> IHyperOptLoss:
"""
Search and loads the specified hyperopt loss class.
:param hyper_loss_name: name of the module to import
:param config: configuration dictionary
:param extra_dir: additional directory to search for the given hyperopt
:return: HyperOptLoss instance or None
"""
current_path = Path(__file__).parent.parent.joinpath('optimize').resolve()
abs_paths = [
config['user_data_dir'].joinpath('hyperopts'),
current_path,
]
if extra_dir:
# Add extra hyperopt directory on top of search paths
abs_paths.insert(0, Path(extra_dir).resolve())
hyperoptloss = self._load_object(paths=abs_paths, object_type=IHyperOptLoss,
object_name=hyper_loss_name)
if hyperoptloss:
return hyperoptloss
raise OperationalException(
f"Impossible to load HyperoptLoss '{hyper_loss_name}'. This class does not exist "
"or contains Python code errors."
)

View File

@@ -7,7 +7,7 @@ import importlib.util
import inspect
import logging
from pathlib import Path
from typing import Optional, Type, Any
from typing import Any, List, Optional, Tuple, Type, Union
logger = logging.getLogger(__name__)
@@ -29,9 +29,14 @@ class IResolver(object):
"""
# Generate spec based on absolute path
spec = importlib.util.spec_from_file_location('unknown', str(module_path))
# Pass object_name as first argument to have logging print a reasonable name.
spec = importlib.util.spec_from_file_location(object_name, str(module_path))
module = importlib.util.module_from_spec(spec)
try:
spec.loader.exec_module(module) # type: ignore # importlib does not use typehints
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}'")
valid_objects_gen = (
obj for name, obj in inspect.getmembers(module, inspect.isclass)
@@ -41,21 +46,45 @@ class IResolver(object):
@staticmethod
def _search_object(directory: Path, object_type, object_name: str,
kwargs: dict = {}) -> Optional[Any]:
kwargs: dict = {}) -> Union[Tuple[Any, Path], Tuple[None, None]]:
"""
Search for the objectname in the given directory
:param directory: relative or absolute directory path
:return: object instance
"""
logger.debug('Searching for %s %s in \'%s\'', object_type.__name__, object_name, directory)
logger.debug("Searching for %s %s in '%s'", object_type.__name__, object_name, directory)
for entry in directory.iterdir():
# Only consider python files
if not str(entry).endswith('.py'):
logger.debug('Ignoring %s', entry)
continue
module_path = entry.resolve()
obj = IResolver._get_valid_object(
object_type, Path.resolve(directory.joinpath(entry)), object_name
object_type, module_path, object_name
)
if obj:
return obj(**kwargs)
return (obj(**kwargs), module_path)
return (None, None)
@staticmethod
def _load_object(paths: List[Path], object_type, object_name: str,
kwargs: dict = {}) -> Optional[Any]:
"""
Try to load object from path list.
"""
for _path in paths:
try:
(module, module_path) = IResolver._search_object(directory=_path,
object_type=object_type,
object_name=object_name,
kwargs=kwargs)
if module:
logger.info(
f"Using resolved {object_type.__name__.lower()[1:]} {object_name} "
f"from '{module_path}'...")
return module
except FileNotFoundError:
logger.warning('Path "%s" does not exist.', _path.resolve())
return None

View File

@@ -6,6 +6,7 @@ This module load custom hyperopts
import logging
from pathlib import Path
from freqtrade import OperationalException
from freqtrade.pairlist.IPairList import IPairList
from freqtrade.resolvers import IResolver
@@ -24,36 +25,30 @@ class PairListResolver(IResolver):
Load the custom class from config parameter
:param config: configuration dictionary or None
"""
self.pairlist = self._load_pairlist(pairlist_name, kwargs={'freqtrade': freqtrade,
self.pairlist = self._load_pairlist(pairlist_name, config, kwargs={'freqtrade': freqtrade,
'config': config})
def _load_pairlist(
self, pairlist_name: str, kwargs: dict) -> IPairList:
self, pairlist_name: str, config: dict, kwargs: dict) -> IPairList:
"""
Search and loads the specified pairlist.
:param pairlist_name: name of the module to import
:param config: configuration dictionary
:param extra_dir: additional directory to search for the given pairlist
:return: PairList instance or None
"""
current_path = Path(__file__).parent.parent.joinpath('pairlist').resolve()
abs_paths = [
current_path.parent.parent.joinpath('user_data/pairlist'),
config['user_data_dir'].joinpath('pairlist'),
current_path,
]
for _path in abs_paths:
try:
pairlist = self._search_object(directory=_path, object_type=IPairList,
object_name=pairlist_name,
kwargs=kwargs)
pairlist = self._load_object(paths=abs_paths, object_type=IPairList,
object_name=pairlist_name, kwargs=kwargs)
if pairlist:
logger.info('Using resolved pairlist %s from \'%s\'', pairlist_name, _path)
return pairlist
except FileNotFoundError:
logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd()))
raise ImportError(
"Impossible to load Pairlist '{}'. This class does not exist"
" or contains Python code errors".format(pairlist_name)
raise OperationalException(
f"Impossible to load Pairlist '{pairlist_name}'. This class does not exist "
"or contains Python code errors."
)

View File

@@ -11,7 +11,7 @@ from inspect import getfullargspec
from pathlib import Path
from typing import Dict, Optional
from freqtrade import constants
from freqtrade import constants, OperationalException
from freqtrade.resolvers import IResolver
from freqtrade.strategy import import_strategy
from freqtrade.strategy.interface import IStrategy
@@ -46,15 +46,18 @@ class StrategyResolver(IResolver):
# Set attributes
# Check if we need to override configuration
# (Attribute name, default, experimental)
attributes = [("minimal_roi", None, False),
attributes = [("minimal_roi", {"0": 10.0}, False),
("ticker_interval", None, False),
("stoploss", None, False),
("trailing_stop", None, False),
("trailing_stop_positive", None, False),
("trailing_stop_positive_offset", 0.0, False),
("trailing_only_offset_is_reached", None, False),
("process_only_new_candles", None, False),
("order_types", None, False),
("order_time_in_force", None, False),
("stake_currency", None, False),
("stake_amount", None, False),
("use_sell_signal", False, True),
("sell_profit_only", False, True),
("ignore_roi_if_buy_signal", False, True),
@@ -120,7 +123,7 @@ class StrategyResolver(IResolver):
current_path = Path(__file__).parent.parent.joinpath('strategy').resolve()
abs_paths = [
Path.cwd().joinpath('user_data/strategies'),
config['user_data_dir'].joinpath('strategies'),
current_path,
]
@@ -129,7 +132,7 @@ class StrategyResolver(IResolver):
abs_paths.insert(0, Path(extra_dir).resolve())
if ":" in strategy_name:
logger.info("loading base64 endocded strategy")
logger.info("loading base64 encoded strategy")
strat = strategy_name.split(":")
if len(strat) == 2:
@@ -144,22 +147,21 @@ class StrategyResolver(IResolver):
# register temp path with the bot
abs_paths.insert(0, temp.resolve())
for _path in abs_paths:
try:
strategy = self._search_object(directory=_path, object_type=IStrategy,
strategy = self._load_object(paths=abs_paths, object_type=IStrategy,
object_name=strategy_name, kwargs={'config': config})
if strategy:
logger.info('Using resolved strategy %s from \'%s\'', strategy_name, _path)
strategy._populate_fun_len = len(
getfullargspec(strategy.populate_indicators).args)
strategy._populate_fun_len = len(getfullargspec(strategy.populate_indicators).args)
strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args)
strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args)
try:
return import_strategy(strategy, config=config)
except FileNotFoundError:
logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd()))
except TypeError as e:
logger.warning(
f"Impossible to load strategy '{strategy_name}'. "
f"Error: {e}")
raise ImportError(
"Impossible to load Strategy '{}'. This class does not exist"
" or contains Python code errors".format(strategy_name)
raise OperationalException(
f"Impossible to load Strategy '{strategy_name}'. This class does not exist "
"or contains Python code errors."
)

375
freqtrade/rpc/api_server.py Normal file
View File

@@ -0,0 +1,375 @@
import logging
import threading
from datetime import date, datetime
from ipaddress import IPv4Address
from typing import Dict
from arrow import Arrow
from flask import Flask, jsonify, request
from flask.json import JSONEncoder
from werkzeug.serving import make_server
from freqtrade.__init__ import __version__
from freqtrade.rpc.rpc import RPC, RPCException
logger = logging.getLogger(__name__)
BASE_URI = "/api/v1"
class ArrowJSONEncoder(JSONEncoder):
def default(self, obj):
try:
if isinstance(obj, Arrow):
return obj.for_json()
elif isinstance(obj, date):
return obj.strftime("%Y-%m-%d")
elif isinstance(obj, datetime):
return obj.strftime("%Y-%m-%d %H:%M:%S")
iterable = iter(obj)
except TypeError:
pass
else:
return list(iterable)
return JSONEncoder.default(self, obj)
class ApiServer(RPC):
"""
This class runs api server and provides rpc.rpc functionality to it
This class starts a none blocking thread the api server runs within
"""
def rpc_catch_errors(func):
def func_wrapper(self, *args, **kwargs):
try:
return func(self, *args, **kwargs)
except RPCException as e:
logger.exception("API Error calling %s: %s", func.__name__, e)
return self.rest_error(f"Error querying {func.__name__}: {e}")
return func_wrapper
def check_auth(self, username, password):
return (username == self._config['api_server'].get('username') and
password == self._config['api_server'].get('password'))
def require_login(func):
def func_wrapper(self, *args, **kwargs):
auth = request.authorization
if auth and self.check_auth(auth.username, auth.password):
return func(self, *args, **kwargs)
else:
return jsonify({"error": "Unauthorized"}), 401
return func_wrapper
def __init__(self, freqtrade) -> None:
"""
Init the api server, and init the super class RPC
:param freqtrade: Instance of a freqtrade bot
:return: None
"""
super().__init__(freqtrade)
self._config = freqtrade.config
self.app = Flask(__name__)
self.app.json_encoder = ArrowJSONEncoder
# Register application handling
self.register_rest_rpc_urls()
thread = threading.Thread(target=self.run, daemon=True)
thread.start()
def cleanup(self) -> None:
logger.info("Stopping API Server")
self.srv.shutdown()
def run(self):
"""
Method that runs flask app in its own thread forever.
Section to handle configuration and running of the Rest server
also to check and warn if not bound to a loopback, warn on security risk.
"""
rest_ip = self._config['api_server']['listen_ip_address']
rest_port = self._config['api_server']['listen_port']
logger.info(f'Starting HTTP Server at {rest_ip}:{rest_port}')
if not IPv4Address(rest_ip).is_loopback:
logger.warning("SECURITY WARNING - Local Rest Server listening to external connections")
logger.warning("SECURITY WARNING - This is insecure please set to your loopback,"
"e.g 127.0.0.1 in config.json")
if not self._config['api_server'].get('password'):
logger.warning("SECURITY WARNING - No password for local REST Server defined. "
"Please make sure that this is intentional!")
# Run the Server
logger.info('Starting Local Rest Server.')
try:
self.srv = make_server(rest_ip, rest_port, self.app)
self.srv.serve_forever()
except Exception:
logger.exception("Api server failed to start.")
logger.info('Local Rest Server started.')
def send_msg(self, msg: Dict[str, str]) -> None:
"""
We don't push to endpoints at the moment.
Take a look at webhooks for that functionality.
"""
pass
def rest_dump(self, return_value):
""" Helper function to jsonify object for a webserver """
return jsonify(return_value)
def rest_error(self, error_msg):
return jsonify({"error": error_msg}), 502
def register_rest_rpc_urls(self):
"""
Registers flask app URLs that are calls to functonality in rpc.rpc.
First two arguments passed are /URL and 'Label'
Label can be used as a shortcut when refactoring
:return:
"""
self.app.register_error_handler(404, self.page_not_found)
# Actions to control the bot
self.app.add_url_rule(f'{BASE_URI}/start', 'start',
view_func=self._start, methods=['POST'])
self.app.add_url_rule(f'{BASE_URI}/stop', 'stop', view_func=self._stop, methods=['POST'])
self.app.add_url_rule(f'{BASE_URI}/stopbuy', 'stopbuy',
view_func=self._stopbuy, methods=['POST'])
self.app.add_url_rule(f'{BASE_URI}/reload_conf', 'reload_conf',
view_func=self._reload_conf, methods=['POST'])
# Info commands
self.app.add_url_rule(f'{BASE_URI}/balance', 'balance',
view_func=self._balance, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/count', 'count', view_func=self._count, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/daily', 'daily', view_func=self._daily, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/edge', 'edge', view_func=self._edge, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/profit', 'profit',
view_func=self._profit, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/performance', 'performance',
view_func=self._performance, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/status', 'status',
view_func=self._status, methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/version', 'version',
view_func=self._version, methods=['GET'])
# Combined actions and infos
self.app.add_url_rule(f'{BASE_URI}/blacklist', 'blacklist', view_func=self._blacklist,
methods=['GET', 'POST'])
self.app.add_url_rule(f'{BASE_URI}/whitelist', 'whitelist', view_func=self._whitelist,
methods=['GET'])
self.app.add_url_rule(f'{BASE_URI}/forcebuy', 'forcebuy',
view_func=self._forcebuy, methods=['POST'])
self.app.add_url_rule(f'{BASE_URI}/forcesell', 'forcesell', view_func=self._forcesell,
methods=['POST'])
# TODO: Implement the following
# help (?)
@require_login
def page_not_found(self, error):
"""
Return "404 not found", 404.
"""
return self.rest_dump({
'status': 'error',
'reason': f"There's no API call for {request.base_url}.",
'code': 404
}), 404
@require_login
@rpc_catch_errors
def _start(self):
"""
Handler for /start.
Starts TradeThread in bot if stopped.
"""
msg = self._rpc_start()
return self.rest_dump(msg)
@require_login
@rpc_catch_errors
def _stop(self):
"""
Handler for /stop.
Stops TradeThread in bot if running
"""
msg = self._rpc_stop()
return self.rest_dump(msg)
@require_login
@rpc_catch_errors
def _stopbuy(self):
"""
Handler for /stopbuy.
Sets max_open_trades to 0 and gracefully sells all open trades
"""
msg = self._rpc_stopbuy()
return self.rest_dump(msg)
@require_login
@rpc_catch_errors
def _version(self):
"""
Prints the bot's version
"""
return self.rest_dump({"version": __version__})
@require_login
@rpc_catch_errors
def _reload_conf(self):
"""
Handler for /reload_conf.
Triggers a config file reload
"""
msg = self._rpc_reload_conf()
return self.rest_dump(msg)
@require_login
@rpc_catch_errors
def _count(self):
"""
Handler for /count.
Returns the number of trades running
"""
msg = self._rpc_count()
return self.rest_dump(msg)
@require_login
@rpc_catch_errors
def _daily(self):
"""
Returns the last X days trading stats summary.
:return: stats
"""
timescale = request.args.get('timescale', 7)
timescale = int(timescale)
stats = self._rpc_daily_profit(timescale,
self._config['stake_currency'],
self._config['fiat_display_currency']
)
return self.rest_dump(stats)
@require_login
@rpc_catch_errors
def _edge(self):
"""
Returns information related to Edge.
:return: edge stats
"""
stats = self._rpc_edge()
return self.rest_dump(stats)
@require_login
@rpc_catch_errors
def _profit(self):
"""
Handler for /profit.
Returns a cumulative profit statistics
:return: stats
"""
logger.info("LocalRPC - Profit Command Called")
stats = self._rpc_trade_statistics(self._config['stake_currency'],
self._config['fiat_display_currency']
)
return self.rest_dump(stats)
@require_login
@rpc_catch_errors
def _performance(self):
"""
Handler for /performance.
Returns a cumulative performance statistics
:return: stats
"""
logger.info("LocalRPC - performance Command Called")
stats = self._rpc_performance()
return self.rest_dump(stats)
@require_login
@rpc_catch_errors
def _status(self):
"""
Handler for /status.
Returns the current status of the trades in json format
"""
results = self._rpc_trade_status()
return self.rest_dump(results)
@require_login
@rpc_catch_errors
def _balance(self):
"""
Handler for /balance.
Returns the current status of the trades in json format
"""
results = self._rpc_balance(self._config.get('fiat_display_currency', ''))
return self.rest_dump(results)
@require_login
@rpc_catch_errors
def _whitelist(self):
"""
Handler for /whitelist.
"""
results = self._rpc_whitelist()
return self.rest_dump(results)
@require_login
@rpc_catch_errors
def _blacklist(self):
"""
Handler for /blacklist.
"""
add = request.json.get("blacklist", None) if request.method == 'POST' else None
results = self._rpc_blacklist(add)
return self.rest_dump(results)
@require_login
@rpc_catch_errors
def _forcebuy(self):
"""
Handler for /forcebuy.
"""
asset = request.json.get("pair")
price = request.json.get("price", None)
trade = self._rpc_forcebuy(asset, price)
if trade:
return self.rest_dump(trade.to_json())
else:
return self.rest_dump({"status": f"Error buying pair {asset}."})
@require_login
@rpc_catch_errors
def _forcesell(self):
"""
Handler for /forcesell.
"""
tradeid = request.json.get("tradeid")
results = self._rpc_forcesell(tradeid)
return self.rest_dump(results)

View File

@@ -10,7 +10,7 @@ from typing import Dict, Any, List, Optional
import arrow
import sqlalchemy as sql
from numpy import mean, nan_to_num, NAN
from numpy import mean, NAN
from pandas import DataFrame
from freqtrade import TemporaryError, DependencyException
@@ -48,6 +48,11 @@ class RPCException(Exception):
def __str__(self):
return self.message
def __json__(self):
return {
'msg': self.message
}
class RPC(object):
"""
@@ -83,7 +88,7 @@ class RPC(object):
a remotely exposed function
"""
# Fetch open trade
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
trades = Trade.get_open_trades()
if not trades:
raise RPCException('no active trade')
else:
@@ -94,31 +99,27 @@ class RPC(object):
order = self._freqtrade.exchange.get_order(trade.open_order_id, trade.pair)
# calculate profit and send message to user
try:
current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid']
current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
except DependencyException:
current_rate = NAN
current_profit = trade.calc_profit_percent(current_rate)
fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%'
if trade.close_profit else None)
results.append(dict(
trade_id=trade.id,
pair=trade.pair,
market_url=self._freqtrade.exchange.get_pair_detail_url(trade.pair),
date=arrow.get(trade.open_date),
open_rate=trade.open_rate,
close_rate=trade.close_rate,
current_rate=current_rate,
amount=round(trade.amount, 8),
trade_dict = trade.to_json()
trade_dict.update(dict(
base_currency=self._freqtrade.config['stake_currency'],
close_profit=fmt_close_profit,
current_rate=current_rate,
current_profit=round(current_profit * 100, 2),
open_order='({} {} rem={:.8f})'.format(
order['type'], order['side'], order['remaining']
) if order else None,
))
results.append(trade_dict)
return results
def _rpc_status_table(self) -> DataFrame:
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
trades = Trade.get_open_trades()
if not trades:
raise RPCException('no active order')
else:
@@ -126,7 +127,7 @@ class RPC(object):
for trade in trades:
# calculate profit and send message to user
try:
current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid']
current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
except DependencyException:
current_rate = NAN
trade_perc = (100 * trade.calc_profit_percent(current_rate))
@@ -194,9 +195,9 @@ class RPC(object):
trades = Trade.query.order_by(Trade.id).all()
profit_all_coin = []
profit_all_percent = []
profit_all_perc = []
profit_closed_coin = []
profit_closed_percent = []
profit_closed_perc = []
durations = []
for trade in trades:
@@ -210,11 +211,11 @@ class RPC(object):
if not trade.is_open:
profit_percent = trade.calc_profit_percent()
profit_closed_coin.append(trade.calc_profit())
profit_closed_percent.append(profit_percent)
profit_closed_perc.append(profit_percent)
else:
# Get current rate
try:
current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid']
current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
except DependencyException:
current_rate = NAN
profit_percent = trade.calc_profit_percent(rate=current_rate)
@@ -222,7 +223,7 @@ class RPC(object):
profit_all_coin.append(
trade.calc_profit(rate=Decimal(trade.close_rate or current_rate))
)
profit_all_percent.append(profit_percent)
profit_all_perc.append(profit_percent)
best_pair = Trade.session.query(
Trade.pair, sql.func.sum(Trade.close_profit).label('profit_sum')
@@ -237,7 +238,8 @@ class RPC(object):
# Prepare data to display
profit_closed_coin_sum = round(sum(profit_closed_coin), 8)
profit_closed_percent = round(nan_to_num(mean(profit_closed_percent)) * 100, 2)
profit_closed_percent = (round(mean(profit_closed_perc) * 100, 2) if profit_closed_perc
else 0.0)
profit_closed_fiat = self._fiat_converter.convert_amount(
profit_closed_coin_sum,
stake_currency,
@@ -245,7 +247,7 @@ class RPC(object):
) if self._fiat_converter else 0
profit_all_coin_sum = round(sum(profit_all_coin), 8)
profit_all_percent = round(nan_to_num(mean(profit_all_percent)) * 100, 2)
profit_all_percent = round(mean(profit_all_perc) * 100, 2) if profit_all_perc else 0.0
profit_all_fiat = self._fiat_converter.convert_amount(
profit_all_coin_sum,
stake_currency,
@@ -280,11 +282,13 @@ class RPC(object):
rate = 1.0
else:
try:
if coin == 'USDT':
rate = 1.0 / self._freqtrade.exchange.get_ticker('BTC/USDT', False)['bid']
pair = self._freqtrade.exchange.get_valid_pair_combination(coin, "BTC")
if pair.startswith("BTC"):
rate = 1.0 / self._freqtrade.get_sell_rate(pair, False)
else:
rate = self._freqtrade.exchange.get_ticker(coin + '/BTC', False)['bid']
rate = self._freqtrade.get_sell_rate(pair, False)
except (TemporaryError, DependencyException):
logger.warning(f" Could not get rate for pair {coin}.")
continue
est_btc: float = rate * balance['total']
total = total + est_btc
@@ -296,7 +300,10 @@ class RPC(object):
'est_btc': est_btc,
})
if total == 0.0:
raise RPCException('all balances are zero')
if self._freqtrade.config.get('dry_run', False):
raise RPCException('Running in Dry Run, balances are not available.')
else:
raise RPCException('All balances are zero.')
symbol = fiat_display_currency
value = self._fiat_converter.convert_amount(total, 'BTC',
@@ -329,7 +336,17 @@ class RPC(object):
self._freqtrade.state = State.RELOAD_CONF
return {'status': 'reloading config ...'}
def _rpc_forcesell(self, trade_id) -> None:
def _rpc_stopbuy(self) -> Dict[str, str]:
"""
Handler to stop buying, but handle open trades gracefully.
"""
if self._freqtrade.state == State.RUNNING:
# Set 'max_open_trades' to 0
self._freqtrade.config['max_open_trades'] = 0
return {'status': 'No more buy will occur from now. Run /reload_conf to reset.'}
def _rpc_forcesell(self, trade_id) -> Dict[str, str]:
"""
Handler for forcesell <id>.
Sells the given trade at current price
@@ -357,7 +374,7 @@ class RPC(object):
return
# Get current rate and execute sell
current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid']
current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
self._freqtrade.execute_sell(trade, current_rate, SellType.FORCE_SELL)
# ---- EOF def _exec_forcesell ----
@@ -366,10 +383,10 @@ class RPC(object):
if trade_id == 'all':
# Execute sell for all open orders
for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
for trade in Trade.get_open_trades():
_exec_forcesell(trade)
Trade.session.flush()
return
return {'result': 'Created sell orders for all open trades.'}
# Query for trade
trade = Trade.query.filter(
@@ -384,6 +401,7 @@ class RPC(object):
_exec_forcesell(trade)
Trade.session.flush()
return {'result': f'Created sell order for trade {trade_id}.'}
def _rpc_forcebuy(self, pair: str, price: Optional[float]) -> Optional[Trade]:
"""
@@ -437,17 +455,43 @@ class RPC(object):
for pair, rate, count in pair_rates
]
def _rpc_count(self) -> List[Trade]:
def _rpc_count(self) -> Dict[str, float]:
""" Returns the number of trades running """
if self._freqtrade.state != State.RUNNING:
raise RPCException('trader is not running')
return Trade.query.filter(Trade.is_open.is_(True)).all()
trades = Trade.get_open_trades()
return {
'current': len(trades),
'max': float(self._freqtrade.config['max_open_trades']),
'total_stake': sum((trade.open_rate * trade.amount) for trade in trades)
}
def _rpc_whitelist(self) -> Dict:
""" Returns the currently active whitelist"""
res = {'method': self._freqtrade.pairlists.name,
'length': len(self._freqtrade.pairlists.whitelist),
'length': len(self._freqtrade.active_pair_whitelist),
'whitelist': self._freqtrade.active_pair_whitelist
}
return res
def _rpc_blacklist(self, add: List[str] = None) -> Dict:
""" Returns the currently active blacklist"""
if add:
stake_currency = self._freqtrade.config.get('stake_currency')
for pair in add:
if (pair.endswith(stake_currency)
and pair not in self._freqtrade.pairlists.blacklist):
self._freqtrade.pairlists.blacklist.append(pair)
res = {'method': self._freqtrade.pairlists.name,
'length': len(self._freqtrade.pairlists.blacklist),
'blacklist': self._freqtrade.pairlists.blacklist,
}
return res
def _rpc_edge(self) -> List[Dict[str, Any]]:
""" Returns information related to Edge """
if not self._freqtrade.edge:
raise RPCException(f'Edge is not enabled.')
return self._freqtrade.edge.accepted_pairs()

View File

@@ -2,7 +2,7 @@
This module contains class to manage RPC communications (Telegram, Slack, ...)
"""
import logging
from typing import List, Dict, Any
from typing import Any, Dict, List
from freqtrade.rpc import RPC, RPCMessageType
@@ -29,6 +29,12 @@ class RPCManager(object):
from freqtrade.rpc.webhook import Webhook
self.registered_modules.append(Webhook(freqtrade))
# Enable local rest api server for cmd line control
if freqtrade.config.get('api_server', {}).get('enabled', False):
logger.info('Enabling rpc.api_server')
from freqtrade.rpc.api_server import ApiServer
self.registered_modules.append(ApiServer(freqtrade))
def cleanup(self) -> None:
""" Stops all enabled rpc modules """
logger.info('Cleaning up rpc modules ...')
@@ -61,6 +67,8 @@ class RPCManager(object):
stake_currency = config['stake_currency']
stake_amount = config['stake_amount']
minimal_roi = config['minimal_roi']
stoploss = config['stoploss']
trailing_stop = config['trailing_stop']
ticker_interval = config['ticker_interval']
exchange_name = config['exchange']['name']
strategy_name = config.get('strategy', '')
@@ -69,6 +77,7 @@ class RPCManager(object):
'status': f'*Exchange:* `{exchange_name}`\n'
f'*Stake per trade:* `{stake_amount} {stake_currency}`\n'
f'*Minimum ROI:* `{minimal_roi}`\n'
f'*{"Trailing " if trailing_stop else ""}Stoploss:* `{stoploss}`\n'
f'*Ticker Interval:* `{ticker_interval}`\n'
f'*Strategy:* `{strategy_name}`'
})

View File

@@ -4,7 +4,7 @@
This module manage Telegram communication
"""
import logging
from typing import Any, Callable, Dict
from typing import Any, Callable, Dict, List
from tabulate import tabulate
from telegram import Bot, ParseMode, ReplyKeyboardMarkup, Update
@@ -20,7 +20,10 @@ logger = logging.getLogger(__name__)
logger.debug('Included module rpc.telegram ...')
def authorized_only(command_handler: Callable[[Any, Bot, Update], None]) -> Callable[..., Any]:
MAX_TELEGRAM_MESSAGE_LENGTH = 4096
def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]:
"""
Decorator to check if the message comes from the correct chat_id
:param command_handler: Telegram CommandHandler
@@ -91,7 +94,10 @@ class Telegram(RPC):
CommandHandler('daily', self._daily),
CommandHandler('count', self._count),
CommandHandler('reload_conf', self._reload_conf),
CommandHandler('stopbuy', self._stopbuy),
CommandHandler('whitelist', self._whitelist),
CommandHandler('blacklist', self._blacklist, pass_args=True),
CommandHandler('edge', self._edge),
CommandHandler('help', self._help),
CommandHandler('version', self._version),
]
@@ -125,8 +131,8 @@ class Telegram(RPC):
else:
msg['stake_amount_fiat'] = 0
message = ("*{exchange}:* Buying [{pair}]({market_url})\n"
"with limit `{limit:.8f}\n"
message = ("*{exchange}:* Buying {pair}\n"
"at rate `{limit:.8f}\n"
"({stake_amount:.6f} {stake_currency}").format(**msg)
if msg.get('fiat_currency', None):
@@ -137,8 +143,8 @@ class Telegram(RPC):
msg['amount'] = round(msg['amount'], 8)
msg['profit_percent'] = round(msg['profit_percent'] * 100, 2)
message = ("*{exchange}:* Selling [{pair}]({market_url})\n"
"*Limit:* `{limit:.8f}`\n"
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"
@@ -187,25 +193,36 @@ class Telegram(RPC):
try:
results = self._rpc_trade_status()
# pre format data
for result in results:
result['date'] = result['date'].humanize()
messages = [
"*Trade ID:* `{trade_id}`\n"
"*Current Pair:* [{pair}]({market_url})\n"
"*Open Since:* `{date}`\n"
"*Amount:* `{amount}`\n"
"*Open Rate:* `{open_rate:.8f}`\n"
"*Close Rate:* `{close_rate}`\n"
"*Current Rate:* `{current_rate:.8f}`\n"
"*Close Profit:* `{close_profit}`\n"
"*Current Profit:* `{current_profit:.2f}%`\n"
"*Open Order:* `{open_order}`".format(**result)
for result in results
messages = []
for r in results:
lines = [
"*Trade ID:* `{trade_id}` `(since {open_date_hum})`",
"*Current Pair:* {pair}",
"*Amount:* `{amount} ({stake_amount} {base_currency})`",
"*Open Rate:* `{open_rate:.8f}`",
"*Close Rate:* `{close_rate}`" if r['close_rate'] else "",
"*Current Rate:* `{current_rate:.8f}`",
"*Close Profit:* `{close_profit}`" if r['close_profit'] else "",
"*Current Profit:* `{current_profit:.2f}%`",
# Adding initial stoploss only if it is different from stoploss
"*Initial Stoploss:* `{initial_stop_loss:.8f}` " +
("`({initial_stop_loss_pct:.2f}%)`" if r['initial_stop_loss_pct'] else "")
if r['stop_loss'] != r['initial_stop_loss'] else "",
# Adding stoploss and stoploss percentage only if it is not None
"*Stoploss:* `{stop_loss:.8f}` " +
("`({stop_loss_pct:.2f}%)`" if r['stop_loss_pct'] else ""),
"*Open Order:* `{open_order}`" if r['open_order'] else ""
]
# Filter empty lines using list-comprehension
messages.append("\n".join([l for l in lines if l]).format(**r))
for msg in messages:
self._send_msg(msg, bot=bot)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@@ -250,7 +267,8 @@ class Telegram(RPC):
headers=[
'Day',
f'Profit {stake_cur}',
f'Profit {fiat_disp_cur}'
f'Profit {fiat_disp_cur}',
f'Trades'
],
tablefmt='simple')
message = f'<b>Daily Profit over the last {timescale} days</b>:\n<pre>{stats_tab}</pre>'
@@ -311,13 +329,20 @@ class Telegram(RPC):
output = ''
for currency in result['currencies']:
if currency['est_btc'] > 0.0001:
output += "*{currency}:*\n" \
curr_output = "*{currency}:*\n" \
"\t`Available: {available: .8f}`\n" \
"\t`Balance: {balance: .8f}`\n" \
"\t`Pending: {pending: .8f}`\n" \
"\t`Est. BTC: {est_btc: .8f}`\n".format(**currency)
else:
output += "*{currency}:* not showing <1$ amount \n".format(**currency)
curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency)
# Handle overflowing messsage length
if len(output + curr_output) >= MAX_TELEGRAM_MESSAGE_LENGTH:
self._send_msg(output, bot=bot)
output = curr_output
else:
output += curr_output
output += "\n*Estimated Value*:\n" \
"\t`BTC: {total: .8f}`\n" \
@@ -362,6 +387,18 @@ class Telegram(RPC):
msg = self._rpc_reload_conf()
self._send_msg('Status: `{status}`'.format(**msg), bot=bot)
@authorized_only
def _stopbuy(self, bot: Bot, update: Update) -> None:
"""
Handler for /stop_buy.
Sets max_open_trades to 0 and gracefully sells all open trades
:param bot: telegram bot
:param update: message update
:return: None
"""
msg = self._rpc_stopbuy()
self._send_msg('Status: `{status}`'.format(**msg), bot=bot)
@authorized_only
def _forcesell(self, bot: Bot, update: Update) -> None:
"""
@@ -374,7 +411,9 @@ class Telegram(RPC):
trade_id = update.message.text.replace('/forcesell', '').strip()
try:
self._rpc_forcesell(trade_id)
msg = self._rpc_forcesell(trade_id)
self._send_msg('Forcesell Result: `{result}`'.format(**msg), bot=bot)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@@ -428,12 +467,10 @@ class Telegram(RPC):
:return: None
"""
try:
trades = self._rpc_count()
message = tabulate({
'current': [len(trades)],
'max': [self._config['max_open_trades']],
'total stake': [sum((trade.open_rate * trade.amount) for trade in trades)]
}, headers=['current', 'max', 'total stake'], tablefmt='simple')
counts = self._rpc_count()
message = tabulate({k: [v] for k, v in counts.items()},
headers=['current', 'max', 'total stake'],
tablefmt='simple')
message = "<pre>{}</pre>".format(message)
logger.debug(message)
self._send_msg(message, parse_mode=ParseMode.HTML)
@@ -457,6 +494,38 @@ class Telegram(RPC):
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only
def _blacklist(self, bot: Bot, update: Update, args: List[str]) -> None:
"""
Handler for /blacklist
Shows the currently active blacklist
"""
try:
blacklist = self._rpc_blacklist(args)
message = f"Blacklist contains {blacklist['length']} pairs\n"
message += f"`{', '.join(blacklist['blacklist'])}`"
logger.debug(message)
self._send_msg(message)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only
def _edge(self, bot: Bot, update: Update) -> None:
"""
Handler for /edge
Shows information related to Edge
"""
try:
edge_pairs = self._rpc_edge()
edge_pairs_tab = tabulate(edge_pairs, headers='keys', tablefmt='simple')
message = f'<b>Edge only validated following pairs:</b>\n<pre>{edge_pairs_tab}</pre>'
self._send_msg(message, bot=bot, parse_mode=ParseMode.HTML)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only
def _help(self, bot: Bot, update: Update) -> None:
"""
@@ -466,6 +535,8 @@ class Telegram(RPC):
:param update: message update
:return: None
"""
forcebuy_text = "*/forcebuy <pair> [<rate>]:* `Instantly buys the given pair. " \
"Optionally takes a rate at which to buy.` \n"
message = "*/start:* `Starts the trader`\n" \
"*/stop:* `Stops the trader`\n" \
"*/status [table]:* `Lists all open trades`\n" \
@@ -473,13 +544,18 @@ class Telegram(RPC):
"*/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" \
f"{forcebuy_text if self._config.get('forcebuy_enable', False) else '' }" \
"*/performance:* `Show performance of each finished trade grouped by pair`\n" \
"*/daily <n>:* `Shows profit or loss per day, over the last n days`\n" \
"*/count:* `Show number of trades running compared to allowed number of trades`" \
"\n" \
"*/balance:* `Show account balance per currency`\n" \
"*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" \
"*/reload_conf:* `Reload configuration file` \n" \
"*/whitelist:* `Show current whitelist` \n" \
"*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs " \
"to the blacklist.` \n" \
"*/edge:* `Shows validated pairs by Edge if it is enabeld` \n" \
"*/help:* `This help message`\n" \
"*/version:* `Show version`"

View File

@@ -18,11 +18,11 @@ class State(Enum):
class RunMode(Enum):
"""
Bot running mode (backtest, hyperopt, ...)
can be "live", "dry-run", "backtest", "edgecli", "hyperopt".
can be "live", "dry-run", "backtest", "edge", "hyperopt".
"""
LIVE = "live"
DRY_RUN = "dry_run"
BACKTEST = "backtest"
EDGECLI = "edgecli"
EDGE = "edge"
HYPEROPT = "hyperopt"
OTHER = "other" # Used for plotting scripts and test

View File

@@ -6,6 +6,7 @@ from freqtrade.strategy.interface import IStrategy
# Import Default-Strategy to have hyperopt correctly resolve
from freqtrade.strategy.default_strategy import DefaultStrategy # noqa: F401
logger = logging.getLogger(__name__)
@@ -16,7 +17,6 @@ def import_strategy(strategy: IStrategy, config: dict) -> IStrategy:
"""
# Copy all attributes from base class and class
comb = {**strategy.__class__.__dict__, **strategy.__dict__}
# Delete '_abc_impl' from dict as deepcopy fails on 3.7 with
@@ -26,6 +26,7 @@ def import_strategy(strategy: IStrategy, config: dict) -> IStrategy:
del comb['_abc_impl']
attr = deepcopy(comb)
# Adjust module name
attr['__module__'] = 'freqtrade.strategy'

View File

@@ -4,19 +4,20 @@ This module defines the interface to apply for strategies
"""
import logging
from abc import ABC, abstractmethod
from datetime import datetime
from datetime import datetime, timezone
from enum import Enum
from typing import Dict, List, NamedTuple, Tuple
from typing import Dict, List, NamedTuple, Optional, Tuple
import warnings
import arrow
from pandas import DataFrame
from freqtrade import constants
from freqtrade.data.dataprovider import DataProvider
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.persistence import Trade
from freqtrade.wallets import Wallets
logger = logging.getLogger(__name__)
@@ -73,6 +74,7 @@ class IStrategy(ABC):
trailing_stop: bool = False
trailing_stop_positive: float
trailing_stop_positive_offset: float
trailing_only_offset_is_reached = False
# associated ticker interval
ticker_interval: str
@@ -105,6 +107,7 @@ class IStrategy(ABC):
self.config = config
# Dict to determine if analysis is necessary
self._last_candle_seen_per_pair: Dict[str, datetime] = {}
self._pair_locked_until: Dict[str, datetime] = {}
@abstractmethod
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
@@ -152,11 +155,46 @@ class IStrategy(ABC):
"""
return self.__class__.__name__
def lock_pair(self, pair: str, until: datetime) -> None:
"""
Locks pair until a given timestamp happens.
Locked pairs are not analyzed, and are prevented from opening new trades.
:param pair: Pair to lock
:param until: datetime in UTC until the pair should be blocked from opening new trades.
Needs to be timezone aware `datetime.now(timezone.utc)`
"""
self._pair_locked_until[pair] = until
def is_pair_locked(self, pair: str) -> bool:
"""
Checks if a pair is currently locked
"""
if pair not in self._pair_locked_until:
return False
return self._pair_locked_until[pair] >= datetime.now(timezone.utc)
def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Parses the given ticker history and returns a populated DataFrame
add several TA indicators and buy signal to it
:return DataFrame with ticker data and indicator data
:param dataframe: Dataframe containing ticker data
:param metadata: Metadata dictionary with additional data (e.g. 'pair')
:return: DataFrame with ticker data and indicator data
"""
logger.debug("TA Analysis Launched")
dataframe = self.advise_indicators(dataframe, metadata)
dataframe = self.advise_buy(dataframe, metadata)
dataframe = self.advise_sell(dataframe, metadata)
return dataframe
def _analyze_ticker_internal(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Parses the given ticker history 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 metadata: Metadata dictionary with additional data (e.g. 'pair')
:return: DataFrame with ticker data and indicator data
"""
pair = str(metadata.get('pair'))
@@ -166,10 +204,7 @@ class IStrategy(ABC):
if (not self.process_only_new_candles or
self._last_candle_seen_per_pair.get(pair, None) != dataframe.iloc[-1]['date']):
# Defs that only make change on new candle data.
logger.debug("TA Analysis Launched")
dataframe = self.advise_indicators(dataframe, metadata)
dataframe = self.advise_buy(dataframe, metadata)
dataframe = self.advise_sell(dataframe, metadata)
dataframe = self.analyze_ticker(dataframe, metadata)
self._last_candle_seen_per_pair[pair] = dataframe.iloc[-1]['date']
else:
logger.debug("Skipping TA Analysis for already analyzed candle")
@@ -196,7 +231,7 @@ class IStrategy(ABC):
return False, False
try:
dataframe = self.analyze_ticker(dataframe, {'pair': pair})
dataframe = self._analyze_ticker_internal(dataframe, {'pair': pair})
except ValueError as error:
logger.warning(
'Unable to analyze ticker for pair %s: %s',
@@ -220,7 +255,7 @@ class IStrategy(ABC):
# Check if dataframe is out of date
signal_date = arrow.get(latest['date'])
interval_minutes = constants.TICKER_INTERVAL_MINUTES[interval]
interval_minutes = timeframe_to_minutes(interval)
offset = self.config.get('exchange', {}).get('outdated_offset', 5)
if signal_date < (arrow.utcnow().shift(minutes=-(interval_minutes * 2 + offset))):
logger.warning(
@@ -244,8 +279,11 @@ class IStrategy(ABC):
sell: bool, low: float = None, high: float = None,
force_stoploss: float = 0) -> SellCheckTuple:
"""
This function evaluate if on the condition required to trigger a sell has been reached
if the threshold is reached and updates the trade record.
This function evaluates if one of the conditions required to trigger a sell
has been reached, which can either be a stop-loss, ROI or sell-signal.
:param low: Only used during backtesting to simulate stoploss
:param high: Only used during backtesting, to simulate ROI
:param force_stoploss: Externally provided stoploss
:return: True if trade should be sold, False otherwise
"""
@@ -253,14 +291,16 @@ class IStrategy(ABC):
current_rate = low or rate
current_profit = trade.calc_profit_percent(current_rate)
trade.adjust_min_max_rates(high or current_rate)
stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade,
current_time=date, current_profit=current_profit,
force_stoploss=force_stoploss)
force_stoploss=force_stoploss, high=high)
if stoplossflag.sell_flag:
return stoplossflag
# Set current rate to low for backtesting sell
# Set current rate to high for backtesting sell
current_rate = high or rate
current_profit = trade.calc_profit_percent(current_rate)
experimental = self.config.get('experimental', {})
@@ -284,8 +324,9 @@ class IStrategy(ABC):
return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime,
current_profit: float, force_stoploss: float) -> SellCheckTuple:
def stop_loss_reached(self, current_rate: float, trade: Trade,
current_time: datetime, current_profit: float,
force_stoploss: float, high: float = None) -> SellCheckTuple:
"""
Based on current profit of the trade and configured (trailing) stoploss,
decides to sell or not
@@ -293,16 +334,39 @@ class IStrategy(ABC):
"""
trailing_stop = self.config.get('trailing_stop', False)
trade.adjust_stop_loss(trade.open_rate, force_stoploss if force_stoploss
else self.stoploss, initial=True)
stop_loss_value = force_stoploss if force_stoploss else self.stoploss
# Initiate stoploss with open_rate. Does nothing if stoploss is already set.
trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True)
if trailing_stop:
# trailing stoploss handling
sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0
tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False)
# Make sure current_profit is calculated using high for backtesting.
high_profit = current_profit if not high else trade.calc_profit_percent(high)
# Don't update stoploss if trailing_only_offset_is_reached is true.
if not (tsl_only_offset and high_profit < sl_offset):
# Specific handling for trailing_stop_positive
if 'trailing_stop_positive' in self.config and high_profit > sl_offset:
# Ignore mypy error check in configuration that this is a float
stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore
logger.debug(f"using positive stop loss: {stop_loss_value} "
f"offset: {sl_offset:.4g} profit: {current_profit:.4f}%")
trade.adjust_stop_loss(high or current_rate, stop_loss_value)
# evaluate if the stoploss was hit if stoploss is not on exchange
if ((self.stoploss is not None) and
(trade.stop_loss >= current_rate) and
(not self.order_types.get('stoploss_on_exchange'))):
selltype = SellType.STOP_LOSS
# If Trailing stop (and max-rate did move above open rate)
if trailing_stop and trade.open_rate != trade.max_rate:
# If initial stoploss is not the same as current one then it is trailing.
if trade.initial_stop_loss != trade.stop_loss:
selltype = SellType.TRAILING_STOP_LOSS
logger.debug(
f"HIT STOP: current price at {current_rate:.6f}, "
@@ -314,44 +378,34 @@ class IStrategy(ABC):
logger.debug('Stop loss hit.')
return SellCheckTuple(sell_flag=True, sell_type=selltype)
# update the stop loss afterwards, after all by definition it's supposed to be hanging
if trailing_stop:
# check if we have a special stop loss for positive condition
# and if profit is positive
stop_loss_value = force_stoploss if force_stoploss else self.stoploss
sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0
if 'trailing_stop_positive' in self.config and current_profit > sl_offset:
# Ignore mypy error check in configuration that this is a float
stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore
logger.debug(f"using positive stop loss mode: {stop_loss_value} "
f"with offset {sl_offset:.4g} "
f"since we have profit {current_profit:.4f}%")
trade.adjust_stop_loss(current_rate, stop_loss_value)
return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
def min_roi_reached_entry(self, trade_dur: int) -> Optional[float]:
"""
Based on trade duration defines the ROI entry that may have been reached.
:param trade_dur: trade duration in minutes
:return: minimal ROI entry value or None if none proper ROI entry was found.
"""
# Get highest entry in ROI dict where key <= trade-duration
roi_list = list(filter(lambda x: x <= trade_dur, self.minimal_roi.keys()))
if not roi_list:
return None
roi_entry = max(roi_list)
return self.minimal_roi[roi_entry]
def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool:
"""
Based an earlier trade and current price and ROI configuration, decides whether bot should
Based on trade duration, current price and ROI configuration, decides whether bot should
sell. Requires current_profit to be in percent!!
:return True if bot should sell at current rate
:return: True if bot should sell at current rate
"""
# Check if time matches and current rate is above threshold
trade_dur = (current_time.timestamp() - trade.open_date.timestamp()) / 60
# Get highest entry in ROI dict where key >= trade-duration
roi_entry = max(list(filter(lambda x: trade_dur >= x, self.minimal_roi.keys())))
threshold = self.minimal_roi[roi_entry]
if current_profit > threshold:
return True
trade_dur = int((current_time.timestamp() - trade.open_date.timestamp()) // 60)
roi = self.min_roi_reached_entry(trade_dur)
if roi is None:
return False
else:
return current_profit > roi
def tickerdata_to_dataframe(self, tickerdata: Dict[str, List]) -> Dict[str, DataFrame]:
"""
@@ -368,6 +422,7 @@ class IStrategy(ABC):
:param metadata: Additional information, like the currently traded pair
:return: a Dataframe with all mandatory indicators for the strategies
"""
logger.debug(f"Populating indicators for pair {metadata.get('pair')}.")
if self._populate_fun_len == 2:
warnings.warn("deprecated - check out the Sample strategy to see "
"the current function headers!", DeprecationWarning)
@@ -383,6 +438,7 @@ class IStrategy(ABC):
:param pair: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
logger.debug(f"Populating buy signals for pair {metadata.get('pair')}.")
if self._buy_fun_len == 2:
warnings.warn("deprecated - check out the Sample strategy to see "
"the current function headers!", DeprecationWarning)
@@ -398,6 +454,7 @@ class IStrategy(ABC):
:param pair: Additional information, like the currently traded pair
:return: DataFrame with sell column
"""
logger.debug(f"Populating sell signals for pair {metadata.get('pair')}.")
if self._sell_fun_len == 2:
warnings.warn("deprecated - check out the Sample strategy to see "
"the current function headers!", DeprecationWarning)

View File

@@ -0,0 +1,133 @@
{
/* Single-line C-style comment */
"max_open_trades": 3,
/*
* Multi-line C-style comment
*/
"stake_currency": "BTC",
"stake_amount": 0.05,
"fiat_display_currency": "USD", // C++-style comment
"amount_reserve_percent" : 0.05, // And more, tabs before this comment
"dry_run": false,
"ticker_interval": "5m",
"trailing_stop": false,
"trailing_stop_positive": 0.005,
"trailing_stop_positive_offset": 0.0051,
"trailing_only_offset_is_reached": false,
"minimal_roi": {
"40": 0.0,
"30": 0.01,
"20": 0.02,
"0": 0.04
},
"stoploss": -0.10,
"unfilledtimeout": {
"buy": 10,
"sell": 30, // Trailing comma should also be accepted now
},
"bid_strategy": {
"use_order_book": false,
"ask_last_balance": 0.0,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"ask_strategy":{
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 9
},
"order_types": {
"buy": "limit",
"sell": "limit",
"stoploss": "market",
"stoploss_on_exchange": false,
"stoploss_on_exchange_interval": 60
},
"order_time_in_force": {
"buy": "gtc",
"sell": "gtc"
},
"pairlist": {
"method": "VolumePairList",
"config": {
"number_assets": 20,
"sort_key": "quoteVolume",
"precision_filter": false
}
},
"exchange": {
"name": "bittrex",
"sandbox": false,
"key": "your_exchange_key",
"secret": "your_exchange_secret",
"password": "",
"ccxt_config": {"enableRateLimit": true},
"ccxt_async_config": {
"enableRateLimit": false,
"rateLimit": 500,
"aiohttp_trust_env": false
},
"pair_whitelist": [
"ETH/BTC",
"LTC/BTC",
"ETC/BTC",
"DASH/BTC",
"ZEC/BTC",
"XLM/BTC",
"NXT/BTC",
"POWR/BTC",
"ADA/BTC",
"XMR/BTC"
],
"pair_blacklist": [
"DOGE/BTC"
],
"outdated_offset": 5,
"markets_refresh_interval": 60
},
"edge": {
"enabled": false,
"process_throttle_secs": 3600,
"calculate_since_number_of_days": 7,
"capital_available_percentage": 0.5,
"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
},
"experimental": {
"use_sell_signal": false,
"sell_profit_only": false,
"ignore_roi_if_buy_signal": false
},
"telegram": {
// We can now comment out some settings
// "enabled": true,
"enabled": false,
"token": "your_telegram_token",
"chat_id": "your_telegram_chat_id"
},
"api_server": {
"enabled": false,
"listen_ip_address": "127.0.0.1",
"listen_port": 8080,
"username": "freqtrader",
"password": "SuperSecurePassword"
},
"db_url": "sqlite:///tradesv3.sqlite",
"initial_state": "running",
"forcebuy_enable": false,
"internals": {
"process_throttle_secs": 5
},
"strategy": "DefaultStrategy",
"strategy_path": "user_data/strategies/"
}

View File

@@ -2,40 +2,62 @@
import json
import logging
import re
from copy import deepcopy
from datetime import datetime
from functools import reduce
from typing import Dict, Optional
from pathlib import Path
from unittest.mock import MagicMock, PropertyMock
import arrow
import pytest
import numpy as np
from telegram import Chat, Message, Update
from freqtrade import constants
from freqtrade import constants, persistence
from freqtrade.configuration import Arguments
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.exchange import Exchange
from freqtrade.edge import Edge, PairInfo
from freqtrade.exchange import Exchange
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.resolvers import ExchangeResolver
from freqtrade.worker import Worker
logging.getLogger('').setLevel(logging.INFO)
# Do not mask numpy errors as warnings that no one read, raise the exсeption
np.seterr(all='raise')
def log_has(line, logs):
# caplog mocker returns log as a tuple: ('freqtrade.something', logging.WARNING, 'foobar')
# and we want to match line against foobar in the tuple
return reduce(lambda a, b: a or b,
filter(lambda x: x[2] == line, logs),
filter(lambda x: x[2] == line, logs.record_tuples),
False)
def log_has_re(line, logs):
return reduce(lambda a, b: a or b,
filter(lambda x: re.match(line, x[2]), logs),
filter(lambda x: re.match(line, x[2]), logs.record_tuples),
False)
def get_args(args):
return Arguments(args, '').get_parsed_arg()
def patched_configuration_load_config_file(mocker, config) -> None:
mocker.patch(
'freqtrade.configuration.configuration.load_config_file',
lambda *args, **kwargs: config
)
def patch_exchange(mocker, api_mock=None, id='bittrex') -> None:
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id))
@@ -49,6 +71,10 @@ def patch_exchange(mocker, api_mock=None, id='bittrex') -> None:
def get_patched_exchange(mocker, config, api_mock=None, id='bittrex') -> Exchange:
patch_exchange(mocker, api_mock, id)
config["exchange"]["name"] = id
try:
exchange = ExchangeResolver(id, config).exchange
except ImportError:
exchange = Exchange(config)
return exchange
@@ -82,24 +108,54 @@ def get_patched_edge(mocker, config) -> Edge:
# Functions for recurrent object patching
def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
def patch_freqtradebot(mocker, config) -> None:
"""
This function patch _init_modules() to not call dependencies
:param mocker: a Mocker object to apply patches
:param config: Config to pass to the bot
:return: None
"""
patch_coinmarketcap(mocker, {'price_usd': 12345.0})
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
persistence.init(config['db_url'])
patch_exchange(mocker, None)
mocker.patch('freqtrade.freqtradebot.RPCManager._init', MagicMock())
mocker.patch('freqtrade.freqtradebot.RPCManager.send_msg', MagicMock())
def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
"""
This function patches _init_modules() to not call dependencies
:param mocker: a Mocker object to apply patches
:param config: Config to pass to the bot
:return: FreqtradeBot
"""
patch_freqtradebot(mocker, config)
return FreqtradeBot(config)
def patch_coinmarketcap(mocker, value: Optional[Dict[str, float]] = None) -> None:
def get_patched_worker(mocker, config) -> Worker:
"""
This function patches _init_modules() to not call dependencies
:param mocker: a Mocker object to apply patches
:param config: Config to pass to the bot
:return: Worker
"""
patch_freqtradebot(mocker, config)
return Worker(args=None, config=config)
def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None:
"""
:param mocker: mocker to patch IStrategy class
:param value: which value IStrategy.get_signal() must return
:return: None
"""
freqtrade.strategy.get_signal = lambda e, s, t: value
freqtrade.exchange.refresh_latest_ohlcv = lambda p: None
@pytest.fixture(autouse=True)
def patch_coinmarketcap(mocker) -> None:
"""
Mocker to coinmarketcap to speed up tests
:param mocker: mocker to patch coinmarketcap class
@@ -120,6 +176,11 @@ def patch_coinmarketcap(mocker, value: Optional[Dict[str, float]] = None) -> Non
)
@pytest.fixture(scope='function')
def init_persistence(default_conf):
persistence.init(default_conf['db_url'], default_conf['dry_run'])
@pytest.fixture(scope="function")
def default_conf():
""" Returns validated configuration suitable for most tests """
@@ -165,6 +226,10 @@ def default_conf():
"LTC/BTC",
"XRP/BTC",
"NEO/BTC"
],
"pair_blacklist": [
"DOGE/BTC",
"HOT/BTC",
]
},
"telegram": {
@@ -174,7 +239,8 @@ def default_conf():
},
"initial_state": "running",
"db_url": "sqlite://",
"loglevel": logging.DEBUG,
"user_data_dir": Path("user_data"),
"verbosity": 3,
}
return configuration
@@ -220,8 +286,8 @@ def ticker_sell_down():
@pytest.fixture
def markets():
return MagicMock(return_value=[
{
return {
'ETH/BTC': {
'id': 'ethbtc',
'symbol': 'ETH/BTC',
'base': 'ETH',
@@ -244,9 +310,9 @@ def markets():
'max': 500000,
},
},
'info': '',
'info': {},
},
{
'TKN/BTC': {
'id': 'tknbtc',
'symbol': 'TKN/BTC',
'base': 'TKN',
@@ -269,9 +335,9 @@ def markets():
'max': 500000,
},
},
'info': '',
'info': {},
},
{
'BLK/BTC': {
'id': 'blkbtc',
'symbol': 'BLK/BTC',
'base': 'BLK',
@@ -294,9 +360,9 @@ def markets():
'max': 500000,
},
},
'info': '',
'info': {},
},
{
'LTC/BTC': {
'id': 'ltcbtc',
'symbol': 'LTC/BTC',
'base': 'LTC',
@@ -319,9 +385,9 @@ def markets():
'max': 500000,
},
},
'info': '',
'info': {},
},
{
'XRP/BTC': {
'id': 'xrpbtc',
'symbol': 'XRP/BTC',
'base': 'XRP',
@@ -344,9 +410,9 @@ def markets():
'max': 500000,
},
},
'info': '',
'info': {},
},
{
'NEO/BTC': {
'id': 'neobtc',
'symbol': 'NEO/BTC',
'base': 'NEO',
@@ -369,9 +435,81 @@ def markets():
'max': 500000,
},
},
'info': '',
'info': {},
},
'BTT/BTC': {
'id': 'BTTBTC',
'symbol': 'BTT/BTC',
'base': 'BTT',
'quote': 'BTC',
'active': True,
'precision': {
'base': 8,
'quote': 8,
'amount': 0,
'price': 8
},
'limits': {
'amount': {
'min': 1.0,
'max': 90000000.0
},
'price': {
'min': None,
'max': None
},
'cost': {
'min': 0.001,
'max': None
}
},
'info': {},
},
'ETH/USDT': {
'id': 'USDT-ETH',
'symbol': 'ETH/USDT',
'base': 'ETH',
'quote': 'USDT',
'precision': {
'amount': 8,
'price': 8
},
'limits': {
'amount': {
'min': 0.02214286,
'max': None
},
'price': {
'min': 1e-08,
'max': None
}
},
'active': True,
'info': {},
},
'LTC/USDT': {
'id': 'USDT-LTC',
'symbol': 'LTC/USDT',
'base': 'LTC',
'quote': 'USDT',
'active': True,
'precision': {
'amount': 8,
'price': 8
},
'limits': {
'amount': {
'min': 0.06646786,
'max': None
},
'price': {
'min': 1e-08,
'max': None
}
},
'info': {},
}
}
])
@pytest.fixture
@@ -549,7 +687,7 @@ def ticker_history_list():
@pytest.fixture
def ticker_history(ticker_history_list):
return parse_ticker_dataframe(ticker_history_list, "5m", True)
return parse_ticker_dataframe(ticker_history_list, "5m", pair="UNITTEST/BTC", fill_missing=True)
@pytest.fixture
@@ -590,6 +728,7 @@ def tickers():
'vwap': 0.01869197,
'open': 0.018585,
'close': 0.018573,
'last': 0.018799,
'baseVolume': 81058.66,
'quoteVolume': 2247.48374509,
},
@@ -637,6 +776,28 @@ def tickers():
'quoteVolume': 1401.65697943,
'info': {}
},
'BTT/BTC': {
'symbol': 'BTT/BTC',
'timestamp': 1550936557206,
'datetime': '2019-02-23T15:42:37.206Z',
'high': 0.00000026,
'low': 0.00000024,
'bid': 0.00000024,
'bidVolume': 2446894197.0,
'ask': 0.00000025,
'askVolume': 2447913837.0,
'vwap': 0.00000025,
'open': 0.00000026,
'close': 0.00000024,
'last': 0.00000024,
'previousClose': 0.00000026,
'change': -0.00000002,
'percentage': -7.692,
'average': None,
'baseVolume': 4886464537.0,
'quoteVolume': 1215.14489611,
'info': {}
},
'ETH/USDT': {
'symbol': 'ETH/USDT',
'timestamp': 1522014804118,
@@ -730,8 +891,9 @@ def tickers():
@pytest.fixture
def result():
with open('freqtrade/tests/testdata/UNITTEST_BTC-1m.json') as data_file:
return parse_ticker_dataframe(json.load(data_file), '1m', True)
with Path('freqtrade/tests/testdata/UNITTEST_BTC-1m.json').open('r') as data_file:
return parse_ticker_dataframe(json.load(data_file), '1m', pair="UNITTEST/BTC",
fill_missing=True)
# FIX:
# Create an fixture/function
@@ -829,9 +991,10 @@ def buy_order_fee():
@pytest.fixture(scope="function")
def edge_conf(default_conf):
default_conf['max_open_trades'] = -1
default_conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
default_conf['edge'] = {
conf = deepcopy(default_conf)
conf['max_open_trades'] = -1
conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
conf['edge'] = {
"enabled": True,
"process_throttle_secs": 1800,
"calculate_since_number_of_days": 14,
@@ -847,4 +1010,40 @@ def edge_conf(default_conf):
"remove_pumps": False
}
return default_conf
return conf
@pytest.fixture
def rpc_balance():
return {
'BTC': {
'total': 12.0,
'free': 12.0,
'used': 0.0
},
'ETH': {
'total': 0.0,
'free': 0.0,
'used': 0.0
},
'USDT': {
'total': 10000.0,
'free': 10000.0,
'used': 0.0
},
'LTC': {
'total': 10.0,
'free': 10.0,
'used': 0.0
},
'XRP': {
'total': 1.0,
'free': 1.0,
'used': 0.0
},
'EUR': {
'total': 10.0,
'free': 10.0,
'used': 0.0
},
}

View File

@@ -0,0 +1,134 @@
from unittest.mock import MagicMock
import pytest
from arrow import Arrow
from pandas import DataFrame, to_datetime
from freqtrade.configuration import TimeRange
from freqtrade.data.btanalysis import (BT_DATA_COLUMNS,
combine_tickers_with_mean,
create_cum_profit,
extract_trades_of_period,
load_backtest_data, load_trades,
load_trades_from_db)
from freqtrade.data.history import (load_data, load_pair_history,
make_testdata_path)
from freqtrade.tests.test_persistence import create_mock_trades
def test_load_backtest_data():
filename = make_testdata_path(None) / "backtest-result_test.json"
bt_data = load_backtest_data(filename)
assert isinstance(bt_data, DataFrame)
assert list(bt_data.columns) == BT_DATA_COLUMNS + ["profitabs"]
assert len(bt_data) == 179
# Test loading from string (must yield same result)
bt_data2 = load_backtest_data(str(filename))
assert bt_data.equals(bt_data2)
with pytest.raises(ValueError, match=r"File .* does not exist\."):
load_backtest_data(str("filename") + "nofile")
@pytest.mark.usefixtures("init_persistence")
def test_load_trades_db(default_conf, fee, mocker):
create_mock_trades(fee)
# remove init so it does not init again
init_mock = mocker.patch('freqtrade.persistence.init', MagicMock())
trades = load_trades_from_db(db_url=default_conf['db_url'])
assert init_mock.call_count == 1
assert len(trades) == 3
assert isinstance(trades, DataFrame)
assert "pair" in trades.columns
assert "open_time" in trades.columns
assert "profitperc" in trades.columns
for col in BT_DATA_COLUMNS:
if col not in ['index', 'open_at_end']:
assert col in trades.columns
def test_extract_trades_of_period():
pair = "UNITTEST/BTC"
timerange = TimeRange(None, 'line', 0, -1000)
data = load_pair_history(pair=pair, ticker_interval='1m',
datadir=None, timerange=timerange)
# timerange = 2017-11-14 06:07 - 2017-11-14 22:58:00
trades = DataFrame(
{'pair': [pair, pair, pair, pair],
'profit_percent': [0.0, 0.1, -0.2, -0.5],
'profit_abs': [0.0, 1, -2, -5],
'open_time': to_datetime([Arrow(2017, 11, 13, 15, 40, 0).datetime,
Arrow(2017, 11, 14, 9, 41, 0).datetime,
Arrow(2017, 11, 14, 14, 20, 0).datetime,
Arrow(2017, 11, 15, 3, 40, 0).datetime,
], utc=True
),
'close_time': to_datetime([Arrow(2017, 11, 13, 16, 40, 0).datetime,
Arrow(2017, 11, 14, 10, 41, 0).datetime,
Arrow(2017, 11, 14, 15, 25, 0).datetime,
Arrow(2017, 11, 15, 3, 55, 0).datetime,
], utc=True)
})
trades1 = extract_trades_of_period(data, trades)
# First and last trade are dropped as they are out of range
assert len(trades1) == 2
assert trades1.iloc[0].open_time == Arrow(2017, 11, 14, 9, 41, 0).datetime
assert trades1.iloc[0].close_time == Arrow(2017, 11, 14, 10, 41, 0).datetime
assert trades1.iloc[-1].open_time == Arrow(2017, 11, 14, 14, 20, 0).datetime
assert trades1.iloc[-1].close_time == Arrow(2017, 11, 14, 15, 25, 0).datetime
def test_load_trades(default_conf, mocker):
db_mock = mocker.patch("freqtrade.data.btanalysis.load_trades_from_db", MagicMock())
bt_mock = mocker.patch("freqtrade.data.btanalysis.load_backtest_data", MagicMock())
default_conf['trade_source'] = "DB"
load_trades(default_conf)
assert db_mock.call_count == 1
assert bt_mock.call_count == 0
db_mock.reset_mock()
bt_mock.reset_mock()
default_conf['trade_source'] = "file"
default_conf['exportfilename'] = "testfile.json"
load_trades(default_conf)
assert db_mock.call_count == 0
assert bt_mock.call_count == 1
def test_combine_tickers_with_mean():
pairs = ["ETH/BTC", "XLM/BTC"]
tickers = load_data(datadir=None,
pairs=pairs,
ticker_interval='5m'
)
df = combine_tickers_with_mean(tickers)
assert isinstance(df, DataFrame)
assert "ETH/BTC" in df.columns
assert "XLM/BTC" in df.columns
assert "mean" in df.columns
def test_create_cum_profit():
filename = make_testdata_path(None) / "backtest-result_test.json"
bt_data = load_backtest_data(filename)
timerange = TimeRange.parse_timerange("20180110-20180112")
df = load_pair_history(pair="POWR/BTC", ticker_interval='5m',
datadir=None, timerange=timerange)
cum_profits = create_cum_profit(df.set_index('date'),
bt_data[bt_data["pair"] == 'POWR/BTC'],
"cum_profits")
assert "cum_profits" in cum_profits.columns
assert cum_profits.iloc[0]['cum_profits'] == 0
assert cum_profits.iloc[-1]['cum_profits'] == 0.0798005

View File

@@ -2,8 +2,7 @@
import logging
from freqtrade.data.converter import parse_ticker_dataframe, ohlcv_fill_up_missing_data
from freqtrade.data.history import load_pair_history
from freqtrade.optimize import validate_backtest_data, get_timeframe
from freqtrade.data.history import load_pair_history, validate_backtest_data, get_timeframe
from freqtrade.tests.conftest import log_has
@@ -16,9 +15,10 @@ def test_parse_ticker_dataframe(ticker_history_list, caplog):
caplog.set_level(logging.DEBUG)
# Test file with BV data
dataframe = parse_ticker_dataframe(ticker_history_list, '5m', fill_missing=True)
dataframe = parse_ticker_dataframe(ticker_history_list, '5m',
pair="UNITTEST/BTC", fill_missing=True)
assert dataframe.columns.tolist() == columns
assert log_has('Parsing tickerlist to dataframe', caplog.record_tuples)
assert log_has('Parsing tickerlist to dataframe', caplog)
def test_ohlcv_fill_up_missing_data(caplog):
@@ -28,18 +28,18 @@ def test_ohlcv_fill_up_missing_data(caplog):
pair='UNITTEST/BTC',
fill_up_missing=False)
caplog.set_level(logging.DEBUG)
data2 = ohlcv_fill_up_missing_data(data, '1m')
data2 = ohlcv_fill_up_missing_data(data, '1m', 'UNITTEST/BTC')
assert len(data2) > len(data)
# Column names should not change
assert (data.columns == data2.columns).all()
assert log_has(f"Missing data fillup: before: {len(data)} - after: {len(data2)}",
caplog.record_tuples)
assert log_has(f"Missing data fillup for UNITTEST/BTC: before: "
f"{len(data)} - after: {len(data2)}", caplog)
# Test fillup actually fixes invalid backtest data
min_date, max_date = get_timeframe({'UNITTEST/BTC': data})
assert validate_backtest_data({'UNITTEST/BTC': data}, min_date, max_date, 1)
assert not validate_backtest_data({'UNITTEST/BTC': data2}, min_date, max_date, 1)
assert validate_backtest_data(data, 'UNITTEST/BTC', min_date, max_date, 1)
assert not validate_backtest_data(data2, 'UNITTEST/BTC', min_date, max_date, 1)
def test_ohlcv_fill_up_missing_data2(caplog):
@@ -79,10 +79,10 @@ def test_ohlcv_fill_up_missing_data2(caplog):
]
# Generate test-data without filling missing
data = parse_ticker_dataframe(ticks, ticker_interval, fill_missing=False)
data = parse_ticker_dataframe(ticks, ticker_interval, pair="UNITTEST/BTC", fill_missing=False)
assert len(data) == 3
caplog.set_level(logging.DEBUG)
data2 = ohlcv_fill_up_missing_data(data, ticker_interval)
data2 = ohlcv_fill_up_missing_data(data, ticker_interval, "UNITTEST/BTC")
assert len(data2) == 4
# 3rd candle has been filled
row = data2.loc[2, :]
@@ -95,5 +95,54 @@ def test_ohlcv_fill_up_missing_data2(caplog):
# Column names should not change
assert (data.columns == data2.columns).all()
assert log_has(f"Missing data fillup: before: {len(data)} - after: {len(data2)}",
caplog.record_tuples)
assert log_has(f"Missing data fillup for UNITTEST/BTC: before: "
f"{len(data)} - after: {len(data2)}", caplog)
def test_ohlcv_drop_incomplete(caplog):
ticker_interval = '1d'
ticks = [[
1559750400000, # 2019-06-04
8.794e-05, # open
8.948e-05, # high
8.794e-05, # low
8.88e-05, # close
2255, # volume (in quote currency)
],
[
1559836800000, # 2019-06-05
8.88e-05,
8.942e-05,
8.88e-05,
8.893e-05,
9911,
],
[
1559923200000, # 2019-06-06
8.891e-05,
8.893e-05,
8.875e-05,
8.877e-05,
2251
],
[
1560009600000, # 2019-06-07
8.877e-05,
8.883e-05,
8.895e-05,
8.817e-05,
123551
]
]
caplog.set_level(logging.DEBUG)
data = parse_ticker_dataframe(ticks, ticker_interval, pair="UNITTEST/BTC",
fill_missing=False, drop_incomplete=False)
assert len(data) == 4
assert not log_has("Dropping last candle", caplog)
# Drop last candle
data = parse_ticker_dataframe(ticks, ticker_interval, pair="UNITTEST/BTC",
fill_missing=False, drop_incomplete=True)
assert len(data) == 3
assert log_has("Dropping last candle", caplog)

View File

@@ -9,39 +9,38 @@ from freqtrade.tests.conftest import get_patched_exchange
def test_ohlcv(mocker, default_conf, ticker_history):
default_conf["runmode"] = RunMode.DRY_RUN
tick_interval = default_conf["ticker_interval"]
ticker_interval = default_conf["ticker_interval"]
exchange = get_patched_exchange(mocker, default_conf)
exchange._klines[("XRP/BTC", tick_interval)] = ticker_history
exchange._klines[("UNITTEST/BTC", tick_interval)] = ticker_history
exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history
exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history
dp = DataProvider(default_conf, exchange)
assert dp.runmode == RunMode.DRY_RUN
assert ticker_history.equals(dp.ohlcv("UNITTEST/BTC", tick_interval))
assert isinstance(dp.ohlcv("UNITTEST/BTC", tick_interval), DataFrame)
assert dp.ohlcv("UNITTEST/BTC", tick_interval) is not ticker_history
assert dp.ohlcv("UNITTEST/BTC", tick_interval, copy=False) is ticker_history
assert not dp.ohlcv("UNITTEST/BTC", tick_interval).empty
assert dp.ohlcv("NONESENSE/AAA", tick_interval).empty
assert ticker_history.equals(dp.ohlcv("UNITTEST/BTC", ticker_interval))
assert isinstance(dp.ohlcv("UNITTEST/BTC", ticker_interval), DataFrame)
assert dp.ohlcv("UNITTEST/BTC", ticker_interval) is not ticker_history
assert dp.ohlcv("UNITTEST/BTC", ticker_interval, copy=False) is ticker_history
assert not dp.ohlcv("UNITTEST/BTC", ticker_interval).empty
assert dp.ohlcv("NONESENSE/AAA", ticker_interval).empty
# Test with and without parameter
assert dp.ohlcv("UNITTEST/BTC", tick_interval).equals(dp.ohlcv("UNITTEST/BTC"))
assert dp.ohlcv("UNITTEST/BTC", ticker_interval).equals(dp.ohlcv("UNITTEST/BTC"))
default_conf["runmode"] = RunMode.LIVE
dp = DataProvider(default_conf, exchange)
assert dp.runmode == RunMode.LIVE
assert isinstance(dp.ohlcv("UNITTEST/BTC", tick_interval), DataFrame)
assert isinstance(dp.ohlcv("UNITTEST/BTC", ticker_interval), DataFrame)
default_conf["runmode"] = RunMode.BACKTEST
dp = DataProvider(default_conf, exchange)
assert dp.runmode == RunMode.BACKTEST
assert dp.ohlcv("UNITTEST/BTC", tick_interval).empty
assert dp.ohlcv("UNITTEST/BTC", ticker_interval).empty
def test_historic_ohlcv(mocker, default_conf, ticker_history):
historymock = MagicMock(return_value=ticker_history)
mocker.patch("freqtrade.data.dataprovider.load_pair_history", historymock)
# exchange = get_patched_exchange(mocker, default_conf)
dp = DataProvider(default_conf, None)
data = dp.historic_ohlcv("UNITTEST/BTC", "5m")
assert isinstance(data, DataFrame)
@@ -51,18 +50,51 @@ def test_historic_ohlcv(mocker, default_conf, ticker_history):
assert historymock.call_args_list[0][1]["ticker_interval"] == "5m"
def test_get_pair_dataframe(mocker, default_conf, ticker_history):
default_conf["runmode"] = RunMode.DRY_RUN
ticker_interval = default_conf["ticker_interval"]
exchange = get_patched_exchange(mocker, default_conf)
exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history
exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history
dp = DataProvider(default_conf, exchange)
assert dp.runmode == RunMode.DRY_RUN
assert ticker_history.equals(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval))
assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame)
assert dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval) is not ticker_history
assert not dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval).empty
assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty
# Test with and without parameter
assert dp.get_pair_dataframe("UNITTEST/BTC",
ticker_interval).equals(dp.get_pair_dataframe("UNITTEST/BTC"))
default_conf["runmode"] = RunMode.LIVE
dp = DataProvider(default_conf, exchange)
assert dp.runmode == RunMode.LIVE
assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame)
assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty
historymock = MagicMock(return_value=ticker_history)
mocker.patch("freqtrade.data.dataprovider.load_pair_history", historymock)
default_conf["runmode"] = RunMode.BACKTEST
dp = DataProvider(default_conf, exchange)
assert dp.runmode == RunMode.BACKTEST
assert isinstance(dp.get_pair_dataframe("UNITTEST/BTC", ticker_interval), DataFrame)
# assert dp.get_pair_dataframe("NONESENSE/AAA", ticker_interval).empty
def test_available_pairs(mocker, default_conf, ticker_history):
exchange = get_patched_exchange(mocker, default_conf)
ticker_interval = default_conf["ticker_interval"]
exchange._klines[("XRP/BTC", ticker_interval)] = ticker_history
exchange._klines[("UNITTEST/BTC", ticker_interval)] = ticker_history
tick_interval = default_conf["ticker_interval"]
exchange._klines[("XRP/BTC", tick_interval)] = ticker_history
exchange._klines[("UNITTEST/BTC", tick_interval)] = ticker_history
dp = DataProvider(default_conf, exchange)
assert len(dp.available_pairs) == 2
assert dp.available_pairs == [
("XRP/BTC", tick_interval),
("UNITTEST/BTC", tick_interval),
("XRP/BTC", ticker_interval),
("UNITTEST/BTC", ticker_interval),
]
@@ -71,10 +103,10 @@ def test_refresh(mocker, default_conf, ticker_history):
mocker.patch("freqtrade.exchange.Exchange.refresh_latest_ohlcv", refresh_mock)
exchange = get_patched_exchange(mocker, default_conf, id="binance")
tick_interval = default_conf["ticker_interval"]
pairs = [("XRP/BTC", tick_interval), ("UNITTEST/BTC", tick_interval)]
ticker_interval = default_conf["ticker_interval"]
pairs = [("XRP/BTC", ticker_interval), ("UNITTEST/BTC", ticker_interval)]
pairs_non_trad = [("ETH/USDT", tick_interval), ("BTC/TUSD", "1h")]
pairs_non_trad = [("ETH/USDT", ticker_interval), ("BTC/TUSD", "1h")]
dp = DataProvider(default_conf, exchange)
dp.refresh(pairs)

View File

@@ -2,24 +2,27 @@
import json
import os
from pathlib import Path
import uuid
from pathlib import Path
from shutil import copyfile
from unittest.mock import MagicMock
import arrow
from pandas import DataFrame
import pytest
from pandas import DataFrame
from freqtrade import OperationalException
from freqtrade.arguments import TimeRange
from freqtrade.configuration import TimeRange
from freqtrade.data import history
from freqtrade.data.history import (download_pair_history,
load_cached_data_for_updating,
load_tickerdata_file,
make_testdata_path,
load_tickerdata_file, make_testdata_path,
trim_tickerlist)
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.misc import file_dump_json
from freqtrade.tests.conftest import get_patched_exchange, log_has
from freqtrade.strategy.default_strategy import DefaultStrategy
from freqtrade.tests.conftest import (get_patched_exchange, log_has,
patch_exchange)
# Change this if modifying UNITTEST/BTC testdatafile
_BTC_UNITTEST_LENGTH = 13681
@@ -59,7 +62,10 @@ def _clean_test_file(file: str) -> None:
def test_load_data_30min_ticker(mocker, caplog, default_conf) -> None:
ld = history.load_pair_history(pair='UNITTEST/BTC', ticker_interval='30m', datadir=None)
assert isinstance(ld, DataFrame)
assert not log_has('Download the pair: "UNITTEST/BTC", Interval: 30m', caplog.record_tuples)
assert not log_has(
'Download history data for pair: "UNITTEST/BTC", interval: 30m '
'and store in None.', caplog
)
def test_load_data_7min_ticker(mocker, caplog, default_conf) -> None:
@@ -67,17 +73,22 @@ def test_load_data_7min_ticker(mocker, caplog, default_conf) -> None:
assert not isinstance(ld, DataFrame)
assert ld is None
assert log_has(
'No data for pair: "UNITTEST/BTC", Interval: 7m. '
'Use --refresh-pairs-cached to download the data', caplog.record_tuples)
'No history data for pair: "UNITTEST/BTC", interval: 7m. '
'Use --refresh-pairs-cached option or `freqtrade download-data` '
'script to download the data', caplog
)
def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=ticker_history)
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ticker_history)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-1m.json')
_backup_file(file, copy_file=True)
history.load_data(datadir=None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
assert os.path.isfile(file) is True
assert not log_has('Download the pair: "UNITTEST/BTC", Interval: 1m', caplog.record_tuples)
assert not log_has(
'Download history data for pair: "UNITTEST/BTC", interval: 1m '
'and store in None.', caplog
)
_clean_test_file(file)
@@ -85,7 +96,7 @@ def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog, defau
"""
Test load_pair_history() with 1 min ticker
"""
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=ticker_history_list)
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ticker_history_list)
exchange = get_patched_exchange(mocker, default_conf)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
@@ -96,9 +107,11 @@ def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog, defau
refresh_pairs=False,
pair='MEME/BTC')
assert os.path.isfile(file) is False
assert log_has('No data for pair: "MEME/BTC", Interval: 1m. '
'Use --refresh-pairs-cached to download the data',
caplog.record_tuples)
assert log_has(
'No history data for pair: "MEME/BTC", interval: 1m. '
'Use --refresh-pairs-cached option or `freqtrade download-data` '
'script to download the data', caplog
)
# download a new pair if refresh_pairs is set
history.load_pair_history(datadir=None,
@@ -107,7 +120,10 @@ def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog, defau
exchange=exchange,
pair='MEME/BTC')
assert os.path.isfile(file) is True
assert log_has('Download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples)
assert log_has(
'Download history data for pair: "MEME/BTC", interval: 1m '
'and store in None.', caplog
)
with pytest.raises(OperationalException, match=r'Exchange needs to be initialized when.*'):
history.load_pair_history(datadir=None,
ticker_interval='1m',
@@ -117,6 +133,31 @@ def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog, defau
_clean_test_file(file)
def test_load_data_live(default_conf, mocker, caplog) -> None:
refresh_mock = MagicMock()
mocker.patch("freqtrade.exchange.Exchange.refresh_latest_ohlcv", refresh_mock)
exchange = get_patched_exchange(mocker, default_conf)
history.load_data(datadir=None, ticker_interval='5m',
pairs=['UNITTEST/BTC', 'UNITTEST2/BTC'],
live=True,
exchange=exchange)
assert refresh_mock.call_count == 1
assert len(refresh_mock.call_args_list[0][0][0]) == 2
assert log_has('Live: Downloading data for all defined pairs ...', caplog)
def test_load_data_live_noexchange(default_conf, mocker, caplog) -> None:
with pytest.raises(OperationalException,
match=r'Exchange needs to be initialized when using live data.'):
history.load_data(datadir=None, ticker_interval='5m',
pairs=['UNITTEST/BTC', 'UNITTEST2/BTC'],
exchange=None,
live=True,
)
def test_testdata_path() -> None:
assert str(Path('freqtrade') / 'tests' / 'testdata') in str(make_testdata_path(None))
@@ -137,16 +178,13 @@ def test_load_cached_data_for_updating(mocker) -> None:
# timeframe starts earlier than the cached data
# should fully update data
timerange = TimeRange('date', None, test_data[0][0] / 1000 - 1, 0)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
data, start_ts = load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
assert data == []
assert start_ts == test_data[0][0] - 1000
# same with 'line' timeframe
num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 120
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
data, start_ts = load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m',
TimeRange(None, 'line', 0, -num_lines))
assert data == []
assert start_ts < test_data[0][0] - 1
@@ -154,36 +192,29 @@ def test_load_cached_data_for_updating(mocker) -> None:
# timeframe starts in the center of the cached data
# should return the chached data w/o the last item
timerange = TimeRange('date', None, test_data[0][0] / 1000 + 1, 0)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
data, start_ts = load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
assert data == test_data[:-1]
assert test_data[-2][0] < start_ts < test_data[-1][0]
# same with 'line' timeframe
num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 30
timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
data, start_ts = load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
assert data == test_data[:-1]
assert test_data[-2][0] < start_ts < test_data[-1][0]
# timeframe starts after the chached data
# should return the chached data w/o the last item
timerange = TimeRange('date', None, test_data[-1][0] / 1000 + 1, 0)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
data, start_ts = load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
assert data == test_data[:-1]
assert test_data[-2][0] < start_ts < test_data[-1][0]
# same with 'line' timeframe
# Try loading last 30 lines.
# Not supported by load_cached_data_for_updating, we always need to get the full data.
num_lines = 30
timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
data, start_ts = load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
assert data == test_data[:-1]
assert test_data[-2][0] < start_ts < test_data[-1][0]
@@ -191,41 +222,33 @@ def test_load_cached_data_for_updating(mocker) -> None:
# should return the chached data w/o the last item
num_lines = 30
timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
data, start_ts = load_cached_data_for_updating(datadir, 'UNITTEST/BTC', '1m', timerange)
assert data == test_data[:-1]
assert test_data[-2][0] < start_ts < test_data[-1][0]
# no datafile exist
# should return timestamp start time
timerange = TimeRange('date', None, now_ts - 10000, 0)
data, start_ts = load_cached_data_for_updating(test_filename.with_name('unexist'),
'1m',
timerange)
data, start_ts = load_cached_data_for_updating(datadir, 'NONEXIST/BTC', '1m', timerange)
assert data == []
assert start_ts == (now_ts - 10000) * 1000
# same with 'line' timeframe
num_lines = 30
timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename.with_name('unexist'),
'1m',
timerange)
data, start_ts = load_cached_data_for_updating(datadir, 'NONEXIST/BTC', '1m', timerange)
assert data == []
assert start_ts == (now_ts - num_lines * 60) * 1000
# no datafile exist, no timeframe is set
# should return an empty array and None
data, start_ts = load_cached_data_for_updating(test_filename.with_name('unexist'),
'1m',
None)
data, start_ts = load_cached_data_for_updating(datadir, 'NONEXIST/BTC', '1m', None)
assert data == []
assert start_ts is None
def test_download_pair_history(ticker_history_list, mocker, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=ticker_history_list)
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=ticker_history_list)
exchange = get_patched_exchange(mocker, default_conf)
file1_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
file1_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-5m.json')
@@ -242,10 +265,10 @@ def test_download_pair_history(ticker_history_list, mocker, default_conf) -> Non
assert download_pair_history(datadir=None, exchange=exchange,
pair='MEME/BTC',
tick_interval='1m')
ticker_interval='1m')
assert download_pair_history(datadir=None, exchange=exchange,
pair='CFI/BTC',
tick_interval='1m')
ticker_interval='1m')
assert not exchange._pairs_last_refresh_time
assert os.path.isfile(file1_1) is True
assert os.path.isfile(file2_1) is True
@@ -259,10 +282,10 @@ def test_download_pair_history(ticker_history_list, mocker, default_conf) -> Non
assert download_pair_history(datadir=None, exchange=exchange,
pair='MEME/BTC',
tick_interval='5m')
ticker_interval='5m')
assert download_pair_history(datadir=None, exchange=exchange,
pair='CFI/BTC',
tick_interval='5m')
ticker_interval='5m')
assert not exchange._pairs_last_refresh_time
assert os.path.isfile(file1_5) is True
assert os.path.isfile(file2_5) is True
@@ -278,16 +301,16 @@ def test_download_pair_history2(mocker, default_conf) -> None:
[1509836580000, 0.00161, 0.00161, 0.00161, 0.00161, 82.390199]
]
json_dump_mock = mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=tick)
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv', return_value=tick)
exchange = get_patched_exchange(mocker, default_conf)
download_pair_history(None, exchange, pair="UNITTEST/BTC", tick_interval='1m')
download_pair_history(None, exchange, pair="UNITTEST/BTC", tick_interval='3m')
download_pair_history(None, exchange, pair="UNITTEST/BTC", ticker_interval='1m')
download_pair_history(None, exchange, pair="UNITTEST/BTC", ticker_interval='3m')
assert json_dump_mock.call_count == 2
def test_download_backtesting_data_exception(ticker_history, mocker, caplog, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_history',
side_effect=BaseException('File Error'))
mocker.patch('freqtrade.exchange.Exchange.get_historic_ohlcv',
side_effect=Exception('File Error'))
exchange = get_patched_exchange(mocker, default_conf)
@@ -298,11 +321,14 @@ def test_download_backtesting_data_exception(ticker_history, mocker, caplog, def
assert not download_pair_history(datadir=None, exchange=exchange,
pair='MEME/BTC',
tick_interval='1m')
ticker_interval='1m')
# clean files freshly downloaded
_clean_test_file(file1_1)
_clean_test_file(file1_5)
assert log_has('Failed to download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples)
assert log_has(
'Failed to download history data for pair: "MEME/BTC", interval: 1m. '
'Error: File Error', caplog
)
def test_load_tickerdata_file() -> None:
@@ -330,7 +356,7 @@ def test_load_partial_missing(caplog) -> None:
start_real = tickerdata['UNITTEST/BTC'].iloc[0, 0]
assert log_has(f'Missing data at start for pair '
f'UNITTEST/BTC, data starts at {start_real.strftime("%Y-%m-%d %H:%M:%S")}',
caplog.record_tuples)
caplog)
# Make sure we start fresh - test missing data at end
caplog.clear()
start = arrow.get('2018-01-10T00:00:00')
@@ -346,7 +372,7 @@ def test_load_partial_missing(caplog) -> None:
end_real = arrow.get(tickerdata['UNITTEST/BTC'].iloc[-1, 0]).shift(minutes=5)
assert log_has(f'Missing data at end for pair '
f'UNITTEST/BTC, data ends at {end_real.strftime("%Y-%m-%d %H:%M:%S")}',
caplog.record_tuples)
caplog)
def test_init(default_conf, mocker) -> None:
@@ -473,3 +499,62 @@ def test_file_dump_json_tofile() -> None:
# Remove the file
_clean_test_file(file)
def test_get_timeframe(default_conf, mocker) -> None:
patch_exchange(mocker)
strategy = DefaultStrategy(default_conf)
data = strategy.tickerdata_to_dataframe(
history.load_data(
datadir=None,
ticker_interval='1m',
pairs=['UNITTEST/BTC']
)
)
min_date, max_date = history.get_timeframe(data)
assert min_date.isoformat() == '2017-11-04T23:02:00+00:00'
assert max_date.isoformat() == '2017-11-14T22:58:00+00:00'
def test_validate_backtest_data_warn(default_conf, mocker, caplog) -> None:
patch_exchange(mocker)
strategy = DefaultStrategy(default_conf)
data = strategy.tickerdata_to_dataframe(
history.load_data(
datadir=None,
ticker_interval='1m',
pairs=['UNITTEST/BTC'],
fill_up_missing=False
)
)
min_date, max_date = history.get_timeframe(data)
caplog.clear()
assert history.validate_backtest_data(data['UNITTEST/BTC'], 'UNITTEST/BTC',
min_date, max_date, timeframe_to_minutes('1m'))
assert len(caplog.record_tuples) == 1
assert log_has(
"UNITTEST/BTC has missing frames: expected 14396, got 13680, that's 716 missing values",
caplog)
def test_validate_backtest_data(default_conf, mocker, caplog) -> None:
patch_exchange(mocker)
strategy = DefaultStrategy(default_conf)
timerange = TimeRange('index', 'index', 200, 250)
data = strategy.tickerdata_to_dataframe(
history.load_data(
datadir=None,
ticker_interval='5m',
pairs=['UNITTEST/BTC'],
timerange=timerange
)
)
min_date, max_date = history.get_timeframe(data)
caplog.clear()
assert not history.validate_backtest_data(data['UNITTEST/BTC'], 'UNITTEST/BTC',
min_date, max_date, timeframe_to_minutes('5m'))
assert len(caplog.record_tuples) == 0

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