Compare commits

..

1630 Commits

Author SHA1 Message Date
Matthias
21f6493b02 Merge pull request #1957 from freqtrade/new_release
New release - 2019.6
2019-06-26 06:05:43 +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
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
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
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
905beef8a3 Merge pull request #1578 from freqtrade/release/0.18.1
Release/0.18.1
2019-02-21 12:33:09 +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
af02e34b57 Version bump to 0.18.1 2019-02-20 21:04:52 +01:00
Matthias
4315c157c7 Move exception handling to resolver, add test 2019-02-20 20:13:23 +01:00
Matthias
9e082ca3a9 Merge pull request #1567 from gaugau3000/patch-1
prevent convert LF->CRLF line ending on window
2019-02-20 19:14:17 +01:00
gautier pialat
ba23f58ff3 change the way to clone git rep for Window with docker 2019-02-20 18:09:26 +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
Samuel Husso
d76dc3ca0e Merge pull request #1577 from freqtrade/pyup/scheduled-update-2019-02-20
Scheduled daily dependency update on Wednesday
2019-02-20 15:03:00 +02:00
hroff-1902
fac0e4e603 more code cleanup in _process() 2019-02-20 16:01:56 +03:00
pyup-bot
7dbb7a52ed Update coveralls from 1.5.1 to 1.6.0 2019-02-20 13:31:08 +01:00
pyup-bot
3ec3438acf Update pytest from 4.2.1 to 4.3.0 2019-02-20 13:31:07 +01:00
pyup-bot
1cd54829cc Update flake8 from 3.7.5 to 3.7.6 2019-02-20 13:31:06 +01:00
pyup-bot
bd6644a91a Update ccxt from 1.18.247 to 1.18.270 2019-02-20 13:31:05 +01: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
Misagh
ab62bbc0a4 Merge pull request #1572 from freqtrade/fix/coverage
Move coveralls to after_success
2019-02-20 09:06:04 +01: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
Matthias
58864adc4a Move coveralls to after_success 2019-02-19 19:54:53 +01:00
Matthias
788cbb6776 Merge pull request #1569 from hroff-1902/patch-8
Cosmetic: move default amount_reserve_percent value into constants
2019-02-19 19:19:54 +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
hroff-1902
f9d68d919c move default amount_reserve_percent value into constants 2019-02-19 11:49:49 +03:00
gautier pialat
dffb27326e prevent convert LF->CRLF line ending on window
During docker built on windows if in  global git config  core.autocrlf = true then when have this message :

Step 6/12 : RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib*
 ---> Running in c0a626821132
/tmp/install_ta-lib.sh: 4: /tmp/install_ta-lib.sh: Syntax error: "&&" unexpected

this behave is because file is in the wrong line ending format

Has script files run on linux they need to use LF lines ending so we need to ensure they are not converted to CRLF lines ending if global git config is at  core.autocrlf = true.
2019-02-18 17:16:27 +01:00
Misagh
6d7834a389 Merge pull request #1566 from freqtrade/pyup/scheduled-update-2019-02-18
Scheduled daily dependency update on Monday
2019-02-18 13:58:48 +01:00
pyup-bot
f63fdf411d Update ccxt from 1.18.245 to 1.18.247 2019-02-18 13:30:10 +01:00
Misagh
cd48556c5a Merge pull request #1562 from freqtrade/test_strategy
Fix typo in test-strategy, add volume > 0 check to buy/sell check
2019-02-18 08:39:49 +01: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
Matthias
9fad83bd15 Fix 1 to 0 2019-02-17 15:55:47 +01:00
iuvbio
d8feceebb5 fix type-hints 2019-02-17 15:54:22 +01:00
Matthias
19625e9e1d Merge pull request #1561 from freqtrade/pyup/scheduled-update-2019-02-17
Scheduled daily dependency update on Sunday
2019-02-17 15:42:09 +01:00
Matthias
f1ededf0eb Fix typo in test-strategy, add volume > 0 check to buy/sell check 2019-02-17 15:40:08 +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
pyup-bot
1bbb04da60 Update ccxt from 1.18.243 to 1.18.245 2019-02-17 13:30:07 +01:00
Matthias
e785a66768 Merge pull request #1560 from hroff-1902/patch-7
fetch amount_reserve_percent from the configuration file
2019-02-17 11:38:51 +01:00
hroff-1902
df8067d6c4 add description of the new configuration option amount_reserve_percent 2019-02-17 12:56:28 +03: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
hroff-1902
66cc600076 add amount_reserve_percent into the full config file 2019-02-16 19:53:35 +03:00
hroff-1902
ea6d4a9d36 fetch amount_reserve_percent from config 2019-02-16 19:50:55 +03:00
Matthias
e0c420b93f Merge pull request #1550 from hroff-1902/patch-2
execute_buy, handle_trade: do not use ticker if use_order_book:true is set in config
2019-02-16 13:55:33 +01:00
Matthias
67cea9dce6 Merge pull request #1558 from freqtrade/pyup/scheduled-update-2019-02-16
Scheduled daily dependency update on Saturday
2019-02-16 13:51:58 +01:00
pyup-bot
7c651632f1 Update joblib from 0.13.1 to 0.13.2 2019-02-16 13:32:06 +01:00
pyup-bot
13a16178d2 Update sqlalchemy from 1.2.17 to 1.2.18 2019-02-16 13:32:05 +01:00
pyup-bot
c2bc316e2f Update ccxt from 1.18.234 to 1.18.243 2019-02-16 13:32: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
Matthias
98bd713624 iUpdate orderbook_bid_test 2019-02-14 19:15:16 +01:00
Matthias
f852be1a9b Fix tests for get_ticker fix 2019-02-14 07:27:13 +01:00
Matthias
dcc86bfa55 Merge pull request #1552 from freqtrade/pyup/scheduled-update-2019-02-13
Scheduled daily dependency update on wednesday
2019-02-13 14:35:43 +01:00
pyup-bot
aee7b2c29d Update pytest from 4.2.0 to 4.2.1 2019-02-13 13:32:10 +01:00
pyup-bot
c17eb89e84 Update arrow from 0.13.0 to 0.13.1 2019-02-13 13:32:08 +01:00
pyup-bot
aaa8567708 Update ccxt from 1.18.230 to 1.18.234 2019-02-13 13:32:06 +01:00
Matthias
7e1e09d45a Merge pull request #1551 from hroff-1902/patch-4
logs: cosmetic changes
2019-02-13 12:39:12 +01:00
hroff-1902
b87e15774b test adjusted 2019-02-13 13:02:57 +03:00
hroff-1902
8e7e670003 Orthography in the log message 2019-02-13 12:42:39 +03:00
hroff-1902
8fc8c985d8 logs: cosmetic changes
"30 seconds" was errorneously hardcoded in the text of the log message, while actually it's RETRY_TIMEOUT which may differ
2019-02-13 12:38:44 +03:00
hroff-1902
69a24c1272 no need for test_ticker parameter just for making current tests happy, tests should be reimplemented 2019-02-13 12:23:22 +03:00
hroff-1902
e8daadfb7e same approach for the sell side (at handle_trade) 2019-02-13 03:54:57 +03:00
hroff-1902
91629807f7 shame on me 2019-02-13 03:17:54 +03:00
hroff-1902
f551fb5ff7 adjusted for passing tests
Don't like this test_ticker parameter, but it's needed for tests to pass prepared ticker.
Any ideas?
2019-02-13 03:14:24 +03:00
hroff-1902
e8ef36fb6e execute_buy: do not use ticker if use_order_book:true is set in config
This PR corresponds to:
https://github.com/freqtrade/freqtrade/issues/1377#issue-386200394
in understanfing that pair Ticker is mostly statistics, but on the other side, create_trade/execute_buy.

It resolves problem with some exchanges (BitMex) where ticker structure returned by ccxt does not contain bid and ask values.

1. On exchanges like Bitmex, set use_order_book: true for buys. FT won't request ticker and will use data from order book only.
2. On exchanges where order book is not available, set use_order_book: false, ticker data (including ask/last balance logic) will be used.
3. On other exchanges, either approach may be used in the config.

Performance: current implementation fetches ticker every time even if order book data will be later used. With this change it's eliminated.

Comparison of order book rate and ticker rate is removed (in order to split fetching order book and ticker completely in execute_buy), so some tests that touch this code may require adjustments.
2019-02-13 02:55:55 +03:00
Matthias
b9a5899c99 Merge pull request #1548 from freqtrade/pyup/scheduled-update-2019-02-12
Scheduled daily dependency update on tuesday
2019-02-12 14:50:30 +01:00
pyup-bot
607190cd38 Update ccxt from 1.18.225 to 1.18.230 2019-02-12 13:31:08 +01:00
Samuel Husso
018cee8413 Merge pull request #1547 from freqtrade/pyup/scheduled-update-2019-02-11
Scheduled daily dependency update on monday
2019-02-11 14:52:25 +02:00
pyup-bot
0b5f4dc38e Update python-rapidjson from 0.6.3 to 0.7.0 2019-02-11 13:31:09 +01:00
pyup-bot
160c467e01 Update ccxt from 1.18.223 to 1.18.225 2019-02-11 13:31:07 +01:00
Matthias
d91dbf4090 Merge pull request #1545 from hroff-1902/patch-3
Cosmetic: fix logging
2019-02-11 06:12:17 +01:00
hroff-1902
69f69d965c test asserts aligned accordingly to new log messages 2019-02-10 23:37:24 +03:00
Misagh
624ce6707a Merge pull request #1546 from hroff-1902/patch-4
OHLCV should be float for TA-LIB indicators in the strategy
2019-02-10 20:39:45 +01:00
hroff-1902
5e741a0f73 fixed flake 2019-02-10 22:28:40 +03:00
hroff-1902
d6c0c107ac fixed flake
hmm, even in the comments?
2019-02-10 22:23:00 +03:00
hroff-1902
7ed15c64ba what else? 2019-02-10 22:13:40 +03:00
hroff-1902
08d35f3e15 fix checks
Should not make cosmetic changes right in the github editor without local smoketests...
2019-02-10 22:09:11 +03:00
hroff-1902
4dffb17dd6 fix flake 2019-02-10 22:01:46 +03:00
hroff-1902
14d6cdf9b2 OHLCV should be float for TA-LIB indicators in the strategy
Some exchanges (BitMEX) return integer values for Volume field. And sometimes even for OHLC -- same, on BitMEX, since price decrease is 0.5. TA-LIB functions assume floats and fail with exception.
Of course, this can be fixed (converted) in ccxt for particular exchange, but TA-LIB will still fail for exchanges for that such a conversion is not implemented in ccxt code. So let's make perform this conversion here in order to be sure our strategy will not crash on a new exchange.
2019-02-10 21:52:33 +03:00
Matthias
7248537d4a Merge pull request #1544 from hroff-1902/patch-2
Cosmetic: fix logging in strategy/interface.py
2019-02-10 19:36:33 +01:00
hroff-1902
b91981f0aa Cosmetic: fix logging
Output stake_amount as it is defined in the config (it may by int) instead of float. In order to avoid unnecessary questions where and why it was converted to float and changed in the last digit while it should be integer for the exchange...

Other small cosmetic improvements to logging in freqtradebot.py
2019-02-10 21:31:13 +03:00
hroff-1902
585f525879 Cosmetic: fix logging 2019-02-10 21:02:53 +03:00
Samuel Husso
58bd272c0f Merge pull request #1543 from freqtrade/pyup/scheduled-update-2019-02-10
Scheduled daily dependency update on sunday
2019-02-10 14:49:25 +02:00
pyup-bot
fe2f98c802 Update ccxt from 1.18.221 to 1.18.223 2019-02-10 13:33:06 +01:00
Matthias
76187cc3d9 Merge pull request #1541 from freqtrade/pyup/scheduled-update-2019-02-09
Scheduled daily dependency update on saturday
2019-02-09 14:00:49 +01:00
pyup-bot
3bb9e17b0d Update plotly from 3.6.0 to 3.6.1 2019-02-09 13:33:11 +01:00
pyup-bot
f0c9064b77 Update mypy from 0.660 to 0.670 2019-02-09 13:33:09 +01:00
pyup-bot
e0142526e3 Update scipy from 1.2.0 to 1.2.1 2019-02-09 13:33:08 +01:00
pyup-bot
e9aba03981 Update ccxt from 1.18.210 to 1.18.221 2019-02-09 13:33:06 +01:00
Samuel Husso
9a50771a27 Merge pull request #1540 from freqtrade/pyup/scheduled-update-2019-02-08
Scheduled daily dependency update on friday
2019-02-08 14:52:56 +02:00
pyup-bot
92eb951966 Update ccxt from 1.18.208 to 1.18.210 2019-02-08 13:34:26 +01:00
Matthias
e6a8ecbf66 Merge pull request #1539 from freqtrade/pyup/scheduled-update-2019-02-07
Scheduled daily dependency update on thursday
2019-02-07 15:37:18 +01:00
pyup-bot
39626bb520 Update ccxt from 1.18.203 to 1.18.208 2019-02-07 13:34:09 +01:00
Samuel Husso
f48936dcde Merge pull request #1538 from freqtrade/pyup/scheduled-update-2019-02-06
Scheduled daily dependency update on wednesday
2019-02-06 15:29:06 +02:00
pyup-bot
395a7b25be Update ccxt from 1.18.197 to 1.18.203 2019-02-06 13:34:09 +01:00
Misagh
02698c7493 Merge pull request #1537 from freqtrade/pyup/scheduled-update-2019-02-05
Scheduled daily dependency update on tuesday
2019-02-05 13:55:09 +01:00
pyup-bot
6fd932bf7d Update pytest-mock from 1.10.0 to 1.10.1 2019-02-05 13:34:07 +01:00
pyup-bot
fcc7cb9892 Update flake8 from 3.7.4 to 3.7.5 2019-02-05 13:34:06 +01:00
Misagh
21ffdbb3a2 Merge pull request #1535 from freqtrade/fix/cancelled_on_exchange
Fix/cancelled on exchange
2019-02-04 22:47:58 +01:00
Misagh
f847bf0b8d Merge pull request #1536 from freqtrade/pyup/scheduled-update-2019-02-04
Scheduled daily dependency update on monday
2019-02-04 14:44:47 +01:00
pyup-bot
64891df122 Update pandas from 0.24.0 to 0.24.1 2019-02-04 13:34:12 +01:00
pyup-bot
218b501119 Update ccxt from 1.18.195 to 1.18.197 2019-02-04 13:34:10 +01:00
Matthias
0a71ebce68 Remove unneeded pair conversation, add docstring 2019-02-04 06:13:22 +01:00
Matthias
80440f25cf Merge pull request #1534 from freqtrade/pyup/scheduled-update-2019-02-03
Scheduled daily dependency update on sunday
2019-02-03 13:53:32 +01:00
Matthias
1d940041e3 Add log test for cancelled order 2019-02-03 13:52:05 +01:00
Matthias
40fea4593f Add log_has_re 2019-02-03 13:52:05 +01:00
Matthias
02c3552954 Adjust comment 2019-02-03 13:52:05 +01:00
Matthias
91ed02134e Add tests for cancelled on exchnage 2019-02-03 13:52:05 +01:00
Matthias
e8ed8a2ea7 Handle orders cancelled on exchange
fix #1527
2019-02-03 13:51:52 +01:00
pyup-bot
f2dd32e319 Update flake8-tidy-imports from 1.1.0 to 2.0.0 2019-02-03 13:34:07 +01:00
Samuel Husso
5243941c4d Merge pull request #1533 from freqtrade/pyup/scheduled-update-2019-02-02
Scheduled daily dependency update on saturday
2019-02-02 15:25:29 +02:00
pyup-bot
8d5474d4d5 Update plotly from 3.5.0 to 3.6.0 2019-02-02 13:34:08 +01:00
pyup-bot
576d893d95 Update ccxt from 1.18.193 to 1.18.195 2019-02-02 13:34:06 +01:00
Misagh
697e698abc Merge pull request #1530 from freqtrade/pyup/scheduled-update-2019-02-01
Scheduled daily dependency update on friday
2019-02-01 13:56:17 +01:00
pyup-bot
24f779eda7 Update flake8 from 3.7.3 to 3.7.4 2019-02-01 13:34:10 +01:00
pyup-bot
2ce3bd956d Update numpy from 1.16.0 to 1.16.1 2019-02-01 13:34:09 +01:00
pyup-bot
ba4e5cae54 Update ccxt from 1.18.190 to 1.18.193 2019-02-01 13:34:07 +01:00
Matthias
645ec30ec5 Merge pull request #1528 from freqtrade/pyup/scheduled-update-2019-01-31
Scheduled daily dependency update on thursday
2019-01-31 14:41:14 +01:00
pyup-bot
d07cc5929e Update pytest from 4.1.1 to 4.2.0 2019-01-31 13:34:11 +01:00
pyup-bot
8d9114aa79 Update flake8 from 3.7.1 to 3.7.3 2019-01-31 13:34:09 +01:00
pyup-bot
68a4e0426e Update ccxt from 1.18.179 to 1.18.190 2019-01-31 13:34:07 +01:00
Matthias
10e548dcab Merge pull request #1526 from freqtrade/pyup/scheduled-update-2019-01-30
Scheduled daily dependency update on wednesday
2019-01-31 06:58:57 +01:00
Samuel Husso
e3ae8d3f69 flake8 3.7.1 fixes 2019-01-31 07:51:03 +02:00
Samuel Husso
576d9b8f5c requirements: move numpy installation earlier as later packages require it 2019-01-31 07:50:13 +02:00
pyup-bot
0c959c22ec Update flake8 from 3.6.0 to 3.7.1 2019-01-30 13:33:10 +01:00
pyup-bot
6ad1089f45 Update cachetools from 3.0.0 to 3.1.0 2019-01-30 13:33:08 +01:00
pyup-bot
9f87a27465 Update ccxt from 1.18.171 to 1.18.179 2019-01-30 13:33:06 +01:00
Matthias
2b71e8de5c Merge pull request #1525 from mishaker/edge_messages
Edge messages enriched
2019-01-30 11:41:32 +01:00
misagh
84b6b8fe97 edge messages enriched 2019-01-30 11:23:23 +01:00
Misagh
421be5da86 Merge pull request #1511 from freqtrade/fix/more_settings_strategy
add more settings to strategy
2019-01-30 11:22:10 +01:00
Misagh
09cb043b24 Merge pull request #1522 from freqtrade/pyup/scheduled-update-2019-01-29
Scheduled daily dependency update on tuesday
2019-01-29 13:50:11 +01:00
pyup-bot
cf283344de Update ccxt from 1.18.160 to 1.18.171 2019-01-29 13:33:08 +01:00
Matthias
dcfa4d421e Merge pull request #1518 from freqtrade/pyup/scheduled-update-2019-01-28
Scheduled daily dependency update on monday
2019-01-28 14:41:23 +01:00
pyup-bot
1a1123a555 Update ccxt from 1.18.155 to 1.18.160 2019-01-28 13:33:07 +01:00
Misagh
382215eb70 Merge pull request #1517 from freqtrade/pyup/scheduled-update-2019-01-27
Scheduled daily dependency update on sunday
2019-01-27 13:49:36 +01:00
pyup-bot
f095492804 Update ccxt from 1.18.152 to 1.18.155 2019-01-27 13:33:07 +01:00
Misagh
22e82f5e47 Merge pull request #1507 from xmatthias/feat/dataprovider
Data Provider
2019-01-27 12:32:18 +01:00
Misagh
69ef743811 Merge pull request #1515 from freqtrade/pyup/scheduled-update-2019-01-26
Scheduled daily dependency update on saturday
2019-01-27 12:28:32 +01:00
Matthias
38f73dafb3 Fix indexing error 2019-01-27 10:47:24 +01:00
Matthias
e0ad095bc7 Simplify conversation to python dates (pandas offers this "for free" 2019-01-27 10:47:02 +01:00
Matthias
1d08ada939 Fix backtest-test with timestamp-conversion 2019-01-27 10:40:52 +01:00
Matthias
3446dd1792 Add test informative_pairs_added 2019-01-26 20:05:49 +01:00
Matthias
02d13645b0 Merge branch 'develop' into feat/dataprovider 2019-01-26 19:29:41 +01:00
Matthias
ba07348b82 Rename additional_pairs to informative_pairs 2019-01-26 19:22:45 +01:00
Matthias
bfd8609352 Fix comment 2019-01-26 19:16:33 +01:00
pyup-bot
b112f2f315 Update pandas from 0.23.4 to 0.24.0 2019-01-26 13:33:20 +01:00
pyup-bot
d222dd6717 Update sqlalchemy from 1.2.16 to 1.2.17 2019-01-26 13:33:18 +01:00
Matthias
3bc96c16ac Merge pull request #1513 from Axel-CH/feature/plot_df_refactoring_multiple_pairs
Feature/plot df refactoring multiple pairs
2019-01-26 11:07:17 +01:00
Matthias
da5210ef5b Merge branch 'develop' into feature/plot_df_refactoring_multiple_pairs 2019-01-26 11:04:35 +01:00
Matthias
e5b0224050 remove unused import 2019-01-26 11:00:12 +01:00
Axel Cherubin
e43aaaef9c add macd signal as default indicator2 2019-01-26 11:00:10 +01:00
Axel Cherubin
422a0ce114 better Path usage, remove arg parameter in generate_graph 2019-01-26 11:00:09 +01:00
Matthias
22e7ad8ec1 Change back to LF lineendings 2019-01-26 11:00:08 +01:00
pyup-bot
b840b9f53a Update ccxt from 1.18.144 to 1.18.146 2019-01-26 11:00:05 +01:00
AxelCh
eec7276393 fix crash when backtest-result.json not exist 2019-01-26 11:00:04 +01:00
Matthias
5e7ba85dbe Fix typo 2019-01-25 19:17:44 +01:00
Matthias
3c316fe3e4 Fix alignment 2019-01-25 19:14:38 +01:00
Matthias
56a3d78128 Fix typo 2019-01-25 19:14:29 +01:00
Misagh
ab8cc5f586 Merge pull request #1514 from freqtrade/pyup/scheduled-update-2019-01-25
Scheduled daily dependency update on friday
2019-01-25 13:58:36 +01:00
pyup-bot
bd24646822 Update tabulate from 0.8.2 to 0.8.3 2019-01-25 13:33:30 +01:00
pyup-bot
a97b3ab04a Update ccxt from 1.18.146 to 1.18.152 2019-01-25 13:33:29 +01:00
Matthias
3afe54790e Merge pull request #1510 from gianlup/add_totprofit_to_bt
Added total profit column to backtest result
2019-01-25 06:38:39 +01:00
Gianluca Puglia
38d293cc26 Updated doc 2019-01-24 15:24:38 +01:00
Samuel Husso
497a467864 Merge pull request #1512 from freqtrade/pyup/scheduled-update-2019-01-24
Scheduled daily dependency update on thursday
2019-01-24 14:50:52 +02:00
pyup-bot
dcceb40fab Update ccxt from 1.18.144 to 1.18.146 2019-01-24 13:33:07 +01:00
Matthias
9960fe07bc Add experimental settings to sample strategy 2019-01-24 07:08:21 +01:00
Matthias
74b03d0529 Add tests and default values for all experimental features 2019-01-24 07:03:41 +01:00
Matthias
ac199b626a Drop mandatory column 2019-01-24 07:03:25 +01:00
Matthias
8750f1be3f Add strategy-override options 2019-01-24 06:43:28 +01:00
Matthias
05d65b81da Fix typo 2019-01-23 21:05:07 +01:00
Matthias
d136cac181 Merge branch 'develop' into feat/dataprovider 2019-01-23 21:01:19 +01:00
Matthias
97f6a45819 Allow more settings to come from strategy 2019-01-23 20:59:41 +01:00
Matthias
ad8b1bbb79 Change default for positive_offset in sample 2019-01-23 20:57:31 +01:00
Matthias
5ea332e9be fix bug with trailing_stop_offset if it's disabled 2019-01-23 20:57:31 +01:00
AxelCh
06e0616fb0 Merge branch 'develop' of https://github.com/freqtrade/freqtrade into develop 2019-01-23 13:26:05 -04:00
Samuel Husso
372c5d813a Merge pull request #1508 from freqtrade/improve_strategy_docs
Add information about dataframe
2019-01-23 10:15:43 +02:00
Samuel Husso
fd94b322be Merge pull request #1509 from freqtrade/test/flake_mock
Add flake8 plugins
2019-01-23 09:06:24 +02:00
Gianluca Puglia
896c9d34fd Added total profit column do backtest result 2019-01-22 22:41:53 +01:00
Matthias
13e2f71d30 Add flake8 plugins and implement small improvements 2019-01-22 20:01:12 +01:00
Matthias
c412cd9e57 Add information about dataframe
fix #1192
2019-01-22 19:44:56 +01:00
Matthias
86a0863e30 Clarify logmessage Done fetching 2019-01-22 19:26:07 +01:00
Matthias
a06593e6e9 Fix test 2019-01-22 19:17:21 +01:00
Matthias
89ddfe08f4 Add additional-pairs (sample) to defaultstrategy 2019-01-22 19:17:08 +01:00
Samuel Husso
580e9ccaf3 Merge pull request #1506 from freqtrade/pyup/scheduled-update-2019-01-22
Scheduled daily dependency update on tuesday
2019-01-22 14:49:44 +02:00
pyup-bot
7c71b9513c Update ccxt from 1.18.141 to 1.18.144 2019-01-22 13:32:11 +01:00
Misagh
188c391444 Merge pull request #1504 from freqtrade/development_guide
Improve developer docs
2019-01-22 11:20:01 +01:00
Matthias
c77607b997 Fix tests after rebase 2019-01-22 07:38:15 +01:00
Matthias
3221f883d3 Wrap line correctly 2019-01-22 07:07:15 +01:00
Matthias
1e7431a7b8 Blackify 2019-01-22 07:07:15 +01:00
Matthias
e66808bb02 Add additional pairs to refresh call 2019-01-22 07:07:15 +01:00
Matthias
fc92491a47 Add documentation for additional_pairs 2019-01-22 07:07:15 +01:00
Matthias
6e2de75bcb Add additional_pairs to strategy 2019-01-22 07:07:15 +01:00
Matthias
d6cdfc58af Fix mypy hickup after changing list to tuples 2019-01-22 07:07:15 +01:00
Matthias
7b138ef3b4 Add warning about strategy/backtesting 2019-01-22 07:07:15 +01:00
Matthias
27b2021726 Only run once per pair 2019-01-22 07:07:15 +01:00
Matthias
e7800aa88a Import only what's necessary 2019-01-22 07:07:15 +01:00
Matthias
a2bc1da669 Remove private var from class instance
it's overwritten in __init__ anyway
2019-01-22 07:07:15 +01:00
Matthias
1e749a0f9b Rename variable to be clearer 2019-01-22 07:07:15 +01:00
Matthias
d7df5d5715 Keep last_pair_refresh as tuple asw ell 2019-01-22 07:07:15 +01:00
Matthias
6525a838d1 Adjust documentation to tuple use 2019-01-22 07:07:15 +01:00
Matthias
f0af4601f9 Adopt plot_dataframe to work with --live 2019-01-22 07:07:15 +01:00
Matthias
a9abc25785 Improve data-provider tests 2019-01-22 07:07:15 +01:00
Matthias
0aa0b1d4fe Store tickers by pair / ticker_interval 2019-01-22 07:07:15 +01:00
Matthias
5f61da30ed Adjust tests to 3tuple return value from async method 2019-01-22 07:06:30 +01:00
Matthias
d6df3e55c0 Return ticker_interval from async routine
used to identify calls in refresh_latest_ohlcv
2019-01-22 07:06:30 +01:00
Matthias
e503d811bd Change logmessages to match functions called 2019-01-22 07:05:09 +01:00
Matthias
b981cfcaa0 remove comment which proves untrue now 2019-01-22 07:05:09 +01:00
Matthias
a206777fe5 Rename refresh_tickers to refresh_latest_ohlcv 2019-01-22 07:05:09 +01:00
Matthias
06ec106079 simplify refresh_tickers 2019-01-22 07:04:19 +01:00
Matthias
646e98da55 Always return dataframe 2019-01-22 07:04:19 +01:00
Matthias
2b029b2a86 Only return ohlcv if available (Live and dry modes) 2019-01-22 07:04:19 +01:00
Matthias
9edb88051d Add dataprovider documentation 2019-01-22 07:04:19 +01:00
Matthias
35c8d1dcbe Update comment 2019-01-22 07:04:19 +01:00
Matthias
8f3ea3608a some cleanup 2019-01-22 07:04:19 +01:00
Matthias
5ecdecd1eb remove unused local variable persistance 2019-01-22 07:04:19 +01:00
Matthias
58f1abf287 Add dp / wallets to strategy interface 2019-01-22 07:04:19 +01:00
Matthias
d3a37db79a Provide available pairs 2019-01-22 07:04:19 +01:00
Matthias
f034235af4 Tests for RunMode 2019-01-22 07:04:19 +01:00
Matthias
1340b71633 Add RunMode setting to determine bot state 2019-01-22 07:04:19 +01:00
Matthias
fed3ebfb46 Change enum from 0 to 1 according to the documentation
see [here](https://docs.python.org/3/library/enum.html#functional-api)
2019-01-22 07:04:19 +01:00
Matthias
a7db4d74cb Add some simple dataprovider tests 2019-01-22 07:04:19 +01:00
Matthias
84cc4887ce Add copy parameter 2019-01-22 07:04:19 +01:00
Matthias
e38c06afe9 Small fixes 2019-01-22 07:04:19 +01:00
Matthias
f1a5a8e20e provide history 2019-01-22 07:04:19 +01:00
Matthias
4ab7edd3d6 small adaptations 2019-01-22 07:04:19 +01:00
Matthias
05570732c6 add get_runmode 2019-01-22 07:04:19 +01:00
Matthias
7206287b00 Use Dataprovider 2019-01-22 07:04:19 +01:00
Matthias
b119a767de Some more restructuring 2019-01-22 07:04:19 +01:00
Matthias
a6d74a1463 Draft of dataprovider 2019-01-22 07:04:19 +01:00
AxelCh
70881f12d2 Merge branch 'master' of https://github.com/freqtrade/freqtrade into develop 2019-01-21 22:08:59 -04:00
Matthias
1be3d57b60 Improve developer docs 2019-01-21 19:53:14 +01:00
Samuel Husso
07577ac18d Merge pull request #1503 from freqtrade/pyup/scheduled-update-2019-01-21
Scheduled daily dependency update on monday
2019-01-21 14:57:42 +02:00
pyup-bot
e4a399039b Update ccxt from 1.18.137 to 1.18.141 2019-01-21 13:32:07 +01:00
Misagh
34b617065d Merge pull request #1500 from freqtrade/fix/1491_typeerror
Fix typeerror when fetching candles
2019-01-20 16:13:02 +01:00
Matthias
2a16e9b6a0 Merge pull request #1501 from freqtrade/pyup/scheduled-update-2019-01-20
Scheduled daily dependency update on sunday
2019-01-20 15:57:54 +01:00
Matthias
8a3615dea3 Merge pull request #1502 from macd2/patch-2
JSON standard does not allow single quoted strings
2019-01-20 15:57:41 +01:00
macd2
6fb50e35c9 JSON standard does not allow single quoted strings 2019-01-20 14:21:42 +01:00
pyup-bot
a733630083 Update ccxt from 1.18.134 to 1.18.137 2019-01-20 13:32:07 +01:00
Matthias
b48430f922 Return list not None 2019-01-19 20:21:33 +01:00
Matthias
4e760e1a5e Test for errors found in 1491
fixes #1491
2019-01-19 20:03:04 +01:00
Matthias
30e3b52b1e catch errors found in #1491 2019-01-19 20:02:37 +01:00
Matthias
b52da0ad09 Merge pull request #1498 from Axel-CH/patch-1
fix stoploss_on_exchange_interval type
2019-01-19 19:10:11 +01:00
Axel CHERUBIN
797ac71376 fix stoploss_on_exchange_interval type
from boolean to number
2019-01-19 13:26:30 -04:00
Matthias
e8423d8155 Merge pull request #1496 from freqtrade/pyup/scheduled-update-2019-01-19
Scheduled daily dependency update on saturday
2019-01-19 13:57:46 +01:00
pyup-bot
b421e437ab Update wrapt from 1.11.0 to 1.11.1 2019-01-19 13:32:07 +01:00
pyup-bot
2b65e3f35c Update ccxt from 1.18.133 to 1.18.134 2019-01-19 13:32:06 +01:00
Matthias
c2578c7321 Merge pull request #1474 from mishaker/tsl_on_exchange
Making trailing stoploss compatible with stoploss on exchange
2019-01-18 19:29:39 +01:00
misagh
87329a393c adding stop loss last update to test persistence 2019-01-18 12:14:00 +01:00
misagh
a2618208ef wrapping in parantheses instead of line breaks 2019-01-18 12:07:51 +01:00
misagh
70780bb01e using dict.get to fetch interval 2019-01-18 12:02:29 +01:00
misagh
89eddfd349 refactoring english … 2019-01-18 12:00:53 +01:00
misagh
1c4ee35eca using italic for “off exchange” 2019-01-18 12:00:02 +01:00
misagh
e41e45413f adding tailing_stop to docs 2019-01-18 11:58:23 +01:00
Matthias
c7ebd8228e Merge pull request #1495 from freqtrade/fix_mypy_issue
Fix mypy issue 2
2019-01-17 20:48:55 +01:00
Matthias
cc6466388e update mypy 2019-01-17 20:31:00 +01:00
Matthias
27d907e71b Merge pull request #1493 from freqtrade/pyup/scheduled-update-2019-01-17
Scheduled daily dependency update on thursday
2019-01-17 20:30:45 +01:00
Matthias
a2c01916e1 Add type-ignores to floatfmt
tabulate supports this:
30554300d7/tabulate.py (tabulate.py-1291):1294
2019-01-17 20:28:21 +01:00
Misagh
357c28d5ea Merge pull request #1494 from freqtrade/fix_mypy_issue
Remove unversioned install of most dev packages
2019-01-17 19:38:05 +01:00
Matthias
648def69ca Remove unversioned install of most dev packages 2019-01-17 19:05:28 +01:00
pyup-bot
97a8341436 Update ccxt from 1.18.131 to 1.18.133 2019-01-17 13:32:07 +01:00
Matthias
30cdf85ffa Merge pull request #1492 from mishaker/patch_unreachable
unreachable code removed
2019-01-16 21:34:50 +01:00
misagh
75cedfafb8 unreachable code removed 2019-01-16 20:03:34 +01:00
misagh
9d6c54791b added note for only binance 2019-01-16 19:23:55 +01:00
misagh
e682eceae4 stop loss documentation added 2019-01-16 19:22:06 +01:00
misagh
2533112254 added referral to stop loss documentation 2019-01-16 19:12:52 +01:00
misagh
08d98773f3 added SL interval to configuration document 2019-01-16 19:10:13 +01:00
misagh
da51ef40f8 SL interval added to CONF_SCHEMA 2019-01-16 19:04:43 +01:00
misagh
91c714c7d1 stoploss_on_exchange_interval added to full config 2019-01-16 18:58:12 +01:00
misagh
5e2e96acd2 compatibility with edge added 2019-01-16 18:38:20 +01:00
misagh
611b48dbb9 fix return value from info hash: value is in string 2019-01-16 16:15:36 +01:00
misagh
50bc20134f adding whitespace 2019-01-16 15:17:28 +01:00
misagh
baa5cc5b9e logs enriched 2019-01-16 15:10:31 +01:00
misagh
aa03a864f7 comments added for TSL on exchange function 2019-01-16 15:00:35 +01:00
misagh
a44f781284 Merge branch 'develop' into tsl_on_exchange 2019-01-16 14:56:27 +01:00
Misagh
d108138999 Merge pull request #1486 from freqtrade/fix/roi_problem
Fix ROI problem
2019-01-16 14:51:27 +01:00
misagh
cffc9ce890 last step: stop loss on exchange added to trailing 2019-01-16 14:49:47 +01:00
misagh
6d588b3b0b trailing stop loss on exchange extracted to a separate function 2019-01-16 14:28:52 +01:00
misagh
bfb7121583 refactoring handle_stoploss_on_exchange 2019-01-16 12:16:32 +01:00
misagh
e31fa8721f Merge branch 'develop' into tsl_on_exchange 2019-01-16 11:52:23 +01:00
misagh
29439c05d6 adding update beat test 2019-01-16 11:51:54 +01:00
misagh
1cd5abde37 removing unnecessary guard 2019-01-16 11:22:25 +01:00
misagh
12e8108015 checking params of cancel order and stop loss order 2019-01-15 15:36:41 +01:00
Misagh
07b4afedf7 Merge pull request #1490 from gaugau3000/patch-1
Win rate formula was wrong in edge doc (but not the definition)
2019-01-15 15:16:58 +01:00
gautier pialat
399d2d89a3 Win rate formula was wrong (but not the definition) 2019-01-15 15:02:01 +01:00
Misagh
22d3881b6e Merge pull request #1489 from freqtrade/pyup/scheduled-update-2019-01-15
Scheduled daily dependency update on tuesday
2019-01-15 13:45:27 +01:00
pyup-bot
494b905d1e Update ccxt from 1.18.126 to 1.18.131 2019-01-15 13:32:08 +01:00
misagh
f0cfab7940 flaking 8 2019-01-15 11:10:28 +01:00
misagh
cfe00c2f0c initial test added for TSL on exchange 2019-01-15 11:04:32 +01:00
Misagh
678162fada Merge pull request #1488 from freqtrade/pyup/scheduled-update-2019-01-14
Scheduled daily dependency update on monday
2019-01-14 14:06:53 +01:00
pyup-bot
da04182287 Update numpy from 1.15.4 to 1.16.0 2019-01-14 13:32:08 +01:00
pyup-bot
a3897c990d Update ccxt from 1.18.120 to 1.18.126 2019-01-14 13:32:07 +01:00
Misagh
929995117f Merge pull request #1487 from freqtrade/pyup/scheduled-update-2019-01-13
Scheduled daily dependency update on sunday
2019-01-13 14:51:14 +01:00
pyup-bot
04786f09f4 Update pytest from 4.1.0 to 4.1.1 2019-01-13 13:32:06 +01:00
Matthias
9b97e1e8fb Merge pull request #1485 from freqtrade/pyup/scheduled-update-2019-01-12
Scheduled daily dependency update on saturday
2019-01-12 13:49:39 +01:00
Matthias
cd2bccd441 Have backtest use the same logic to get the ROI entry 2019-01-12 13:45:43 +01:00
Matthias
e9d61eb35d Fix ROI calculation problem
Prior to that all ROI entries with a key > trade_duration where active.
This causes a problem if the ROI is not linearly declining
2019-01-12 13:45:03 +01:00
Matthias
9e0902e72f Add test for case for odd ROI dict - #1478 2019-01-12 13:38:49 +01:00
pyup-bot
690fbeb907 Update joblib from 0.13.0 to 0.13.1 2019-01-12 13:32:08 +01:00
pyup-bot
e95351fd04 Update sqlalchemy from 1.2.15 to 1.2.16 2019-01-12 13:32:06 +01:00
Matthias
a095ccd1d6 Merge pull request #1484 from freqtrade/pyup/scheduled-update-2019-01-11
Scheduled daily dependency update on friday
2019-01-12 09:30:15 +01:00
pyup-bot
3867f73c8c Update ccxt from 1.18.119 to 1.18.120 2019-01-11 13:32:07 +01:00
Samuel Husso
d8f2d868c1 Merge pull request #1483 from freqtrade/pyup/scheduled-update-2019-01-10
Scheduled daily dependency update on thursday
2019-01-10 15:01:10 +02:00
pyup-bot
4920ee3455 Update wrapt from 1.10.11 to 1.11.0 2019-01-10 13:32:09 +01:00
pyup-bot
3f8092192e Update ccxt from 1.18.117 to 1.18.119 2019-01-10 13:32:07 +01:00
misagh
e025ad3918 temp test commit 2019-01-09 16:23:13 +01:00
Matthias
5ac5b18e6d Merge pull request #1481 from freqtrade/pyup/scheduled-update-2019-01-09
Scheduled daily dependency update on wednesday
2019-01-09 14:02:39 +01:00
pyup-bot
c1007f95b3 Update ccxt from 1.18.114 to 1.18.117 2019-01-09 13:32:07 +01:00
Misagh
4d52301ee6 Merge pull request #1479 from freqtrade/feat/improve_travis
Fix travis failures
2019-01-09 12:21:34 +01:00
Matthias
a494755449 Update .travis.yml 2019-01-09 08:17:03 +01:00
Matthias
2e530a3e03 Update install_ta-lib.sh 2019-01-09 08:16:33 +01:00
Matthias
e76ed31b08 fix ta-lib cache 2019-01-09 08:15:36 +01:00
Matthias
f4979e0e8a Cache /usr/loca/lib 2019-01-09 06:27:58 +01:00
Matthias
dd7d655a63 remove unwriteable cache-dir 2019-01-08 21:30:11 +01:00
Matthias
f9a99f4ad3 Change caching 2019-01-08 21:27:50 +01:00
Matthias
99e2d795c5 Merge pull request #1475 from freqtrade/feat/hyperopt_sell
Feat/hyperopt sell
2019-01-08 21:19:53 +01:00
Matthias
7a13565efb travis - python - test 2019-01-08 21:13:56 +01:00
Matthias
bb3d78757d Test python 3.7 2019-01-08 21:11:36 +01:00
Matthias
356a17cdaa Build python 3.7 2019-01-08 21:07:08 +01:00
Matthias
da436c920f switch travis to xenial 2019-01-08 20:58:14 +01:00
Matthias
69eed95a54 cd out of the build-helpers dir 2019-01-08 20:39:50 +01:00
Matthias
df97652f6e Try fix ta-lib install 2019-01-08 20:35:08 +01:00
Matthias
64372ea6fb Fix ta-lib installation build helpers 2019-01-08 20:12:39 +01:00
Matthias
b3f67bb8c6 Fix git pull in docker-image 2019-01-08 20:09:32 +01:00
Matthias
d29c294f6a Merge pull request #1477 from freqtrade/pyup/scheduled-update-2019-01-08
Scheduled daily dependency update on tuesday
2019-01-08 19:04:43 +01:00
Matthias
b5adfcf51a Fix documentation typo 2019-01-08 17:07:32 +01:00
misagh
1a27258469 condition fixed 2019-01-08 16:34:23 +01:00
misagh
9e133eb32e adding guard not to cancel the previous stop loss on exchange if market
is falling quickly
2019-01-08 16:31:02 +01:00
misagh
f4ceeca438 Merge branch 'develop' into tsl_on_exchange 2019-01-08 15:33:32 +01:00
misagh
aed855284c comparing with stopPrice instead of price 2019-01-08 13:44:51 +01:00
pyup-bot
f4e0e04462 Update pytest-cov from 2.6.0 to 2.6.1 2019-01-08 13:33:13 +01:00
pyup-bot
4069e2fdfb Update pytest-asyncio from 0.9.0 to 0.10.0 2019-01-08 13:33:11 +01:00
pyup-bot
ec22512fd9 Update pytest from 4.0.2 to 4.1.0 2019-01-08 13:33:10 +01:00
pyup-bot
c3107272d3 Update arrow from 0.12.1 to 0.13.0 2019-01-08 13:33:08 +01:00
pyup-bot
b98526d32c Update ccxt from 1.18.102 to 1.18.114 2019-01-08 13:33:06 +01:00
misagh
4fbb9d4462 adding stoploss_on_exchange_interval to order_types dict. default to 1
minute (60)
2019-01-08 12:39:53 +01:00
misagh
16472535eb adding stoploss_last_update to persistence 2019-01-08 12:39:10 +01:00
Matthias
f620449bec Add test for hyperoptresolver 2019-01-06 19:38:32 +01:00
Matthias
440a7ec9c2 fix pytest 2019-01-06 19:31:25 +01:00
Matthias
40b1d8f067 Fix CI problems 2019-01-06 14:57:14 +01:00
Matthias
5dd1f9b38a improve hyperopt docs 2019-01-06 14:47:53 +01:00
Matthias
dd2af86a41 pprint results 2019-01-06 14:47:38 +01:00
misagh
821e299afb adjusting trailing stoploss on exchange 2019-01-06 14:45:29 +01:00
Matthias
85bca58905 Merge pull request #1473 from gianlup/fix_dburl
Fix custom db_url ignored if provided by conf.json
2019-01-06 14:20:55 +01:00
Matthias
f5fc9e69cf Merge pull request #1472 from freqtrade/pyup/scheduled-update-2019-01-06
Scheduled daily dependency update on sunday
2019-01-06 14:19:24 +01:00
Matthias
167088827a include default buy/sell trends for the hyperopt strategy 2019-01-06 14:13:15 +01:00
Matthias
a0df7b9d7c Use sell/buy trends from hyperopt file if available 2019-01-06 14:12:55 +01:00
Gianluca Puglia
87cbff5d0e Fix warning for max_open_trades when edge is enabled 2019-01-06 13:48:27 +01:00
Gianluca Puglia
13800701ce Fix custom db_url ignored if provided by conf.json 2019-01-06 13:47:36 +01:00
pyup-bot
3f82dd05aa Update ccxt from 1.18.101 to 1.18.102 2019-01-06 13:33:06 +01:00
Matthias
2147bd8847 Fix problem when no experimental dict is available 2019-01-06 13:29:14 +01:00
Matthias
798ae460d8 Add check if trigger is in parameters 2019-01-06 13:29:14 +01:00
Matthias
5e08769366 Update hyperopt documentation with sell-stuff 2019-01-06 13:29:14 +01:00
Matthias
68ba1e1f37 Add sell signal hyperopt 2019-01-06 13:29:14 +01:00
Matthias
b731973c7a Merge pull request #1471 from freqtrade/setup_text
fixes few wordings
2019-01-06 12:53:04 +01:00
Misagh
a07353d3c7 fixes few wordings 2019-01-06 12:02:57 +01:00
Misagh
86023744d9 Merge pull request #1470 from freqtrade/fix/installscript
fix and improve installation script
2019-01-06 11:55:43 +01:00
Misagh
e91be7aff9 Merge pull request #1468 from freqtrade/update_dockerfile
Update dockerfile to python 3.7.2
2019-01-06 11:43:03 +01:00
Misagh
5af656d3ba Merge pull request #1469 from freqtrade/improve_doc_structure
fix typo, improve doc sequence
2019-01-06 11:42:35 +01:00
Misagh
4a0bc8937d Merge pull request #1466 from freqtrade/feat/strategy_trailing
trailing stoploss configuration in strategy
2019-01-06 11:40:33 +01:00
Matthias
506237e3b4 Don't use --quiet on pip install
this hides errors from users and complicates debugging in case of
problmes
2019-01-05 20:06:15 +01:00
Matthias
f088f43b40 Install numpy before py_find_1st 2019-01-05 20:04:54 +01:00
Matthias
01e2dc17b5 Remove whitespace in fucntion definition 2019-01-05 20:00:10 +01:00
Matthias
337ebdeccb Avoid installing ta-lib multiple times 2019-01-05 19:51:51 +01:00
Matthias
9e5e485d0a put --upgrade flag to the same location in subsequent requests 2019-01-05 19:51:28 +01:00
Matthias
31da42a485 Show error when no python is found 2019-01-05 19:50:46 +01:00
Matthias
94aa1aaff3 fix typo, improve doc sequence 2019-01-05 19:34:45 +01:00
Matthias
dbf8ec6a20 Update dockerfile to python 3.7.2 2019-01-05 19:24:07 +01:00
Samuel Husso
c8d40e81f0 Merge pull request #1467 from freqtrade/pyup/scheduled-update-2019-01-05
Scheduled daily dependency update on saturday
2019-01-05 15:43:12 +02:00
pyup-bot
16512d9918 Update plotly from 3.4.2 to 3.5.0 2019-01-05 13:33:07 +01:00
pyup-bot
8505ffbe78 Update ccxt from 1.18.97 to 1.18.101 2019-01-05 13:33:06 +01:00
Matthias
f7b96d839d Add trailing options to sample-strategy 2019-01-05 09:03:26 +01:00
Matthias
f32232ba96 Add documentation for stoploss in strategy 2019-01-05 07:32:35 +01:00
Matthias
cacb9ef3ad Loop twice 2019-01-05 07:25:35 +01:00
Matthias
00c5ac56d4 Print startup strategy summary 2019-01-05 07:24:15 +01:00
Matthias
a7dc6b18aa Overridable attributs as list 2019-01-05 07:22:19 +01:00
Matthias
5e23442032 Simplify StrategyResolver by code deduplication 2019-01-05 07:20:38 +01:00
Matthias
4599c80e79 Add trailing-stop to strategy 2019-01-05 07:10:25 +01:00
Misagh
29db2078d6 Merge pull request #1463 from mishaker/readme
Readme refactoring.
2019-01-04 23:00:36 +01:00
misagh
67cbd5d77f putting back requirements 2019-01-04 22:39:44 +01:00
Misagh
26a77e193e Merge pull request #1454 from freqtrade/feat/interpolate_missing
interpolate missing candles
2019-01-04 22:33:53 +01:00
Matthias
55235ce20d Merge pull request #1464 from freqtrade/pyup/scheduled-update-2019-01-04
Scheduled daily dependency update on friday
2019-01-04 14:10:42 +01:00
pyup-bot
a5ec564fc3 Update ccxt from 1.18.95 to 1.18.97 2019-01-04 13:33:09 +01:00
misagh
f1bb4233c9 telegram documentation link 2019-01-03 14:40:27 +01:00
misagh
afffa2f313 changed to “Free, open source …” 2019-01-03 14:38:38 +01:00
Misagh
221bca0aaa Merge pull request #1462 from freqtrade/pyup/scheduled-update-2019-01-03
Scheduled daily dependency update on thursday
2019-01-03 14:02:03 +01:00
pyup-bot
de278a77d7 Update ccxt from 1.18.94 to 1.18.95 2019-01-03 13:33:08 +01:00
Matthias
56924e6909 Merge pull request #1461 from mishaker/fix_talib
cd build_helpers added before extracting Tar
2019-01-02 19:31:37 +01:00
misagh
2c31fd662c cd build_helpers added before extracting Tar 2019-01-02 19:26:58 +01:00
Misagh
3b5785884f Merge pull request #1460 from freqtrade/fix/stop_loss_result
Stop loss should also be shown when trailing is active
2019-01-02 16:41:15 +01:00
Matthias
3329ffd071 improve comment 2019-01-02 14:44:17 +01:00
misagh
05ce7787d6 readme enriched 2019-01-02 14:41:27 +01:00
misagh
68a9d1b2b8 description 2019-01-02 14:21:02 +01:00
misagh
207daf084e change description 2019-01-02 14:18:34 +01:00
Misagh
dcdd7d7436 Merge pull request #1456 from freqtrade/fix/refresh_pairs_cached
Fix bug on --refresh-pairs-cached
2019-01-02 14:02:11 +01:00
Misagh
138de389e2 Merge pull request #1459 from freqtrade/pyup/scheduled-update-2019-01-02
Scheduled daily dependency update on wednesday
2019-01-02 13:50:00 +01:00
Matthias
41a4621caf Merge pull request #1458 from freqtrade/readthedoc-badge
Readthedoc badge
2019-01-02 13:38:02 +01:00
Matthias
516217b6cb Stop loss should also be shown when trailing is active 2019-01-02 13:34:08 +01:00
pyup-bot
6cc6ce359b Update ccxt from 1.18.89 to 1.18.94 2019-01-02 13:34:06 +01:00
Misagh
848af3755e Update README.md 2019-01-02 13:33:33 +01:00
Misagh
71eba2afba Merge pull request #1457 from freqtrade/fix/minroi
minroi sequence of keys should not matter
2019-01-02 12:32:13 +01:00
Matthias
1b84aa82eb dont use 55 for regular check as that's a key in the dict 2019-01-01 16:54:44 +01:00
Matthias
2bc76771bf Align backtest to interface.py
interface.py roi calculation skips on <= duration
the correct selection is therefore trade_duration > x.
2019-01-01 16:50:10 +01:00
Matthias
1d518885a9 fix roi-reached when list is unsorted 2019-01-01 16:45:52 +01:00
Matthias
b55994cb71 Clarify documentation 2019-01-01 16:33:02 +01:00
Matthias
da6f1a3945 Sequence of minroi dict must be irrelevant 2019-01-01 16:32:45 +01:00
Matthias
2b5e02fae8 Merge pull request #1452 from mishaker/doc_interface
Documentation structure revised and content beautifulized.
2019-01-01 14:13:37 +01:00
Matthias
e3cf838bc6 Merge pull request #1455 from freqtrade/pyup/scheduled-update-2019-01-01
Scheduled daily dependency update on tuesday
2019-01-01 14:08:50 +01:00
Matthias
a54d8f0e16 Create datadir when not exists 2019-01-01 14:07:40 +01:00
Matthias
c337a931c2 Fix bug on --refresh-pairs-cached 2019-01-01 13:42:30 +01:00
pyup-bot
a909322f60 Update ccxt from 1.18.87 to 1.18.89 2019-01-01 13:34:06 +01:00
Matthias
672d115eca Change default value and add docstring 2018-12-31 19:42:14 +01:00
Matthias
dd1d3430b9 Add explicit test for ohlcv fillup 2018-12-31 19:40:14 +01:00
Matthias
fae875f588 Implement missing_data_fillup to tests and operations 2018-12-31 19:15:49 +01:00
Matthias
ef4555735a Fill up missing as part of loading data 2018-12-31 19:13:34 +01:00
Matthias
8b9cc45f41 move test for data completeness
should be done before analyzing strategy
2018-12-31 15:09:50 +01:00
Misagh
0824db03e7 Merge pull request #1453 from freqtrade/pyup/scheduled-update-2018-12-31
Scheduled daily dependency update on monday
2018-12-31 14:35:03 +01:00
misagh
7b1f4aec76 typo 2018-12-31 14:01:55 +01:00
misagh
827a8309d7 more links corrected 2018-12-31 14:00:36 +01:00
misagh
366980fd62 broken link corrected 2018-12-31 13:39:18 +01:00
misagh
42cc3e525e link second try 2018-12-31 13:36:27 +01:00
pyup-bot
5be21fd9d8 Update ccxt from 1.18.86 to 1.18.87 2018-12-31 13:34:07 +01:00
misagh
361b294e43 block “==“ removed 2018-12-31 13:27:59 +01:00
misagh
dd91b5c731 links corrected 2018-12-31 13:26:25 +01:00
misagh
a07a004bb6 test relative link 2018-12-31 13:19:00 +01:00
misagh
a86b34e41c old files removed 2018-12-31 11:52:09 +01:00
misagh
7bf1a92dc3 “inofficial” => unofficial 2018-12-31 11:12:56 +01:00
misagh
db1c9b8edf relative links 2018-12-31 11:04:22 +01:00
misagh
79ac20636f typo + broken link 2018-12-31 10:49:14 +01:00
misagh
04483da8df typo corrected 2018-12-31 10:31:28 +01:00
Matthias
d409211908 add test to verify data does not contain missing data afterwards 2018-12-31 09:24:04 +01:00
Matthias
03389d961f ADd test for data_interpolate 2018-12-31 09:18:22 +01:00
Matthias
a021cd3ae2 Add ohlcv data interpolator 2018-12-31 07:12:54 +01:00
misagh
be1969adc8 added widgets to index documents 2018-12-30 18:22:28 +01:00
misagh
689ca76456 added “ready to try?” 2018-12-30 18:06:09 +01:00
misagh
034bcd64d5 requirements added to “about” 2018-12-30 18:03:09 +01:00
misagh
faad07aa3d changing index to about page 2018-12-30 17:58:06 +01:00
misagh
4d415205d1 about document enriched 2018-12-30 17:53:33 +01:00
misagh
c357483eef typo 2018-12-30 17:47:41 +01:00
misagh
aa542784ab unnecessary config removed 2018-12-30 17:32:03 +01:00
misagh
49c37692f3 content bar completed 2018-12-30 17:29:07 +01:00
misagh
f42df56a88 edge reformatted 2018-12-30 17:21:54 +01:00
misagh
808ce3e7ba edge added to content bar 2018-12-30 17:20:28 +01:00
misagh
87cbf6aaaa Plotting reformatted 2018-12-30 17:20:16 +01:00
misagh
863cf303e3 hyperopt reformatted 2018-12-30 17:17:52 +01:00
misagh
b029a98980 backtesting reformatted 2018-12-30 17:14:56 +01:00
misagh
219e9d9e2b optimization page reformatted 2018-12-30 17:14:03 +01:00
misagh
da380e6a0d start the bot documentation reformatted 2018-12-30 17:08:21 +01:00
misagh
83b9732106 permalink installed 2018-12-30 16:56:27 +01:00
misagh
016522b151 configuration reformatetd 2018-12-30 16:56:13 +01:00
misagh
cb1d9b6200 restructuring docs content 2018-12-30 15:22:19 +01:00
Misagh
e215373ad0 Merge pull request #1450 from freqtrade/pyup/scheduled-update-2018-12-30
Scheduled daily dependency update on sunday
2018-12-30 14:47:46 +01:00
pyup-bot
f32dfc57ca Update ccxt from 1.18.84 to 1.18.86 2018-12-30 13:34:05 +01:00
Misagh
b321c66654 Merge pull request #1448 from freqtrade/wallet_remove_wrong_namedtuple
Remove unused and duplicate datastructure
2018-12-30 12:09:06 +01:00
Matthias
a6e23e6b9b Merge pull request #1449 from freqtrade/pyup/scheduled-update-2018-12-29
Scheduled daily dependency update on saturday
2018-12-29 14:08:16 +01:00
pyup-bot
af85080113 Update ccxt from 1.18.78 to 1.18.84 2018-12-29 13:34:05 +01:00
Matthias
460900ddd7 Remove unused and duplicate datastructure 2018-12-29 09:01:58 +01:00
Matthias
884e1bd9a9 Merge pull request #1447 from mishaker/docs
Readthedocs configuration
2018-12-28 19:46:33 +01:00
misagh
f6ff9b0419 changed to introduction 2018-12-28 16:16:04 +01:00
misagh
0bcadbd854 bigger logo 2018-12-28 15:55:51 +01:00
misagh
78f29a7454 added theme configuration 2018-12-28 15:37:14 +01:00
misagh
71de820adf changing doc theme 2018-12-28 14:52:23 +01:00
misagh
dcf0feefb9 move requirements to docs 2018-12-28 14:28:09 +01:00
misagh
ad696a9d12 removing numpy from docs 2018-12-28 14:22:17 +01:00
misagh
793dd38445 removing unnecessary packages 2018-12-28 14:20:43 +01:00
misagh
ffbe95ef02 adding requirements for docs 2018-12-28 14:18:34 +01:00
misagh
9c442455a2 adding readdoc config 2018-12-28 14:11:22 +01:00
Matthias
a5452d2c75 Merge pull request #1446 from freqtrade/pyup/scheduled-update-2018-12-28
Scheduled daily dependency update on friday
2018-12-28 13:48:58 +01:00
misagh
b45af56199 initial commit for docs 2018-12-28 13:37:44 +01:00
pyup-bot
cf0e31c5a2 Update plotly from 3.4.1 to 3.4.2 2018-12-28 13:34:07 +01:00
pyup-bot
305f2b74e8 Update ccxt from 1.18.74 to 1.18.78 2018-12-28 13:34:05 +01:00
Misagh
b859fa1e42 Merge pull request #1445 from freqtrade/add_plotly_dev
Add plotly requirements file
2018-12-28 11:47:00 +01:00
Misagh
f286e092fb Merge pull request #1444 from freqtrade/replace_ujson
Replace ujson with rapidjson
2018-12-28 11:45:57 +01:00
Matthias
fab7663ab3 Log when dumping to file (instead of print) 2018-12-28 10:46:48 +01:00
Matthias
61f8ce5c0e remove unused imports 2018-12-28 10:44:24 +01:00
Matthias
0f86e218c1 Add plotly requirements file 2018-12-28 10:41:54 +01:00
Matthias
7dc40cdac5 refactor file_load_json to be standalone 2018-12-28 10:25:41 +01:00
Matthias
27abdd9788 Move load_json to misc 2018-12-28 10:04:28 +01:00
Matthias
065b469a10 rename test to avoid naming collision 2018-12-28 10:04:07 +01:00
Matthias
c955415cc3 Switch from ujson to rapidjson 2018-12-28 10:01:16 +01:00
Misagh
98ac2b15ca Merge pull request #1443 from freqtrade/download_data_fix
Reset stake-currency when using config to download pairs
2018-12-27 14:56:10 +01:00
Matthias
1ce8f416ca Reset stake-currency when using config to download pairs 2018-12-27 14:29:26 +01:00
Matthias
29e0a45b5b Merge pull request #1442 from freqtrade/pyup/scheduled-update-2018-12-27
Scheduled daily dependency update on thursday
2018-12-27 14:22:29 +01:00
pyup-bot
1167b24eeb Update ccxt from 1.18.71 to 1.18.74 2018-12-27 13:34:07 +01:00
Misagh
9e735de3c6 Merge pull request #1441 from freqtrade/upd/release_docs
Add release documentation
2018-12-27 13:07:00 +01:00
Matthias
cb654a82db Add release documentation 2018-12-27 12:56:56 +01:00
Matthias
407a978e08 Merge pull request #1437 from freqtrade/after_release
After release
2018-12-27 12:51:05 +01:00
Matthias
ecb5cdc9e3 Version bump to 0.18.1-dev 2018-12-27 11:47:00 +01:00
135 changed files with 11990 additions and 4970 deletions

1
.gitignore vendored
View File

@@ -81,6 +81,7 @@ target/
# Jupyter Notebook
.ipynb_checkpoints
*.ipynb
# pyenv
.python-version

View File

@@ -11,7 +11,10 @@ update: all
# allowed: True, False
pin: True
schedule: "every day"
# update schedule
# default: empty
# allowed: "every day", "every week", ..
schedule: "every week"
search: False
@@ -21,6 +24,8 @@ search: False
requirements:
- requirements.txt
- requirements-dev.txt
- requirements-plot.txt
- requirements-common.txt
# configure the branch prefix the bot is using

8
.readthedocs.yml Normal file
View File

@@ -0,0 +1,8 @@
# .readthedocs.yml
build:
image: latest
python:
version: 3.6
setup_py_install: false

View File

@@ -1,7 +1,7 @@
sudo: true
os:
- linux
dist: trusty
dist: xenial
language: python
python:
- 3.6
@@ -17,29 +17,31 @@ addons:
- libdw-dev
- binutils-dev
install:
- ./build_helpers/install_ta-lib.sh
- cd build_helpers && ./install_ta-lib.sh; cd ..
- export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
- pip install --upgrade flake8 coveralls pytest-random-order pytest-asyncio mypy
- pip install --upgrade pytest-random-order
- pip install -r requirements-dev.txt
- pip install -e .
jobs:
include:
- stage: tests
script:
- pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/
- coveralls
# Allow failure for coveralls
- coveralls || true
name: pytest
- script:
- cp config.json.example config.json
- python freqtrade/main.py --datadir freqtrade/tests/testdata backtesting
- python 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
- python freqtrade --datadir freqtrade/tests/testdata hyperopt -e 5
name: hyperopt
- script: flake8 freqtrade
- script: flake8 freqtrade scripts
name: flake8
- script: mypy freqtrade
- script: mypy freqtrade scripts
name: mypy
- stage: docker
@@ -48,11 +50,10 @@ jobs:
- build_helpers/publish_docker.sh
name: "Build and test and push docker image"
notifications:
slack:
secure: bKLXmOrx8e2aPZl7W8DA5BdPAXWGpI5UzST33oc1G/thegXcDVmHBTJrBs4sZak6bgAclQQrdZIsRd2eFYzHLalJEaw6pk7hoAw8SvLnZO0ZurWboz7qg2+aZZXfK4eKl/VUe4sM9M4e/qxjkK+yWG7Marg69c4v1ypF7ezUi1fPYILYw8u0paaiX0N5UX8XNlXy+PBlga2MxDjUY70MuajSZhPsY2pDUvYnMY1D/7XN3cFW0g+3O8zXjF0IF4q1Z/1ASQe+eYjKwPQacE+O8KDD+ZJYoTOFBAPllrtpO1jnOPFjNGf3JIbVMZw4bFjIL0mSQaiSUaUErbU3sFZ5Or79rF93XZ81V7uEZ55vD8KMfR2CB1cQJcZcj0v50BxLo0InkFqa0Y8Nra3sbpV4fV5Oe8pDmomPJrNFJnX6ULQhQ1gTCe0M5beKgVms5SITEpt4/Y0CmLUr6iHDT0CUiyMIRWAXdIgbGh1jfaWOMksybeRevlgDsIsNBjXmYI1Sw2ZZR2Eo2u4R6zyfyjOMLwYJ3vgq9IrACv2w5nmf0+oguMWHf6iWi2hiOqhlAN1W74+3HsYQcqnuM3LGOmuCnPprV1oGBqkPXjIFGpy21gNx4vHfO1noLUyJnMnlu2L7SSuN1CdLsnjJ1hVjpJjPfqB4nn8g12x87TqM1bOm+3Q=
cache:
pip: True
directories:
- $HOME/.cache/pip
- ta-lib
- /usr/local/lib

View File

@@ -11,9 +11,13 @@ 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
Best start by reading the [documentation](https://www.freqtrade.io/) to get a feel for what is possible with the bot, or head straight to the [Developer-documentation](https://www.freqtrade.io/en/latest/developer/) (WIP) which should help you getting started.
## Before sending the PR:
### 1. Run unit tests
@@ -41,12 +45,6 @@ pytest freqtrade/tests/test_<file_name>.py::test_<method_name>
### 2. Test if your code is PEP8 compliant
#### Install packages
```bash
pip3.6 install flake8 coveralls
```
#### Run Flake8
```bash
@@ -60,22 +58,12 @@ Guide for installing them is [here](http://flake8.pycqa.org/en/latest/user/using
### 3. Test if all type-hints are correct
#### Install packages
``` bash
pip3.6 install mypy
```
#### Run mypy
``` bash
mypy freqtrade
```
## Getting started
Best start by reading the [documentation](https://github.com/freqtrade/freqtrade/blob/develop/docs/index.md) to get a feel for what is possible with the bot, or head straight to the [Developer-documentation](https://github.com/freqtrade/freqtrade/blob/develop/docs/developer.md) (WIP) which should help you getting started.
## (Core)-Committer Guide
### Process: Pull Requests

View File

@@ -1,7 +1,7 @@
FROM python:3.7.0-slim-stretch
FROM python:3.7.3-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

144
README.md
View File

@@ -1,24 +1,25 @@
# freqtrade
# Freqtrade
[![Build Status](https://travis-ci.org/freqtrade/freqtrade.svg?branch=develop)](https://travis-ci.org/freqtrade/freqtrade)
[![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
[![Documentation](https://readthedocs.org/projects/freqtrade/badge/)](https://www.freqtrade.io)
[![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](https://codeclimate.com/github/freqtrade/freqtrade/maintainability)
Simple High frequency trading bot for crypto currencies designed to support multi exchanges and be controlled via Telegram.
Freqtrade is a free and open source crypto trading bot written in Python. It is designed to support all major exchanges and be controlled via Telegram. It contains backtesting, plotting and money management tools as well as strategy optimization by machine learning.
![freqtrade](https://raw.githubusercontent.com/freqtrade/freqtrade/develop/docs/assets/freqtrade-screenshot.png)
## Disclaimer
This software is for educational purposes only. Do not risk money which
you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS
AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.
This software is for educational purposes only. Do not risk money which
you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS
AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.
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
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.
## Exchange marketplaces supported
@@ -27,48 +28,27 @@ hesitate to read the source code and understand the mechanism of this bot.
- [X] [Binance](https://www.binance.com/) ([*Note for binance users](#a-note-on-binance))
- [ ] [113 others to tests](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
## Documentation
We invite you to read the bot documentation to ensure you understand how the bot is working.
Please find the complete documentation on our [website](https://www.freqtrade.io).
## Features
- [x] **Based on Python 3.6+**: For botting on any operating system - Windows, macOS and Linux
- [x] **Persistence**: Persistence is achieved through sqlite
- [x] **Based on Python 3.6+**: For botting on any operating system - Windows, macOS and Linux.
- [x] **Persistence**: Persistence is achieved through sqlite.
- [x] **Dry-run**: Run the bot without playing money.
- [x] **Backtesting**: Run a simulation of your buy/sell strategy.
- [x] **Strategy Optimization by machine learning**: Use machine learning to optimize your buy/sell strategy parameters with real exchange data.
- [x] **Edge position sizing** Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. [Learn more](https://github.com/freqtrade/freqtrade/blob/develop/docs/edge.md)
- [x] **Edge position sizing** Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. [Learn more](https://www.freqtrade.io/en/latest/edge/).
- [x] **Whitelist crypto-currencies**: Select which crypto-currency you want to trade or use dynamic whitelists.
- [x] **Blacklist crypto-currencies**: Select which crypto-currency you want to avoid.
- [x] **Manageable via Telegram**: Manage the bot with Telegram
- [x] **Manageable via Telegram**: Manage the bot with Telegram.
- [x] **Display profit/loss in fiat**: Display your profit/loss in 33 fiat.
- [x] **Daily summary of profit/loss**: Provide a daily summary of your profit/loss.
- [x] **Performance status report**: Provide a performance status of your current trades.
## Table of Contents
- [Quick start](#quick-start)
- [Documentations](https://github.com/freqtrade/freqtrade/blob/develop/docs/index.md)
- [Installation](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md)
- [Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)
- [Strategy Optimization](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md)
- [Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md)
- [Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
- [Sandbox Testing](https://github.com/freqtrade/freqtrade/blob/develop/docs/sandbox-testing.md)
- [Edge](https://github.com/freqtrade/freqtrade/blob/develop/docs/edge.md)
- [Basic Usage](#basic-usage)
- [Bot commands](#bot-commands)
- [Telegram RPC commands](#telegram-rpc-commands)
- [Support](#support)
- [Help](#help--slack)
- [Bugs](#bugs--issues)
- [Feature Requests](#feature-requests)
- [Pull Requests](#pull-requests)
- [Requirements](#requirements)
- [Min hardware required](#min-hardware-required)
- [Software requirements](#software-requirements)
- [Wanna help?](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
- [Dev - getting started](https://github.com/freqtrade/freqtrade/blob/develop/docs/developer.md) (WIP)
## Quick start
Freqtrade provides a Linux/macOS script to install all dependencies and help you to configure the bot.
@@ -80,63 +60,53 @@ git checkout develop
./setup.sh --install
```
_Windows installation is explained in [Installation doc](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md)_
For any other type of installation please refer to [Installation doc](https://www.freqtrade.io/en/latest/installation/).
## Documentation
We invite you to read the bot documentation to ensure you understand how the bot is working.
- [Index](https://github.com/freqtrade/freqtrade/blob/develop/docs/index.md)
- [Installation](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md)
- [Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)
- [Bot usage](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md)
- [How to run the bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#bot-commands)
- [How to use Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#backtesting-commands)
- [How to use Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands)
- [Strategy Optimization](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md)
- [Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md)
- [Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
## Basic Usage
### Bot commands
```bash
usage: main.py [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
[--strategy-path PATH] [--dynamic-whitelist [INT]]
[--dry-run-db]
{backtesting,hyperopt} ...
```
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} ...
Simple High Frequency Trading Bot for crypto currencies
Free, open source crypto trading bot
positional arguments:
{backtesting,hyperopt}
backtesting backtesting module
hyperopt hyperopt module
{backtesting,edge,hyperopt}
backtesting Backtesting module.
edge Edge module.
hyperopt Hyperopt module.
optional arguments:
-h, --help show this help message and exit
-v, --verbose be verbose
-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 (default:
freqtrade/tests/testdata
Path to backtest data.
-s NAME, --strategy NAME
specify strategy class name (default: DefaultStrategy)
--strategy-path PATH specify additional strategy lookup path
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 currencies)
--dry-run-db Force dry run to use a local DB
"tradesv3.dry_run.sqlite" instead of memory DB. Work
only if dry_run is enabled.
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).
--sd-notify Notify systemd service manager.
```
### Telegram RPC commands
Telegram is not mandatory. However, this is a great way to control your bot. More details on our [documentation](https://github.com/freqtrade/freqtrade/blob/develop/docs/index.md)
Telegram is not mandatory. However, this is a great way to control your bot. More details on our [documentation](https://www.freqtrade.io/en/latest/telegram-usage/)
- `/start`: Starts the trader
- `/stop`: Stops the trader
@@ -159,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.
@@ -172,38 +141,38 @@ 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)
If you discover a bug in the bot, please
[search our issue tracker](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
first. If it hasn't been reported, please
[create a new issue](https://github.com/freqtrade/freqtrade/issues/new) and
ensure you follow the template guide so that our team can assist you as
If you discover a bug in the bot, please
[search our issue tracker](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
first. If it hasn't been reported, please
[create a new issue](https://github.com/freqtrade/freqtrade/issues/new) and
ensure you follow the template guide so that our team can assist you as
quickly as possible.
### [Feature Requests](https://github.com/freqtrade/freqtrade/labels/enhancement)
Have you a great idea to improve the bot you want to share? Please,
first search if this feature was not [already discussed](https://github.com/freqtrade/freqtrade/labels/enhancement).
If it hasn't been requested, please
[create a new request](https://github.com/freqtrade/freqtrade/issues/new)
and ensure you follow the template guide so that it does not get lost
If it hasn't been requested, please
[create a new request](https://github.com/freqtrade/freqtrade/issues/new)
and ensure you follow the template guide so that it does not get lost
in the bug reports.
### [Pull Requests](https://github.com/freqtrade/freqtrade/pulls)
Feel like our bot is missing a feature? We welcome your pull requests!
Please read our
Please read our
[Contributing document](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
to understand the requirements before sending your pull-requests.
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`.
@@ -221,10 +190,9 @@ To run this bot we recommend you a cloud instance with a minimum of:
### Software requirements
- [Python 3.6.x](http://docs.python-guide.org/en/latest/starting/installation/)
- [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)
- [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html)
- [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) (Recommended)
- [Docker](https://www.docker.com/products/docker) (Recommended)

View File

@@ -1,7 +1,13 @@
#!/usr/bin/env python3
import sys
import warnings
from freqtrade.main import main, set_loggers
set_loggers()
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,4 +1,4 @@
if [ ! -f "ta-lib/CHANGELOG.TXT" ]; then
if [ ! -f "/usr/local/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 \
@@ -7,7 +7,5 @@ if [ ! -f "ta-lib/CHANGELOG.TXT" ]; then
&& which sudo && sudo make install || make install \
&& cd ..
else
echo "TA-lib already installed, skipping download and build."
cd ta-lib && sudo make install && cd ..
echo "TA-lib already installed, skipping installation"
fi

View File

@@ -13,7 +13,7 @@ if [ "${TRAVIS_EVENT_TYPE}" = "cron" ]; then
else
echo "event ${TRAVIS_EVENT_TYPE}: building with cache"
# Pull last build to avoid rebuilding the whole image
docker pull ${REPO}:${TAG}
docker pull ${IMAGE_NAME}:${TAG}
docker build --cache-from ${IMAGE_NAME}:${TAG} -t freqtrade:${TAG} .
fi

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

@@ -3,11 +3,13 @@
"stake_currency": "BTC",
"stake_amount": 0.05,
"fiat_display_currency": "USD",
"amount_reserve_percent" : 0.05,
"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,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,
@@ -37,26 +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": [
@@ -74,7 +81,8 @@
"pair_blacklist": [
"DOGE/BTC"
],
"outdated_offset": 5
"outdated_offset": 5,
"markets_refresh_interval": 60
},
"edge": {
"enabled": false,
@@ -101,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,

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

@@ -1,24 +1,19 @@
# Backtesting
This page explains how to validate your strategy performance by using
This page explains how to validate your strategy performance by using
Backtesting.
## Table of Contents
- [Test your strategy with Backtesting](#test-your-strategy-with-backtesting)
- [Understand the backtesting result](#understand-the-backtesting-result)
## Test your strategy with Backtesting
Now you have good Buy and Sell strategies, you want to test it against
real data. This is what we call
real data. This is what we call
[backtesting](https://en.wikipedia.org/wiki/Backtesting).
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
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
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.
@@ -29,37 +24,37 @@ The backtesting is very easy with freqtrade.
#### With 5 min tickers (Per default)
```bash
python3 ./freqtrade/main.py backtesting
python3 freqtrade backtesting
```
#### With 1 min tickers
```bash
python3 ./freqtrade/main.py backtesting --ticker-interval 1m
python3 freqtrade backtesting --ticker-interval 1m
```
#### Update cached pairs with the latest data
```bash
python3 ./freqtrade/main.py backtesting --refresh-pairs-cached
python3 freqtrade backtesting --refresh-pairs-cached
```
#### With live data (do not alter your testdata files)
```bash
python3 ./freqtrade/main.py backtesting --live
python3 freqtrade backtesting --live
```
#### Using a different on-disk ticker-data source
```bash
python3 ./freqtrade/main.py backtesting --datadir freqtrade/tests/testdata-20180101
python3 freqtrade backtesting --datadir freqtrade/tests/testdata-20180101
```
#### With a (custom) strategy file
```bash
python3 ./freqtrade/main.py -s TestStrategy backtesting
python3 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
@@ -67,43 +62,15 @@ Where `-s TestStrategy` refers to the class name within the strategy file `test_
#### Exporting trades to file
```bash
python3 ./freqtrade/main.py backtesting --export trades
python3 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 folder.
#### Exporting trades to file specifying a custom filename
```bash
python3 ./freqtrade/main.py backtesting --export trades --export-filename=backtest_teststrategy.json
python3 freqtrade backtesting --export trades --export-filename=backtest_teststrategy.json
```
#### Running backtest with smaller testset
@@ -114,7 +81,7 @@ you want to use. The last N ticks/timeframes will be used.
Example:
```bash
python3 ./freqtrade/main.py backtesting --timerange=-200
python3 freqtrade backtesting --timerange=-200
```
#### Advanced use of timerange
@@ -156,11 +123,12 @@ 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 use a different folder than the exchange specific default, use `--datadir 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.
- 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 other options.
For help about backtesting usage, please refer to [Backtesting commands](#backtesting-commands).
@@ -171,60 +139,72 @@ The most important in the backtesting is to understand the result.
A backtesting result will look like that:
```
======================================== BACKTESTING REPORT =========================================
| pair | buy count | avg profit % | total profit BTC | avg duration | profit | loss |
|:---------|------------:|---------------:|-------------------:|---------------:|---------:|-------:|
| ETH/BTC | 44 | 0.18 | 0.00159118 | 50.9 | 44 | 0 |
| LTC/BTC | 27 | 0.10 | 0.00051931 | 103.1 | 26 | 1 |
| ETC/BTC | 24 | 0.05 | 0.00022434 | 166.0 | 22 | 2 |
| DASH/BTC | 29 | 0.18 | 0.00103223 | 192.2 | 29 | 0 |
| ZEC/BTC | 65 | -0.02 | -0.00020621 | 202.7 | 62 | 3 |
| XLM/BTC | 35 | 0.02 | 0.00012877 | 242.4 | 32 | 3 |
| BCH/BTC | 12 | 0.62 | 0.00149284 | 50.0 | 12 | 0 |
| POWR/BTC | 21 | 0.26 | 0.00108215 | 134.8 | 21 | 0 |
| ADA/BTC | 54 | -0.19 | -0.00205202 | 191.3 | 47 | 7 |
| XMR/BTC | 24 | -0.43 | -0.00206013 | 120.6 | 20 | 4 |
| TOTAL | 335 | 0.03 | 0.00175246 | 157.9 | 315 | 20 |
2018-06-13 06:57:27,347 - freqtrade.optimize.backtesting - INFO -
====================================== LEFT OPEN TRADES REPORT ======================================
| pair | buy count | avg profit % | total profit BTC | avg duration | profit | loss |
|:---------|------------:|---------------:|-------------------:|---------------:|---------:|-------:|
| ETH/BTC | 3 | 0.16 | 0.00009619 | 25.0 | 3 | 0 |
| LTC/BTC | 1 | -1.00 | -0.00020118 | 1085.0 | 0 | 1 |
| ETC/BTC | 2 | -1.80 | -0.00071933 | 1092.5 | 0 | 2 |
| DASH/BTC | 0 | nan | 0.00000000 | nan | 0 | 0 |
| ZEC/BTC | 3 | -4.27 | -0.00256826 | 1301.7 | 0 | 3 |
| XLM/BTC | 3 | -1.11 | -0.00066744 | 965.0 | 0 | 3 |
| BCH/BTC | 0 | nan | 0.00000000 | nan | 0 | 0 |
| POWR/BTC | 0 | nan | 0.00000000 | nan | 0 | 0 |
| ADA/BTC | 7 | -3.58 | -0.00503604 | 850.0 | 0 | 7 |
| XMR/BTC | 4 | -3.79 | -0.00303456 | 291.2 | 0 | 4 |
| TOTAL | 23 | -2.63 | -0.01213062 | 750.4 | 3 | 20 |
========================================================= BACKTESTING REPORT ========================================================
| pair | buy count | avg profit % | cum profit % | tot profit BTC | tot profit % | avg duration | profit | loss |
|:---------|------------:|---------------:|---------------:|-----------------:|---------------:|:---------------|---------:|-------:|
| ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 | 21 |
| ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 | 8 |
| BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 | 14 |
| DASH/BTC | 13 | -0.08 | -1.07 | -0.00005343 | -0.53 | 4:39:00 | 6 | 7 |
| ENG/BTC | 18 | 1.36 | 24.54 | 0.00122807 | 12.27 | 2:50:00 | 8 | 10 |
| EOS/BTC | 36 | 0.08 | 3.06 | 0.00015304 | 1.53 | 3:34:00 | 16 | 20 |
| ETC/BTC | 26 | 0.37 | 9.51 | 0.00047576 | 4.75 | 6:14:00 | 11 | 15 |
| ETH/BTC | 33 | 0.30 | 9.96 | 0.00049856 | 4.98 | 7:31:00 | 16 | 17 |
| IOTA/BTC | 32 | 0.03 | 1.09 | 0.00005444 | 0.54 | 3:12:00 | 14 | 18 |
| LSK/BTC | 15 | 1.75 | 26.26 | 0.00131413 | 13.13 | 2:58:00 | 6 | 9 |
| LTC/BTC | 32 | -0.04 | -1.38 | -0.00006886 | -0.69 | 4:49:00 | 11 | 21 |
| NANO/BTC | 17 | 1.26 | 21.39 | 0.00107058 | 10.70 | 1:55:00 | 10 | 7 |
| NEO/BTC | 23 | 0.82 | 18.97 | 0.00094936 | 9.48 | 2:59:00 | 10 | 13 |
| REQ/BTC | 9 | 1.17 | 10.54 | 0.00052734 | 5.27 | 3:47:00 | 4 | 5 |
| XLM/BTC | 16 | 1.22 | 19.54 | 0.00097800 | 9.77 | 3:15:00 | 7 | 9 |
| XMR/BTC | 23 | -0.18 | -4.13 | -0.00020696 | -2.07 | 5:30:00 | 12 | 11 |
| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 | 23 |
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 | 15 |
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 243 |
========================================================= SELL REASON STATS =========================================================
| Sell Reason | Count |
|:-------------------|--------:|
| trailing_stop_loss | 205 |
| stop_loss | 166 |
| sell_signal | 56 |
| force_sell | 2 |
====================================================== LEFT OPEN TRADES REPORT ======================================================
| pair | buy count | avg profit % | cum profit % | tot profit BTC | tot profit % | avg duration | profit | loss |
|:---------|------------:|---------------:|---------------:|-----------------:|---------------:|:---------------|---------:|-------:|
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 | 0 |
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 | 0 |
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 | 0 |
```
The 1st table will contain all trades the bot made.
The 2nd table will contain all trades the bot had to `forcesell` at the end of the backtest period to prsent a full picture.
The 2nd table will contain a recap of sell reasons.
The 3rd table will contain all trades the bot had to `forcesell` at the end of the backtest period to present a full picture.
These trades are also included in the first table, but are extracted separately for clarity.
The last line will give you the overall performance of your strategy,
here:
```
TOTAL 419 -0.41 -0.00348593 52.9
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 243 |
```
We understand the bot has made `419` trades for an average duration of
`52.9` min, with a performance of `-0.41%` (loss), that means it has
lost a total of `-0.00348593 BTC`.
As you will see your strategy performance will be influenced by your buy
strategy, your sell strategy, and also by the `minimal_roi` and
`stop_loss` you have set.
We understand the bot has made `429` trades for an average duration of
`4:12:00`, with a performance of `76.20%` (profit), that means it has
earned a total of `0.00762792 BTC` starting with a capital of 0.01 BTC.
The column `avg profit %` shows the average profit for all trades made while the column `cum profit %` sums all the profits/losses.
The column `tot profit %` shows instead the total profit % in relation to allocated capital
(`max_open_trades * stake_amount`). In the above results we have `max_open_trades=2 stake_amount=0.005` in config
so `(76.20/100) * (0.005 * 2) =~ 0.00762792 BTC`.
As you will see your strategy performance will be influenced by your buy
strategy, your sell strategy, and also by the `minimal_roi` and
`stop_loss` you have set.
As for an example if your minimal_roi is only `"0": 0.01`. You cannot
expect the bot to make more profit than 1% (because it will sell every
expect the bot to make more profit than 1% (because it will sell every
time a trade will reach 1%).
```json
@@ -234,21 +214,27 @@ time a trade will reach 1%).
```
On the other hand, if you set a too high `minimal_roi` like `"0": 0.55`
(55%), there is a lot of chance that the bot will never reach this
profit. Hence, keep in mind that your performance is a mix of your
(55%), there is a lot of chance that the bot will never reach this
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.
This is limited to 1 ticker-interval per run, however, data is only loaded once from disk so if you have multiple
This is limited to 1 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.
``` bash
freqtrade backtesting --timerange 20180401-20180410 --ticker-interval 5m --strategy-list Strategy001 Strategy002 --export trades
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.
@@ -256,15 +242,15 @@ There will be an additional table comparing win/losses of the different strategi
Detailed output for all strategies one after the other will be available, so make sure to scroll up.
```
=================================================== Strategy Summary ====================================================
| Strategy | buy count | avg profit % | cum profit % | total profit ETH | avg duration | profit | loss |
|:-----------|------------:|---------------:|---------------:|-------------------:|:----------------|---------:|-------:|
| Strategy1 | 19 | -0.76 | -14.39 | -0.01440287 | 15:48:00 | 15 | 4 |
| Strategy2 | 6 | -2.73 | -16.40 | -0.01641299 | 1 day, 14:12:00 | 3 | 3 |
=========================================================== Strategy Summary ===========================================================
| Strategy | buy count | avg profit % | cum profit % | tot profit BTC | tot profit % | avg duration | profit | loss |
|:------------|------------:|---------------:|---------------:|-----------------:|---------------:|:---------------|---------:|-------:|
| Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 243 |
| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 825 |
```
## Next step
Great, your strategy is profitable. What if the bot can give your the
optimal parameters to use for your strategy?
Your next step is to learn [how to find optimal parameters with Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
optimal parameters to use for your strategy?
Your next step is to learn [how to find optimal parameters with Hyperopt](hyperopt.md)

View File

@@ -1,57 +1,88 @@
# Bot usage
# Start the bot
This page explains the difference parameters of the bot and how to run it.
This page explains the different parameters of the bot and how to run it.
## Table of Contents
- [Bot commands](#bot-commands)
- [Backtesting commands](#backtesting-commands)
- [Hyperopt commands](#hyperopt-commands)
## Bot commands
```
usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
[--strategy-path PATH] [--dynamic-whitelist [INT]]
[--db-url PATH]
{backtesting,hyperopt} ...
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} ...
Simple High Frequency Trading Bot for crypto currencies
Free, open source crypto trading bot
positional arguments:
{backtesting,hyperopt}
backtesting backtesting module
hyperopt hyperopt module
{backtesting,edge,hyperopt}
backtesting Backtesting module.
edge Edge module.
hyperopt Hyperopt module.
optional arguments:
-h, --help show this help message and exit
-v, --verbose be verbose
-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. Can be set to '-' to
read config from stdin.
-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
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:
sqlite:///tradesv3.sqlite)
None).
--sd-notify Notify systemd service manager.
```
### How to use a different config file?
### How to use a different configuration file?
The bot allows you to select which config file you want to use. Per
The bot allows you to select which configuration file you want to use. Per
default, the bot will load the file `./config.json`
```bash
python3 ./freqtrade/main.py -c path/far/far/away/config.json
python3 freqtrade -c path/far/far/away/config.json
```
### How to use --strategy?
### How to use multiple configuration files?
The bot allows you to use multiple configuration files by specifying multiple
`-c/--config` configuration options in the command line. Configuration parameters
defined in the last configuration file override parameters with the same name
defined in the previous configuration file specified in the command line.
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
python3 freqtrade -c ./config.json
```
and specify both configuration files when running in the normal Live Trade Mode:
```bash
python3 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).
### How to use **--strategy**?
This parameter will allow you to load your custom strategy class.
Per default without `--strategy` or `-s` the bot will load the
@@ -66,20 +97,21 @@ 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
python3 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?
### 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!):
```bash
python3 ./freqtrade/main.py --strategy AwesomeStrategy --strategy-path /some/folder
python3 freqtrade --strategy AwesomeStrategy --strategy-path /some/folder
```
#### How to install a strategy?
@@ -87,33 +119,18 @@ python3 ./freqtrade/main.py --strategy AwesomeStrategy --strategy-path /some/fol
This is very simple. Copy paste your strategy file into the folder
`user_data/strategies` or use `--strategy-path`. And voila, the bot is ready to use it.
### How to use --dynamic-whitelist?
### How to use **--dynamic-whitelist**?
> Dynamic-whitelist is deprecated. Please move your configurations to the configuration as outlined [here](docs/configuration.md#Dynamic-Pairlists)
!!! danger "DEPRECATED"
This command line option is deprecated. Please move your configurations using it
to the configurations that utilize the `StaticPairList` or `VolumePairList` methods set
in the configuration file
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.
Description of this deprecated feature was moved to [here](deprecated.md).
Please no longer use it.
**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?
### How to use **--db-url**?
When you run the bot in Dry-run mode, per default no transactions are
stored in a database. If you want to store your bot actions in a DB
@@ -121,7 +138,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
python3 freqtrade -c config.json --db-url sqlite:///tradesv3.dry_run.sqlite
```
## Backtesting commands
@@ -129,29 +146,35 @@ python3 ./freqtrade/main.py -c config.json --db-url sqlite:///tradesv3.dry_run.s
Backtesting also uses the config specified via `-c/--config`.
```
usage: freqtrade backtesting [-h] [-i TICKER_INTERVAL] [--eps] [--dmmp]
[--timerange TIMERANGE] [-l] [-r]
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
[--export EXPORT] [--export-filename PATH]
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.
--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)
--timerange TIMERANGE
specify what timerange of data to use.
-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).
-l, --live Use live data.
--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]
Provide a commaseparated list of strategies to
backtest Please note that ticker-interval needs to be
@@ -159,7 +182,7 @@ optional arguments:
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
@@ -167,18 +190,18 @@ optional arguments:
filename=user_data/backtest_data/backtest_today.json
(default: user_data/backtest_data/backtest-
result.json)
```
### How to use --refresh-pairs-cached parameter?
### How to use **--refresh-pairs-cached** parameter?
The first time your run Backtesting, it will take the pairs you have
set in your config file and download data from Bittrex.
set in your config file and download data from the Exchange.
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.
**Use it only if you want to update your data set. You will not be able
to come back to the previous version.**
!!! 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`.
@@ -189,30 +212,48 @@ 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 MAX_OPEN_TRADES]
[--stake_amount STAKE_AMOUNT] [-r]
[--customhyperopt NAME] [--eps] [--dmmp] [-e INT]
[-s {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]]
[--print-all] [-j JOBS]
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 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.
--customhyperopt NAME
Specify hyperopt class name (default:
DefaultHyperOpts).
--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)
--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} ...]
number).
-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 separate
list. Default: all
list. Default: all.
--print-all Print all results, not only the best ones.
-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.
```
## Edge commands
@@ -220,22 +261,28 @@ optional arguments:
To know your trade expectacny 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
```
@@ -250,4 +297,4 @@ in [misc.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/misc.
## 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](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md).
[Strategy Customization](strategy-customization.md).

View File

@@ -2,12 +2,6 @@
This page explains how to configure your `config.json` file.
## Table of Contents
- [Bot commands](#bot-commands)
- [Backtesting commands](#backtesting-commands)
- [Hyperopt commands](#hyperopt-commands)
## Setup config.json
We recommend to copy and use the `config.json.example` as a template
@@ -15,78 +9,113 @@ for your bot configuration.
The table below will list all configuration parameters.
| Command | Default | Mandatory | Description |
|----------|---------|----------|-------------|
| `max_open_trades` | 3 | Yes | Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades)
| `stake_currency` | BTC | Yes | Crypto-currency used for trading.
| `stake_amount` | 0.05 | Yes | 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 avaliable balance.
| `ticker_interval` | [1m, 5m, 30m, 1h, 1d] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Default is 5 minutes
| `fiat_display_currency` | USD | Yes | Fiat currency used to show your profits. More information below.
| `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode.
| `process_only_new_candles` | false | No | 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. Can be set either in Configuration or in the strategy.
| `minimal_roi` | See below | No | Set the threshold in percent the bot will use to sell a trade. More information below. If set, this parameter will override `minimal_roi` from your strategy file.
| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file.
| `trailing_stop` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file).
| `trailing_stop_positve` | 0 | No | Changes stop-loss once profit has been reached.
| `trailing_stop_positve_offset` | 0 | No | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive.
| `unfilledtimeout.buy` | 10 | Yes | 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 | Yes | 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 | Yes | Set the bidding price. More information below.
| `bid_strategy.use_order_book` | false | No | Allows buying of pair using the rates in Order Book Bids.
| `bid_strategy.order_book_top` | 0 | No | Bot will use the top N rate in Order Book Bids. Ie. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids.
| `bid_strategy. check_depth_of_market.enabled` | false | No | Does not buy if the % difference of buy orders and sell orders is met in Order Book.
| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | 0 | No | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher.
| `ask_strategy.use_order_book` | false | No | Allows selling of open traded pair using the rates in Order Book Asks.
| `ask_strategy.order_book_min` | 0 | No | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
| `ask_strategy.order_book_max` | 0 | No | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
| `order_types` | None | No | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types).
| `order_time_in_force` | None | No | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force).
| `exchange.name` | bittrex | Yes | Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
| `exchange.key` | key | No | API key to use for the exchange. Only required when you are in production mode.
| `exchange.secret` | secret | No | API secret to use for the exchange. Only required when you are in production mode.
| `exchange.pair_whitelist` | [] | No | List of currency to use by the bot. Can be overrided with `--dynamic-whitelist` param.
| `exchange.pair_blacklist` | [] | No | List of currency the bot must avoid. Useful when using `--dynamic-whitelist` param.
| `exchange.ccxt_rate_limit` | True | No | DEPRECATED!! Have CCXT handle Exchange rate limits. Depending on the exchange, having this to false can lead to temporary bans from the exchange.
| `exchange.ccxt_config` | None | No | 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 | No | 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)
| `edge` | false | No | Please refer to [edge configuration document](edge.md) for detailed explanation.
| `experimental.use_sell_signal` | false | No | Use your sell strategy in addition of the `minimal_roi`.
| `experimental.sell_profit_only` | false | No | waits until you have made a positive profit before taking a sell decision.
| `experimental.ignore_roi_if_buy_signal` | false | No | Does not sell if the buy-signal is still active. Takes preference over `minimal_roi` and `use_sell_signal`
| `pairlist.method` | StaticPairList | No | Use Static whitelist. [More information below](#dynamic-pairlists).
| `pairlist.config` | None | No | Additional configuration for dynamic pairlists. [More information below](#dynamic-pairlists).
| `telegram.enabled` | true | Yes | Enable or not the usage of Telegram.
| `telegram.token` | token | No | Your Telegram bot token. Only required if `telegram.enabled` is `true`.
| `telegram.chat_id` | chat_id | No | Your personal Telegram account id. Only required if `telegram.enabled` is `true`.
| `webhook.enabled` | false | No | Enable usage of Webhook notifications
| `webhook.url` | false | No | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
| `webhook.webhookbuy` | false | No | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `webhook.webhooksell` | false | No | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `webhook.webhookstatus` | false | No | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `db_url` | `sqlite:///tradesv3.sqlite` | No | Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `True`.
| `initial_state` | running | No | Defines the initial application state. More information below.
| `forcebuy_enable` | false | No | Enables the RPC Commands to force a buy. More information below.
| `strategy` | DefaultStrategy | No | Defines Strategy class to use.
| `strategy_path` | null | No | Adds an additional strategy lookup path (must be a folder).
| `internals.process_throttle_secs` | 5 | Yes | Set the process throttle. Value in second.
Mandatory Parameters are marked as **Required**.
The definition of each config parameters is in [misc.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/misc.py#L205).
| 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. [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, 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.
| `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).
| `bid_strategy.use_order_book` | false | Allows buying of pair using the rates in Order Book Bids.
| `bid_strategy.order_book_top` | 0 | Bot will use the top N rate in Order Book Bids. Ie. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids.
| `bid_strategy. check_depth_of_market.enabled` | false | Does not buy if the % difference of buy orders and sell orders is met in Order Book.
| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | 0 | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher.
| `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-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 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_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-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).
| `pairlist.method` | StaticPairList | Use Static whitelist. [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`.
| `telegram.chat_id` | chat_id | Your personal Telegram account id. Only required if `telegram.enabled` is `true`.
| `webhook.enabled` | false | Enable usage of Webhook notifications
| `webhook.url` | false | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
| `webhook.webhookbuy` | false | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `webhook.webhooksell` | false | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `webhook.webhookstatus` | false | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `db_url` | `sqlite:///tradesv3.sqlite`| Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `True`.
| `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).
| `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.
### Parameters 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.
* `stake_currency`
* `stake_amount`
* `ticker_interval`
* `minimal_roi`
* `stoploss`
* `trailing_stop`
* `trailing_stop_positive`
* `trailing_stop_positive_offset`
* `process_only_new_candles`
* `order_types`
* `order_time_in_force`
* `use_sell_signal` (experimental)
* `sell_profit_only` (experimental)
* `ignore_roi_if_buy_signal` (experimental)
### 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 avaliable `stake_currency` in your account set `stake_amount` = `unlimited`.
In this case a trade amount is calclulated as `currency_balanse / (max_open_trades - current_open_trades)`.
To allow the bot to trade all the available `stake_currency` in your account set
```json
"stake_amount" : "unlimited",
```
In this case a trade amount is calclulated as:
```python
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:
```
```json
"minimal_roi": {
"40": 0.0, # Sell after 40 minutes if the profit is not negative
"30": 0.01, # Sell after 30 minutes if there is at least 1% profit
@@ -95,95 +124,145 @@ 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. 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.
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"`, `"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.
``` python
"order_types": {
"buy": "limit",
"sell": "limit",
"stoploss": "market",
"stoploss_on_exchange": False
},
Syntax for Strategy:
```python
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."`
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."`
!!! Note
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",
"sell": "gtc"
},
"order_time_in_force": {
"buy": "gtc",
"sell": "gtc"
},
```
**NOTE**: 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.<br/>
### What values for exchange.name?
!!! 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.
Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports 115 cryptocurrency
### Exchange configuration
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.
@@ -195,31 +274,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
`fiat_display_currency` set the base currency to use for the conversion from coin to fiat in Telegram.
The valid values are: "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 central bank currencies, a range of cryto currencies are supported.
The valid values are: "BTC", "ETH", "XRP", "LTC", "BCH", "USDT".
A exchange configuration for "binance" would look as follows:
## Switch to dry-run mode
```json
"exchange": {
"name": "binance",
"key": "your_exchange_key",
"secret": "your_exchange_secret",
"ccxt_config": {"enableRateLimit": true},
"ccxt_async_config": {
"enableRateLimit": true,
"rateLimit": 200
},
```
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
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:
```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.
The valid values are:
```json
"BTC", "ETH", "XRP", "LTC", "BCH", "USDT"
```
## Switch to Dry-run mode
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.
### To switch your bot in Dry-run mode:
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",
"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": {
@@ -230,51 +362,61 @@ 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
**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`
* Formerly available as `--dynamic-whitelist [<number_assets>]`. This command line
option is deprecated and should no longer be used.
* 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.
### To switch your bot in production mode
1. Edit your `config.json` file
**Edit your `config.json` file.**
2. Switch dry-run to false and don't forget to adapt your database URL if set
**Switch dry-run to false and don't forget to adapt your database URL if set:**
```json
"dry_run": false,
```
3. Insert your Exchange API key (change them by fake api keys)
**Insert your Exchange API key (change them by fake api keys):**
```json
"exchange": {
@@ -285,8 +427,8 @@ you run it in production mode.
}
```
If you have not your Bittrex API key yet, [see our tutorial](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md).
!!! Note
If you have an exchange API key yet, [see our tutorial](/pre-requisite).
### Using proxy with FreqTrade
@@ -337,4 +479,4 @@ Please ensure that 'NameOfStrategy' is identical to the strategy name!
## Next step
Now you have configured your config.json, the next step is to [start your bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md).
Now you have configured your config.json, the next step is to [start your bot](bot-usage.md).

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

@@ -0,0 +1,42 @@
# Analyzing bot data
After performing backtests, or after running the bot for some time, it will be interesting to analyze the results your bot generated.
A good way for this is using Jupyter (notebook or lab) - which provides an interactive environment to analyze the data.
The following helpers will help you loading the data into Pandas DataFrames, and may also give you some starting points in analyzing the results.
## Backtesting
To analyze your backtest results, you can [export the trades](#exporting-trades-to-file).
You can then load the trades to perform further analysis.
Freqtrade provides the `load_backtest_data()` helper function to easily load the backtest results, which takes the path to the the backtest-results file as parameter.
``` python
from freqtrade.data.btanalysis import load_backtest_data
df = load_backtest_data("user_data/backtest-result.json")
# Show value-counts per pair
df.groupby("pair")["sell_reason"].value_counts()
```
This will allow you to drill deeper into your backtest results, and perform analysis which otherwise would make the regular backtest-output very difficult to digest due to information overload.
If you have some ideas for interesting / helpful backtest data analysis ideas, please submit a Pull Request so the community can benefit from it.
## Live data
To analyze the trades your bot generated, you can load them to a DataFrame as follows:
``` python
from freqtrade.data.btanalysis import load_trades_from_db
df = load_trades_from_db("sqlite:///tradesv3.sqlite")
df.groupby("pair")["sell_reason"].value_counts()
```
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.

31
docs/deprecated.md Normal file
View File

@@ -0,0 +1,31 @@
# 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.
### The **--dynamic-whitelist** command line option
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 --dynamic-whitelist
```
**Customize the number of currencies to retrieve**
Get the 30 currencies based on BaseVolume.
```bash
python3 freqtrade --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).

View File

@@ -2,10 +2,22 @@
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
## Module
Documentation is available at [https://freqtrade.io](https://www.freqtrade.io/) and needs to be provided with every new feature PR.
Special fields for the documentation (like Note boxes, ...) can be found [here](https://squidfunk.github.io/mkdocs-material/extensions/admonition/).
## 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`.
This will install all required tools for development, including `pytest`, `flake8`, `mypy`, and `coveralls`.
## Modules
### Dynamic Pairlist
@@ -68,3 +80,85 @@ 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
``` bash
# make sure you're in develop branch
git checkout develop
# create new branch
git checkout -b new_release
```
* Edit `freqtrade/__init__.py` and add the desired version (for example `0.18.0`)
* Commit this part
* push that branch to the remote and create a PR against the master branch
### create changelog from git commits
``` bash
# Needs to be done before merging / pulling that branch.
git log --oneline --no-decorate --no-merges master..develop
```
### Create github release / tag
* 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)
### After-release
* Update version in develop to next valid version and postfix that with `-dev` (`0.18.0 -> 0.18.1-dev`).
* Create a PR against develop to update that branch.

204
docs/docker.md Normal file
View File

@@ -0,0 +1,204 @@
# 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).
### 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 folder in your home directory. Feel free to use a different folder and replace the folder 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

@@ -2,170 +2,214 @@
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.
**NOTICE:** Edge positioning is not compatible with dynamic whitelist. it overrides dynamic whitelist.
**NOTICE2:** Edge won't consider anything else than buy/sell/stoploss signals. So trailing stoploss, ROI, and everything else will be ignored in its calculation.
!!! Warning
Edge positioning is not compatible with dynamic whitelist. If enabled, it overrides the dynamic whitelist option.
## Table of Contents
- [Introduction](#introduction)
- [How does it work?](#how-does-it-work?)
- [Configurations](#configurations)
- [Running Edge independently](#running-edge-independently)
!!! Note
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) / (Number of losing 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
python3 freqtrade edge
```
An example of its output:
@@ -189,28 +233,31 @@ 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
python3 freqtrade edge --refresh-pairs-cached
```
### Precising stoploss range
```bash
python3 ./freqtrade/main.py edge --stoplosses=-0.01,-0.1,-0.001 #min,max,step
python3 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
python3 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,71 +1,89 @@
# freqtrade FAQ
# Freqtrade FAQ
### Freqtrade commons
#### 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
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 understand your disappointment but unfortunately 12 trades is just
not enough to say anything. If you run backtesting, you can see that our
current algorithm does leave you on the plus side, but that is after
thousands of trades and even there, you will be left with losses on
specific coins that you have traded tens if not hundreds of times. We
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
I understand your disappointment but unfortunately 12 trades is just
not enough to say anything. If you run backtesting, you can see that our
current algorithm does leave you on the plus side, but that is after
thousands of trades and even there, you will be left with losses on
specific coins that you have traded tens if not hundreds of times. We
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
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
That's great. We have a nice backtesting and hyperoptimizing setup. See
the tutorial [here|Testing-new-strategies-with-Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands).
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.
You can use the `/forcesell all` command from Telegram.
### 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
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
### 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
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
python3 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 python3 freqtrade hyperopt -e 100; done
```
#### Why it is so long to run hyperopt?
Finding a great Hyperopt results takes time.
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.
Did you run 100 000 evals? Congrats, you've done roughly 1 / 100 000 th
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.
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

@@ -1,69 +1,92 @@
# Hyperopt
This page explains how to tune your strategy by finding the optimal
parameters, a process called hyperparameter optimization. The bot uses several
This page explains how to tune your strategy by finding the optimal
parameters, a process called hyperparameter optimization. The bot uses several
algorithms included in the `scikit-optimize` package to accomplish this. The
search will burn all your CPU cores, make your laptop sound like a fighter jet
and still take a long time.
*Note:* Hyperopt will crash when used with only 1 CPU Core as found out in [Issue #1133](https://github.com/freqtrade/freqtrade/issues/1133)
## Table of Contents
- [Prepare your Hyperopt](#prepare-hyperopt)
- [Configure your Guards and Triggers](#configure-your-guards-and-triggers)
- [Solving a Mystery](#solving-a-mystery)
- [Adding New Indicators](#adding-new-indicators)
- [Execute Hyperopt](#execute-hyperopt)
- [Understand the hyperopt result](#understand-the-hyperopt-result)
!!! Bug
Hyperopt will crash when used with only 1 CPU Core as found out in [Issue #1133](https://github.com/freqtrade/freqtrade/issues/1133)
## Prepare Hyperopting
Before we start digging in Hyperopt, we recommend you to take a look at
an example hyperopt file located into [user_data/hyperopts/](https://github.com/gcarq/freqtrade/blob/develop/user_data/hyperopts/test_hyperopt.py)
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/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.
* 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
### 1. Install a Custom Hyperopt File
This is very simple. Put your hyperopt file into the folder
`user_data/hyperopts`.
Let assume you want a hyperopt file `awesome_hyperopt.py`:
1. Copy the file `user_data/hyperopts/sample_hyperopt.py` into `user_data/hyperopts/awesome_hyperopt.py`
Put your hyperopt file into the folder`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`
### 2. Configure your Guards and Triggers
There are two places you need to change in your hyperopt file to add a
new buy hyperopt for testing:
- Inside [populate_buy_trend()](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/test_hyperopt.py#L230-L251).
- Inside [indicator_space()](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/test_hyperopt.py#L207-L223).
There are two places you need to change in your hyperopt file to add a new buy hyperopt for testing:
- Inside `indicator_space()` - the parameters hyperopt shall be optimizing.
- Inside `populate_buy_trend()` - applying the parameters.
There you have two different types of indicators: 1. `guards` and 2. `triggers`.
1. Guards are conditions like "never buy if ADX < 10", or never buy if
current price is over EMA10.
2. Triggers are ones that actually trigger buy in specific moment, like
"buy when EMA5 crosses over EMA10" or "buy when close price touches lower
bollinger band".
1. Guards are conditions like "never buy if ADX < 10", or never buy if current price is over EMA10.
2. Triggers are ones that actually trigger buy in specific moment, like "buy when EMA5 crosses over EMA10" or "buy when close price touches lower bollinger band".
Hyperoptimization will, for each eval round, pick one trigger and possibly
multiple guards. The constructed strategy will be something like
"*buy exactly when close price touches lower bollinger band, BUT only if
Hyperoptimization will, for each eval round, pick one trigger and possibly
multiple guards. The constructed strategy will be something like
"*buy exactly when close price touches lower bollinger band, BUT only if
ADX > 10*".
If you have updated the buy strategy, ie. changed the contents of
`populate_buy_trend()` method you have to update the `guards` and
`populate_buy_trend()` method you have to update the `guards` and
`triggers` hyperopts must use.
#### Sell optimization
Similar to the buy-signal above, sell-signals can also be optimized.
Place the corresponding settings into the following methods
* Inside `sell_indicator_space()` - the parameters hyperopt shall be optimizing.
* Inside `populate_sell_trend()` - applying the parameters.
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
Bands to trigger your buys. And you also wonder should you use RSI or ADX to
help with those buy decisions. If you decide to use RSI or ADX, which values
should I use for them? So let's use hyperparameter optimization to solve this
Let's say you are curious: should you use MACD crossings or lower Bollinger
Bands to trigger your buys. And you also wonder should you use RSI or ADX to
help with those buy decisions. If you decide to use RSI or ADX, which values
should I use for them? So let's use hyperparameter optimization to solve this
mystery.
We will start by defining a search space:
```
```python
def indicator_space() -> List[Dimension]:
"""
Define your Hyperopt space for searching strategy parameters
@@ -77,8 +100,8 @@ We will start by defining a search space:
]
```
Above definition says: I have five parameters I want you to randomly combine
to find the best combination. Two of them are integer values (`adx-value`
Above definition says: I have five parameters I want you to randomly combine
to find the best combination. Two of them are integer values (`adx-value`
and `rsi-value`) and I want you test in the range of values 20 to 40.
Then we have three category variables. First two are either `True` or `False`.
We use these to either enable or disable the ADX and RSI guards. The last
@@ -86,7 +109,7 @@ one we call `trigger` and use it to decide which buy trigger we want to use.
So let's write the buy strategy using these values:
```
``` python
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
conditions = []
# GUARDS AND TRENDS
@@ -96,16 +119,18 @@ So let's write the buy strategy using these values:
conditions.append(dataframe['rsi'] < params['rsi-value'])
# TRIGGERS
if params['trigger'] == 'bb_lower':
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
if params['trigger'] == 'macd_cross_signal':
conditions.append(qtpylib.crossed_above(
dataframe['macd'], dataframe['macdsignal']
))
if 'trigger' in params:
if params['trigger'] == 'bb_lower':
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
if params['trigger'] == 'macd_cross_signal':
conditions.append(qtpylib.crossed_above(
dataframe['macd'], dataframe['macdsignal']
))
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
return dataframe
@@ -117,12 +142,12 @@ with different value combinations. It will then use the given historical data an
buys based on the buy signals generated with the above function and based on the results
it will end with telling you which paramter combination produced the best profits.
The search for best parameters starts with a few random combinations and then uses a
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`.
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
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`.
## Execute Hyperopt
@@ -133,15 +158,19 @@ Because hyperopt tries a lot of combinations to find the best parameters it will
We strongly recommend to use `screen` or `tmux` to prevent any connection loss.
```bash
python3 ./freqtrade/main.py -s <strategyname> --hyperopt <hyperoptname> -c config.json hyperopt -e 5000
python3 freqtrade -c config.json hyperopt --customhyperopt <hyperoptname> -e 5000 --spaces all
```
Use `<strategyname>` and `<hyperoptname>` as the names of the custom strategy
(only required for generating sells) and the custom hyperopt used.
Use `<hyperoptname>` as the name of the custom hyperopt used.
The `-e` flag will set how many evaluations hyperopt will do. We recommend
running at least several thousand evaluations.
The `--spaces all` flag determines that all possible parameters should be optimized. Possibilities are listed below.
!!! 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.
### Execute Hyperopt with Different Ticker-Data Source
If you would like to hyperopt parameters using an alternate ticker data that
@@ -155,21 +184,22 @@ you want to use. The last N ticks/timeframes will be used.
Example:
```bash
python3 ./freqtrade/main.py hyperopt --timerange -200
python3 freqtrade hyperopt --timerange -200
```
### Running Hyperopt with Smaller Search Space
Use the `--spaces` argument to limit the search space used by hyperopt.
Letting Hyperopt optimize everything is a huuuuge search space. Often it
might make more sense to start by just searching for initial buy algorithm.
Or maybe you just want to optimize your stoploss or roi table for that awesome
Letting Hyperopt optimize everything is a huuuuge search space. Often it
might make more sense to start by just searching for initial buy algorithm.
Or maybe you just want to optimize your stoploss or roi table for that awesome
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`
@@ -183,25 +213,29 @@ 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:
{'adx-value': 44, 'rsi-value': 29, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'bb_lower'}
{ 'adx-value': 44,
'rsi-value': 29,
'adx-enabled': False,
'rsi-enabled': True,
'trigger': 'bb_lower'}
```
You should understand this result like:
- The buy trigger that worked best was `bb_lower`.
- You should not use ADX because `adx-enabled: False`)
- You should not use ADX because `adx-enabled: False`)
- You should **consider** using the RSI indicator (`rsi-enabled: True` and the best value is `29.0` (`rsi-value: 29.0`)
You have to look inside your strategy file into `buy_strategy_generator()`
You have to look inside your strategy file into `buy_strategy_generator()`
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:
```
(dataframe['rsi'] < 29.0)
```
Translating your whole hyperopt result as the new buy-signal
Translating your whole hyperopt result as the new buy-signal
would then look like:
```python
@@ -223,9 +257,24 @@ If you are optimizing ROI, you're result will look as follows and include a ROI
Best result:
135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins.
with values:
{'adx-value': 44, 'rsi-value': 29, '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}
{ 'adx-value': 44,
'rsi-value': 29,
'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
}
ROI table:
{0: 0.10674752302642071, 21: 0.09158372701087236, 78: 0.03634636907306948, 118: 0}
{ 0: 0.10674752302642071,
21: 0.09158372701087236,
78: 0.03634636907306948,
118: 0}
```
This would translate to the following ROI table:
@@ -242,12 +291,18 @@ This would translate to the following ROI table:
### Validate backtest result
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).
To archive the same results (number of trades, ...) than during hyperopt, please use the command line flags `--disable-max-market-positions` and `--enable-position-stacking` for backtesting.
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.
This configuration is the default in hyperopt for performance reasons.
You can overwrite position stacking in the configuration by explicitly 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#L191).
Enabling the market-position for hyperopt is currently not possible.
!!! 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.
## Next Step
Now you have a perfect bot and want to control it from Telegram. Your
next step is to learn the [Telegram usage](https://github.com/freqtrade/freqtrade/blob/develop/docs/telegram-usage.md).
next step is to learn the [Telegram usage](telegram-usage.md).

BIN
docs/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,39 +1,71 @@
# freqtrade documentation
# Freqtrade
[![Build Status](https://travis-ci.org/freqtrade/freqtrade.svg?branch=develop)](https://travis-ci.org/freqtrade/freqtrade)
[![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
[![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](https://codeclimate.com/github/freqtrade/freqtrade/maintainability)
Welcome to freqtrade documentation. Please feel free to contribute to
this documentation if you see it became outdated by sending us a
Pull-request. Do not hesitate to reach us on
[Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE)
if you do not find the answer to your questions.
<!-- Place this tag where you want the button to render. -->
<a class="github-button" href="https://github.com/freqtrade/freqtrade" data-icon="octicon-star" data-size="large" aria-label="Star freqtrade/freqtrade on GitHub">Star</a>
<!-- Place this tag where you want the button to render. -->
<a class="github-button" href="https://github.com/freqtrade/freqtrade/fork" data-icon="octicon-repo-forked" data-size="large" aria-label="Fork freqtrade/freqtrade on GitHub">Fork</a>
<!-- Place this tag where you want the button to render. -->
<a class="github-button" href="https://github.com/freqtrade/freqtrade/archive/master.zip" data-icon="octicon-cloud-download" data-size="large" aria-label="Download freqtrade/freqtrade on GitHub">Download</a>
<!-- Place this tag where you want the button to render. -->
<a class="github-button" href="https://github.com/freqtrade" data-size="large" aria-label="Follow @freqtrade on GitHub">Follow @freqtrade</a>
## Introduction
Freqtrade is a cryptocurrency trading bot written in Python.
## Table of Contents
!!! Danger "DISCLAIMER"
This software is for educational purposes only. Do not risk money which you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.
- [Pre-requisite](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md)
- [Setup your Bittrex account](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md#setup-your-bittrex-account)
- [Setup your Telegram bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md#setup-your-telegram-bot)
- [Bot Installation](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md)
- [Install with Docker (all platforms)](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md#docker)
- [Install on Linux Ubuntu](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md#21-linux---ubuntu-1604)
- [Install on MacOS](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md#23-macos-installation)
- [Install on Windows](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md#windows)
- [Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)
- [Bot usage (Start your bot)](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md)
- [Bot commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#bot-commands)
- [Backtesting commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#backtesting-commands)
- [Hyperopt commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands)
- [Edge commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#edge-commands)
- [Bot Optimization](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md)
- [Change your strategy](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md#change-your-strategy)
- [Add more Indicator](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md#add-more-indicator)
- [Test your strategy with Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md)
- [Edge positioning](https://github.com/freqtrade/freqtrade/blob/develop/docs/edge.md)
- [Find optimal parameters with Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
- [Control the bot with telegram](https://github.com/freqtrade/freqtrade/blob/develop/docs/telegram-usage.md)
- [Receive notifications via webhook](https://github.com/freqtrade/freqtrade/blob/develop/docs/webhook-config.md)
- [Contribute to the project](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
- [How to contribute](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
- [Run tests & Check PEP8 compliance](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
- [FAQ](https://github.com/freqtrade/freqtrade/blob/develop/docs/faq.md)
- [SQL cheatsheet](https://github.com/freqtrade/freqtrade/blob/develop/docs/sql_cheatsheet.md)
- [Sandbox Testing](https://github.com/freqtrade/freqtrade/blob/develop/docs/sandbox-testing.md)
- [Developer Docs](https://github.com/freqtrade/freqtrade/blob/develop/docs/developer.md)
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 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 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
### 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
- 1GB disk space
- 2vCPU
### Software requirements
- Python 3.6.x
- 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/enQtNjU5ODcwNjI1MDU3LWEyODBiNzkzNzcyNzU0MWYyYzE5NjIyOTQxMzBmMGUxOTIzM2YyN2Y4NWY1YTEwZDgwYTRmMzE2NmM5ZmY2MTg) to join Slack channel.
## Ready to try?
Begin by reading our installation guide [here](installation).

View File

@@ -2,23 +2,31 @@
This page explains how to prepare your environment for running the bot.
To understand how to set up the bot please read the [Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md) page.
## Prerequisite
## Table of Contents
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).
* [Table of Contents](#table-of-contents)
* [Easy Installation - Linux Script](#easy-installation---linux-script)
* [Automatic Installation - Docker](#automatic-installation---docker)
* [Custom Linux MacOS Installation](#custom-installation)
- [Requirements](#requirements)
- [Linux - Ubuntu 16.04](#linux---ubuntu-1604)
- [MacOS](#macos)
- [Setup Config and virtual env](#setup-config-and-virtual-env)
* [Windows](#windows)
- [Setup your exchange account](#setup-your-exchange-account)
<!-- /TOC -->
### Setup your exchange account
------
You will need to create API Keys (Usually you get `key` and `secret`) from the Exchange website and insert this into the appropriate fields in the configuration or when asked by the installation script.
## Quick start
Freqtrade provides a Linux/MacOS script to install all dependencies and help you to configure the bot.
```bash
git clone git@github.com:freqtrade/freqtrade.git
cd freqtrade
git checkout develop
./setup.sh --install
```
!!! Note
Windows installation is explained [here](#windows).
## Easy Installation - Linux Script
@@ -33,7 +41,7 @@ usage:
-c,--config Easy config generator (Will override your existing file).
```
### --install
** --install **
This script will install everything you need to run the bot:
@@ -43,198 +51,24 @@ This script will install everything you need to run the bot:
This script is a combination of `install script` `--reset`, `--config`
### --update
** --update **
Update parameter will pull the last version of your current branch and update your virtualenv.
### --reset
** --reset **
Reset parameter will hard reset your branch (only if you are on `master` or `develop`) and recreate your virtualenv.
### --config
** --config **
Config parameter is a `config.json` configurator. This script will ask you questions to setup your bot and create your `config.json`.
------
## 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
```bash
git clone 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](https://github.com/freqtrade/freqtrade/blob/develop/docs/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](https://github.com/freqtrade/freqtrade/blob/develop/docs/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.
OS Specific steps are listed first, the [Common](#common) section below is necessary for all systems.
### Requirements
@@ -262,7 +96,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).
@@ -274,7 +107,7 @@ 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 .
```
@@ -286,7 +119,7 @@ python3 -m pip install -e .
brew install python3 git wget
```
### common
### Common
#### 1. Install TA-Lib
@@ -304,11 +137,13 @@ cd ..
rm -rf ./ta-lib*
```
*Note*: An already downloaded version of ta-lib is included in the repository, as the sourceforge.net source seems to have problems frequently.
!!! Note
An already downloaded version of ta-lib is included in the repository, as the sourceforge.net source seems to have problems frequently.
#### 2. Setup your Python virtual environment (virtualenv)
*Note*: This step is optional but strongly recommended to keep your system organized
!!! Note
This step is optional but strongly recommended to keep your system organized
```bash
python3 -m venv .env
@@ -337,7 +172,7 @@ cd freqtrade
cp config.json.example config.json
```
> *To edit the config please refer to [Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md).*
> *To edit the config please refer to [Bot Configuration](configuration.md).*
#### 5. Install python dependencies
@@ -352,10 +187,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
python3.6 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
@@ -373,11 +208,23 @@ 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.
------
## 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.
@@ -396,7 +243,7 @@ copy paste `config.json` to ``\path\freqtrade-develop\freqtrade`
Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows).
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of inofficial precompiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which needs to be downloaded and installed using `pip install TA_Lib0.4.17cp36cp36mwin32.whl` (make sure to use the version matching your python version)
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial precompiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which needs to be downloaded and installed using `pip install TA_Lib0.4.17cp36cp36mwin32.whl` (make sure to use the version matching your python version)
```cmd
>cd \path\freqtrade-develop
@@ -421,9 +268,9 @@ 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.
---
Now you have an environment ready, the next step is
[Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)...
[Bot Configuration](configuration.md).

52
docs/partials/header.html Normal file
View File

@@ -0,0 +1,52 @@
<header class="md-header" data-md-component="header">
<nav class="md-header-nav md-grid">
<div class="md-flex">
<div class="md-flex__cell md-flex__cell--shrink">
<a href="{{ config.site_url | default(nav.homepage.url, true) | url }}" title="{{ config.site_name }}"
class="md-header-nav__button md-logo">
{% if config.theme.logo.icon %}
<i class="md-icon">{{ config.theme.logo.icon }}</i>
{% else %}
<img src="{{ config.theme.logo | url }}" width="24" height="24">
{% endif %}
</a>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
<label class="md-icon md-icon--menu md-header-nav__button" for="__drawer"></label>
</div>
<div class="md-flex__cell md-flex__cell--stretch">
<div class="md-flex__ellipsis md-header-nav__title" data-md-component="title">
{% block site_name %}
{% if config.site_name == page.title %}
{{ config.site_name }}
{% else %}
<span class="md-header-nav__topic">
{{ config.site_name }}
</span>
<span class="md-header-nav__topic">
{{ page.title }}
</span>
{% endif %}
{% endblock %}
</div>
</div>
<div class="md-flex__cell md-flex__cell--shrink">
{% block search_box %}
{% if "search" in config["plugins"] %}
<label class="md-icon md-icon--search md-header-nav__button" for="__search"></label>
{% include "partials/search.html" %}
{% endif %}
{% endblock %}
</div>
{% if config.repo_url %}
<div class="md-flex__cell md-flex__cell--shrink">
<div class="md-header-nav__source">
{% include "partials/source.html" %}
</div>
</div>
{% endif %}
</div>
</nav>
<!-- Place this tag in your head or just before your close body tag. -->
<script async defer src="https://buttons.github.io/buttons.js"></script>
</header>

View File

@@ -1,62 +1,83 @@
# Plotting
This page explains how to plot prices, indicator, profits.
## Table of Contents
- [Plot price and indicators](#plot-price-and-indicators)
- [Plot profit](#plot-profit)
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 pair] [--live]
``` bash
python3 script/plot_dataframe.py [-h] [-p pairs] [--live]
```
Example
```
python scripts/plot_dataframe.py -p BTC/ETH
``` bash
python3 scripts/plot_dataframe.py -p BTC/ETH
```
The `-p` pair argument, can be used to specify what
pair 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:
``` bash
python3 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 --live
```
To plot a timerange (to zoom in):
``` bash
python3 scripts/plot_dataframe.py -p BTC/ETH --timerange=100-200
```
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_data/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
@@ -66,7 +87,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.
@@ -75,13 +96,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,48 +0,0 @@
# Pre-requisite
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).
## Table of Contents
- [Setup your Bittrex account](#setup-your-bittrex-account)
- [Backtesting commands](#setup-your-telegram-bot)
## Setup your Bittrex 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.
### 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.g "`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.g "`My_own_freqtrade_bot`")**
**1.5. Father bot will return you the token (API key)**
Copy it and keep it you will use it for the config parameter `token`.
*BotFather response:*
```
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`.**

View File

@@ -0,0 +1 @@
mkdocs-material==3.1.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,17 +1,36 @@
# 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:
1. static stop loss, defined in either the strategy or configuration
2. trailing stop loss, defined in the configuration
3. trailing stop loss, custom positive loss, defined in configuration
1. static stop loss, defined in either the strategy or configuration.
2. trailing stop loss, defined in the configuration.
3. trailing stop loss, custom positive loss, defined in configuration.
!!! Note
All stoploss properties can be configured in either Strategy or configuration. Configuration values override strategy values.
Those stoploss modes can be *on exchange* or *off exchange*. If the stoploss is *on exchange* it means a stoploss limit order is placed on the exchange immediately after buy order happens successfuly. This will protect you against sudden crashes in market as the order will be in the queue immediately and if market goes down then the order has more chance of being fulfilled.
In case of stoploss on exchange there is another parameter called `stoploss_on_exchange_interval`. This configures the interval in seconds at which the bot will check the stoploss and update it if necessary. As an example in case of trailing stoploss if the order is on the exchange and the market is going up then the bot automatically cancels the previous stoploss order and put a new one with a stop value higher than previous one. It is clear that the bot cannot do it every 5 seconds otherwise it gets banned. So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute).
!!! 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:
@@ -44,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

@@ -1,28 +1,8 @@
# Bot Optimization
# Optimization
This page explains where to customize your strategies, and add new
indicators.
## Table of Contents
- [Install a custom strategy file](#install-a-custom-strategy-file)
- [Customize your strategy](#change-your-strategy)
- [Anatomy of a strategy](#anatomy-of-a-strategy)
- [Customize indicators](#customize-indicators)
- [Buy signal rules](#buy-signal-rules)
- [Sell signal rules](#sell-signal-rules)
- [Minimal ROI](#minimal-roi)
- [Stoploss](#stoploss)
- [Ticker interval](#ticker-interval)
- [Metadata dict](#metadata-dict)
- [Where is the default strategy](#where-is-the-default-strategy)
- [Specify custom strategy location](#specify-custom-strategy-location)
- [Further strategy ideas](#further-strategy-ideas)
- [Where is the default strategy](#where-is-the-default-strategy)
Since the version `0.16.0` the bot allows using custom strategy file.
## Install a custom strategy file
This is very simple. Copy paste your strategy file into the folder
@@ -34,7 +14,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
python3 freqtrade --strategy AwesomeStrategy
```
## Change your strategy
@@ -60,13 +40,25 @@ A strategy file contains all the information needed to build a good strategy:
The bot also include a sample strategy called `TestStrategy` you can update: `user_data/strategies/test_strategy.py`.
You can test it with the parameter: `--strategy TestStrategy`
``` bash
python3 ./freqtrade/main.py --strategy AwesomeStrategy
```bash
python3 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
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.
@@ -118,10 +110,10 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame
return dataframe
```
#### Want more indicator examples
Look into the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py).
Then uncomment indicators you need.
!!! Note "Want more indicator examples?"
Look into the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py).<br/>
Then uncomment indicators you need.
### Buy signal rules
@@ -187,7 +179,7 @@ This dict defines the minimal Return On Investment (ROI) a trade should reach be
It is of the following format, with the dict key (left side of the colon) being the minutes passed since the trade opened, and the value (right side of the colon) being the percentage.
```python
```python
minimal_roi = {
"40": 0.0,
"30": 0.01,
@@ -199,10 +191,9 @@ minimal_roi = {
The above configuration would therefore mean:
- Sell whenever 4% profit was reached
- Sell after 20 minutes when 2% profit was reached
- Sell after 20 minutes when 2% profit was reached
- Sell after 30 minutes when 1% profit was reached
- Sell after 40 minutes when the trade is non-loosing (no profit)
- Sell when 2% profit was reached (in effect after 20 minutes)
- Sell when 1% profit was reached (in effect after 30 minutes)
- Sell when trade is non-loosing (in effect after 40 minutes)
The calculation does include fees.
@@ -227,9 +218,12 @@ stoploss = -0.10
```
This would signify a stoploss of -10%.
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 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](configuration.md#understand-order_types).
### Ticker interval
@@ -237,12 +231,166 @@ This is the set of candles the bot should download and use for the analysis.
Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported by your exchange should work.
Please note that the same buy/sell signals may work with one interval, but not the other.
This setting is accessible within the strategy by using `self.ticker_interval`.
### Metadata dict
The metadata-dict (available for `populate_buy_trend`, `populate_sell_trend`, `populate_indicators`) contains additional information.
Currently this is `pair`, which can be accessed using `metadata['pair']` - and will return a pair in the format `XRP/BTC`.
The Metadata-dict should not be modified and does not persist information across multiple calls.
Instead, have a look at the section [Storing information](#Storing-information)
### Storing information
Storing information can be accomplished by crating a new dictionary within the strategy class.
The name of the variable can be choosen at will, but should be prefixed with `cust_` to avoid naming collisions with predefined strategy variables.
```python
class Awesomestrategy(IStrategy):
# Create custom dictionary
cust_info = {}
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Check if the entry already exists
if "crosstime" in self.cust_info[metadata["pair"]:
self.cust_info[metadata["pair"]["crosstime"] += 1
else:
self.cust_info[metadata["pair"]["crosstime"] = 1
```
!!! 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
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.
All methods return `None` in case of failure (do not raise an exception).
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
- `runmode` - Property containing the current runmode.
#### ohlcv / historic_ohlcv
``` python
if self.dp:
if self.dp.runmode in ('live', 'dry_run'):
if (f'{self.stake_currency}/BTC', self.ticker_interval) in self.dp.available_pairs:
data_eth = self.dp.ohlcv(pair='{self.stake_currency}/BTC',
ticker_interval=self.ticker_interval)
else:
# Get historic ohlcv data (cached on disk).
history_eth = self.dp.historic_ohlcv(pair='{self.stake_currency}/BTC',
ticker_interval='1h')
```
!!! Warning Warning about backtesting
Be carefull when using dataprovider in backtesting. `historic_ohlcv()` 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
if self.dp:
for pair, ticker in self.dp.available_pairs:
print(f"available {pair}, {ticker}")
```
#### Get data for non-tradeable pairs
Data for additional, informative pairs (reference pairs) can be beneficial for some strategies.
Ohlcv data for these pairs will be downloaded as part of the regular whitelist refresh process and is available via `DataProvider` just as other pairs (see above).
These parts will **not** be traded unless they are also specified in the pair whitelist, or have been selected by Dynamic Whitelisting.
The pairs need to be specified as tuples in the format `("pair", "interval")`, with pair as the first and time interval as the second argument.
Sample:
``` python
def informative_pairs(self):
return [("ETH/USDT", "5m"),
("BTC/TUSD", "15m"),
]
```
!!! 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
to avoid hammering the exchange with too many requests and risk beeing blocked.
### Additional data - Wallets
The strategy provides access to the `Wallets` object. This contains the current balances on the exchange.
!!! Note
Wallets is not available during backtesting / hyperopt.
Please always check if `Wallets` is available to avoid failures during backtesting.
``` python
if self.wallets:
free_eth = self.wallets.get_free('ETH')
used_eth = self.wallets.get_used('ETH')
total_eth = self.wallets.get_total('ETH')
```
#### Possible options for Wallets
- `get_free(asset)` - currently available balance to trade
- `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
@@ -253,7 +401,7 @@ The default buy strategy is located in the file
If you want to use a strategy from a different folder you can pass `--strategy-path`
```bash
python3 ./freqtrade/main.py --strategy AwesomeStrategy --strategy-path /some/folder
python3 freqtrade --strategy AwesomeStrategy --strategy-path /some/folder
```
### Further strategy ideas
@@ -262,9 +410,9 @@ 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
Now you have a perfect strategy you probably want to backtest it.
Your next step is to learn [How to use the Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md).
Your next step is to learn [How to use the Backtesting](backtesting.md).

View File

@@ -1,21 +1,57 @@
# Telegram usage
This page explains how to command your bot with Telegram.
## Setup your Telegram bot
## Pre-requisite
To control your bot with Telegram, you need first to
[set up a Telegram bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.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
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`.
| 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
@@ -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
@@ -40,44 +79,56 @@ Below, example of Telegram message you will receive for each command.
### /stop
> `Stopping trader ...`
> `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`
> **Current Pair:** CVC/BTC
> **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.
```
ID Pair Since Profit
---- -------- ------- --------
ID Pair Since Profit
---- -------- ------- --------
67 SC/BTC 1 d 13.33%
123 CVC/BTC 1 h 12.95%
```
## /count
### /count
Return the number of trades used and available.
```
current max
--------- -----
2 10
--------- -----
2 10
```
## /profit
### /profit
Return a summary of your profit/loss and performance.
@@ -87,35 +138,37 @@ Return a summary of your profit/loss and performance.
> **ROI:** All trades
> ∙ `0.00255280 BTC (143.43%)`
> ∙ `33.095 EUR`
>
>
> **Total Trade Count:** `138`
> **First Trade opened:** `3 days ago`
> **Latest Trade opened:** `2 minutes ago`
> **Avg. Duration:** `2:33:45`
> **Best Performing:** `PAY/BTC: 50.23%`
> **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:
> 1. `RCN/BTC 57.77%`
> 2. `PAY/BTC 56.91%`
> 3. `VIB/BTC 47.07%`
> 4. `SALT/BTC 30.24%`
> 5. `STORJ/BTC 27.24%`
> ...
> Performance:
> 1. `RCN/BTC 57.77%`
> 2. `PAY/BTC 56.91%`
> 3. `VIB/BTC 47.07%`
> 4. `SALT/BTC 30.24%`
> 5. `STORJ/BTC 27.24%`
> ...
## /balance
### /balance
Return the balance of all crypto-currency your have on the exchange.
@@ -127,23 +180,54 @@ Return the balance of all crypto-currency your have on the exchange.
> **Currency:** CVC
> **Available:** 86.64180098
> **Balance:** 86.64180098
> **Pending:** 0.0
> **Pending:** 0.0
## /daily <n>
### /daily <n>
Per default `/daily` will return the 7 last days.
Per default `/daily` will return the 7 last days.
The example below if for `/daily 3`:
> **Daily Profit over the last 3 days:**
```
Day Profit BTC Profit USD
---------- -------------- ------------
2018-01-03 0.00224175 BTC 29,142 USD
2018-01-02 0.00033131 BTC 4,307 USD
Day Profit BTC Profit USD
---------- -------------- ------------
2018-01-03 0.00224175 BTC 29,142 USD
2018-01-02 0.00033131 BTC 4,307 USD
2018-01-01 0.00269130 BTC 34.986 USD
```
## /version
### /whitelist
> **Version:** `0.14.3`
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

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.0'
__version__ = '2019.6'
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

@@ -6,9 +6,7 @@ import argparse
import os
import re
from typing import List, NamedTuple, Optional
import arrow
from freqtrade import __version__, constants
@@ -29,13 +27,14 @@ class Arguments(object):
Arguments Class. Manage the arguments received by the cli
"""
def __init__(self, args: List[str], description: str) -> None:
def __init__(self, args: Optional[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.common_options()
self.main_options()
self._build_subcommands()
def get_parsed_arg(self) -> argparse.Namespace:
@@ -49,220 +48,240 @@ class Arguments(object):
return self.parsed_arg
def parse_args(self) -> argparse.Namespace:
def parse_args(self, no_default_config: bool = False) -> 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)
if not no_default_config and parsed_arg.config is None:
parsed_arg.config = [constants.DEFAULT_CONFIG]
return parsed_arg
def common_args_parser(self) -> None:
def common_options(self) -> None:
"""
Parses given common arguments and returns them as a parsed object.
Parses arguments that are common for the main Freqtrade, all subcommands and scripts.
"""
self.parser.add_argument(
parser = self.parser
parser.add_argument(
'-v', '--verbose',
help='verbose mode (-vv for more, -vvv to get all messages)',
help='Verbose mode (-vv for more, -vvv to get all messages).',
action='count',
dest='loglevel',
default=0,
)
self.parser.add_argument(
parser.add_argument(
'--logfile',
help='Log to the file specified.',
dest='logfile',
metavar='FILE',
)
parser.add_argument(
'--version',
action='version',
version=f'%(prog)s {__version__}'
)
self.parser.add_argument(
parser.add_argument(
'-c', '--config',
help='specify configuration file (default: %(default)s)',
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.',
dest='config',
default='config.json',
type=str,
action='append',
metavar='PATH',
)
self.parser.add_argument(
parser.add_argument(
'-d', '--datadir',
help='path to backtest data',
help='Path to backtest data.',
dest='datadir',
default=None,
type=str,
metavar='PATH',
)
self.parser.add_argument(
def main_options(self) -> None:
"""
Parses arguments for the main Freqtrade.
"""
parser = self.parser
parser.add_argument(
'-s', '--strategy',
help='specify strategy class name (default: %(default)s)',
help='Specify strategy class name (default: `%(default)s`).',
dest='strategy',
default='DefaultStrategy',
type=str,
metavar='NAME',
)
self.parser.add_argument(
parser.add_argument(
'--strategy-path',
help='specify additional strategy lookup 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(
parser.add_argument(
'--dynamic-whitelist',
help='dynamically generate and update whitelist'
' based on 24h BaseVolume (default: %(const)s)'
' DEPRECATED.',
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(
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)',
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).',
dest='db_url',
type=str,
metavar='PATH',
)
parser.add_argument(
'--sd-notify',
help='Notify systemd service manager.',
action='store_true',
dest='sd_notify',
)
@staticmethod
def backtesting_options(parser: argparse.ArgumentParser) -> None:
def common_optimize_options(self, subparser: argparse.ArgumentParser = None) -> None:
"""
Parses given arguments for Backtesting scripts.
Parses arguments common for Backtesting, Edge and Hyperopt modules.
:param parser:
"""
parser = subparser or self.parser
parser.add_argument(
'-i', '--ticker-interval',
help='Specify ticker interval (`1m`, `5m`, `30m`, `1h`, `1d`).',
dest='ticker_interval',
)
parser.add_argument(
'--timerange',
help='Specify what timerange of data to use.',
dest='timerange',
)
parser.add_argument(
'--max_open_trades',
help='Specify max_open_trades to use.',
type=int,
dest='max_open_trades',
)
parser.add_argument(
'--stake_amount',
help='Specify stake_amount.',
type=float,
dest='stake_amount',
)
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 optimization commands with '
'up-to-date data.',
action='store_true',
dest='refresh_pairs',
)
def backtesting_options(self, subparser: argparse.ArgumentParser = None) -> None:
"""
Parses given arguments for Backtesting module.
"""
parser = subparser or self.parser
parser.add_argument(
'--eps', '--enable-position-stacking',
help='Allow buying the same pair multiple times (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)',
'(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',
help='Use 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 '
help='Provide a comma-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, '
'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',
'(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,
help='Export backtest results, argument are: trades. '
'Example: `--export=trades`',
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,
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_data/backtest_today.json`',
default=os.path.join('user_data', 'backtest_data', 'backtest-result.json'),
dest='exportfilename',
metavar='PATH',
)
@staticmethod
def edge_options(parser: argparse.ArgumentParser) -> None:
def edge_options(self, subparser: argparse.ArgumentParser = None) -> None:
"""
Parses given arguments for Backtesting scripts.
Parses given arguments for Edge module.
"""
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 = subparser or self.parser
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,
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`',
dest='stoploss_range',
)
@staticmethod
def optimizer_shared_options(parser: argparse.ArgumentParser) -> None:
def hyperopt_options(self, subparser: argparse.ArgumentParser = None) -> None:
"""
Parses given common arguments for Backtesting and Hyperopt scripts.
:param parser:
:return:
Parses given arguments for Hyperopt module.
"""
parser.add_argument(
'-i', '--ticker-interval',
help='specify ticker interval (1m, 5m, 30m, 1h, 1d)',
dest='ticker_interval',
type=str,
)
parser = subparser or self.parser
parser.add_argument(
'--timerange',
help='specify what timerange of data to use.',
default=None,
type=str,
dest='timerange',
'--customhyperopt',
help='Specify hyperopt class name (default: `%(default)s`).',
dest='hyperopt',
default=constants.DEFAULT_HYPEROPT,
metavar='NAME',
)
@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)',
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)',
'(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)',
help='Specify number of epochs (default: %(default)d).',
dest='epochs',
default=constants.HYPEROPT_EPOCH,
type=int,
@@ -270,41 +289,97 @@ class Arguments(object):
)
parser.add_argument(
'-s', '--spaces',
help='Specify which parameters to hyperopt. Space separate list. \
Default: %(default)s',
choices=['all', 'buy', 'roi', 'stoploss'],
help='Specify which parameters to hyperopt. Space-separated list. '
'Default: `%(default)s`.',
choices=['all', 'buy', 'sell', 'roi', 'stoploss'],
default='all',
nargs='+',
dest='spaces',
)
parser.add_argument(
'--print-all',
help='Print all results, not only the best ones.',
action='store_true',
dest='print_all',
default=False
)
parser.add_argument(
'-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.',
dest='hyperopt_jobs',
default=-1,
type=int,
metavar='JOBS',
)
parser.add_argument(
'--random-state',
help='Set random state to some positive integer for reproducible hyperopt results.',
dest='hyperopt_random_state',
type=Arguments.check_int_positive,
metavar='INT',
)
parser.add_argument(
'--min-trades',
help="Set minimal desired number of trades for evaluations in the hyperopt "
"optimization path (default: 1).",
dest='hyperopt_min_trades',
default=1,
type=Arguments.check_int_positive,
metavar='INT',
)
def list_exchanges_options(self, subparser: argparse.ArgumentParser = None) -> None:
"""
Parses given arguments for the list-exchanges command.
"""
parser = subparser or self.parser
parser.add_argument(
'-1', '--one-column',
help='Print exchanges in one column.',
action='store_true',
dest='print_one_column',
)
def _build_subcommands(self) -> None:
"""
Builds and attaches all subcommands
Builds and attaches all subcommands.
:return: None
"""
from freqtrade.optimize import backtesting, hyperopt, edge_cli
from freqtrade.optimize import start_backtesting, start_hyperopt, start_edge
from freqtrade.utils import 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=backtesting.start)
self.optimizer_shared_options(backtesting_cmd)
backtesting_cmd = subparsers.add_parser('backtesting', help='Backtesting module.')
backtesting_cmd.set_defaults(func=start_backtesting)
self.common_optimize_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)
edge_cmd = subparsers.add_parser('edge', help='Edge module.')
edge_cmd.set_defaults(func=start_edge)
self.common_optimize_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)
hyperopt_cmd = subparsers.add_parser('hyperopt', help='Hyperopt module.')
hyperopt_cmd.set_defaults(func=start_hyperopt)
self.common_optimize_options(hyperopt_cmd)
self.hyperopt_options(hyperopt_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.list_exchanges_options(list_exchanges_cmd)
@staticmethod
def parse_timerange(text: Optional[str]) -> TimeRange:
"""
@@ -347,77 +422,105 @@ class Arguments(object):
return TimeRange(stype[0], stype[1], start, stop)
raise Exception('Incorrect syntax for timerange "%s"' % text)
def scripts_options(self) -> None:
@staticmethod
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
def common_scripts_options(self, subparser: argparse.ArgumentParser = None) -> None:
"""
Parses given arguments for scripts.
Parses arguments common for scripts.
"""
self.parser.add_argument(
'-p', '--pair',
help='Show profits for only this pairs. Pairs are comma-separated.',
dest='pair',
default=None
parser = subparser or self.parser
parser.add_argument(
'-p', '--pairs',
help='Show profits for only these pairs. Pairs are comma-separated.',
dest='pairs',
)
def testdata_dl_options(self) -> None:
def download_data_options(self) -> None:
"""
Parses given arguments for testdata download
Parses given arguments for testdata download script
"""
self.parser.add_argument(
parser = self.parser
parser.add_argument(
'--pairs-file',
help='File containing a list of pairs to download',
help='File containing a list of pairs to download.',
dest='pairs_file',
default=None,
metavar='PATH',
metavar='FILE',
)
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(
parser.add_argument(
'--days',
help='Download data for number of days',
help='Download data for given number of days.',
dest='days',
type=int,
type=Arguments.check_int_positive,
metavar='INT',
default=None
)
self.parser.add_argument(
parser.add_argument(
'--exchange',
help='Exchange name (default: %(default)s). Only valid if no config is provided',
help=f'Exchange name (default: `{constants.DEFAULT_EXCHANGE}`). '
f'Only valid if no config is provided.',
dest='exchange',
type=str,
default='bittrex'
)
self.parser.add_argument(
parser.add_argument(
'-t', '--timeframes',
help='Specify which tickers to download. Space separated list. \
Default: %(default)s',
help=f'Specify which tickers to download. Space-separated list. '
f'Default: `{constants.DEFAULT_DOWNLOAD_TICKER_INTERVALS}`.',
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(
parser.add_argument(
'--erase',
help='Clean all existing data for the selected exchange/pairs/timeframes',
help='Clean all existing data for the selected exchange/pairs/timeframes.',
dest='erase',
action='store_true'
)
def plot_dataframe_options(self) -> None:
"""
Parses given arguments for plot dataframe script
"""
parser = self.parser
parser.add_argument(
'--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',
dest='indicators1',
)
parser.add_argument(
'--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',
dest='indicators2',
)
parser.add_argument(
'--plot-limit',
help='Specify tick limit for plotting. Notice: too high values cause huge files. '
'Default: %(default)s.',
dest='plot_limit',
default=750,
type=int,
)
parser.add_argument(
'--trade-source',
help='Specify the source for trades (Can be DB or file (backtest file)) '
'Default: %(default)s',
dest='trade_source',
default="file",
choices=["DB", "file"]
)

View File

@@ -4,14 +4,20 @@ This module contains the configuration class
import json
import logging
import os
import sys
from argparse import Namespace
from typing import Any, Dict, Optional
from logging.handlers import RotatingFileHandler
from typing import Any, Callable, Dict, List, Optional
import ccxt
from jsonschema import Draft4Validator, validate
from jsonschema import Draft4Validator, validators
from jsonschema.exceptions import ValidationError, best_match
from freqtrade import OperationalException, constants
from freqtrade.exchange import (is_exchange_bad, is_exchange_available,
is_exchange_officially_supported, available_exchanges)
from freqtrade.misc import deep_merge_dicts
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@@ -28,23 +34,61 @@ def set_loggers(log_level: int = 0) -> None:
logging.getLogger('telegram').setLevel(logging.INFO)
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)
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) -> None:
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)
config: Dict[str, Any] = {}
# Now expecting a list of config filenames here, not a string
for path in self.args.config:
logger.info('Using config: %s ...', path)
# Merge config options, overwriting old values
config = deep_merge_dicts(self._load_config_file(path), config)
if 'internals' not in config:
config['internals'] = {}
logger.info('Validating configuration ...')
self._validate_config_schema(config)
self._validate_config_consistency(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'):
@@ -53,20 +97,21 @@ class Configuration(object):
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 Optimize configurations
config = self._load_optimize_config(config)
# Load Edge
config = self._load_edge_config(config)
# Add plotting options if available
config = self._load_plot_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
@@ -77,36 +122,56 @@ class Configuration(object):
:return: configuration as dictionary
"""
try:
with open(path) as file:
# Read config from stdin if requested in the options
with open(path) if path != '-' else sys.stdin 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 conf
return self._validate_config(conf)
def _load_logging_config(self, config: Dict[str, Any]) -> None:
"""
Extract information for sys.argv and load logging configuration:
the --loglevel, --logfile options
"""
# Log level
if 'loglevel' in self.args and self.args.loglevel:
config.update({'verbosity': self.args.loglevel})
else:
config.update({'verbosity': 0})
# Log to stdout, not stderr
log_handlers: List[logging.Handler] = [logging.StreamHandler(sys.stdout)]
if 'logfile' in self.args and self.args.logfile:
config.update({'logfile': self.args.logfile})
# Allow setting this as either configuration or argument
if 'logfile' in config:
log_handlers.append(RotatingFileHandler(config['logfile'],
maxBytes=1024 * 1024, # 1Mb
backupCount=10))
logging.basicConfig(
level=logging.INFO if config['verbosity'] < 1 else logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=log_handlers
)
set_loggers(config['verbosity'])
logger.info('Verbosity set to %s', config['verbosity'])
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
"""
self._load_logging_config(config)
# 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'])
# Support for sd_notify
if self.args.sd_notify:
config['internals'].update({'sd_notify': True})
# Add dynamic_whitelist if found
if 'dynamic_whitelist' in self.args and self.args.dynamic_whitelist:
@@ -124,9 +189,6 @@ class Configuration(object):
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 ...')
else:
# Set default here
config.update({'db_url': constants.DEFAULT_DB_PROD_URL})
if config.get('dry_run', False):
logger.info('Dry run is enabled')
@@ -152,94 +214,100 @@ class Configuration(object):
return config
def _create_default_datadir(self, config: Dict[str, Any]) -> str:
exchange_name = config.get('exchange', {}).get('name').lower()
default_path = os.path.join('user_data', 'data', exchange_name)
if not os.path.isdir(default_path):
os.makedirs(default_path)
logger.info(f'Created data directory: {default_path}')
return default_path
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)
def _load_backtesting_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
if not os.path.isdir(datadir):
os.makedirs(datadir)
logger.info(f'Created data directory: {datadir}')
return datadir
def _args_to_config(self, config: Dict[str, Any], argname: str,
logstring: str, logfun: Optional[Callable] = None) -> None:
"""
Extract information for sys.argv and load Backtesting configuration
: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]))
def _load_datadir_config(self, config: Dict[str, Any]) -> None:
"""
Extract information for sys.argv and load datadir configuration:
the --datadir option
"""
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'))
def _load_optimize_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
"""
Extract information for sys.argv and load Optimize 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'))
# This will override the strategy configuration
self._args_to_config(config, argname='ticker_interval',
logstring='Parameter -i/--ticker-interval detected ... '
'Using 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 ...')
self._args_to_config(config, argname='live',
logstring='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 ...')
self._args_to_config(config, argname='position_stacking',
logstring='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 ...')
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'))
# 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)
self._args_to_config(config, argname='stake_amount',
logstring='Parameter --stake_amount detected, '
'overriding stake_amount to: {} ...')
# If --datadir is used we add it to the configuration
if 'datadir' in self.args and self.args.datadir:
config.update({'datadir': self.args.datadir})
else:
config.update({'datadir': self._create_default_datadir(config)})
logger.info('Using data folder: %s ...', config.get('datadir'))
self._args_to_config(config, argname='timerange',
logstring='Parameter --timerange detected: {} ...')
# 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 ...')
self._load_datadir_config(config)
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))
self._args_to_config(config, argname='refresh_pairs',
logstring='Parameter -r/--refresh-pairs-cached detected ...')
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')
self._args_to_config(config, argname='strategy_list',
logstring='Using strategy list of {} Strategies', logfun=len)
# 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)
self._args_to_config(config, argname='ticker_interval',
logstring='Overriding ticker interval with Command line argument')
# 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)
self._args_to_config(config, argname='export',
logstring='Parameter --export detected: {} ...')
return config
self._args_to_config(config, argname='exportfilename',
logstring='Storing backtest results to {} ...')
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
# 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]})
@@ -247,39 +315,61 @@ class Configuration(object):
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 ...')
# Hyperopt section
self._args_to_config(config, argname='hyperopt',
logstring='Using Hyperopt file {}')
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 ...')
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: {}')
return config
def _load_hyperopt_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
def _load_plot_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
"""
Extract information for sys.argv and load Hyperopt configuration
Extract information for sys.argv Plotting 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'))
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: {}')
return config
def _validate_config(self, conf: Dict[str, Any]) -> Dict[str, Any]:
def _validate_config_schema(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)
FreqtradeValidator(constants.CONF_SCHEMA).validate(conf)
return conf
except ValidationError as exception:
logger.critical(
@@ -290,6 +380,35 @@ class Configuration(object):
best_match(Draft4Validator(constants.CONF_SCHEMA).iter_errors(conf)).message
)
def _validate_config_consistency(self, conf: Dict[str, Any]) -> None:
"""
Validate the configuration consistency
:param conf: Config in JSON format
:return: Returns None if everything is ok, otherwise throw an OperationalException
"""
# validating trailing stoploss
self._validate_trailing_stoploss(conf)
def _validate_trailing_stoploss(self, conf: Dict[str, Any]) -> None:
# 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(
f'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(
f'The config trailing_stop_positive_offset needs '
'to be greater than trailing_stop_positive_offset in your config.')
def get_config(self) -> Dict[str, Any]:
"""
Return the config. Use this method to get the bot config
@@ -300,26 +419,40 @@ class Configuration(object):
return self.config
def check_exchange(self, config: Dict[str, Any]) -> bool:
def check_exchange(self, config: Dict[str, Any], check_for_bad: bool = True) -> 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
: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 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)
if not is_exchange_available(exchange):
raise OperationalException(
exception_msg
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())}'
)
# 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)
if check_for_bad and is_exchange_bad(exchange):
logger.warning(f'Exchange "{exchange}" is known to not work with the bot yet. '
f'Use it only for development and testing purposes.')
return False
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

@@ -3,9 +3,11 @@
"""
bot constants
"""
DEFAULT_CONFIG = 'config.json'
DEFAULT_EXCHANGE = 'bittrex'
DYNAMIC_WHITELIST = 20 # pairs
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'
@@ -13,28 +15,20 @@ DEFAULT_HYPEROPT = 'DefaultHyperOpts'
DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite'
DEFAULT_DB_DRYRUN_URL = 'sqlite://'
UNLIMITED_STAKE_AMOUNT = 'unlimited'
DEFAULT_AMOUNT_RESERVE_PERCENT = 0.05
REQUIRED_ORDERTIF = ['buy', 'sell']
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
DEFAULT_DOWNLOAD_TICKER_INTERVALS = '1m 5m'
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",
@@ -49,7 +43,7 @@ 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"],
@@ -58,6 +52,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',
@@ -66,10 +61,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': {
@@ -112,7 +109,8 @@ CONF_SCHEMA = {
'buy': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
'sell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
'stoploss': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
'stoploss_on_exchange': {'type': 'boolean'}
'stoploss_on_exchange': {'type': 'boolean'},
'stoploss_on_exchange_interval': {'type': 'number'}
},
'required': ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']
},
@@ -137,7 +135,7 @@ CONF_SCHEMA = {
'pairlist': {
'type': 'object',
'properties': {
'method': {'type': 'string', 'enum': AVAILABLE_PAIRLISTS},
'method': {'type': 'string', 'enum': AVAILABLE_PAIRLISTS},
'config': {'type': 'object'}
},
'required': ['method']
@@ -160,6 +158,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'},
@@ -167,7 +180,8 @@ CONF_SCHEMA = {
'type': 'object',
'properties': {
'process_throttle_secs': {'type': 'number'},
'interval': {'type': 'integer'}
'interval': {'type': 'integer'},
'sd_notify': {'type': 'boolean'},
}
}
},
@@ -176,10 +190,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',
@@ -198,10 +212,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,111 @@
"""
Helpers when analyzing backtest data
"""
import logging
from pathlib import Path
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("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 = df2.astype(dtype={"open_time": "datetime64", "close_time": "datetime64"})
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", "profit", "open_time", "close_time",
"open_rate", "close_rate", "duration", "sell_reason",
"max_rate", "min_rate"]
trades = pd.DataFrame([(t.pair, t.calc_profit(),
t.open_date.replace(tzinfo=pytz.UTC),
t.close_date.replace(tzinfo=pytz.UTC) if t.close_date else None,
t.open_rate, t.close_rate,
t.close_date.timestamp() - t.open_date.timestamp()
if t.close_date else None,
t.sell_reason,
t.max_rate,
t.min_rate,
)
for t in Trade.query.all()],
columns=columns)
return trades
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

View File

@@ -2,16 +2,25 @@
Functions to convert data from one format to another
"""
import logging
import pandas as pd
from pandas import DataFrame, to_datetime
logger = logging.getLogger(__name__)
def parse_ticker_dataframe(ticker: list) -> 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")
@@ -23,6 +32,12 @@ def parse_ticker_dataframe(ticker: list) -> DataFrame:
utc=True,
infer_datetime_format=True)
# Some exchanges return int values for volume and even for ohlc.
# Convert them since TA-LIB indicators used in the strategy assume floats
# and fail with exception...
frame = frame.astype(dtype={'open': 'float', 'high': 'float', 'low': 'float', 'close': 'float',
'volume': 'float'})
# group by index and aggregate results to eliminate duplicate ticks
frame = frame.groupby(by='date', as_index=False, sort=True).agg({
'open': 'first',
@@ -31,9 +46,50 @@ def parse_ticker_dataframe(ticker: list) -> DataFrame:
'close': 'last',
'volume': 'max',
})
frame.drop(frame.tail(1).index, inplace=True) # eliminate partial candle
logger.debug('Dropping last candle')
return frame
# 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, pair)
else:
return frame
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',
'low': 'min',
'close': 'last',
'volume': 'sum'
}
ticker_minutes = timeframe_to_minutes(ticker_interval)
# Resample to create "NAN" values
df = dataframe.resample(f'{ticker_minutes}min', on='date').agg(ohlc_dict)
# Forwardfill close for missing columns
df['close'] = df['close'].fillna(method='ffill')
# Use close for "open, high, low"
df.loc[:, ['open', 'high', 'low']] = df[['open', 'high', 'low']].fillna(
value={'open': df['close'],
'high': df['close'],
'low': df['close'],
})
df.reset_index(inplace=True)
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
def order_book_to_dataframe(bids: list, asks: list) -> DataFrame:

View File

@@ -0,0 +1,96 @@
"""
Dataprovider
Responsible to provide data to the bot
including Klines, tickers, historic data
Common Interface for bot and strategy to access data.
"""
import logging
from pathlib import Path
from typing import List, Tuple
from pandas import DataFrame
from freqtrade.data.history import load_pair_history
from freqtrade.exchange import Exchange
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
class DataProvider(object):
def __init__(self, config: dict, exchange: Exchange) -> None:
self._config = config
self._exchange = exchange
def refresh(self,
pairlist: List[Tuple[str, str]],
helping_pairs: List[Tuple[str, str]] = None) -> None:
"""
Refresh data, called with each cycle
"""
if helping_pairs:
self._exchange.refresh_latest_ohlcv(pairlist + helping_pairs)
else:
self._exchange.refresh_latest_ohlcv(pairlist)
@property
def available_pairs(self) -> List[Tuple[str, str]]:
"""
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, 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.
:param pair: pair to get the data for
:param ticker_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)
"""
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
if ticker_interval:
pairtick = (pair, ticker_interval)
else:
pairtick = (pair, self._config['ticker_interval'])
return self._exchange.klines(pairtick, copy=copy)
else:
return DataFrame()
def historic_ohlcv(self, pair: str, ticker_interval: str) -> DataFrame:
"""
get stored historic ohlcv data
:param pair: pair to get the data for
:param ticker_interval: ticker_interval to get pair for
"""
return load_pair_history(pair=pair,
ticker_interval=ticker_interval,
refresh_pairs=False,
datadir=Path(self._config['datadir']) if self._config.get(
'datadir') else None
)
def ticker(self, pair: str):
"""
Return last ticker data
"""
# TODO: Implement me
pass
def orderbook(self, pair: str, max: int):
"""
return latest orderbook data
"""
return self._exchange.get_order_book(pair, max)
@property
def runmode(self) -> RunMode:
"""
Get runmode of the bot
can be "live", "dry-run", "backtest", "edgecli", "hyperopt" or "other".
"""
return RunMode(self._config.get('runmode', RunMode.OTHER))

View File

@@ -1,37 +1,28 @@
"""
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 gzip
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
import ujson
from freqtrade import misc, constants, OperationalException
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.exchange import Exchange
from freqtrade import OperationalException, misc
from freqtrade.arguments import TimeRange
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.exchange import Exchange, timeframe_to_minutes
logger = logging.getLogger(__name__)
def json_load(data):
"""
load data with ujson
Use this to have a consistent experience,
otherwise "precise_float" needs to be passed to all load operations
"""
return ujson.load(data, precise_float=True)
def trim_tickerlist(tickerlist: List[Dict], timerange: TimeRange) -> List[Dict]:
"""
Trim tickerlist based on given timerange
@@ -72,23 +63,11 @@ def load_tickerdata_file(
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')
gzipfile = file.with_suffix(file.suffix + '.gz')
# Try gzip file first, otherwise regular json file.
if gzipfile.is_file():
logger.debug('Loading ticker data from file %s', gzipfile)
with gzip.open(gzipfile) as tickerdata:
pairdata = json_load(tickerdata)
elif file.is_file():
logger.debug('Loading ticker data from file %s', file)
with open(file) as tickerdata:
pairdata = json_load(tickerdata)
else:
filename = pair_data_filename(datadir, pair, ticker_interval)
pairdata = misc.file_load_json(filename)
if not pairdata:
return None
if timerange:
@@ -102,26 +81,33 @@ def load_pair_history(pair: str,
timerange: TimeRange = TimeRange(None, None, 0, 0),
refresh_pairs: bool = False,
exchange: Optional[Exchange] = None,
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
"""
pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
# 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 all pairs 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)
if pairdata:
if timerange.starttype == 'date' and pairdata[0][0] > timerange.startts * 1000:
logger.warning('Missing data at start for pair %s, data starts at %s',
@@ -130,11 +116,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)
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 download_backtest_data.py '
'script to download the data'
)
return None
@@ -143,20 +133,35 @@ def load_data(datadir: Optional[Path],
pairs: List[str],
refresh_pairs: bool = False,
exchange: Optional[Exchange] = None,
timerange: TimeRange = TimeRange(None, None, 0, 0)) -> Dict[str, DataFrame]:
timerange: TimeRange = TimeRange(None, None, 0, 0),
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,
datadir=datadir, timerange=timerange,
refresh_pairs=refresh_pairs,
exchange=exchange)
if hist is not None:
result[pair] = hist
for pair in pairs:
hist = load_pair_history(pair=pair, ticker_interval=ticker_interval,
datadir=datadir, timerange=timerange,
refresh_pairs=refresh_pairs,
exchange=exchange,
fill_up_missing=fill_up_missing)
if hist is not None:
result[pair] = hist
return result
@@ -165,7 +170,14 @@ 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(filename: Path, ticker_interval: str,
timerange: Optional[TimeRange]) -> Tuple[List[Any],
Optional[int]]:
"""
@@ -179,13 +191,13 @@ 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 = json_load(file)
data = misc.json_load(file)
# remove the last item, could be incomplete candle
if data:
data.pop()
@@ -204,9 +216,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
@@ -215,26 +227,32 @@ 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')
filename = pair_data_filename(datadir, pair, ticker_interval)
logger.info('Download the pair: "%s", Interval: %s', pair, tick_interval)
logger.info(
f'Download history data for pair: "{pair}", interval: {ticker_interval} '
f'and store in {datadir}.'
)
data, since_ms = load_cached_data_for_updating(filename, tick_interval, timerange)
data, since_ms = load_cached_data_for_updating(filename, 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_history(pair=pair, ticker_interval=ticker_interval,
since_ms=since_ms if since_ms
else
int(arrow.utcnow().shift(days=-30).float_timestamp) * 1000)
@@ -245,7 +263,46 @@ def download_pair_history(datadir: Optional[Path],
misc.file_dump_json(filename, data)
return True
except BaseException:
logger.info('Failed to download the pair: "%s", Interval: %s',
pair, tick_interval)
return False
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

@@ -13,7 +13,6 @@ from freqtrade import constants, OperationalException
from freqtrade.arguments import Arguments
from freqtrade.arguments import TimeRange
from freqtrade.data import history
from freqtrade.optimize import get_timeframe
from freqtrade.strategy.interface import SellType
@@ -47,11 +46,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
@@ -59,7 +53,7 @@ class Edge():
# checking max_open_trades. it should be -1 as with Edge
# the number of trades is determined by position size
if self.config['max_open_trades'] != -1:
if self.config['max_open_trades'] != float('inf'):
logger.critical('max_open_trades should be -1 in config !')
if self.config['stake_amount'] != constants.UNLIMITED_STAKE_AMOUNT:
@@ -102,7 +96,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 +108,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 +126,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.
@@ -190,12 +185,35 @@ class Edge():
if self._final_pairs != final:
self._final_pairs = final
if self._final_pairs:
logger.info('Edge validated only %s', self._final_pairs)
logger.info(
'Minimum expectancy and minimum winrate are met only for %s,'
' so other pairs are filtered out.',
self._final_pairs
)
else:
logger.info('Edge removed all pairs as no pair with minimum expectancy was found !')
logger.info(
'Edge removed all pairs as no pair with minimum expectancy '
'and minimum winrate was found !'
)
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
@@ -344,91 +362,93 @@ 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 = []
open_trade_index = utf1st.find_1st(buy_column, 1, utf1st.cmp_equal)
start_point = 0
# return empty if we don't find trade entry (i.e. buy==1) or
# we find a buy but at the of array
if open_trade_index == -1 or open_trade_index == len(buy_column) - 1:
return []
else:
open_trade_index += 1 # when a buy signal is seen,
# trade opens in reality on the next candle
while True:
open_trade_index = utf1st.find_1st(buy_column, 1, utf1st.cmp_equal)
stop_price_percentage = stoploss + 1
open_price = ohlc_columns[open_trade_index, 0]
stop_price = (open_price * stop_price_percentage)
# 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:
break
else:
# When a buy signal is seen,
# trade opens in reality on the next candle
open_trade_index += 1
# Searching for the index where stoploss is hit
stop_index = utf1st.find_1st(
ohlc_columns[open_trade_index:, 2], stop_price, utf1st.cmp_smaller)
stop_price_percentage = stoploss + 1
open_price = ohlc_columns[open_trade_index, 0]
stop_price = (open_price * stop_price_percentage)
# If we don't find it then we assume stop_index will be far in future (infinite number)
if stop_index == -1:
stop_index = float('inf')
# Searching for the index where stoploss is hit
stop_index = utf1st.find_1st(
ohlc_columns[open_trade_index:, 2], stop_price, utf1st.cmp_smaller)
# Searching for the index where sell is hit
sell_index = utf1st.find_1st(sell_column[open_trade_index:], 1, utf1st.cmp_equal)
# If we don't find it then we assume stop_index will be far in future (infinite number)
if stop_index == -1:
stop_index = float('inf')
# If we don't find it then we assume sell_index will be far in future (infinite number)
if sell_index == -1:
sell_index = float('inf')
# Searching for the index where sell is hit
sell_index = utf1st.find_1st(sell_column[open_trade_index:], 1, utf1st.cmp_equal)
# Check if we don't find any stop or sell point (in that case trade remains open)
# 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 []
# If we don't find it then we assume sell_index will be far in future (infinite number)
if sell_index == -1:
sell_index = float('inf')
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
exit_index = open_trade_index + sell_index + 1
# Check if we don't find any stop or sell point (in that case trade remains open)
# 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'):
break
# check if we have the next candle
if len(ohlc_columns) - 1 < exit_index:
return []
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
exit_index = open_trade_index + sell_index + 1
exit_type = SellType.SELL_SIGNAL
exit_price = ohlc_columns[exit_index, 0]
# Check if we have the next candle
if len(ohlc_columns) - 1 < exit_index:
break
trade = {'pair': pair,
'stoploss': stoploss,
'profit_percent': '',
'profit_abs': '',
'open_time': date_column[open_trade_index],
'close_time': date_column[exit_index],
'open_index': start_point + open_trade_index,
'close_index': start_point + exit_index,
'trade_duration': '',
'open_rate': round(open_price, 15),
'close_rate': round(exit_price, 15),
'exit_type': exit_type
}
exit_type = SellType.SELL_SIGNAL
exit_price = ohlc_columns[exit_index, 0]
result.append(trade)
trade = {'pair': pair,
'stoploss': stoploss,
'profit_percent': '',
'profit_abs': '',
'open_time': date_column[open_trade_index],
'close_time': date_column[exit_index],
'open_index': start_point + open_trade_index,
'close_index': start_point + exit_index,
'trade_duration': '',
'open_rate': round(open_price, 15),
'close_rate': round(exit_price, 15),
'exit_type': exit_type
}
# 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)
)
result.append(trade)
# 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,713 +1,10 @@
# 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[str, int] = {}
# Holds candles
self._klines: Dict[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: str, copy=True) -> DataFrame:
if pair in self._klines:
return self._klines[pair].copy() if copy else self._klines[pair]
else:
return None
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 is not '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 is not '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:
return self._api.create_order(pair, 'stop_loss_limit', 'sell',
amount, rate, {'stopPrice': stop_price})
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 in tickers:
if p == pair:
data.extend(ticker)
# Sort data again after extending the result - above calls return in "async order" order
data = sorted(data, key=lambda x: x[0])
logger.info("downloaded %s with length %s.", pair, len(data))
return data
def refresh_tickers(self, pair_list: List[str], ticker_interval: str) -> None:
"""
Refresh tickers asyncronously and set `_klines` of this object with the result
"""
logger.debug("Refreshing klines for %d pairs", len(pair_list))
asyncio.get_event_loop().run_until_complete(
self.async_get_candles_history(pair_list, ticker_interval))
async def async_get_candles_history(self, pairs: List[str],
tick_interval: str) -> List[Tuple[str, List]]:
"""Download ohlcv history for pair-list asyncronously """
# Calculating ticker interval in second
interval_in_sec = constants.TICKER_INTERVAL_MINUTES[tick_interval] * 60
input_coroutines = []
# Gather corotines to run
for pair in pairs:
if not (self._pairs_last_refresh_time.get(pair, 0) + interval_in_sec >=
arrow.utcnow().timestamp and pair in self._klines):
input_coroutines.append(self._async_get_candle_history(pair, tick_interval))
else:
logger.debug("Using cached klines data for %s ...", pair)
tickers = await asyncio.gather(*input_coroutines, return_exceptions=True)
# handle caching
for pair, ticks in tickers:
# keeping last candle time as last refreshed time of the pair
if ticks:
self._pairs_last_refresh_time[pair] = ticks[-1][0] // 1000
# keeping parsed dataframe in cache
self._klines[pair] = parse_ticker_dataframe(ticks)
return tickers
@retrier_async
async def _async_get_candle_history(self, pair: str, tick_interval: str,
since_ms: Optional[int] = None) -> Tuple[str, List]:
try:
# fetch ohlcv asynchronously
logger.debug("fetching %s since %s ...", pair, 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
if data and data[0][0] > data[-1][0]:
data = sorted(data, key=lambda x: x[0])
logger.debug("done fetching %s ...", pair)
return pair, 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 (is_exchange_bad, # noqa: F401
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)
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,761 @@
# pragma pylint: disable=W0603
"""
Cryptocurrency Exchanges support
"""
import asyncio
import inspect
import logging
from copy import deepcopy
from datetime import datetime
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
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._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: 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)
# 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):
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
@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.')
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")
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': "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)
rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' 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} (total {rate*amount}).'
f'Message: {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} (total {rate*amount}).'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place {side} order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(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}')
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, ticker_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, ticker_interval=ticker_interval,
since_ms=since_ms))
async def _async_get_history(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 asyncronously and set `_klines` with the result
"""
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]:
"""
Asyncronously 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}')
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._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}')
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._config['dry_run']:
order = self._dry_run_open_orders[order_id]
return order
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}')
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 -.-
"""
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}')
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._config['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)
@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)
def is_exchange_bad(exchange: str) -> bool:
return exchange in ['bitmex']
def is_exchange_available(exchange: str, ccxt_module=None) -> bool:
return exchange in available_exchanges(ccxt_module)
def is_exchange_officially_supported(exchange: str) -> bool:
return exchange 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 above, but returns minutes.
"""
return ccxt.Exchange.parse_timeframe(ticker_interval) // 60
def timeframe_to_msecs(ticker_interval: str) -> int:
"""
Same as above, but returns milliseconds.
"""
return ccxt.Exchange.parse_timeframe(ticker_interval) * 1000

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,22 +4,22 @@ 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.exchange import timeframe_to_minutes
from freqtrade.persistence import Trade
from freqtrade.rpc import RPCManager, RPCMessageType
from freqtrade.resolvers import StrategyResolver, PairListResolver
from freqtrade.resolvers import ExchangeResolver, StrategyResolver, PairListResolver
from freqtrade.state import State
from freqtrade.strategy.interface import SellType, IStrategy
from freqtrade.wallets import Wallets
@@ -34,29 +34,35 @@ class FreqtradeBot(object):
This is from here the bot start its logic.
"""
def __init__(self, config: Dict[str, Any])-> None:
def __init__(self, config: Dict[str, Any]) -> None:
"""
Init all variables and object the bot need to work
:param config: configuration dict, you can use the Configuration.get_config()
method to get the config dict.
Init all variables and objects the bot needs to work
:param config: configuration dict, you can use Configuration.get_config()
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
self.rpc: RPCManager = RPCManager(self)
self.persistence = None
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
IStrategy.dp = self.dataprovider
# Attach Wallets to Strategy baseclass
IStrategy.wallets = self.wallets
pairlistname = self.config.get('pairlist', {}).get('method', 'StaticPairList')
self.pairlists = PairListResolver(pairlistname, self, self.config).pairlist
@@ -65,24 +71,13 @@ 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
# 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:
"""
@@ -90,125 +85,83 @@ 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)
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) -> bool:
"""
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:
# Refresh whitelist
self.pairlists.refresh_pairlist()
self.active_pair_whitelist = self.pairlists.whitelist
# Calculating Edge positiong
# Should be called before refresh_tickers
# Otherwise it will override cached klines in exchange
# with delta value (klines only from last refresh_pairs)
if self.edge:
self.edge.calculate()
self.active_pair_whitelist = self.edge.adjust(self.active_pair_whitelist)
# Check whether markets have to be reloaded
self.exchange._reload_markets()
# Query trades from persistence layer
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
# Refresh whitelist
self.pairlists.refresh_pairlist()
self.active_pair_whitelist = self.pairlists.whitelist
# 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])
# Calculating Edge positioning
if self.edge:
self.edge.calculate()
self.active_pair_whitelist = self.edge.adjust(self.active_pair_whitelist)
# Refreshing candles
self.exchange.refresh_tickers(self.active_pair_whitelist, self.strategy.ticker_interval)
# Query trades from persistence layer
trades = Trade.get_open_trades()
# First process current opened trades
for trade in trades:
state_changed |= self.process_maybe_execute_sell(trade)
# Extend active-pair whitelist with pairs from open trades
# It ensures that tickers are downloaded for open trades
self._extend_whitelist_with_trades(self.active_pair_whitelist, trades)
# Then looking for buy opportunities
if len(trades) < self.config['max_open_trades']:
state_changed = self.process_maybe_execute_buy()
# Refreshing candles
self.dataprovider.refresh(self._create_pair_whitelist(self.active_pair_whitelist),
self.strategy.informative_pairs())
if 'unfilledtimeout' in self.config:
# Check and handle any timed out open orders
self.check_handle_timedout()
Trade.session.flush()
# First process current opened trades
for trade in trades:
state_changed |= 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()
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('%s, retrying in 30 seconds...', error)
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 get_target_bid(self, pair: str, ticker: Dict[str, float]) -> float:
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 _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
:param ticker: Ticker to use for getting Ask and Last Price
:return: float: Price
"""
if ticker['ask'] < ticker['last']:
ticker_rate = ticker['ask']
else:
balance = self.config['bid_strategy']['ask_last_balance']
ticker_rate = ticker['ask'] + balance * (ticker['last'] - ticker['ask'])
used_rate = ticker_rate
config_bid_strategy = self.config.get('bid_strategy', {})
if 'use_order_book' in config_bid_strategy and\
config_bid_strategy.get('use_order_book', False):
@@ -218,15 +171,19 @@ class FreqtradeBot(object):
logger.debug('order_book %s', order_book)
# top 1 = index 0
order_book_rate = order_book['bids'][order_book_top - 1][0]
# if ticker has lower rate, then use ticker ( usefull if down trending )
logger.info('...top %s order book buy rate %0.8f', order_book_top, order_book_rate)
if ticker_rate < order_book_rate:
logger.info('...using ticker rate instead %0.8f', ticker_rate)
used_rate = ticker_rate
else:
used_rate = order_book_rate
used_rate = order_book_rate
else:
logger.info('Using Last Ask / Last Price')
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:
balance = self.config['bid_strategy']['ask_last_balance']
ticker_rate = ticker['ask'] + balance * (ticker['last'] - ticker['ask'])
used_rate = ticker_rate
return used_rate
@@ -247,32 +204,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(
'Available balance(%f %s) is lower than stake amount(%f %s)' % (
avaliable_amount, self.config['stake_currency'],
stake_amount, self.config['stake_currency'])
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
@@ -290,7 +244,9 @@ class FreqtradeBot(object):
if not min_stake_amounts:
return None
amount_reserve_percent = 1 - 0.05 # reserve 5% + stoploss
# reserve some percent defined in config (5% default) + stoploss
amount_reserve_percent = 1.0 - self.config.get('amount_reserve_percent',
constants.DEFAULT_AMOUNT_RESERVE_PERCENT)
if self.strategy.stoploss is not None:
amount_reserve_percent += self.strategy.stoploss
# it should not be more than 50%
@@ -306,27 +262,32 @@ class FreqtradeBot(object):
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
# running get_signal on historical data fetched
for _pair in whitelist:
(buy, sell) = self.strategy.get_signal(_pair, interval, self.exchange.klines(_pair))
(buy, sell) = self.strategy.get_signal(
_pair, interval, self.dataprovider.ohlcv(_pair, self.strategy.ticker_interval))
if buy and not sell:
stake_amount = self._get_trade_stake_amount(_pair)
if not stake_amount:
return False
logger.info(
'Buy signal found: about create a new trade with stake_amount: %f ...',
stake_amount
)
logger.info(f"Buy signal found: about create a new trade with stake_amount: "
f"{stake_amount} ...")
bidstrat_check_depth_of_market = self.config.get('bid_strategy', {}).\
get('check_depth_of_market', {})
@@ -364,7 +325,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']
@@ -373,19 +333,19 @@ class FreqtradeBot(object):
buy_limit_requested = price
else:
# Calculate amount
buy_limit_requested = self.get_target_bid(pair, self.exchange.get_ticker(pair))
buy_limit_requested = self.get_target_bid(pair)
min_stake_amount = self._get_min_pair_stake_amount(pair_s, buy_limit_requested)
if min_stake_amount is not None and min_stake_amount > stake_amount:
logger.warning(
f'Can\'t open a new trade for {pair_s}: stake amount'
f' is too small ({stake_amount} < {min_stake_amount})'
f'Can\'t open a new trade for {pair_s}: stake amount '
f'is too small ({stake_amount} < {min_stake_amount})'
)
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']
@@ -395,7 +355,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
@@ -423,14 +382,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
@@ -450,9 +408,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()
@@ -483,23 +445,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)
@@ -524,7 +470,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
@@ -556,30 +502,75 @@ class FreqtradeBot(object):
fee_abs += exectrade['fee']['cost']
if amount != order_amount:
logger.warning(f"amount {amount} does not match amount {trade.amount}")
logger.warning(f"Amount {amount} does not match amount {trade.amount}")
raise OperationalException("Half bought? Amounts don't match")
real_amount = amount - fee_abs
if fee_abs != 0:
logger.info(f"""Applying fee on amount for {trade} \
(from {order_amount} to {real_amount}) from Trades""")
logger.info(f"Applying fee on amount for {trade} "
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)
order = action_order or 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)
# 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.
:return: True if trade has been sold, False otherwise
"""
if not trade.is_open:
raise ValueError(f'attempt to handle closed trade: {trade}')
raise ValueError(f'Attempt to handle closed trade: {trade}')
logger.debug('Handling %s ...', trade)
sell_rate = self.exchange.get_ticker(trade.pair)['bid']
(buy, sell) = (False, False)
experimental = self.config.get('experimental', {})
if experimental.get('use_sell_signal') or experimental.get('ignore_roi_if_buy_signal'):
(buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.ticker_interval,
self.exchange.klines(trade.pair))
(buy, sell) = self.strategy.get_signal(
trade.pair, self.strategy.ticker_interval,
self.dataprovider.ohlcv(trade.pair, self.strategy.ticker_interval))
config_ask_strategy = self.config.get('ask_strategy', {})
if config_ask_strategy.get('use_order_book', False):
@@ -592,18 +583,15 @@ class FreqtradeBot(object):
for i in range(order_book_min, order_book_max + 1):
order_book_rate = order_book['asks'][i - 1][0]
# if orderbook has higher rate (high profit),
# use orderbook, otherwise just use bids rate
logger.info(' order book asks top %s: %0.8f', i, order_book_rate)
if sell_rate < order_book_rate:
sell_rate = order_book_rate
sell_rate = order_book_rate
if self.check_sell(trade, sell_rate, buy, sell):
return True
break
else:
logger.debug('checking sell')
sell_rate = self.get_sell_rate(trade.pair, True)
if self.check_sell(trade, sell_rate, buy, sell):
return True
@@ -613,15 +601,29 @@ class FreqtradeBot(object):
def handle_stoploss_on_exchange(self, trade: Trade) -> bool:
"""
Check if trade is fulfilled in which case the stoploss
on exchange should be added immediately if stoploss on exchnage
on exchange should be added immediately if stoploss on exchange
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:
@@ -630,26 +632,80 @@ class FreqtradeBot(object):
stop_price = trade.open_rate * (1 + stoploss)
# limit price should be less than stop price.
# 0.98 is arbitrary here.
limit_price = stop_price * 0.98
limit_price = stop_price * limit_price_pct
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)
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':
trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
trade.update(order)
result = True
else:
result = False
return result
except DependencyException as exception:
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:
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(stoploss_order)
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, stoploss_order)
return False
def handle_trailing_stoploss_on_exchange(self, trade: Trade, order):
"""
Check to see if stoploss on exchange should be updated
in case of trailing stoploss on exchange
:param Trade: Corresponding Trade
:param order: Current on exchange stoploss order
:return: None
"""
if trade.stop_loss > float(order['info']['stopPrice']):
# we check if the update is neccesary
update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60)
if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() > update_beat:
# cancelling the current stoploss on exchange first
logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s})'
'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:
logger.exception(f"Could 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:
@@ -698,8 +754,15 @@ class FreqtradeBot(object):
self.wallets.update()
continue
# Check if trade is still actually open
if order['status'] == 'open':
# Handle cancelled on exchange
if order['status'] == 'canceled':
if order['side'] == 'buy':
self.handle_buy_order_full_cancel(trade, "canceled on Exchange")
elif order['side'] == 'sell':
self.handle_timedout_limit_sell(trade, order)
self.wallets.update()
# Check if order is still actually open
elif order['status'] == 'open':
if order['side'] == 'buy' and ordertime < buy_timeoutthreashold:
self.handle_timedout_limit_buy(trade, order)
self.wallets.update()
@@ -707,24 +770,24 @@ class FreqtradeBot(object):
self.handle_timedout_limit_sell(trade, order)
self.wallets.update()
# FIX: 20180110, why is cancel.order unconditionally here, whereas
# it is conditionally called in the
# handle_timedout_limit_sell()?
def handle_buy_order_full_cancel(self, trade: Trade, reason: str) -> None:
"""Close trade in database and send message"""
Trade.session.delete(trade)
Trade.session.flush()
logger.info('Buy order %s for %s.', reason, trade)
self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Unfilled buy order for {trade.pair} {reason}'
})
def handle_timedout_limit_buy(self, trade: Trade, order: Dict) -> bool:
"""Buy timeout - cancel order
:return: True if order was fully cancelled
"""
pair_s = trade.pair.replace('_', '/')
self.exchange.cancel_order(trade.open_order_id, trade.pair)
if order['remaining'] == order['amount']:
# if trade is not partially completed, just delete the trade
Trade.session.delete(trade)
Trade.session.flush()
logger.info('Buy order timeout for %s.', trade)
self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Unfilled buy order for {pair_s} cancelled due to timeout'
})
self.handle_buy_order_full_cancel(trade, "cancelled due to timeout")
return True
# if trade is partially complete, edit the stake details for the trade
@@ -735,20 +798,24 @@ class FreqtradeBot(object):
logger.info('Partial buy order timeout for %s.', trade)
self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Remaining buy order for {pair_s} cancelled due to timeout'
'status': f'Remaining buy order for {trade.pair} cancelled due to timeout'
})
return False
# FIX: 20180110, should cancel_order() be cond. or unconditionally called?
def handle_timedout_limit_sell(self, trade: Trade, order: Dict) -> bool:
"""
Sell timeout - cancel order and update trade
:return: True if order was fully cancelled
"""
pair_s = trade.pair.replace('_', '/')
if order['remaining'] == order['amount']:
# if trade is not partially completed, just cancel the trade
self.exchange.cancel_order(trade.open_order_id, trade.pair)
if order["status"] != "canceled":
reason = "due to timeout"
self.exchange.cancel_order(trade.open_order_id, trade.pair)
logger.info('Sell order timeout for %s.', trade)
else:
reason = "on exchange"
logger.info('Sell order canceled on exchange for %s.', trade)
trade.close_rate = None
trade.close_profit = None
trade.close_date = None
@@ -756,9 +823,9 @@ class FreqtradeBot(object):
trade.open_order_id = None
self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Unfilled sell order for {pair_s} cancelled due to timeout'
'status': f'Unfilled sell order for {trade.pair} cancelled {reason}'
})
logger.info('Sell order timeout for %s.', trade)
return True
# TODO: figure out how to handle partially complete sell orders
@@ -780,11 +847,14 @@ class FreqtradeBot(object):
# we consider the sell price stop price
if self.config.get('dry_run', False) and sell_type == 'stoploss' \
and self.strategy.order_types['stoploss_on_exchange']:
limit = trade.stop_loss
limit = trade.stop_loss
# First cancelling stoploss on exchange ...
if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id:
self.exchange.cancel_order(trade.stoploss_order_id, trade.pair)
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),
@@ -796,11 +866,18 @@ class FreqtradeBot(object):
trade.open_order_id = order_id
trade.close_rate_requested = limit
trade.sell_reason = sell_reason.value
Trade.session.flush()
self.notify_sell(trade)
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)
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 = {
@@ -808,14 +885,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
@@ -829,4 +906,3 @@ class FreqtradeBot(object):
# Send the message
self.rpc.send_msg(msg)
Trade.session.flush()

View File

@@ -3,87 +3,69 @@
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 set_loggers
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
"""
arguments = Arguments(
sysargv,
'Simple High Frequency Trading Bot for crypto currencies'
)
args = 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
return_code: Any = 1
worker = None
try:
# Load and validate configuration
config = Configuration(args).get_config()
set_loggers()
# Init the bot
freqtrade = FreqtradeBot(config)
arguments = Arguments(
sysargv,
'Free, open source crypto trading bot'
)
args: Namespace = arguments.get_parsed_arg()
state = None
while 1:
state = freqtrade.worker(old_state=state)
if state == State.RELOAD_CONF:
freqtrade = reconfigure(freqtrade, args)
# A subcommand has been issued.
# Means if Backtesting or Hyperopt have been called we exit the bot
if hasattr(args, 'func'):
args.func(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).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,9 +1,7 @@
"""
Various tool function for Freqtrade and scripts
"""
import gzip
import json
import logging
import re
from datetime import datetime
@@ -11,6 +9,8 @@ from typing import Dict
import numpy as np
from pandas import DataFrame
import rapidjson
logger = logging.getLogger(__name__)
@@ -38,12 +38,7 @@ def datesarray_to_datetimearray(dates: np.ndarray) -> np.ndarray:
An numpy-array of datetimes
:return: numpy-array of datetime
"""
times = []
dates = dates.astype(datetime)
for index in range(0, dates.size):
date = dates[index].to_pydatetime()
times.append(date)
return np.array(times)
return dates.dt.to_pydatetime()
def common_datearray(dfs: Dict[str, DataFrame]) -> np.ndarray:
@@ -71,16 +66,45 @@ def file_dump_json(filename, data, is_zip=False) -> None:
:param data: JSON Data to save
:return:
"""
print(f'dumping json to "{filename}"')
logger.info(f'dumping json to "{filename}"')
if is_zip:
if not filename.endswith('.gz'):
filename = filename + '.gz'
with gzip.open(filename, 'w') as fp:
json.dump(data, fp, default=str)
rapidjson.dump(data, fp, default=str, number_mode=rapidjson.NM_NATIVE)
else:
with open(filename, 'w') as fp:
json.dump(data, fp, default=str)
rapidjson.dump(data, fp, default=str, number_mode=rapidjson.NM_NATIVE)
logger.debug(f'done json to "{filename}"')
def json_load(datafile):
"""
load data with rapidjson
Use this to have a consistent experience,
sete number_mode to "NM_NATIVE" for greatest speed
"""
return rapidjson.load(datafile, number_mode=rapidjson.NM_NATIVE)
def file_load_json(file):
gzipfile = file.with_suffix(file.suffix + '.gz')
# Try gzip file first, otherwise regular json file.
if gzipfile.is_file():
logger.debug('Loading ticker data from file %s', gzipfile)
with gzip.open(gzipfile) as tickerdata:
pairdata = json_load(tickerdata)
elif file.is_file():
logger.debug('Loading ticker data from file %s', file)
with open(file) as tickerdata:
pairdata = json_load(tickerdata)
else:
return None
return pairdata
def format_ms_time(date: int) -> str:
@@ -89,3 +113,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, HYPEROPT_LOCKFILE
# Initialize configuration
config = setup_configuration(args, RunMode.HYPEROPT)
logger.info('Starting freqtrade in Hyperopt mode')
lock = FileLock(HYPEROPT_LOCKFILE)
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,7 +4,6 @@
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
@@ -13,16 +12,15 @@ from typing import Any, Dict, List, NamedTuple, Optional
from pandas import DataFrame
from tabulate import tabulate
import freqtrade.optimize as optimize
from freqtrade import DependencyException, constants
from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration
from freqtrade.exchange import Exchange
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.strategy.interface import SellType, IStrategy
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.state import RunMode
from freqtrade.strategy.interface import IStrategy, SellType
logger = logging.getLogger(__name__)
@@ -64,34 +62,41 @@ 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
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:
@@ -100,11 +105,13 @@ class Backtesting(object):
:return: pretty printed table with tabulate as str
"""
stake_currency = str(self.config.get('stake_currency'))
max_open_trades = self.config.get('max_open_trades')
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', 'd', '.1f', '.1f')
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', '.2f', 'd', '.1f', '.1f')
tabular_data = []
headers = ['pair', 'buy count', 'avg profit %', 'cum profit %',
'total profit ' + stake_currency, 'avg duration', 'profit', 'loss']
'tot profit ' + stake_currency, 'tot profit %', 'avg duration',
'profit', 'loss']
for pair in data:
result = results[results.pair == pair]
if skip_nan and result.profit_abs.isnull().all():
@@ -116,6 +123,7 @@ class Backtesting(object):
result.profit_percent.mean() * 100.0,
result.profit_percent.sum() * 100.0,
result.profit_abs.sum(),
result.profit_percent.sum() * 100.0 / max_open_trades,
str(timedelta(
minutes=round(result.trade_duration.mean()))) if not result.empty else '0:00',
len(result[result.profit_abs > 0]),
@@ -129,12 +137,15 @@ class Backtesting(object):
results.profit_percent.mean() * 100.0,
results.profit_percent.sum() * 100.0,
results.profit_abs.sum(),
results.profit_percent.sum() * 100.0 / max_open_trades,
str(timedelta(
minutes=round(results.trade_duration.mean()))) if not results.empty else '0:00',
len(results[results.profit_abs > 0]),
len(results[results.profit_abs < 0])
])
return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe")
# Ignore type as floatfmt does allow tuples but mypy does not know that
return tabulate(tabular_data, headers=headers, # type: ignore
floatfmt=floatfmt, tablefmt="pipe")
def _generate_text_table_sell_reason(self, data: Dict[str, Dict], results: DataFrame) -> str:
"""
@@ -151,11 +162,13 @@ class Backtesting(object):
Generate summary table per strategy
"""
stake_currency = str(self.config.get('stake_currency'))
max_open_trades = self.config.get('max_open_trades')
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', 'd', '.1f', '.1f')
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', '.2f', 'd', '.1f', '.1f')
tabular_data = []
headers = ['Strategy', 'buy count', 'avg profit %', 'cum profit %',
'total profit ' + stake_currency, 'avg duration', 'profit', 'loss']
'tot profit ' + stake_currency, 'tot profit %', 'avg duration',
'profit', 'loss']
for strategy, results in all_results.items():
tabular_data.append([
strategy,
@@ -163,12 +176,15 @@ class Backtesting(object):
results.profit_percent.mean() * 100.0,
results.profit_percent.sum() * 100.0,
results.profit_abs.sum(),
results.profit_percent.sum() * 100.0 / max_open_trades,
str(timedelta(
minutes=round(results.trade_duration.mean()))) if not results.empty else '0:00',
len(results[results.profit_abs > 0]),
len(results[results.profit_abs < 0])
])
return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe")
# Ignore type as floatfmt does allow tuples but mypy does not know that
return tabulate(tabular_data, headers=headers, # type: ignore
floatfmt=floatfmt, tablefmt="pipe")
def _store_backtest_result(self, recordfilename: str, results: DataFrame,
strategyname: Optional[str] = None) -> None:
@@ -187,12 +203,37 @@ class Backtesting(object):
logger.info('Dumping backtest results to %s', 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,
@@ -208,8 +249,7 @@ 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:
@@ -219,7 +259,8 @@ class Backtesting(object):
# Set close_rate to stoploss
closerate = trade.stop_loss
elif sell.sell_type == (SellType.ROI):
# get entry in min_roi >= to trade duration
# 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]
@@ -280,74 +321,70 @@ class Backtesting(object):
position_stacking: do we allow position stacking? (default: False)
:return: DataFrame
"""
headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high']
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]:
continue
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)
trade_entry = self._get_sell_trade_entry(pair, row, ticker[pair][indexes[pair]:],
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:
@@ -360,23 +397,17 @@ 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_tickers(pairs, self.ticker_interval)
data = self.exchange._klines
else:
logger.info('Using local backtesting data (using whitelist in given config) ...')
timerange = Arguments.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,
pairs=pairs,
ticker_interval=self.ticker_interval,
refresh_pairs=self.config.get('refresh_pairs', False),
exchange=self.exchange,
timerange=timerange
)
timerange = Arguments.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,
pairs=pairs,
ticker_interval=self.ticker_interval,
refresh_pairs=self.config.get('refresh_pairs', False),
exchange=self.exchange,
timerange=timerange,
live=self.config.get('live', False)
)
if not data:
logger.critical("No data found. Terminating.")
@@ -389,6 +420,15 @@ class Backtesting(object):
max_open_trades = 0
all_results = {}
min_date, max_date = history.get_timeframe(data)
logger.info(
'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)
@@ -396,17 +436,6 @@ class Backtesting(object):
# need to reprocess data every time to populate signals
preprocessed = self.strategy.tickerdata_to_dataframe(data)
min_date, max_date = optimize.get_timeframe(preprocessed)
# Validate dataframe for missing values
optimize.validate_backtest_data(preprocessed, min_date, max_date,
constants.TICKER_INTERVAL_MINUTES[self.ticker_interval])
logger.info(
'Measuring data from %s up to %s (%s days)..',
min_date.isoformat(),
max_date.isoformat(),
(max_date - min_date).days
)
# Execute backtest and print results
all_results[self.strategy.get_strategy_name()] = self.backtest(
{
@@ -426,52 +455,17 @@ class Backtesting(object):
strategy if len(self.strategylist) > 1 else None)
print(f"Result for strategy {strategy}")
print(' BACKTESTING REPORT '.center(119, '='))
print(' BACKTESTING REPORT '.center(133, '='))
print(self._generate_text_table(data, results))
print(' SELL REASON STATS '.center(119, '='))
print(' SELL REASON STATS '.center(133, '='))
print(self._generate_text_table_sell_reason(data, results))
print(' LEFT OPEN TRADES REPORT '.center(119, '='))
print(' LEFT OPEN TRADES REPORT '.center(133, '='))
print(self._generate_text_table(data, results.loc[results.open_at_end], True))
print()
if len(all_results) > 1:
# Print Strategy summary table
print(' Strategy Summary '.center(119, '='))
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)
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

@@ -33,6 +33,7 @@ class DefaultHyperOpts(IHyperOpt):
# Bollinger bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_upperband'] = bollinger['upper']
dataframe['sar'] = ta.SAR(dataframe)
return dataframe
@@ -57,20 +58,22 @@ class DefaultHyperOpts(IHyperOpt):
conditions.append(dataframe['rsi'] < params['rsi-value'])
# TRIGGERS
if params['trigger'] == 'bb_lower':
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
if params['trigger'] == 'macd_cross_signal':
conditions.append(qtpylib.crossed_above(
dataframe['macd'], dataframe['macdsignal']
))
if params['trigger'] == 'sar_reversal':
conditions.append(qtpylib.crossed_above(
dataframe['close'], dataframe['sar']
))
if 'trigger' in params:
if params['trigger'] == 'bb_lower':
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
if params['trigger'] == 'macd_cross_signal':
conditions.append(qtpylib.crossed_above(
dataframe['macd'], dataframe['macdsignal']
))
if params['trigger'] == 'sar_reversal':
conditions.append(qtpylib.crossed_above(
dataframe['close'], dataframe['sar']
))
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
return dataframe
@@ -93,6 +96,68 @@ class DefaultHyperOpts(IHyperOpt):
Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger')
]
@staticmethod
def sell_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
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
"""
# 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'])
if 'sell-fastd-enabled' in params and params['sell-fastd-enabled']:
conditions.append(dataframe['fastd'] > params['sell-fastd-value'])
if 'sell-adx-enabled' in params and params['sell-adx-enabled']:
conditions.append(dataframe['adx'] < params['sell-adx-value'])
if 'sell-rsi-enabled' in params and params['sell-rsi-enabled']:
conditions.append(dataframe['rsi'] > params['sell-rsi-value'])
# TRIGGERS
if 'sell-trigger' in params:
if params['sell-trigger'] == 'sell-bb_upper':
conditions.append(dataframe['close'] > dataframe['bb_upperband'])
if params['sell-trigger'] == 'sell-macd_cross_signal':
conditions.append(qtpylib.crossed_above(
dataframe['macdsignal'], dataframe['macd']
))
if params['sell-trigger'] == 'sell-sar_reversal':
conditions.append(qtpylib.crossed_above(
dataframe['sar'], dataframe['close']
))
if conditions:
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'sell'] = 1
return dataframe
return populate_sell_trend
@staticmethod
def sell_indicator_space() -> List[Dimension]:
"""
Define your Hyperopt space for searching sell strategy parameters
"""
return [
Integer(75, 100, name='sell-mfi-value'),
Integer(50, 100, name='sell-fastd-value'),
Integer(50, 100, name='sell-adx-value'),
Integer(60, 100, name='sell-rsi-value'),
Categorical([True, False], name='sell-mfi-enabled'),
Categorical([True, False], name='sell-fastd-enabled'),
Categorical([True, False], name='sell-adx-enabled'),
Categorical([True, False], name='sell-rsi-enabled'),
Categorical(['sell-bb_upper',
'sell-macd_cross_signal',
'sell-sar_reversal'], name='sell-trigger')
]
@staticmethod
def generate_roi_table(params: Dict) -> Dict[int, float]:
"""
@@ -128,3 +193,36 @@ class DefaultHyperOpts(IHyperOpt):
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
"""
dataframe.loc[
(
(dataframe['close'] < dataframe['bb_lowerband']) &
(dataframe['mfi'] < 16) &
(dataframe['adx'] > 25) &
(dataframe['rsi'] < 21)
),
'buy'] = 1
return dataframe
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
"""
dataframe.loc[
(
(qtpylib.crossed_above(
dataframe['macdsignal'], dataframe['macd']
)) &
(dataframe['fastd'] > 54)
),
'sell'] = 1
return dataframe

View File

@@ -4,12 +4,11 @@
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.configuration import Configuration
from freqtrade.arguments import Arguments
from freqtrade.exchange import Exchange
from freqtrade.resolvers import StrategyResolver
@@ -34,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
@@ -67,40 +67,12 @@ class EdgeCli(object):
round(result[1].avg_trade_duration)
])
return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe")
# Ignore type as floatfmt does allow tuples but mypy does not know that
return tabulate(tabular_data, headers=headers, # type: ignore
floatfmt=floatfmt, tablefmt="pipe")
def start(self) -> None:
self.edge.calculate()
print('') # blank like 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)
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()
result = self.edge.calculate()
if result:
print('') # blank line for readability
print(self._generate_edge_table(self.edge._cached_pairs))

View File

@@ -5,32 +5,33 @@ This module contains the hyperopt logic
"""
import logging
from argparse import Namespace
import os
import sys
from pathlib import Path
from math import exp
import multiprocessing
from operator import itemgetter
from pathlib import Path
from pprint import pprint
from typing import Any, Dict, List
from joblib import Parallel, delayed, dump, load, wrap_non_picklable_objects, cpu_count
from pandas import DataFrame
from joblib import Parallel, delayed, dump, load, wrap_non_picklable_objects
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.data.history import load_data, get_timeframe
from freqtrade.optimize.backtesting import Backtesting
from freqtrade.resolvers import HyperOptResolver
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver
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')
TRIALSDATA_PICKLE = os.path.join('user_data', 'hyperopt_results.pickle')
HYPEROPT_LOCKFILE = os.path.join('user_data', 'hyperopt.lock')
class Hyperopt(Backtesting):
@@ -43,7 +44,6 @@ class Hyperopt(Backtesting):
"""
def __init__(self, config: Dict[str, Any]) -> None:
super().__init__(config)
self.config = config
self.custom_hyperopt = HyperOptResolver(self.config).hyperopt
# set TARGET_TRADES to suit your number concurrent trades so its realistic
@@ -56,13 +56,15 @@ class Hyperopt(Backtesting):
# 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!
# 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,
# self.expected_max_profit = 3.85
# Check that the reported Σ% values do not exceed this!
# Note, this is ratio. 3.85 stated above means 385Σ%.
self.expected_max_profit = 3.0
# Previous evaluations
self.trials_file = os.path.join('user_data', 'hyperopt_results.pickle')
self.trials_file = TRIALSDATA_PICKLE
self.trials: List = []
def get_args(self, params):
@@ -102,26 +104,32 @@ class Hyperopt(Backtesting):
results = sorted(self.trials, key=itemgetter('loss'))
best_result = results[0]
logger.info(
'Best result:\n%s\nwith values:\n%s',
best_result['result'],
best_result['params']
'Best result:\n%s\nwith values:\n',
best_result['result']
)
pprint(best_result['params'], indent=4)
if 'roi_t1' in best_result['params']:
logger.info('ROI table:\n%s',
self.custom_hyperopt.generate_roi_table(best_result['params']))
logger.info('ROI table:')
pprint(self.custom_hyperopt.generate_roi_table(best_result['params']), indent=4)
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']
print_all = self.config.get('print_all', False)
if print_all or results['loss'] < self.current_best_loss:
# Output human-friendly index here (starting from 1)
current = results['current_tries'] + 1
total = results['total_tries']
res = results['result']
loss = results['loss']
self.current_best_loss = results['loss']
log_msg = f'\n{current:5d}/{total}: {res}. Loss {loss:.5f}'
print(log_msg)
log_msg = f'{current:5d}/{total}: {res} Objective: {loss:.5f}'
log_msg = f'*{log_msg}' if results['initial_point'] else f' {log_msg}'
if print_all:
print(log_msg)
else:
print('\n' + log_msg)
else:
print('.', end='')
sys.stdout.flush()
@@ -151,6 +159,12 @@ class Hyperopt(Backtesting):
spaces: List[Dimension] = []
if self.has_space('buy'):
spaces += self.custom_hyperopt.indicator_space()
if self.has_space('sell'):
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'):
spaces += self.custom_hyperopt.roi_space()
if self.has_space('stoploss'):
@@ -164,6 +178,13 @@ class Hyperopt(Backtesting):
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
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
if self.has_space('stoploss'):
self.strategy.stoploss = params['stoploss']
@@ -185,7 +206,11 @@ class Hyperopt(Backtesting):
trade_count = len(results.index)
trade_duration = results.trade_duration.mean()
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,
@@ -208,20 +233,21 @@ class Hyperopt(Backtesting):
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,69 +270,68 @@ class Hyperopt(Backtesting):
datadir=Path(self.config['datadir']) if self.config.get('datadir') else None,
pairs=self.config['exchange']['pair_whitelist'],
ticker_interval=self.ticker_interval,
refresh_pairs=self.config.get('refresh_pairs', False),
exchange=self.exchange,
timerange=timerange
)
if self.has_space('buy'):
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
)
if self.has_space('buy') or self.has_space('sell'):
self.strategy.advise_indicators = \
self.custom_hyperopt.populate_indicators # type: ignore
dump(self.strategy.tickerdata_to_dataframe(data), TICKERDATA_PICKLE)
preprocessed = self.strategy.tickerdata_to_dataframe(data)
dump(preprocessed, TICKERDATA_PICKLE)
# We don't need exchange instance anymore while running hyperopt
self.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(cpus)
EVALS = max(self.total_tries // cpus, 1)
opt = self.get_optimizer(config_jobs)
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_tries // 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):
for j in range(jobs):
current = i * jobs + j
self.log_results({
'loss': f_val[j]['loss'],
'current_tries': i * cpus + j,
'current_tries': current,
'initial_point': current < INITIAL_POINTS,
'total_tries': self.total_tries,
'result': f_val[j]['result'],
})
logger.debug(f"Optimizer params: {f_val[j]['params']}")
for j in range(jobs):
logger.debug(f"Optimizer state: Xi: {opt.Xi[-j-1]}, yi: {opt.yi[-j-1]}")
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)
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

@@ -20,6 +20,7 @@ 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
@@ -37,6 +38,13 @@ class IHyperOpt(ABC):
Create a buy strategy generator
"""
@staticmethod
@abstractmethod
def sell_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Create a sell strategy generator
"""
@staticmethod
@abstractmethod
def indicator_space() -> List[Dimension]:
@@ -44,6 +52,13 @@ class IHyperOpt(ABC):
Create an indicator space
"""
@staticmethod
@abstractmethod
def sell_indicator_space() -> List[Dimension]:
"""
Create a sell indicator space
"""
@staticmethod
@abstractmethod
def generate_roi_table(params: Dict) -> Dict[int, float]:

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,9 @@ 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]
logger.info(f"Searching pairs: {self._whitelist}")
@cached(TTLCache(maxsize=1, ttl=1800))
def _gen_pair_whitelist(self, base_currency: str, key: str) -> List[str]:
@@ -68,8 +69,27 @@ 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]
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_order_id'):
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,9 +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')
@@ -111,7 +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, 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),
@@ -126,10 +133,12 @@ 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,
{stoploss_order_id} stoploss_order_id, {max_rate} max_rate,
{sell_reason} sell_reason, {strategy} strategy,
{ticker_interval} ticker_interval
{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, {min_rate} min_rate, {sell_reason} sell_reason,
{strategy} strategy, {ticker_interval} ticker_interval
from {table_back_name}
""")
@@ -181,12 +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)
@@ -197,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
@@ -206,23 +257,21 @@ 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:
logger.debug("keeping current stop loss")
@@ -260,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:
@@ -365,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

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

@@ -0,0 +1,223 @@
import logging
from typing import List
import pandas as pd
from pathlib import Path
logger = logging.getLogger(__name__)
try:
from plotly import tools
from plotly.offline import plot
import plotly.graph_objs as go
except ImportError:
logger.exception("Module plotly not found \n Please install using `pip install plotly`")
exit(1)
def generate_row(fig, row, indicators: List[str], data: pd.DataFrame) -> tools.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.append_trace(scattergl, row, 1)
else:
logger.info(
'Indicator "%s" ignored. Reason: This indicator is not found '
'in your strategy.',
indicator
)
return fig
def plot_trades(fig, trades: pd.DataFrame):
"""
Plot 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.append_trace(trade_buys, 1, 1)
fig.append_trace(trade_sells, 1, 1)
else:
logger.warning("No trades found.")
return fig
def generate_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 = tools.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.append_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.append_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.append_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.append_trace(bb_lower, 1, 1)
fig.append_trace(bb_upper, 1, 1)
# Add indicators to main plot
fig = generate_row(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.append_trace(volume, 2, 1)
# Add indicators to seperate row
fig = generate_row(fig=fig, row=3, indicators=indicators2, data=data)
return fig
def generate_plot_file(fig, pair, ticker_interval) -> 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
"""
logger.info('Generate plot file for %s', pair)
pair_name = pair.replace("/", "_")
file_name = 'freqtrade-plot-' + pair_name + '-' + ticker_interval + '.html'
Path("user_data/plots").mkdir(parents=True, exist_ok=True)
plot(fig, filename=str(Path('user_data/plots').joinpath(file_name)),
auto_open=False)

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,56 @@
"""
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.")
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("Using resolved exchange %s", exchange_name)
return exchange
except AttributeError:
# Pass and raise ImportError instead
pass
raise ImportError(
"Impossible to load Exchange '{}'. This class does not exist"
" or contains Python code errors".format(exchange_name)
)

View File

@@ -32,6 +32,16 @@ class HyperOptResolver(IResolver):
hyperopt_name = config.get('hyperopt') or DEFAULT_HYPEROPT
self.hyperopt = self._load_hyperopt(hyperopt_name, 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. "
"Using populate_buy_trend from DefaultStrategy.")
if not hasattr(self.hyperopt, 'populate_sell_trend'):
logger.warning("Custom Hyperopt does not provide populate_sell_trend. "
"Using populate_sell_trend from DefaultStrategy.")
def _load_hyperopt(
self, hyperopt_name: str, extra_dir: Optional[str] = None) -> IHyperOpt:
"""
@@ -56,7 +66,7 @@ class HyperOptResolver(IResolver):
hyperopt = self._search_object(directory=_path, object_type=IHyperOpt,
object_name=hyperopt_name)
if hyperopt:
logger.info('Using resolved hyperopt %s from \'%s\'', hyperopt_name, _path)
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()))

View File

@@ -31,7 +31,11 @@ class IResolver(object):
# Generate spec based on absolute path
spec = importlib.util.spec_from_file_location('unknown', str(module_path))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) # type: ignore # importlib does not use typehints
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)
@@ -47,7 +51,7 @@ class IResolver(object):
: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'):

View File

@@ -48,7 +48,7 @@ class PairListResolver(IResolver):
object_name=pairlist_name,
kwargs=kwargs)
if pairlist:
logger.info('Using resolved pairlist %s from \'%s\'', pairlist_name, _path)
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()))

View File

@@ -3,11 +3,11 @@
"""
This module load custom strategies
"""
import inspect
import logging
import tempfile
from base64 import urlsafe_b64decode
from collections import OrderedDict
from inspect import getfullargspec
from pathlib import Path
from typing import Dict, Optional
@@ -39,59 +39,70 @@ class StrategyResolver(IResolver):
config=config,
extra_dir=config.get('strategy_path'))
# make sure experimental dict is available
if 'experimental' not in config:
config['experimental'] = {}
# Set attributes
# Check if we need to override configuration
if 'minimal_roi' in config:
self.strategy.minimal_roi = config['minimal_roi']
logger.info("Override strategy 'minimal_roi' with value in config file: %s.",
config['minimal_roi'])
else:
config['minimal_roi'] = self.strategy.minimal_roi
# (Attribute name, default, experimental)
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),
]
for attribute, default, experimental in attributes:
if experimental:
self._override_attribute_helper(config['experimental'], attribute, default)
else:
self._override_attribute_helper(config, attribute, default)
if 'stoploss' in config:
self.strategy.stoploss = config['stoploss']
logger.info(
"Override strategy 'stoploss' with value in config file: %s.", config['stoploss']
)
else:
config['stoploss'] = self.strategy.stoploss
# Loop this list again to have output combined
for attribute, _, exp in attributes:
if exp and attribute in config['experimental']:
logger.info("Strategy using %s: %s", attribute, config['experimental'][attribute])
elif attribute in config:
logger.info("Strategy using %s: %s", attribute, config[attribute])
if 'ticker_interval' in config:
self.strategy.ticker_interval = config['ticker_interval']
logger.info(
"Override strategy 'ticker_interval' with value in config file: %s.",
config['ticker_interval']
)
else:
config['ticker_interval'] = self.strategy.ticker_interval
# Sort and apply type conversions
self.strategy.minimal_roi = OrderedDict(sorted(
{int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(),
key=lambda t: t[0]))
self.strategy.stoploss = float(self.strategy.stoploss)
if 'process_only_new_candles' in config:
self.strategy.process_only_new_candles = config['process_only_new_candles']
logger.info(
"Override process_only_new_candles 'process_only_new_candles' "
"with value in config file: %s.", config['process_only_new_candles']
)
else:
config['process_only_new_candles'] = self.strategy.process_only_new_candles
self._strategy_sanity_validations()
if 'order_types' in config:
self.strategy.order_types = config['order_types']
logger.info(
"Override strategy 'order_types' with value in config file: %s.",
config['order_types']
)
else:
config['order_types'] = self.strategy.order_types
if 'order_time_in_force' in config:
self.strategy.order_time_in_force = config['order_time_in_force']
logger.info(
"Override strategy 'order_time_in_force' with value in config file: %s.",
config['order_time_in_force']
)
else:
config['order_time_in_force'] = self.strategy.order_time_in_force
def _override_attribute_helper(self, config, attribute: str, default):
"""
Override attributes in the strategy.
Prevalence:
- Configuration
- Strategy
- default (if not None)
"""
if attribute in config:
setattr(self.strategy, attribute, config[attribute])
logger.info("Override strategy '%s' with value in config file: %s.",
attribute, config[attribute])
elif hasattr(self.strategy, attribute):
config[attribute] = getattr(self.strategy, attribute)
# Explicitly check for None here as other "falsy" values are possible
elif default is not None:
setattr(self.strategy, attribute, default)
config[attribute] = default
def _strategy_sanity_validations(self):
if not all(k in self.strategy.order_types for k in constants.REQUIRED_ORDERTYPES):
raise ImportError(f"Impossible to load Strategy '{self.strategy.__class__.__name__}'. "
f"Order-types mapping is incomplete.")
@@ -100,12 +111,6 @@ class StrategyResolver(IResolver):
raise ImportError(f"Impossible to load Strategy '{self.strategy.__class__.__name__}'. "
f"Order-time-in-force mapping is incomplete.")
# Sort and apply type conversions
self.strategy.minimal_roi = OrderedDict(sorted(
{int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(),
key=lambda t: t[0]))
self.strategy.stoploss = float(self.strategy.stoploss)
def _load_strategy(
self, strategy_name: str, config: dict, extra_dir: Optional[str] = None) -> IStrategy:
"""
@@ -147,19 +152,20 @@ class StrategyResolver(IResolver):
strategy = self._search_object(directory=_path, object_type=IStrategy,
object_name=strategy_name, kwargs={'config': config})
if strategy:
logger.info('Using resolved strategy %s from \'%s\'', strategy_name, _path)
logger.info("Using resolved strategy %s from '%s'", strategy_name, _path)
strategy._populate_fun_len = len(
inspect.getfullargspec(strategy.populate_indicators).args)
strategy._buy_fun_len = len(
inspect.getfullargspec(strategy.populate_buy_trend).args)
strategy._sell_fun_len = len(
inspect.getfullargspec(strategy.populate_sell_trend).args)
return import_strategy(strategy, config=config)
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 TypeError as e:
logger.warning(
f"Impossible to load strategy '{strategy}' from {_path}. Error: {e}")
except FileNotFoundError:
logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd()))
raise ImportError(
"Impossible to load Strategy '{}'. This class does not exist"
" or contains Python code errors".format(strategy_name)
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

@@ -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']
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))
@@ -214,7 +215,7 @@ class RPC(object):
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)
@@ -280,11 +281,12 @@ class RPC(object):
rate = 1.0
else:
try:
if coin == 'USDT':
rate = 1.0 / self._freqtrade.exchange.get_ticker('BTC/USDT', False)['bid']
if coin in('USDT', 'USD', 'EUR'):
rate = 1.0 / self._freqtrade.get_sell_rate('BTC/' + coin, False)
else:
rate = self._freqtrade.exchange.get_ticker(coin + '/BTC', False)['bid']
rate = self._freqtrade.get_sell_rate(coin + '/BTC', 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
@@ -329,7 +331,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 +369,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 +378,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 +396,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 +450,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,35 @@ 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 ""
]
messages.append("\n".join(filter(None, lines)).format(**r))
for msg in messages:
self._send_msg(msg, bot=bot)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@@ -246,14 +262,15 @@ class Telegram(RPC):
stake_cur,
fiat_disp_cur
)
stats = tabulate(stats,
headers=[
'Day',
f'Profit {stake_cur}',
f'Profit {fiat_disp_cur}'
],
tablefmt='simple')
message = f'<b>Daily Profit over the last {timescale} days</b>:\n<pre>{stats}</pre>'
stats_tab = tabulate(stats,
headers=[
'Day',
f'Profit {stake_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>'
self._send_msg(message, bot=bot, parse_mode=ParseMode.HTML)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@@ -311,13 +328,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 +386,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 +410,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 +466,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 +493,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 +534,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 +543,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

@@ -3,13 +3,26 @@
"""
Bot state constant
"""
import enum
from enum import Enum
class State(enum.Enum):
class State(Enum):
"""
Bot application states
"""
RUNNING = 0
STOPPED = 1
RELOAD_CONF = 2
RUNNING = 1
STOPPED = 2
RELOAD_CONF = 3
class RunMode(Enum):
"""
Bot running mode (backtest, hyperopt, ...)
can be "live", "dry-run", "backtest", "edge", "hyperopt".
"""
LIVE = "live"
DRY_RUN = "dry_run"
BACKTEST = "backtest"
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

@@ -42,6 +42,19 @@ class DefaultStrategy(IStrategy):
'sell': 'gtc',
}
def informative_pairs(self):
"""
Define additional, informative pair/interval combinations to be cached from the exchange.
These pair/interval combinations are non-tradeable, unless they are part
of the whitelist as well.
For more information, please consult the documentation
:return: List of tuples in the format (pair, interval)
Sample: return [("ETH/USDT", "5m"),
("BTC/USDT", "15m"),
]
"""
return []
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Adds several different TA indicators to the given DataFrame

View File

@@ -12,8 +12,11 @@ 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__)
@@ -67,6 +70,12 @@ class IStrategy(ABC):
# associated stoploss
stoploss: float
# trailing stoploss
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
@@ -75,7 +84,8 @@ class IStrategy(ABC):
'buy': 'limit',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False
'stoploss_on_exchange': False,
'stoploss_on_exchange_interval': 60,
}
# Optional time in force
@@ -87,12 +97,16 @@ class IStrategy(ABC):
# run "populate_indicators" only for new candle
process_only_new_candles: bool = False
# Dict to determine if analysis is necessary
_last_candle_seen_per_pair: Dict[str, datetime] = {}
# Class level variables (intentional) containing
# the dataprovider (dp) (access to other candles, historic data, ...)
# and wallets - access to the current balance.
dp: DataProvider
wallets: Wallets
def __init__(self, config: dict) -> None:
self.config = config
self._last_candle_seen_per_pair = {}
# Dict to determine if analysis is necessary
self._last_candle_seen_per_pair: Dict[str, datetime] = {}
@abstractmethod
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
@@ -121,6 +135,19 @@ class IStrategy(ABC):
:return: DataFrame with sell column
"""
def informative_pairs(self) -> List[Tuple[str, str]]:
"""
Define additional, informative pair/interval combinations to be cached from the exchange.
These pair/interval combinations are non-tradeable, unless they are part
of the whitelist as well.
For more information, please consult the documentation
:return: List of tuples in the format (pair, interval)
Sample: return [("ETH/USDT", "5m"),
("BTC/USDT", "15m"),
]
"""
return []
def get_strategy_name(self) -> str:
"""
Returns strategy class name
@@ -131,7 +158,7 @@ class IStrategy(ABC):
"""
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
:return: DataFrame with ticker data and indicator data
"""
pair = str(metadata.get('pair'))
@@ -141,19 +168,19 @@ 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.
logging.debug("TA Analysis Launched")
logger.debug("TA Analysis Launched")
dataframe = self.advise_indicators(dataframe, metadata)
dataframe = self.advise_buy(dataframe, metadata)
dataframe = self.advise_sell(dataframe, metadata)
self._last_candle_seen_per_pair[pair] = dataframe.iloc[-1]['date']
else:
logging.debug("Skippinig TA Analysis for already analyzed candle")
logger.debug("Skipping TA Analysis for already analyzed candle")
dataframe['buy'] = 0
dataframe['sell'] = 0
# Other Defs in strategy that want to be called every loop here
# twitter_sell = self.watch_twitter_feed(dataframe, metadata)
logging.debug("Loop Analysis Launched")
logger.debug("Loop Analysis Launched")
return dataframe
@@ -195,7 +222,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(
@@ -221,6 +248,9 @@ class IStrategy(ABC):
"""
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.
: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
"""
@@ -228,17 +258,16 @@ class IStrategy(ABC):
current_rate = low or rate
current_profit = trade.calc_profit_percent(current_rate)
if self.order_types.get('stoploss_on_exchange'):
stoplossflag = SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
else:
stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade,
current_time=date, current_profit=current_profit,
force_stoploss=force_stoploss)
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, 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', {})
@@ -262,8 +291,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
@@ -271,14 +301,39 @@ class IStrategy(ABC):
"""
trailing_stop = self.config.get('trailing_stop', False)
stop_loss_value = force_stoploss if force_stoploss else self.stoploss
trade.adjust_stop_loss(trade.open_rate, force_stoploss if force_stoploss
else self.stoploss, initial=True)
# 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'))):
# evaluate if the stoploss was hit
if self.stoploss is not None and trade.stop_loss >= current_rate:
selltype = SellType.STOP_LOSS
if trailing_stop:
# 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}, "
@@ -290,40 +345,23 @@ 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 = self.stoploss
sl_offset = self.config.get('trailing_stop_positive_offset', 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(self, trade: Trade, current_profit: float, current_time: datetime) -> bool:
"""
Based an earlier trade and current price and ROI configuration, decides whether bot should
sell
:return True if bot should sell at current rate
sell. Requires current_profit to be in percent!!
:return: True if bot should sell at current rate
"""
# Check if time matches and current rate is above threshold
time_diff = (current_time.timestamp() - trade.open_date.timestamp()) / 60
for duration, threshold in self.minimal_roi.items():
if time_diff <= duration:
return False
if current_profit > threshold:
return True
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
return False
@@ -342,6 +380,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)
@@ -357,6 +396,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)
@@ -372,6 +412,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

@@ -1,20 +1,26 @@
# pragma pylint: disable=missing-docstring
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 typing import List
from unittest.mock import MagicMock, PropertyMock
import arrow
import pytest
from telegram import Chat, Message, Update
from freqtrade import constants
from freqtrade import constants, persistence
from freqtrade.arguments 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)
@@ -27,8 +33,19 @@ def log_has(line, logs):
False)
def log_has_re(line, logs):
return reduce(lambda a, b: a or b,
filter(lambda x: re.match(line, x[2]), logs),
False)
def get_args(args) -> List[str]:
return Arguments(args, '').get_parsed_arg()
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))
@@ -42,7 +59,11 @@ 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)
exchange = Exchange(config)
config["exchange"]["name"] = id
try:
exchange = ExchangeResolver(id, config).exchange
except ImportError:
exchange = Exchange(config)
return exchange
@@ -75,24 +96,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
@@ -113,6 +164,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 """
@@ -158,6 +214,10 @@ def default_conf():
"LTC/BTC",
"XRP/BTC",
"NEO/BTC"
],
"pair_blacklist": [
"DOGE/BTC",
"HOT/BTC",
]
},
"telegram": {
@@ -213,8 +273,8 @@ def ticker_sell_down():
@pytest.fixture
def markets():
return MagicMock(return_value=[
{
return {
'ETH/BTC': {
'id': 'ethbtc',
'symbol': 'ETH/BTC',
'base': 'ETH',
@@ -239,7 +299,7 @@ def markets():
},
'info': '',
},
{
'TKN/BTC': {
'id': 'tknbtc',
'symbol': 'TKN/BTC',
'base': 'TKN',
@@ -264,7 +324,7 @@ def markets():
},
'info': '',
},
{
'BLK/BTC': {
'id': 'blkbtc',
'symbol': 'BLK/BTC',
'base': 'BLK',
@@ -289,7 +349,7 @@ def markets():
},
'info': '',
},
{
'LTC/BTC': {
'id': 'ltcbtc',
'symbol': 'LTC/BTC',
'base': 'LTC',
@@ -314,7 +374,7 @@ def markets():
},
'info': '',
},
{
'XRP/BTC': {
'id': 'xrpbtc',
'symbol': 'XRP/BTC',
'base': 'XRP',
@@ -339,7 +399,7 @@ def markets():
},
'info': '',
},
{
'NEO/BTC': {
'id': 'neobtc',
'symbol': 'NEO/BTC',
'base': 'NEO',
@@ -363,8 +423,80 @@ def markets():
},
},
'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
@@ -542,7 +674,7 @@ def ticker_history_list():
@pytest.fixture
def ticker_history(ticker_history_list):
return parse_ticker_dataframe(ticker_history_list)
return parse_ticker_dataframe(ticker_history_list, "5m", pair="UNITTEST/BTC", fill_missing=True)
@pytest.fixture
@@ -583,6 +715,7 @@ def tickers():
'vwap': 0.01869197,
'open': 0.018585,
'close': 0.018573,
'last': 0.018799,
'baseVolume': 81058.66,
'quoteVolume': 2247.48374509,
},
@@ -630,6 +763,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,
@@ -723,8 +878,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))
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
@@ -822,9 +978,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,
@@ -840,4 +997,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,76 @@
from unittest.mock import MagicMock
from arrow import Arrow
import pytest
from pandas import DataFrame, to_datetime
from freqtrade.arguments import TimeRange
from freqtrade.data.btanalysis import (BT_DATA_COLUMNS,
extract_trades_of_period,
load_backtest_data, load_trades_from_db)
from freqtrade.data.history import 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
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

View File

@@ -1,25 +1,150 @@
# pragma pylint: disable=missing-docstring, C0103
import logging
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.data.converter import parse_ticker_dataframe, ohlcv_fill_up_missing_data
from freqtrade.data.history import load_pair_history, validate_backtest_data, get_timeframe
from freqtrade.tests.conftest import log_has
def test_dataframe_correct_length(result):
dataframe = parse_ticker_dataframe(result)
assert len(result.index) - 1 == len(dataframe.index) # last partial candle removed
def test_dataframe_correct_columns(result):
assert result.columns.tolist() == \
['date', 'open', 'high', 'low', 'close', 'volume']
assert result.columns.tolist() == ['date', 'open', 'high', 'low', 'close', 'volume']
def test_parse_ticker_dataframe(ticker_history, caplog):
def test_parse_ticker_dataframe(ticker_history_list, caplog):
columns = ['date', 'open', 'high', 'low', 'close', 'volume']
caplog.set_level(logging.DEBUG)
# Test file with BV data
dataframe = parse_ticker_dataframe(ticker_history)
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)
def test_ohlcv_fill_up_missing_data(caplog):
data = load_pair_history(datadir=None,
ticker_interval='1m',
refresh_pairs=False,
pair='UNITTEST/BTC',
fill_up_missing=False)
caplog.set_level(logging.DEBUG)
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 for UNITTEST/BTC: before: "
f"{len(data)} - after: {len(data2)}",
caplog.record_tuples)
# Test fillup actually fixes invalid backtest data
min_date, max_date = get_timeframe({'UNITTEST/BTC': data})
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):
ticker_interval = '5m'
ticks = [[
1511686200000, # 8:50:00
8.794e-05, # open
8.948e-05, # high
8.794e-05, # low
8.88e-05, # close
2255, # volume (in quote currency)
],
[
1511686500000, # 8:55:00
8.88e-05,
8.942e-05,
8.88e-05,
8.893e-05,
9911,
],
[
1511687100000, # 9:05:00
8.891e-05,
8.893e-05,
8.875e-05,
8.877e-05,
2251
],
[
1511687400000, # 9:10:00
8.877e-05,
8.883e-05,
8.895e-05,
8.817e-05,
123551
]
]
# Generate test-data without filling missing
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, "UNITTEST/BTC")
assert len(data2) == 4
# 3rd candle has been filled
row = data2.loc[2, :]
assert row['volume'] == 0
# close shoult match close of previous candle
assert row['close'] == data.loc[1, 'close']
assert row['open'] == row['close']
assert row['high'] == row['close']
assert row['low'] == row['close']
# Column names should not change
assert (data.columns == data2.columns).all()
assert log_has(f"Missing data fillup for UNITTEST/BTC: before: "
f"{len(data)} - after: {len(data2)}",
caplog.record_tuples)
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.record_tuples)
# 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.record_tuples)

View File

@@ -0,0 +1,92 @@
from unittest.mock import MagicMock
from pandas import DataFrame
from freqtrade.data.dataprovider import DataProvider
from freqtrade.state import RunMode
from freqtrade.tests.conftest import get_patched_exchange
def test_ohlcv(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.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", 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", ticker_interval), DataFrame)
default_conf["runmode"] = RunMode.BACKTEST
dp = DataProvider(default_conf, exchange)
assert dp.runmode == RunMode.BACKTEST
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)
assert historymock.call_count == 1
assert historymock.call_args_list[0][1]["datadir"] is None
assert historymock.call_args_list[0][1]["refresh_pairs"] is False
assert historymock.call_args_list[0][1]["ticker_interval"] == "5m"
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
dp = DataProvider(default_conf, exchange)
assert len(dp.available_pairs) == 2
assert dp.available_pairs == [
("XRP/BTC", ticker_interval),
("UNITTEST/BTC", ticker_interval),
]
def test_refresh(mocker, default_conf, ticker_history):
refresh_mock = MagicMock()
mocker.patch("freqtrade.exchange.Exchange.refresh_latest_ohlcv", refresh_mock)
exchange = get_patched_exchange(mocker, default_conf, id="binance")
ticker_interval = default_conf["ticker_interval"]
pairs = [("XRP/BTC", ticker_interval), ("UNITTEST/BTC", ticker_interval)]
pairs_non_trad = [("ETH/USDT", ticker_interval), ("BTC/TUSD", "1h")]
dp = DataProvider(default_conf, exchange)
dp.refresh(pairs)
assert refresh_mock.call_count == 1
assert len(refresh_mock.call_args[0]) == 1
assert len(refresh_mock.call_args[0][0]) == len(pairs)
assert refresh_mock.call_args[0][0] == pairs
refresh_mock.reset_mock()
dp.refresh(pairs, pairs_non_trad)
assert refresh_mock.call_count == 1
assert len(refresh_mock.call_args[0]) == 1
assert len(refresh_mock.call_args[0][0]) == len(pairs) + len(pairs_non_trad)
assert refresh_mock.call_args[0][0] == pairs + pairs_non_trad

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.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,11 @@ 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.record_tuples
)
def test_load_data_7min_ticker(mocker, caplog, default_conf) -> None:
@@ -67,8 +74,11 @@ 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 download_backtest_data.py '
'script to download the data',
caplog.record_tuples
)
def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None:
@@ -77,7 +87,11 @@ def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None:
_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.record_tuples
)
_clean_test_file(file)
@@ -96,9 +110,12 @@ 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 download_backtest_data.py '
'script to download the data',
caplog.record_tuples
)
# download a new pair if refresh_pairs is set
history.load_pair_history(datadir=None,
@@ -107,7 +124,11 @@ 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.record_tuples
)
with pytest.raises(OperationalException, match=r'Exchange needs to be initialized when.*'):
history.load_pair_history(datadir=None,
ticker_interval='1m',
@@ -117,6 +138,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.record_tuples)
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))
@@ -242,10 +288,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 +305,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
@@ -280,14 +326,14 @@ def test_download_pair_history2(mocker, default_conf) -> None:
json_dump_mock = mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
mocker.patch('freqtrade.exchange.Exchange.get_history', 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'))
side_effect=Exception('File Error'))
exchange = get_patched_exchange(mocker, default_conf)
@@ -298,11 +344,15 @@ 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.record_tuples
)
def test_load_tickerdata_file() -> None:
@@ -450,7 +500,7 @@ def test_trim_tickerlist() -> None:
assert not ticker
def test_file_dump_json() -> None:
def test_file_dump_json_tofile() -> None:
file = os.path.join(os.path.dirname(__file__), '..', 'testdata',
'test_{id}.json'.format(id=str(uuid.uuid4())))
data = {'bar': 'foo'}
@@ -473,3 +523,62 @@ def test_file_dump_json() -> 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.record_tuples)
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

View File

@@ -10,10 +10,11 @@ import numpy as np
import pytest
from pandas import DataFrame, to_datetime
from freqtrade import OperationalException
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.edge import Edge, PairInfo
from freqtrade.strategy.interface import SellType
from freqtrade.tests.conftest import get_patched_freqtradebot
from freqtrade.tests.conftest import get_patched_freqtradebot, log_has
from freqtrade.tests.optimize import (BTContainer, BTrade,
_build_backtest_dataframe,
_get_frame_time_from_offset)
@@ -30,7 +31,50 @@ ticker_start_time = arrow.get(2018, 10, 3)
ticker_interval_in_minute = 60
_ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7}
# Helpers for this test file
def _validate_ohlc(buy_ohlc_sell_matrice):
for index, ohlc in enumerate(buy_ohlc_sell_matrice):
# if not high < open < low or not high < close < low
if not ohlc[3] >= ohlc[2] >= ohlc[4] or not ohlc[3] >= ohlc[5] >= ohlc[4]:
raise Exception('Line ' + str(index + 1) + ' of ohlc has invalid values!')
return True
def _build_dataframe(buy_ohlc_sell_matrice):
_validate_ohlc(buy_ohlc_sell_matrice)
tickers = []
for ohlc in buy_ohlc_sell_matrice:
ticker = {
'date': ticker_start_time.shift(
minutes=(
ohlc[0] *
ticker_interval_in_minute)).timestamp *
1000,
'buy': ohlc[1],
'open': ohlc[2],
'high': ohlc[3],
'low': ohlc[4],
'close': ohlc[5],
'sell': ohlc[6]}
tickers.append(ticker)
frame = DataFrame(tickers)
frame['date'] = to_datetime(frame['date'],
unit='ms',
utc=True,
infer_datetime_format=True)
return frame
def _time_on_candle(number):
return np.datetime64(ticker_start_time.shift(
minutes=(number * ticker_interval_in_minute)).timestamp * 1000, 'ms')
# End helper functions
# Open trade should be removed from the end
tc0 = BTContainer(data=[
# D O H L C V B S
@@ -122,8 +166,8 @@ def test_edge_results(edge_conf, mocker, caplog, data) -> None:
for c, trade in enumerate(data.trades):
res = results.iloc[c]
assert res.exit_type == trade.sell_reason
assert res.open_time == _get_frame_time_from_offset(trade.open_tick)
assert res.close_time == _get_frame_time_from_offset(trade.close_tick)
assert arrow.get(res.open_time) == _get_frame_time_from_offset(trade.open_tick)
assert arrow.get(res.close_time) == _get_frame_time_from_offset(trade.close_tick)
def test_adjust(mocker, edge_conf):
@@ -203,46 +247,6 @@ def test_nonexisting_stake_amount(mocker, edge_conf):
assert edge.stake_amount('N/O', 1, 2, 1) == 0.15
def _validate_ohlc(buy_ohlc_sell_matrice):
for index, ohlc in enumerate(buy_ohlc_sell_matrice):
# if not high < open < low or not high < close < low
if not ohlc[3] >= ohlc[2] >= ohlc[4] or not ohlc[3] >= ohlc[5] >= ohlc[4]:
raise Exception('Line ' + str(index + 1) + ' of ohlc has invalid values!')
return True
def _build_dataframe(buy_ohlc_sell_matrice):
_validate_ohlc(buy_ohlc_sell_matrice)
tickers = []
for ohlc in buy_ohlc_sell_matrice:
ticker = {
'date': ticker_start_time.shift(
minutes=(
ohlc[0] *
ticker_interval_in_minute)).timestamp *
1000,
'buy': ohlc[1],
'open': ohlc[2],
'high': ohlc[3],
'low': ohlc[4],
'close': ohlc[5],
'sell': ohlc[6]}
tickers.append(ticker)
frame = DataFrame(tickers)
frame['date'] = to_datetime(frame['date'],
unit='ms',
utc=True,
infer_datetime_format=True)
return frame
def _time_on_candle(number):
return np.datetime64(ticker_start_time.shift(
minutes=(number * ticker_interval_in_minute)).timestamp * 1000, 'ms')
def test_edge_heartbeat_calculate(mocker, edge_conf):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
@@ -259,7 +263,7 @@ def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=Fals
hz = 0.1
base = 0.001
ETHBTC = [
NEOBTC = [
[
ticker_start_time.shift(minutes=(x * ticker_interval_in_minute)).timestamp * 1000,
math.sin(x * hz) / 1000 + base,
@@ -281,8 +285,8 @@ def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=Fals
123.45
] for x in range(0, 500)]
pairdata = {'NEO/BTC': parse_ticker_dataframe(ETHBTC),
'LTC/BTC': parse_ticker_dataframe(LTCBTC)}
pairdata = {'NEO/BTC': parse_ticker_dataframe(NEOBTC, '1h', pair="NEO/BTC", fill_missing=True),
'LTC/BTC': parse_ticker_dataframe(LTCBTC, '1h', pair="LTC/BTC", fill_missing=True)}
return pairdata
@@ -298,6 +302,40 @@ def test_edge_process_downloaded_data(mocker, edge_conf):
assert edge._last_updated <= arrow.utcnow().timestamp + 2
def test_edge_process_no_data(mocker, edge_conf, caplog):
edge_conf['datadir'] = None
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
mocker.patch('freqtrade.data.history.load_data', MagicMock(return_value={}))
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
assert not edge.calculate()
assert len(edge._cached_pairs) == 0
assert log_has("No data found. Edge is stopped ...", caplog.record_tuples)
assert edge._last_updated == 0
def test_edge_process_no_trades(mocker, edge_conf, caplog):
edge_conf['datadir'] = None
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
mocker.patch('freqtrade.data.history.load_data', mocked_load_data)
# Return empty
mocker.patch('freqtrade.edge.Edge._find_trades_for_stoploss_range', MagicMock(return_value=[]))
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
assert not edge.calculate()
assert len(edge._cached_pairs) == 0
assert log_has("No trades found.", caplog.record_tuples)
def test_edge_init_error(mocker, edge_conf,):
edge_conf['stake_amount'] = 0.5
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
with pytest.raises(OperationalException, match='Edge works only with unlimited stake amount'):
get_patched_freqtradebot(mocker, edge_conf)
def test_process_expectancy(mocker, edge_conf):
edge_conf['edge']['min_trade_number'] = 2
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
@@ -360,3 +398,11 @@ def test_process_expectancy(mocker, edge_conf):
assert round(final['TEST/BTC'].risk_reward_ratio, 10) == 306.5384615384
assert round(final['TEST/BTC'].required_risk_reward, 10) == 2.0
assert round(final['TEST/BTC'].expectancy, 10) == 101.5128205128
# Pop last item so no trade is profitable
trades.pop()
trades_df = DataFrame(trades)
trades_df = edge._fill_calculable_fields(trades_df)
final = edge._process_expectancy(trades_df)
assert len(final) == 0
assert isinstance(final, dict)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,67 @@
# pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement
# pragma pylint: disable=protected-access
from random import randint
from unittest.mock import MagicMock
from freqtrade.tests.conftest import get_patched_exchange
def test_buy_kraken_trading_agreement(default_conf, mocker):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_type = 'limit'
time_in_force = 'ioc'
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
order = exchange.buy(pair='ETH/BTC', ordertype=order_type,
amount=1, rate=200, time_in_force=time_in_force)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'buy'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] == 200
assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'ioc',
'trading_agreement': 'agree'}
def test_sell_kraken_trading_agreement(default_conf, mocker):
api_mock = MagicMock()
order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6))
order_type = 'market'
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="kraken")
order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'sell'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is None
assert api_mock.create_order.call_args[0][5] == {'trading_agreement': 'agree'}

View File

@@ -3,11 +3,11 @@ from typing import NamedTuple, List
import arrow
from pandas import DataFrame
from freqtrade.exchange import timeframe_to_minutes
from freqtrade.strategy.interface import SellType
from freqtrade.constants import TICKER_INTERVAL_MINUTES
ticker_start_time = arrow.get(2018, 10, 3)
tests_ticker_interval = "1h"
tests_ticker_interval = '1h'
class BTrade(NamedTuple):
@@ -28,11 +28,16 @@ class BTContainer(NamedTuple):
roi: float
trades: List[BTrade]
profit_perc: float
trailing_stop: bool = False
trailing_only_offset_is_reached: bool = False
trailing_stop_positive: float = None
trailing_stop_positive_offset: float = 0.0
use_sell_signal: bool = False
def _get_frame_time_from_offset(offset):
return ticker_start_time.shift(minutes=(offset * TICKER_INTERVAL_MINUTES[tests_ticker_interval])
).datetime.replace(tzinfo=None)
return ticker_start_time.shift(minutes=(offset * timeframe_to_minutes(tests_ticker_interval))
).datetime
def _build_backtest_dataframe(ticker_with_signals):

View File

@@ -2,22 +2,37 @@
import logging
from unittest.mock import MagicMock
from pandas import DataFrame
import pytest
from pandas import DataFrame
from freqtrade.optimize import get_timeframe
from freqtrade.data.history import get_timeframe
from freqtrade.optimize.backtesting import Backtesting
from freqtrade.strategy.interface import SellType
from freqtrade.tests.optimize import (BTrade, BTContainer, _build_backtest_dataframe,
_get_frame_time_from_offset, tests_ticker_interval)
from freqtrade.tests.conftest import patch_exchange
from freqtrade.tests.optimize import (BTContainer, BTrade,
_build_backtest_dataframe,
_get_frame_time_from_offset,
tests_ticker_interval)
# Test 0 Sell signal sell
# Test with Stop-loss at 1%
# TC0: Sell signal in candle 3
tc0 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4986, 4600, 6172, 0, 0], # exit with stoploss hit
[3, 5010, 5000, 4980, 5010, 6172, 0, 1],
[4, 5010, 4987, 4977, 4995, 6172, 0, 0],
[5, 4995, 4995, 4995, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi=1, profit_perc=0.002, use_sell_signal=True,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=4)]
)
# Test 0 Minus 8% Close
# Test 1 Minus 8% Close
# Test with Stop-loss at 1%
# TC1: Stop-Loss Triggered 1% loss
tc0 = BTContainer(data=[
tc1 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
@@ -30,10 +45,10 @@ tc0 = BTContainer(data=[
)
# Test 1 Minus 4% Low, minus 1% close
# Test 2 Minus 4% Low, minus 1% close
# Test with Stop-Loss at 3%
# TC2: Stop-Loss Triggered 3% Loss
tc1 = BTContainer(data=[
tc2 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
@@ -49,11 +64,10 @@ tc1 = BTContainer(data=[
# Test 3 Candle drops 4%, Recovers 1%.
# Entry Criteria Met
# Candle drops 20%
# Candle Data for test 3
# Test with Stop-Loss at 2%
# TC3: Trade-A: Stop-Loss Triggered 2% Loss
# Trade-B: Stop-Loss Triggered 2% Loss
tc2 = BTContainer(data=[
tc3 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
@@ -71,7 +85,7 @@ tc2 = BTContainer(data=[
# Candle Data for test 3 Candle drops 3% Closed 15% up
# Test with Stop-loss at 2% ROI 6%
# TC4: Stop-Loss Triggered 2% Loss
tc3 = BTContainer(data=[
tc4 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
@@ -83,10 +97,10 @@ tc3 = BTContainer(data=[
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 4 / Drops 0.5% Closes +20%
# Test 5 / Drops 0.5% Closes +20%
# Set stop-loss at 1% ROI 3%
# TC5: ROI triggers 3% Gain
tc4 = BTContainer(data=[
tc5 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4980, 4987, 6172, 1, 0],
[1, 5000, 5025, 4980, 4987, 6172, 0, 0], # enter trade (signal on last candle)
@@ -99,10 +113,9 @@ tc4 = BTContainer(data=[
)
# Test 6 / Drops 3% / Recovers 6% Positive / Closes 1% positve
# Candle Data for test 6
# Set stop-loss at 2% ROI at 5%
# TC6: Stop-Loss triggers 2% Loss
tc5 = BTContainer(data=[
tc6 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
@@ -115,10 +128,9 @@ tc5 = BTContainer(data=[
)
# Test 7 - 6% Positive / 1% Negative / Close 1% Positve
# Candle Data for test 7
# Set stop-loss at 2% ROI at 3%
# TC7: ROI Triggers 3% Gain
tc6 = BTContainer(data=[
tc7 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
@@ -130,6 +142,88 @@ tc6 = BTContainer(data=[
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
)
# Test 8 - trailing_stop should raise so candle 3 causes a stoploss.
# Set stop-loss at 10%, ROI at 10% (should not apply)
# TC8: Trailing stoploss - stoploss should be adjusted candle 2
tc8 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5050, 4950, 5000, 6172, 0, 0],
[2, 5000, 5250, 4750, 4850, 6172, 0, 0],
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi=0.10, profit_perc=-0.055, trailing_stop=True,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 9 - trailing_stop should raise - high and low in same candle.
# Candle Data for test 9
# Set stop-loss at 10%, ROI at 10% (should not apply)
# TC9: Trailing stoploss - stoploss should be adjusted candle 3
tc9 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5050, 4950, 5000, 6172, 0, 0],
[2, 5000, 5050, 4950, 5000, 6172, 0, 0],
[3, 5000, 5200, 4550, 4850, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi=0.10, profit_perc=-0.064, trailing_stop=True,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 10 - trailing_stop should raise so candle 3 causes a stoploss
# without applying trailing_stop_positive since stoploss_offset is at 10%.
# Set stop-loss at 10%, ROI at 10% (should not apply)
# TC10: Trailing stoploss - stoploss should be adjusted candle 2
tc10 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5050, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi=0.10, profit_perc=-0.1, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.10,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=4)]
)
# Test 11 - trailing_stop should raise so candle 3 causes a stoploss
# applying a positive trailing stop of 3% since stop_positive_offset is reached.
# Set stop-loss at 10%, ROI at 10% (should not apply)
# TC11: Trailing stoploss - stoploss should be adjusted candle 2,
tc11 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5050, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 5100, 5100, 6172, 0, 0],
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi=0.10, profit_perc=0.019, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 12 - trailing_stop should raise in candle 2 and cause a stoploss in the same candle
# applying a positive trailing stop of 3% since stop_positive_offset is reached.
# Set stop-loss at 10%, ROI at 10% (should not apply)
# TC12: Trailing stoploss - stoploss should be adjusted candle 2,
tc12 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5050, 4950, 5100, 6172, 0, 0],
[2, 5100, 5251, 4650, 5100, 6172, 0, 0],
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi=0.10, profit_perc=0.019, trailing_stop=True,
trailing_only_offset_is_reached=True, trailing_stop_positive_offset=0.05,
trailing_stop_positive=0.03,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=2)]
)
TESTS = [
tc0,
tc1,
@@ -138,6 +232,12 @@ TESTS = [
tc4,
tc5,
tc6,
tc7,
tc8,
tc9,
tc10,
tc11,
tc12,
]
@@ -148,8 +248,16 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
"""
default_conf["stoploss"] = data.stop_loss
default_conf["minimal_roi"] = {"0": data.roi}
default_conf['ticker_interval'] = tests_ticker_interval
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.0))
default_conf["ticker_interval"] = tests_ticker_interval
default_conf["trailing_stop"] = data.trailing_stop
default_conf["trailing_only_offset_is_reached"] = data.trailing_only_offset_is_reached
# Only add this to configuration If it's necessary
if data.trailing_stop_positive:
default_conf["trailing_stop_positive"] = data.trailing_stop_positive
default_conf["trailing_stop_positive_offset"] = data.trailing_stop_positive_offset
default_conf["experimental"] = {"use_sell_signal": data.use_sell_signal}
mocker.patch("freqtrade.exchange.Exchange.get_fee", MagicMock(return_value=0.0))
patch_exchange(mocker)
frame = _build_backtest_dataframe(data.data)
backtesting = Backtesting(default_conf)
@@ -157,7 +265,7 @@ def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
backtesting.advise_sell = lambda a, m: frame
caplog.set_level(logging.DEBUG)
pair = 'UNITTEST/BTC'
pair = "UNITTEST/BTC"
# Dummy data as we mock the analyze functions
data_processed = {pair: DataFrame()}
min_date, max_date = get_timeframe({pair: frame})

View File

@@ -3,7 +3,6 @@
import json
import math
import random
from typing import List
from unittest.mock import MagicMock
import numpy as np
@@ -12,25 +11,24 @@ import pytest
from arrow import Arrow
from freqtrade import DependencyException, constants
from freqtrade.arguments import Arguments, TimeRange
from freqtrade.arguments import TimeRange
from freqtrade.data import history
from freqtrade.data.btanalysis import evaluate_result_multi
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.optimize import get_timeframe
from freqtrade.optimize.backtesting import (Backtesting, setup_configuration,
start)
from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.history import get_timeframe
from freqtrade.optimize import setup_configuration, start_backtesting
from freqtrade.optimize.backtesting import Backtesting
from freqtrade.state import RunMode
from freqtrade.strategy.default_strategy import DefaultStrategy
from freqtrade.strategy.interface import SellType
from freqtrade.tests.conftest import log_has, patch_exchange
def get_args(args) -> List[str]:
return Arguments(args, '').get_parsed_arg()
from freqtrade.tests.conftest import get_args, log_has, log_has_re, patch_exchange
def trim_dictlist(dict_list, num):
new = {}
for pair, pair_data in dict_list.items():
new[pair] = pair_data[num:]
new[pair] = pair_data[num:].reset_index()
return new
@@ -75,7 +73,8 @@ def load_data_test(what):
pair[x][5] # Keep old volume
] for x in range(0, datalen)
]
return {'UNITTEST/BTC': parse_ticker_dataframe(data)}
return {'UNITTEST/BTC': parse_ticker_dataframe(data, '1m', pair="UNITTEST/BTC",
fill_missing=True)}
def simple_backtest(config, contour, num_results, mocker) -> None:
@@ -102,9 +101,10 @@ def simple_backtest(config, contour, num_results, mocker) -> None:
def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=False,
timerange=None, exchange=None):
timerange=None, exchange=None, live=False):
tickerdata = history.load_tickerdata_file(datadir, 'UNITTEST/BTC', '1m', timerange=timerange)
pairdata = {'UNITTEST/BTC': parse_ticker_dataframe(tickerdata)}
pairdata = {'UNITTEST/BTC': parse_ticker_dataframe(tickerdata, '1m', pair="UNITTEST/BTC",
fill_missing=True)}
return pairdata
@@ -175,7 +175,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
'backtesting'
]
config = setup_configuration(get_args(args))
config = setup_configuration(get_args(args), RunMode.BACKTEST)
assert 'max_open_trades' in config
assert 'stake_currency' in config
assert 'stake_amount' in config
@@ -187,7 +187,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
caplog.record_tuples
)
assert 'ticker_interval' in config
assert not log_has('Parameter -i/--ticker-interval detected ...', caplog.record_tuples)
assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog.record_tuples)
assert 'live' not in config
assert not log_has('Parameter -l/--live detected ...', caplog.record_tuples)
@@ -200,12 +200,15 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
assert 'timerange' not in config
assert 'export' not in config
assert 'runmode' in config
assert config['runmode'] == RunMode.BACKTEST
def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> None:
def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) -> None:
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf)
))
mocker.patch('freqtrade.configuration.Configuration._create_datadir', lambda s, c, x: x)
args = [
'--config', 'config.json',
@@ -222,23 +225,22 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
'--export-filename', 'foo_bar.json'
]
config = setup_configuration(get_args(args))
config = setup_configuration(get_args(args), RunMode.BACKTEST)
assert 'max_open_trades' in config
assert 'stake_currency' in config
assert 'stake_amount' in config
assert 'exchange' in config
assert 'pair_whitelist' in config['exchange']
assert 'datadir' in config
assert config['runmode'] == RunMode.BACKTEST
assert log_has(
'Using data folder: {} ...'.format(config['datadir']),
caplog.record_tuples
)
assert 'ticker_interval' in config
assert log_has('Parameter -i/--ticker-interval detected ...', caplog.record_tuples)
assert log_has(
'Using ticker_interval: 1m ...',
caplog.record_tuples
)
assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...',
caplog.record_tuples)
assert 'live' in config
assert log_has('Parameter -l/--live detected ...', caplog.record_tuples)
@@ -252,6 +254,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
assert 'refresh_pairs' in config
assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples)
assert 'timerange' in config
assert log_has(
'Parameter --timerange detected: {} ...'.format(config['timerange']),
@@ -284,7 +287,7 @@ def test_setup_configuration_unlimited_stake_amount(mocker, default_conf, caplog
]
with pytest.raises(DependencyException, match=r'.*stake amount.*'):
setup_configuration(get_args(args))
setup_configuration(get_args(args), RunMode.BACKTEST)
def test_start(mocker, fee, default_conf, caplog) -> None:
@@ -301,7 +304,7 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
'backtesting'
]
args = get_args(args)
start(args)
start_backtesting(args)
assert log_has(
'Starting freqtrade in Backtesting mode',
caplog.record_tuples
@@ -309,7 +312,28 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
assert start_mock.call_count == 1
def test_backtesting_init(mocker, default_conf) -> None:
ORDER_TYPES = [
{
'buy': 'limit',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False
},
{
'buy': 'limit',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': True
}]
@pytest.mark.parametrize("order_types", ORDER_TYPES)
def test_backtesting_init(mocker, default_conf, order_types) -> None:
"""
Check that stoploss_on_exchange is set to False while backtesting
since backtesting assumes a perfect stoploss anyway.
"""
default_conf["order_types"] = order_types
patch_exchange(mocker)
get_fee = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
backtesting = Backtesting(default_conf)
@@ -318,19 +342,22 @@ def test_backtesting_init(mocker, default_conf) -> None:
assert callable(backtesting.strategy.tickerdata_to_dataframe)
assert callable(backtesting.advise_buy)
assert callable(backtesting.advise_sell)
assert isinstance(backtesting.strategy.dp, DataProvider)
get_fee.assert_called()
assert backtesting.fee == 0.5
assert not backtesting.strategy.order_types["stoploss_on_exchange"]
def test_tickerdata_to_dataframe(default_conf, mocker) -> None:
def test_tickerdata_to_dataframe_bt(default_conf, mocker) -> None:
patch_exchange(mocker)
timerange = TimeRange(None, 'line', 0, -100)
tick = history.load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick)}
tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick, '1m', pair="UNITTEST/BTC",
fill_missing=True)}
backtesting = Backtesting(default_conf)
data = backtesting.strategy.tickerdata_to_dataframe(tickerlist)
assert len(data['UNITTEST/BTC']) == 99
assert len(data['UNITTEST/BTC']) == 102
# Load strategy to compare the result between Backtesting function and strategy are the same
strategy = DefaultStrategy(default_conf)
@@ -340,6 +367,7 @@ def test_tickerdata_to_dataframe(default_conf, mocker) -> None:
def test_generate_text_table(default_conf, mocker):
patch_exchange(mocker)
default_conf['max_open_trades'] = 2
backtesting = Backtesting(default_conf)
results = pd.DataFrame(
@@ -355,13 +383,13 @@ def test_generate_text_table(default_conf, mocker):
result_str = (
'| pair | buy count | avg profit % | cum profit % | '
'total profit BTC | avg duration | profit | loss |\n'
'tot profit BTC | tot profit % | avg duration | profit | loss |\n'
'|:--------|------------:|---------------:|---------------:|'
'-------------------:|:---------------|---------:|-------:|\n'
'| ETH/BTC | 2 | 15.00 | 30.00 | '
'0.60000000 | 0:20:00 | 2 | 0 |\n'
'| TOTAL | 2 | 15.00 | 30.00 | '
'0.60000000 | 0:20:00 | 2 | 0 |'
'-----------------:|---------------:|:---------------|---------:|-------:|\n'
'| ETH/BTC | 2 | 15.00 | 30.00 | '
'0.60000000 | 15.00 | 0:20:00 | 2 | 0 |\n'
'| TOTAL | 2 | 15.00 | 30.00 | '
'0.60000000 | 15.00 | 0:20:00 | 2 | 0 |'
)
assert backtesting._generate_text_table(data={'ETH/BTC': {}}, results=results) == result_str
@@ -397,6 +425,7 @@ def test_generate_text_table_strategyn(default_conf, mocker):
Test Backtesting.generate_text_table_sell_reason() method
"""
patch_exchange(mocker)
default_conf['max_open_trades'] = 2
backtesting = Backtesting(default_conf)
results = {}
results['ETH/BTC'] = pd.DataFrame(
@@ -424,13 +453,13 @@ def test_generate_text_table_strategyn(default_conf, mocker):
result_str = (
'| Strategy | buy count | avg profit % | cum profit % '
'| total profit BTC | avg duration | profit | loss |\n'
'| tot profit BTC | tot profit % | avg duration | profit | loss |\n'
'|:-----------|------------:|---------------:|---------------:'
'|-------------------:|:---------------|---------:|-------:|\n'
'|-----------------:|---------------:|:---------------|---------:|-------:|\n'
'| ETH/BTC | 3 | 20.00 | 60.00 '
'| 1.10000000 | 0:17:00 | 3 | 0 |\n'
'| 1.10000000 | 30.00 | 0:17:00 | 3 | 0 |\n'
'| LTC/BTC | 3 | 30.00 | 90.00 '
'| 1.30000000 | 0:20:00 | 3 | 0 |'
'| 1.30000000 | 45.00 | 0:20:00 | 3 | 0 |'
)
print(backtesting._generate_text_table_strategy(all_results=results))
assert backtesting._generate_text_table_strategy(all_results=results) == result_str
@@ -441,8 +470,8 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
mocker.patch('freqtrade.data.history.load_data', mocked_load_data)
mocker.patch('freqtrade.optimize.get_timeframe', get_timeframe)
mocker.patch('freqtrade.exchange.Exchange.refresh_tickers', MagicMock())
mocker.patch('freqtrade.data.history.get_timeframe', get_timeframe)
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', MagicMock())
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.optimize.backtesting.Backtesting',
@@ -461,10 +490,9 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
backtesting.start()
# check the logs, that will contain the backtest result
exists = [
'Using local backtesting data (using whitelist in given config) ...',
'Using stake_currency: BTC ...',
'Using stake_amount: 0.001 ...',
'Measuring data from 2017-11-14T21:17:00+00:00 '
'Backtesting with data from 2017-11-14T21:17:00+00:00 '
'up to 2017-11-14T22:59:00+00:00 (0 days)..'
]
for line in exists:
@@ -476,8 +504,8 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog) -> None:
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
mocker.patch('freqtrade.data.history.load_data', MagicMock(return_value={}))
mocker.patch('freqtrade.optimize.get_timeframe', get_timeframe)
mocker.patch('freqtrade.exchange.Exchange.refresh_tickers', MagicMock())
mocker.patch('freqtrade.data.history.get_timeframe', get_timeframe)
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', MagicMock())
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.optimize.backtesting.Backtesting',
@@ -526,13 +554,14 @@ def test_backtest(default_conf, fee, mocker) -> None:
{'pair': [pair, pair],
'profit_percent': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
'open_time': [Arrow(2018, 1, 29, 18, 40, 0).datetime,
Arrow(2018, 1, 30, 3, 30, 0).datetime],
'close_time': [Arrow(2018, 1, 29, 22, 35, 0).datetime,
Arrow(2018, 1, 30, 4, 15, 0).datetime],
'open_time': pd.to_datetime([Arrow(2018, 1, 29, 18, 40, 0).datetime,
Arrow(2018, 1, 30, 3, 30, 0).datetime], utc=True
),
'close_time': pd.to_datetime([Arrow(2018, 1, 29, 22, 35, 0).datetime,
Arrow(2018, 1, 30, 4, 10, 0).datetime], utc=True),
'open_index': [78, 184],
'close_index': [125, 193],
'trade_duration': [235, 45],
'close_index': [125, 192],
'trade_duration': [235, 40],
'open_at_end': [False, False],
'open_rate': [0.104445, 0.10302485],
'close_rate': [0.104969, 0.103541],
@@ -593,7 +622,7 @@ def test_processed(default_conf, mocker) -> None:
def test_backtest_pricecontours(default_conf, fee, mocker) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
tests = [['raise', 18], ['lower', 0], ['sine', 19]]
tests = [['raise', 19], ['lower', 0], ['sine', 18]]
# We need to enable sell-signal - otherwise it sells on ROI!!
default_conf['experimental'] = {"use_sell_signal": True}
@@ -651,40 +680,32 @@ def test_backtest_alternate_buy_sell(default_conf, fee, mocker):
assert len(results.loc[results.open_at_end]) == 0
def test_backtest_multi_pair(default_conf, fee, mocker):
def evaluate_result_multi(results, freq, max_open_trades):
# Find overlapping trades by expanding each trade once per period
# and then counting overlaps
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 = df2.astype(dtype={"open_time": "datetime64", "close_time": "datetime64"})
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]
@pytest.mark.parametrize("pair", ['ADA/BTC', 'LTC/BTC'])
@pytest.mark.parametrize("tres", [0, 20, 30])
def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair):
def _trend_alternate_hold(dataframe=None, metadata=None):
"""
Buy every 8th candle - sell every other 8th -2 (hold on to pairs a bit)
Buy every xth candle - sell every other xth -2 (hold on to pairs a bit)
"""
multi = 8
if metadata['pair'] in('ETH/BTC', 'LTC/BTC'):
multi = 20
else:
multi = 18
dataframe['buy'] = np.where(dataframe.index % multi == 0, 1, 0)
dataframe['sell'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0)
if metadata['pair'] in('ETH/BTC', 'LTC/BTC'):
dataframe['buy'] = dataframe['buy'].shift(-4)
dataframe['sell'] = dataframe['sell'].shift(-4)
return dataframe
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
patch_exchange(mocker)
pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC']
data = history.load_data(datadir=None, ticker_interval='5m', pairs=pairs)
# Only use 500 lines to increase performance
data = trim_dictlist(data, -500)
# Remove data for one pair from the beginning of the data
data[pair] = data[pair][tres:].reset_index()
# We need to enable sell-signal - otherwise it sells on ROI!!
default_conf['experimental'] = {"use_sell_signal": True}
default_conf['ticker_interval'] = '5m'
@@ -823,19 +844,19 @@ def test_backtest_start_live(default_conf, mocker, caplog):
'--disable-max-market-positions'
]
args = get_args(args)
start(args)
start_backtesting(args)
# check the logs, that will contain the backtest result
exists = [
'Parameter -i/--ticker-interval detected ...',
'Using ticker_interval: 1m ...',
'Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...',
'Parameter -l/--live detected ...',
'Ignoring max_open_trades (--disable-max-market-positions was used) ...',
'Parameter --timerange detected: -100 ...',
'Using data folder: freqtrade/tests/testdata ...',
'Using stake_currency: BTC ...',
'Using stake_amount: 0.001 ...',
'Downloading data for all pairs in whitelist ...',
'Measuring data from 2017-11-14T19:31:00+00:00 up to 2017-11-14T22:58:00+00:00 (0 days)..',
'Live: Downloading data for all defined pairs ...',
'Backtesting with data from 2017-11-14T19:31:00+00:00 '
'up to 2017-11-14T22:58:00+00:00 (0 days)..',
'Parameter --enable-position-stacking detected ...'
]
@@ -877,7 +898,7 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog):
'TestStrategy',
]
args = get_args(args)
start(args)
start_backtesting(args)
# 2 backtests, 4 tables
assert backtestmock.call_count == 2
assert gen_table_mock.call_count == 4
@@ -885,16 +906,16 @@ def test_backtest_start_multi_strat(default_conf, mocker, caplog):
# check the logs, that will contain the backtest result
exists = [
'Parameter -i/--ticker-interval detected ...',
'Using ticker_interval: 1m ...',
'Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...',
'Parameter -l/--live detected ...',
'Ignoring max_open_trades (--disable-max-market-positions was used) ...',
'Parameter --timerange detected: -100 ...',
'Using data folder: freqtrade/tests/testdata ...',
'Using stake_currency: BTC ...',
'Using stake_amount: 0.001 ...',
'Downloading data for all pairs in whitelist ...',
'Measuring data from 2017-11-14T19:31:00+00:00 up to 2017-11-14T22:58:00+00:00 (0 days)..',
'Live: Downloading data for all defined pairs ...',
'Backtesting with data from 2017-11-14T19:31:00+00:00 '
'up to 2017-11-14T22:58:00+00:00 (0 days)..',
'Parameter --enable-position-stacking detected ...',
'Running backtesting for Strategy DefaultStrategy',
'Running backtesting for Strategy TestStrategy',

View File

@@ -1,17 +1,14 @@
# pragma pylint: disable=missing-docstring, C0103, C0330
# pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments
from unittest.mock import MagicMock
import json
from typing import List
from unittest.mock import MagicMock
from freqtrade.edge import PairInfo
from freqtrade.arguments import Arguments
from freqtrade.optimize.edge_cli import (EdgeCli, setup_configuration, start)
from freqtrade.tests.conftest import log_has, patch_exchange
def get_args(args) -> List[str]:
return Arguments(args, '').get_parsed_arg()
from freqtrade.optimize import setup_configuration, start_edge
from freqtrade.optimize.edge_cli import EdgeCli
from freqtrade.state import RunMode
from freqtrade.tests.conftest import get_args, log_has, log_has_re, patch_exchange
def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None:
@@ -25,7 +22,9 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
'edge'
]
config = setup_configuration(get_args(args))
config = setup_configuration(get_args(args), RunMode.EDGE)
assert config['runmode'] == RunMode.EDGE
assert 'max_open_trades' in config
assert 'stake_currency' in config
assert 'stake_amount' in config
@@ -37,7 +36,7 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
caplog.record_tuples
)
assert 'ticker_interval' in config
assert not log_has('Parameter -i/--ticker-interval detected ...', caplog.record_tuples)
assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog.record_tuples)
assert 'refresh_pairs' not in config
assert not log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples)
@@ -46,10 +45,11 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
assert 'stoploss_range' not in config
def test_setup_configuration_with_arguments(mocker, edge_conf, caplog) -> None:
def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> None:
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(edge_conf)
))
mocker.patch('freqtrade.configuration.Configuration._create_datadir', lambda s, c, x: x)
args = [
'--config', 'config.json',
@@ -62,23 +62,21 @@ def test_setup_configuration_with_arguments(mocker, edge_conf, caplog) -> None:
'--stoplosses=-0.01,-0.10,-0.001'
]
config = setup_configuration(get_args(args))
config = setup_configuration(get_args(args), RunMode.EDGE)
assert 'max_open_trades' in config
assert 'stake_currency' in config
assert 'stake_amount' in config
assert 'exchange' in config
assert 'pair_whitelist' in config['exchange']
assert 'datadir' in config
assert config['runmode'] == RunMode.EDGE
assert log_has(
'Using data folder: {} ...'.format(config['datadir']),
caplog.record_tuples
)
assert 'ticker_interval' in config
assert log_has('Parameter -i/--ticker-interval detected ...', caplog.record_tuples)
assert log_has(
'Using ticker_interval: 1m ...',
caplog.record_tuples
)
assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...',
caplog.record_tuples)
assert 'refresh_pairs' in config
assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples)
@@ -103,7 +101,7 @@ def test_start(mocker, fee, edge_conf, caplog) -> None:
'edge'
]
args = get_args(args)
start(args)
start_edge(args)
assert log_has(
'Starting freqtrade in Edge mode',
caplog.record_tuples
@@ -113,8 +111,10 @@ def test_start(mocker, fee, edge_conf, caplog) -> None:
def test_edge_init(mocker, edge_conf) -> None:
patch_exchange(mocker)
edge_conf['stake_amount'] = 20
edge_cli = EdgeCli(edge_conf)
assert edge_cli.config == edge_conf
assert edge_cli.config['stake_amount'] == 'unlimited'
assert callable(edge_cli.edge.calculate)

View File

@@ -1,17 +1,22 @@
# pragma pylint: disable=missing-docstring,W0212,C0103
from datetime import datetime
import json
import os
from datetime import datetime
from unittest.mock import MagicMock
from filelock import Timeout
import pandas as pd
import pytest
from freqtrade import DependencyException
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.data.history import load_tickerdata_file
from freqtrade.optimize.hyperopt import Hyperopt, start
from freqtrade.resolvers import StrategyResolver
from freqtrade.tests.conftest import log_has, patch_exchange
from freqtrade.tests.optimize.test_backtesting import get_args
from freqtrade.optimize.default_hyperopt import DefaultHyperOpts
from freqtrade.optimize.hyperopt import Hyperopt, HYPEROPT_LOCKFILE
from freqtrade.optimize import setup_configuration, start_hyperopt
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver
from freqtrade.state import RunMode
from freqtrade.tests.conftest import get_args, log_has, log_has_re, patch_exchange
@pytest.fixture(scope='function')
@@ -38,6 +43,133 @@ def create_trials(mocker, hyperopt) -> None:
return [{'loss': 1, 'result': 'foo', 'params': {}}]
def test_setup_hyperopt_configuration_without_arguments(mocker, default_conf, caplog) -> None:
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf)
))
args = [
'--config', 'config.json',
'hyperopt'
]
config = setup_configuration(get_args(args), RunMode.HYPEROPT)
assert 'max_open_trades' in config
assert 'stake_currency' in config
assert 'stake_amount' in config
assert 'exchange' in config
assert 'pair_whitelist' in config['exchange']
assert 'datadir' in config
assert log_has(
'Using data folder: {} ...'.format(config['datadir']),
caplog.record_tuples
)
assert 'ticker_interval' in config
assert not log_has_re('Parameter -i/--ticker-interval detected .*', caplog.record_tuples)
assert 'live' not in config
assert not log_has('Parameter -l/--live detected ...', caplog.record_tuples)
assert 'position_stacking' not in config
assert not log_has('Parameter --enable-position-stacking detected ...', caplog.record_tuples)
assert 'refresh_pairs' not in config
assert not log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples)
assert 'timerange' not in config
assert 'runmode' in config
assert config['runmode'] == RunMode.HYPEROPT
def test_setup_hyperopt_configuration_with_arguments(mocker, default_conf, caplog) -> None:
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf)
))
mocker.patch('freqtrade.configuration.Configuration._create_datadir', lambda s, c, x: x)
args = [
'--config', 'config.json',
'--datadir', '/foo/bar',
'hyperopt',
'--ticker-interval', '1m',
'--timerange', ':100',
'--refresh-pairs-cached',
'--enable-position-stacking',
'--disable-max-market-positions',
'--epochs', '1000',
'--spaces', 'all',
'--print-all'
]
config = setup_configuration(get_args(args), RunMode.HYPEROPT)
assert 'max_open_trades' in config
assert 'stake_currency' in config
assert 'stake_amount' in config
assert 'exchange' in config
assert 'pair_whitelist' in config['exchange']
assert 'datadir' in config
assert config['runmode'] == RunMode.HYPEROPT
assert log_has(
'Using data folder: {} ...'.format(config['datadir']),
caplog.record_tuples
)
assert 'ticker_interval' in config
assert log_has('Parameter -i/--ticker-interval detected ... Using ticker_interval: 1m ...',
caplog.record_tuples)
assert 'position_stacking' in config
assert log_has('Parameter --enable-position-stacking detected ...', caplog.record_tuples)
assert 'use_max_market_positions' in config
assert log_has('Parameter --disable-max-market-positions detected ...', caplog.record_tuples)
assert log_has('max_open_trades set to unlimited ...', caplog.record_tuples)
assert 'refresh_pairs' in config
assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples)
assert 'timerange' in config
assert log_has(
'Parameter --timerange detected: {} ...'.format(config['timerange']),
caplog.record_tuples
)
assert 'epochs' in config
assert log_has('Parameter --epochs detected ... Will run Hyperopt with for 1000 epochs ...',
caplog.record_tuples)
assert 'spaces' in config
assert log_has(
'Parameter -s/--spaces detected: {}'.format(config['spaces']),
caplog.record_tuples
)
assert 'print_all' in config
assert log_has('Parameter --print-all detected ...', caplog.record_tuples)
def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
mocker.patch(
'freqtrade.configuration.Configuration._load_config_file',
lambda *args, **kwargs: default_conf
)
hyperopts = DefaultHyperOpts
delattr(hyperopts, 'populate_buy_trend')
delattr(hyperopts, 'populate_sell_trend')
mocker.patch(
'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver._load_hyperopt',
MagicMock(return_value=hyperopts)
)
x = HyperOptResolver(default_conf, ).hyperopt
assert not hasattr(x, 'populate_buy_trend')
assert not hasattr(x, 'populate_sell_trend')
assert log_has("Custom Hyperopt does not provide populate_sell_trend. "
"Using populate_sell_trend from DefaultStrategy.", caplog.record_tuples)
assert log_has("Custom Hyperopt does not provide populate_buy_trend. "
"Using populate_buy_trend from DefaultStrategy.", caplog.record_tuples)
assert hasattr(x, "ticker_interval")
def test_start(mocker, default_conf, caplog) -> None:
start_mock = MagicMock()
mocker.patch(
@@ -49,13 +181,11 @@ def test_start(mocker, default_conf, caplog) -> None:
args = [
'--config', 'config.json',
'--strategy', 'DefaultStrategy',
'hyperopt',
'--epochs', '5'
]
args = get_args(args)
StrategyResolver({'strategy': 'DefaultStrategy'})
start(args)
start_hyperopt(args)
import pprint
pprint.pprint(caplog.record_tuples)
@@ -67,6 +197,33 @@ def test_start(mocker, default_conf, caplog) -> None:
assert start_mock.call_count == 1
def test_start_no_data(mocker, default_conf, caplog) -> None:
mocker.patch(
'freqtrade.configuration.Configuration._load_config_file',
lambda *args, **kwargs: default_conf
)
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock(return_value={}))
mocker.patch(
'freqtrade.optimize.hyperopt.get_timeframe',
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13)))
)
patch_exchange(mocker)
args = [
'--config', 'config.json',
'hyperopt',
'--epochs', '5'
]
args = get_args(args)
start_hyperopt(args)
import pprint
pprint.pprint(caplog.record_tuples)
assert log_has('No data found. Terminating.', caplog.record_tuples)
def test_start_failure(mocker, default_conf, caplog) -> None:
start_mock = MagicMock()
mocker.patch(
@@ -83,17 +240,37 @@ def test_start_failure(mocker, default_conf, caplog) -> None:
'--epochs', '5'
]
args = get_args(args)
StrategyResolver({'strategy': 'DefaultStrategy'})
with pytest.raises(ValueError):
start(args)
with pytest.raises(DependencyException):
start_hyperopt(args)
assert log_has(
"Please don't use --strategy for hyperopt.",
caplog.record_tuples
)
def test_start_filelock(mocker, default_conf, caplog) -> None:
start_mock = MagicMock(side_effect=Timeout(HYPEROPT_LOCKFILE))
mocker.patch(
'freqtrade.configuration.Configuration._load_config_file',
lambda *args, **kwargs: default_conf
)
mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock)
patch_exchange(mocker)
args = [
'--config', 'config.json',
'hyperopt',
'--epochs', '5'
]
args = get_args(args)
start_hyperopt(args)
assert log_has(
"Another running instance of freqtrade Hyperopt detected.",
caplog.record_tuples
)
def test_loss_calculation_prefer_correct_trade_count(hyperopt) -> None:
StrategyResolver({'strategy': 'DefaultStrategy'})
correct = hyperopt.calculate_loss(1, hyperopt.target_trades, 20)
over = hyperopt.calculate_loss(1, hyperopt.target_trades + 100, 20)
@@ -123,11 +300,12 @@ def test_log_results_if_loss_improves(hyperopt, capsys) -> None:
'loss': 1,
'current_tries': 1,
'total_tries': 2,
'result': 'foo'
'result': 'foo.',
'initial_point': False
}
)
out, err = capsys.readouterr()
assert ' 1/2: foo. Loss 1.00000' in out
assert ' 2/2: foo. Objective: 1.00000' in out
def test_no_log_if_loss_does_not_improve(hyperopt, caplog) -> None:
@@ -183,26 +361,32 @@ def test_roi_table_generation(hyperopt) -> None:
def test_start_calls_optimizer(mocker, default_conf, caplog) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
mocker.patch('freqtrade.optimize.hyperopt.multiprocessing.cpu_count', MagicMock(return_value=1))
mocker.patch(
'freqtrade.optimize.hyperopt.get_timeframe',
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13)))
)
parallel = mocker.patch(
'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel',
MagicMock(return_value=[{'loss': 1, 'result': 'foo result', 'params': {}}])
)
patch_exchange(mocker)
default_conf.update({'config': 'config.json.example'})
default_conf.update({'epochs': 1})
default_conf.update({'timerange': None})
default_conf.update({'spaces': 'all'})
default_conf.update({'config': 'config.json.example',
'epochs': 1,
'timerange': None,
'spaces': 'all',
'hyperopt_jobs': 1, })
hyperopt = Hyperopt(default_conf)
hyperopt.strategy.tickerdata_to_dataframe = MagicMock()
hyperopt.start()
parallel.assert_called_once()
assert 'Best result:\nfoo result\nwith values:\n{}' in caplog.text
assert log_has('Best result:\nfoo result\nwith values:\n', caplog.record_tuples)
assert dumper.called
# Should be called twice, once for tickerdata, once to save evaluations
assert dumper.call_count == 2
def test_format_results(hyperopt):
@@ -243,7 +427,8 @@ def test_has_space(hyperopt):
def test_populate_indicators(hyperopt) -> None:
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick)}
tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick, '1m', pair="UNITTEST/BTC",
fill_missing=True)}
dataframes = hyperopt.strategy.tickerdata_to_dataframe(tickerlist)
dataframe = hyperopt.custom_hyperopt.populate_indicators(dataframes['UNITTEST/BTC'],
{'pair': 'UNITTEST/BTC'})
@@ -256,7 +441,8 @@ def test_populate_indicators(hyperopt) -> None:
def test_buy_strategy_generator(hyperopt) -> None:
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick)}
tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick, '1m', pair="UNITTEST/BTC",
fill_missing=True)}
dataframes = hyperopt.strategy.tickerdata_to_dataframe(tickerlist)
dataframe = hyperopt.custom_hyperopt.populate_indicators(dataframes['UNITTEST/BTC'],
{'pair': 'UNITTEST/BTC'})
@@ -284,6 +470,7 @@ def test_generate_optimizer(mocker, default_conf) -> None:
default_conf.update({'config': 'config.json.example'})
default_conf.update({'timerange': None})
default_conf.update({'spaces': 'all'})
default_conf.update({'hyperopt_min_trades': 1})
trades = [
('POWR/BTC', 0.023117, 0.000233, 100)
@@ -312,6 +499,15 @@ def test_generate_optimizer(mocker, default_conf) -> None:
'mfi-enabled': False,
'rsi-enabled': False,
'trigger': 'macd_cross_signal',
'sell-adx-value': 0,
'sell-fastd-value': 75,
'sell-mfi-value': 0,
'sell-rsi-value': 0,
'sell-adx-enabled': False,
'sell-fastd-enabled': True,
'sell-mfi-enabled': False,
'sell-rsi-enabled': False,
'sell-trigger': 'macd_cross_signal',
'roi_t1': 60.0,
'roi_t2': 30.0,
'roi_t3': 20.0,
@@ -323,7 +519,7 @@ def test_generate_optimizer(mocker, default_conf) -> None:
response_expected = {
'loss': 1.9840569076926293,
'result': ' 1 trades. Avg profit 2.31%. Total profit 0.00023300 BTC '
'(0.0231Σ%). Avg duration 100.0 mins.',
'( 2.31Σ%). Avg duration 100.0 mins.',
'params': optimizer_param
}

View File

@@ -1,64 +0,0 @@
# pragma pylint: disable=missing-docstring, protected-access, C0103
from freqtrade import optimize, constants
from freqtrade.arguments import TimeRange
from freqtrade.data import history
from freqtrade.strategy.default_strategy import DefaultStrategy
from freqtrade.tests.conftest import log_has, patch_exchange
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 = optimize.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']
)
)
min_date, max_date = optimize.get_timeframe(data)
caplog.clear()
assert optimize.validate_backtest_data(data, min_date, max_date,
constants.TICKER_INTERVAL_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.record_tuples)
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 = optimize.get_timeframe(data)
caplog.clear()
assert not optimize.validate_backtest_data(data, min_date, max_date,
constants.TICKER_INTERVAL_MINUTES["5m"])
assert len(caplog.record_tuples) == 0

View File

@@ -1,6 +1,6 @@
# pragma pylint: disable=missing-docstring,C0103,protected-access
from unittest.mock import MagicMock
from unittest.mock import MagicMock, PropertyMock
from freqtrade import OperationalException
from freqtrade.constants import AVAILABLE_PAIRLISTS
@@ -33,7 +33,7 @@ def whitelist_conf(default_conf):
def test_load_pairlist_noexist(mocker, markets, default_conf):
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.get_markets', markets)
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
with pytest.raises(ImportError,
match=r"Impossible to load Pairlist 'NonexistingPairList'."
r" This class does not exist or contains Python code errors"):
@@ -44,7 +44,7 @@ def test_refresh_market_pair_not_in_whitelist(mocker, markets, whitelist_conf):
freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf)
mocker.patch('freqtrade.exchange.Exchange.get_markets', markets)
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
freqtradebot.pairlists.refresh_pairlist()
# List ordered by BaseVolume
whitelist = ['ETH/BTC', 'TKN/BTC']
@@ -58,7 +58,7 @@ def test_refresh_market_pair_not_in_whitelist(mocker, markets, whitelist_conf):
def test_refresh_pairlists(mocker, markets, whitelist_conf):
freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf)
mocker.patch('freqtrade.exchange.Exchange.get_markets', markets)
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
freqtradebot.pairlists.refresh_pairlist()
# List ordered by BaseVolume
whitelist = ['ETH/BTC', 'TKN/BTC']
@@ -73,14 +73,14 @@ def test_refresh_pairlist_dynamic(mocker, markets, tickers, whitelist_conf):
}
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_markets=markets,
markets=PropertyMock(return_value=markets),
get_tickers=tickers,
exchange_has=MagicMock(return_value=True)
)
freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf)
# argument: use the whitelist dynamically by exchange-volume
whitelist = ['ETH/BTC', 'TKN/BTC']
whitelist = ['ETH/BTC', 'TKN/BTC', 'BTT/BTC']
freqtradebot.pairlists.refresh_pairlist()
assert whitelist == freqtradebot.pairlists.whitelist
@@ -96,7 +96,7 @@ def test_refresh_pairlist_dynamic(mocker, markets, tickers, whitelist_conf):
def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf)
mocker.patch('freqtrade.exchange.Exchange.get_markets', markets_empty)
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets_empty))
# argument: use the whitelist dynamically by exchange-volume
whitelist = []
@@ -107,28 +107,27 @@ def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
assert set(whitelist) == set(pairslist)
def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, markets, tickers) -> None:
@pytest.mark.parametrize("precision_filter,base_currency,key,whitelist_result", [
(False, "BTC", "quoteVolume", ['ETH/BTC', 'TKN/BTC', 'BTT/BTC']),
(False, "BTC", "bidVolume", ['BTT/BTC', 'TKN/BTC', 'ETH/BTC']),
(False, "USDT", "quoteVolume", ['ETH/USDT', 'LTC/USDT']),
(False, "ETH", "quoteVolume", []), # this replaces tests that were removed from test_exchange
(True, "BTC", "quoteVolume", ["ETH/BTC", "TKN/BTC"]),
(True, "BTC", "bidVolume", ["TKN/BTC", "ETH/BTC"])
])
def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, markets, tickers, base_currency, key,
whitelist_result, precision_filter) -> None:
whitelist_conf['pairlist']['method'] = 'VolumePairList'
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
mocker.patch('freqtrade.exchange.Exchange.get_markets', markets)
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers)
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, p, r: round(r, 8))
# Test to retrieved BTC sorted on quoteVolume (default)
whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='BTC', key='quoteVolume')
assert whitelist == ['ETH/BTC', 'TKN/BTC', 'BLK/BTC', 'LTC/BTC']
# Test to retrieve BTC sorted on bidVolume
whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='BTC', key='bidVolume')
assert whitelist == ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'BLK/BTC']
# Test with USDT sorted on quoteVolume (default)
whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='USDT', key='quoteVolume')
assert whitelist == ['TKN/USDT', 'ETH/USDT', 'LTC/USDT', 'BLK/USDT']
# Test with ETH (our fixture does not have ETH, so result should be empty)
whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='ETH', key='quoteVolume')
assert whitelist == []
freqtrade.pairlists._precision_filter = precision_filter
freqtrade.config['stake_currency'] = base_currency
whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency=base_currency, key=key)
assert whitelist == whitelist_result
def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None:
@@ -145,7 +144,7 @@ def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None
@pytest.mark.parametrize("pairlist", AVAILABLE_PAIRLISTS)
def test_pairlist_class(mocker, whitelist_conf, markets, pairlist):
whitelist_conf['pairlist']['method'] = pairlist
mocker.patch('freqtrade.exchange.Exchange.get_markets', markets)
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
@@ -154,17 +153,24 @@ def test_pairlist_class(mocker, whitelist_conf, markets, pairlist):
assert isinstance(freqtrade.pairlists.whitelist, list)
assert isinstance(freqtrade.pairlists.blacklist, list)
whitelist = ['ETH/BTC', 'TKN/BTC']
@pytest.mark.parametrize("pairlist", AVAILABLE_PAIRLISTS)
@pytest.mark.parametrize("whitelist,log_message", [
(['ETH/BTC', 'TKN/BTC'], ""),
(['ETH/BTC', 'TKN/BTC', 'TRX/ETH'], "is not compatible with exchange"), # TRX/ETH wrong stake
(['ETH/BTC', 'TKN/BTC', 'BCH/BTC'], "is not compatible with exchange"), # BCH/BTC not available
(['ETH/BTC', 'TKN/BTC', 'BLK/BTC'], "is not compatible with exchange"), # BLK/BTC in blacklist
(['ETH/BTC', 'TKN/BTC', 'LTC/BTC'], "Market is not active") # LTC/BTC is inactive
])
def test_validate_whitelist(mocker, whitelist_conf, markets, pairlist, whitelist, caplog,
log_message):
whitelist_conf['pairlist']['method'] = pairlist
mocker.patch('freqtrade.exchange.Exchange.markets', PropertyMock(return_value=markets))
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
caplog.clear()
new_whitelist = freqtrade.pairlists._validate_whitelist(whitelist)
assert set(whitelist) == set(new_whitelist)
whitelist = ['ETH/BTC', 'TKN/BTC', 'TRX/ETH']
new_whitelist = freqtrade.pairlists._validate_whitelist(whitelist)
# TRX/ETH was removed
assert set(['ETH/BTC', 'TKN/BTC']) == set(new_whitelist)
whitelist = ['ETH/BTC', 'TKN/BTC', 'BLK/BTC']
new_whitelist = freqtrade.pairlists._validate_whitelist(whitelist)
# BLK/BTC is in blacklist ...
assert set(['ETH/BTC', 'TKN/BTC']) == set(new_whitelist)
assert set(new_whitelist) == set(['ETH/BTC', 'TKN/BTC'])
assert log_message in caplog.text

View File

@@ -8,7 +8,7 @@ import pytest
from requests.exceptions import RequestException
from freqtrade.rpc.fiat_convert import CryptoFiat, CryptoToFiatConverter
from freqtrade.tests.conftest import log_has, patch_coinmarketcap
from freqtrade.tests.conftest import log_has
def test_pair_convertion_object():
@@ -40,7 +40,6 @@ def test_pair_convertion_object():
def test_fiat_convert_is_supported(mocker):
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter()
assert fiat_convert._is_supported_fiat(fiat='USD') is True
assert fiat_convert._is_supported_fiat(fiat='usd') is True
@@ -49,7 +48,6 @@ def test_fiat_convert_is_supported(mocker):
def test_fiat_convert_add_pair(mocker):
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter()
@@ -72,8 +70,6 @@ def test_fiat_convert_add_pair(mocker):
def test_fiat_convert_find_price(mocker):
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter()
with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'):
@@ -93,15 +89,12 @@ def test_fiat_convert_find_price(mocker):
def test_fiat_convert_unsupported_crypto(mocker, caplog):
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[])
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter()
assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0
assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog.record_tuples)
def test_fiat_convert_get_price(mocker):
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
return_value=28000.0)
@@ -117,7 +110,7 @@ def test_fiat_convert_get_price(mocker):
assert fiat_convert._pairs[0].crypto_symbol == 'BTC'
assert fiat_convert._pairs[0].fiat_symbol == 'USD'
assert fiat_convert._pairs[0].price == 28000.0
assert fiat_convert._pairs[0]._expiration is not 0
assert fiat_convert._pairs[0]._expiration != 0
assert len(fiat_convert._pairs) == 1
# Verify the cached is used
@@ -134,21 +127,18 @@ def test_fiat_convert_get_price(mocker):
def test_fiat_convert_same_currencies(mocker):
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter()
assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='USD') == 1.0
def test_fiat_convert_two_FIAT(mocker):
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter()
assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='EUR') == 0.0
def test_loadcryptomap(mocker):
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter()
assert len(fiat_convert._cryptomap) == 2
@@ -174,7 +164,6 @@ def test_fiat_init_network_exception(mocker):
def test_fiat_convert_without_network(mocker):
# Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter()
@@ -205,7 +194,6 @@ def test_fiat_invalid_response(mocker, caplog):
def test_convert_amount(mocker):
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0)
fiat_convert = CryptoToFiatConverter()

View File

@@ -2,19 +2,19 @@
# pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments
from datetime import datetime
from unittest.mock import MagicMock, ANY
from unittest.mock import ANY, MagicMock, PropertyMock
import pytest
from numpy import isnan
from freqtrade import TemporaryError, DependencyException
from freqtrade import DependencyException, TemporaryError
from freqtrade.edge import PairInfo
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import Trade
from freqtrade.rpc import RPC, RPCException
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
from freqtrade.state import State
from freqtrade.tests.test_freqtradebot import patch_get_signal
from freqtrade.tests.conftest import patch_coinmarketcap, patch_exchange
from freqtrade.tests.conftest import patch_exchange, patch_get_signal
# Functions for recurrent object patching
@@ -27,14 +27,13 @@ def prec_satoshi(a, b) -> float:
# Unit tests
def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
_load_markets=MagicMock(return_value={}),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
markets=PropertyMock(return_value=markets)
)
freqtradebot = FreqtradeBot(default_conf)
@@ -47,18 +46,25 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
freqtradebot.create_trade()
results = rpc._rpc_trade_status()
assert {
'trade_id': 1,
'pair': 'ETH/BTC',
'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH',
'date': ANY,
'base_currency': 'BTC',
'open_date': ANY,
'open_date_hum': ANY,
'close_date': None,
'close_date_hum': None,
'open_rate': 1.099e-05,
'close_rate': None,
'current_rate': 1.098e-05,
'amount': 90.99181074,
'stake_amount': 0.001,
'close_profit': None,
'current_profit': -0.59,
'stop_loss': 0.0,
'initial_stop_loss': 0.0,
'initial_stop_loss_pct': None,
'stop_loss_pct': None,
'open_order': '(limit buy rem=0.00000000)'
} == results[0]
@@ -72,27 +78,34 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
assert {
'trade_id': 1,
'pair': 'ETH/BTC',
'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH',
'date': ANY,
'base_currency': 'BTC',
'open_date': ANY,
'open_date_hum': ANY,
'close_date': None,
'close_date_hum': None,
'open_rate': 1.099e-05,
'close_rate': None,
'current_rate': ANY,
'amount': 90.99181074,
'stake_amount': 0.001,
'close_profit': None,
'current_profit': ANY,
'stop_loss': 0.0,
'initial_stop_loss': 0.0,
'initial_stop_loss_pct': None,
'stop_loss_pct': None,
'open_order': '(limit buy rem=0.00000000)'
} == results[0]
def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
get_fee=fee,
get_markets=markets
markets=PropertyMock(return_value=markets)
)
freqtradebot = FreqtradeBot(default_conf)
@@ -105,7 +118,7 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
freqtradebot.create_trade()
result = rpc._rpc_status_table()
assert 'just now' in result['Since'].all()
assert 'instantly' in result['Since'].all()
assert 'ETH/BTC' in result['Pair'].all()
assert '-0.59%' in result['Profit'].all()
@@ -114,21 +127,20 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
# invalidate ticker cache
rpc._freqtrade.exchange._cached_ticker = {}
result = rpc._rpc_status_table()
assert 'just now' in result['Since'].all()
assert 'instantly' in result['Since'].all()
assert 'ETH/BTC' in result['Pair'].all()
assert 'nan%' in result['Profit'].all()
def test_rpc_daily_profit(default_conf, update, ticker, fee,
limit_buy_order, limit_sell_order, markets, mocker) -> None:
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
get_fee=fee,
get_markets=markets
markets=PropertyMock(return_value=markets)
)
freqtradebot = FreqtradeBot(default_conf)
@@ -174,7 +186,6 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
'freqtrade.rpc.fiat_convert.Market',
ticker=MagicMock(return_value={'price_usd': 15000.0}),
)
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@@ -182,7 +193,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
'freqtrade.exchange.Exchange',
get_ticker=ticker,
get_fee=fee,
get_markets=markets
markets=PropertyMock(return_value=markets)
)
freqtradebot = FreqtradeBot(default_conf)
@@ -270,7 +281,7 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets,
'freqtrade.exchange.Exchange',
get_ticker=ticker,
get_fee=fee,
get_markets=markets
markets=PropertyMock(return_value=markets)
)
freqtradebot = FreqtradeBot(default_conf)
@@ -332,7 +343,6 @@ def test_rpc_balance_handle(default_conf, mocker):
'freqtrade.rpc.fiat_convert.Market',
ticker=MagicMock(return_value={'price_usd': 15000.0}),
)
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@@ -362,7 +372,6 @@ def test_rpc_balance_handle(default_conf, mocker):
def test_rpc_start(mocker, default_conf) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
@@ -385,7 +394,6 @@ def test_rpc_start(mocker, default_conf) -> None:
def test_rpc_stop(mocker, default_conf) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
@@ -408,8 +416,26 @@ def test_rpc_stop(mocker, default_conf) -> None:
assert freqtradebot.state == State.STOPPED
def test_rpc_stopbuy(mocker, default_conf) -> None:
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock()
)
freqtradebot = FreqtradeBot(default_conf)
patch_get_signal(freqtradebot, (True, False))
rpc = RPC(freqtradebot)
freqtradebot.state = State.RUNNING
assert freqtradebot.config['max_open_trades'] != 0
result = rpc._rpc_stopbuy()
assert {'status': 'No more buy will occur from now. Run /reload_conf to reset.'} == result
assert freqtradebot.config['max_open_trades'] == 0
def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@@ -426,7 +452,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
}
),
get_fee=fee,
get_markets=markets
markets=PropertyMock(return_value=markets)
)
freqtradebot = FreqtradeBot(default_conf)
@@ -441,12 +467,15 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
with pytest.raises(RPCException, match=r'.*invalid argument*'):
rpc._rpc_forcesell(None)
rpc._rpc_forcesell('all')
msg = rpc._rpc_forcesell('all')
assert msg == {'result': 'Created sell orders for all open trades.'}
freqtradebot.create_trade()
rpc._rpc_forcesell('all')
msg = rpc._rpc_forcesell('all')
assert msg == {'result': 'Created sell orders for all open trades.'}
rpc._rpc_forcesell('1')
msg = rpc._rpc_forcesell('1')
assert msg == {'result': 'Created sell order for trade 1.'}
freqtradebot.state = State.STOPPED
with pytest.raises(RPCException, match=r'.*trader is not running*'):
@@ -489,7 +518,8 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
}
)
# check that the trade is called, which is done by ensuring exchange.cancel_order is called
rpc._rpc_forcesell('2')
msg = rpc._rpc_forcesell('2')
assert msg == {'result': 'Created sell order for trade 2.'}
assert cancel_order_mock.call_count == 2
assert trade.amount == amount
@@ -503,14 +533,14 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
'side': 'sell'
}
)
rpc._rpc_forcesell('3')
msg = rpc._rpc_forcesell('3')
assert msg == {'result': 'Created sell order for trade 3.'}
# status quo, no exchange calls
assert cancel_order_mock.call_count == 2
def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
limit_sell_order, markets, mocker) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
@@ -518,7 +548,7 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
markets=PropertyMock(return_value=markets)
)
freqtradebot = FreqtradeBot(default_conf)
@@ -546,7 +576,6 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
@@ -554,27 +583,24 @@ def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None:
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
markets=PropertyMock(return_value=markets)
)
freqtradebot = FreqtradeBot(default_conf)
patch_get_signal(freqtradebot, (True, False))
rpc = RPC(freqtradebot)
trades = rpc._rpc_count()
nb_trades = len(trades)
assert nb_trades == 0
counts = rpc._rpc_count()
assert counts["current"] == 0
# Create some test data
freqtradebot.create_trade()
trades = rpc._rpc_count()
nb_trades = len(trades)
assert nb_trades == 1
counts = rpc._rpc_count()
assert counts["current"] == 1
def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order) -> None:
default_conf['forcebuy_enable'] = True
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
buy_mm = MagicMock(return_value={'id': limit_buy_order['id']})
@@ -583,7 +609,7 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
get_fee=fee,
get_markets=markets,
markets=PropertyMock(return_value=markets),
buy=buy_mm
)
@@ -623,7 +649,6 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order
def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
default_conf['forcebuy_enable'] = True
default_conf['initial_state'] = 'stopped'
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@@ -636,7 +661,6 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@@ -649,7 +673,6 @@ def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
def test_rpc_whitelist(mocker, default_conf) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@@ -661,7 +684,6 @@ def test_rpc_whitelist(mocker, default_conf) -> None:
def test_rpc_whitelist_dynamic(mocker, default_conf) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
default_conf['pairlist'] = {'method': 'VolumePairList',
'config': {'number_assets': 4}
@@ -675,3 +697,51 @@ def test_rpc_whitelist_dynamic(mocker, default_conf) -> None:
assert ret['method'] == 'VolumePairList'
assert ret['length'] == 4
assert ret['whitelist'] == default_conf['exchange']['pair_whitelist']
def test_rpc_blacklist(mocker, default_conf) -> None:
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot)
ret = rpc._rpc_blacklist(None)
assert ret['method'] == 'StaticPairList'
assert len(ret['blacklist']) == 2
assert ret['blacklist'] == default_conf['exchange']['pair_blacklist']
assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC']
ret = rpc._rpc_blacklist(["ETH/BTC"])
assert ret['method'] == 'StaticPairList'
assert len(ret['blacklist']) == 3
assert ret['blacklist'] == default_conf['exchange']['pair_blacklist']
assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC']
def test_rpc_edge_disabled(mocker, default_conf) -> None:
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot)
with pytest.raises(RPCException, match=r'Edge is not enabled.'):
rpc._rpc_edge()
def test_rpc_edge_enabled(mocker, edge_conf) -> None:
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
return_value={
'E/F': PairInfo(-0.02, 0.66, 3.71, 0.50, 1.71, 10, 60),
}
))
freqtradebot = FreqtradeBot(edge_conf)
rpc = RPC(freqtradebot)
ret = rpc._rpc_edge()
assert len(ret) == 1
assert ret[0]['Pair'] == 'E/F'
assert ret[0]['Winrate'] == 0.66
assert ret[0]['Expectancy'] == 1.71
assert ret[0]['Stoploss'] == -0.02

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