Compare commits

...

261 Commits

Author SHA1 Message Date
Samuel Husso
e63e808521 Merge pull request #1252 from freqtrade/release-0.17.2
Release 0.17.2
2018-10-03 13:09:11 +03:00
Samuel Husso
d549fe351c Prepare master for release 0.17.2 2018-10-02 09:24:22 +03:00
Matthias
9137338771 Merge pull request #1251 from freqtrade/pyup-scheduled-update-2018-10-01
Scheduled daily dependency update on monday
2018-10-01 19:27:17 +02:00
pyup-bot
d0c7b7c582 Update ccxt from 1.17.360 to 1.17.363 2018-10-01 14:29:06 +02:00
Matthias
b130a923f7 Merge pull request #1249 from freqtrade/pyup-scheduled-update-2018-09-30
Scheduled daily dependency update on sunday
2018-09-30 17:03:15 +02:00
Matthias
3af3094a56 Merge pull request #1247 from freqtrade/fix_hyperopt_pickle
Fix hyperopt pickle
2018-09-30 16:51:33 +02:00
pyup-bot
9d70d25064 Update scikit-learn from 0.19.2 to 0.20.0 2018-09-30 14:28:07 +02:00
pyup-bot
05adebb536 Update ccxt from 1.17.351 to 1.17.360 2018-09-30 14:28:06 +02:00
Matthias
e1ddddad4f Merge pull request #1246 from freqtrade/fix/network_test
Patch exchange to not cause network delays during tests
2018-09-30 08:42:38 +02:00
Matthias
84622dc84b Move test for strategy out of constructor 2018-09-29 14:23:53 +02:00
Matthias
36e9abc841 Manually update scikit-learn to 0.20.0 2018-09-29 13:50:02 +02:00
Matthias
1b290ffb5d Update hyperopt to show errors if non-supported variables are used 2018-09-29 13:49:38 +02:00
Matthias
334e7553e1 Fix hyperopt not working after update of scikit-learn to 0.20.0 2018-09-29 13:49:27 +02:00
Matthias
f4585a2745 Patch exchange to not cause network delays during tests 2018-09-29 13:35:48 +02:00
Matthias
448f3a7197 Merge pull request #1241 from freqtrade/fix/loadstrategyonce
Only load strategy once during backtesting
2018-09-29 09:12:41 +02:00
Matthias
6e66763e5f Only load strategy once during backtesting 2018-09-27 19:23:55 +02:00
Matthias
89b515be60 Merge pull request #1220 from freqtrade/fix/plot_dataframe
Fix plot dataframe
2018-09-27 12:40:34 +02:00
Matthias
d481895763 Merge pull request #1211 from freqtrade/fix_no_trades_found
Add offset to "get_trades_for_order"
2018-09-27 12:40:17 +02:00
Matthias
4ad3e96a2f Merge pull request #1225 from freqtrade/test_acl_improvement
Remove direct call to pytest fixture to elliminate pytest warning
2018-09-27 12:39:56 +02:00
Matthias
3893b638fe Merge pull request #1213 from freqtrade/fix_mac_install
Fix mac install documentation
2018-09-27 12:39:42 +02:00
Matthias
5dac3b5664 Merge pull request #1238 from freqtrade/fix/buyexception
Fix exception when order cannot be found
2018-09-26 19:26:17 +02:00
Matthias
bcb13d041e Merge pull request #1239 from freqtrade/pyup-scheduled-update-2018-09-26
Scheduled daily dependency update on wednesday
2018-09-26 19:25:50 +02:00
pyup-bot
f790f95319 Update ccxt from 1.17.350 to 1.17.351 2018-09-26 14:28:07 +02:00
Matthias
766d32897d Merge pull request #1204 from freqtrade/move_load_markets
refactor load_markets out of validate_pairs
2018-09-26 06:38:37 +02:00
Matthias
e09674b77f Merge pull request #1227 from freqtrade/feat/reduce_backtestnoise
don't print "NAN" lines in "left_open_trades"
2018-09-26 06:37:33 +02:00
Matthias
88ccdc0366 Fix exception when order cannot be found 2018-09-25 20:45:01 +02:00
Matthias
d04247cd9e Merge pull request #1235 from freqtrade/pyup-scheduled-update-2018-09-25
Scheduled daily dependency update on tuesday
2018-09-25 19:20:54 +02:00
pyup-bot
d13e87d7a4 Update ccxt from 1.17.341 to 1.17.350 2018-09-25 14:28:07 +02:00
Matthias
bbcbf6adc8 Merge pull request #1234 from freqtrade/pyup-scheduled-update-2018-09-23
Scheduled daily dependency update on sunday
2018-09-23 19:20:57 +02:00
pyup-bot
6116c27aa9 Update pytest from 3.8.0 to 3.8.1 2018-09-23 14:28:09 +02:00
pyup-bot
12e6287875 Update numpy from 1.15.1 to 1.15.2 2018-09-23 14:28:08 +02:00
pyup-bot
0e168159c1 Update ccxt from 1.17.335 to 1.17.341 2018-09-23 14:28:06 +02:00
Matthias
e1c9b77c44 Merge pull request #1230 from freqtrade/pyup-scheduled-update-2018-09-22
Scheduled daily dependency update on saturday
2018-09-22 15:44:51 +02:00
pyup-bot
54b714ba3f Update ccxt from 1.17.327 to 1.17.335 2018-09-22 14:28:05 +02:00
Matthias
f302882f67 Merge pull request #1228 from freqtrade/pyup-scheduled-update-2018-09-21
Scheduled daily dependency update on friday
2018-09-21 16:03:29 +02:00
pyup-bot
8e659af580 Update ccxt from 1.17.324 to 1.17.327 2018-09-21 14:28:07 +02:00
Matthias
567211e9f9 don't print "NAN" lines in "left_open_trades" 2018-09-20 20:35:26 +02:00
Matthias
95f884f4f3 Merge pull request #1226 from freqtrade/pyup-scheduled-update-2018-09-20
Scheduled daily dependency update on thursday
2018-09-20 19:22:08 +02:00
pyup-bot
53c0f01bef Update sqlalchemy from 1.2.11 to 1.2.12 2018-09-20 14:28:10 +02:00
pyup-bot
0aa8557c03 Update ccxt from 1.17.316 to 1.17.324 2018-09-20 14:28:08 +02:00
Matthias
4d5e368c2e Remove direct call to pytest fixture to elliminate pytest warning 2018-09-19 19:40:32 +02:00
Matthias
2d4d1d7306 Merge pull request #1224 from freqtrade/pyup-scheduled-update-2018-09-19
Scheduled daily dependency update on wednesday
2018-09-19 19:14:47 +02:00
pyup-bot
2c5b6aca91 Update ccxt from 1.17.311 to 1.17.316 2018-09-19 14:28:06 +02:00
Matthias
eaa657aa3b Merge pull request #1222 from freqtrade/pyup-scheduled-update-2018-09-18
Scheduled daily dependency update on tuesday
2018-09-18 19:15:01 +02:00
pyup-bot
a5d4de8037 Update ccxt from 1.17.305 to 1.17.311 2018-09-18 14:28:06 +02:00
Matthias
52b75c5997 Merge pull request #1218 from jin10086/develop
use --no-cache-dir for docker build
2018-09-17 20:49:55 +02:00
Matthias
f04e4f2123 Fix trailing whitespace 2018-09-17 20:49:41 +02:00
Matthias
176bae2d59 Set default-db url in configuration, not arguments
* Fixes a bug in plot_dataframe.py (#1217)
* db_url is eventually overwritten here anyway.
2018-09-17 19:57:47 +02:00
Matthias
14e21765f2 Fix missing column to load current backtesting export files 2018-09-17 19:44:40 +02:00
Matthias
eebaede80d Merge pull request #1219 from freqtrade/pyup-scheduled-update-2018-09-17
Scheduled daily dependency update on monday
2018-09-17 19:20:00 +02:00
pyup-bot
9b83a09224 Update ccxt from 1.17.300 to 1.17.305 2018-09-17 14:28:06 +02:00
gaojin
0a4b2f19e3 use --no-cache-dir for docker build
use --no-cache can save about 90M
```
➜  freqtrade git:(develop) ✗ docker images freq
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
freq                latest              b15db8341067        7 minutes ago       800MB
➜  freqtrade git:(develop) ✗ docker images freq_nocache
REPOSITORY          TAG                 IMAGE ID            CREATED             SIZE
freq_nocache        latest              e5731f28ac54        20 seconds ago      709MB
```
2018-09-17 10:37:25 +08:00
Matthias
3abc294e5f Merge pull request #1216 from 0xflotus/patch-1
fixed being
2018-09-16 20:11:08 +02:00
0xflotus
6aa18bddc9 fixed being 2018-09-16 17:34:01 +02:00
Matthias
16279bc171 Merge pull request #1215 from freqtrade/pyup-scheduled-update-2018-09-16
Scheduled daily dependency update on sunday
2018-09-16 15:12:43 +02:00
pyup-bot
14961e2e38 Update ccxt from 1.17.294 to 1.17.300 2018-09-16 14:28:06 +02:00
Matthias
30ae5829f5 Fix SED command for macos
Mac uses the bsd version, where -i without backup is not allowed.
2018-09-16 11:26:20 +02:00
Matthias
200dfa7575 Wording for readme.md 2018-09-16 11:22:15 +02:00
Matthias
51b3eb78d7 Add section about about clock accuracy to readme.md 2018-09-15 20:38:09 +02:00
Matthias
9685c09c1a Add offset to "get_trades_for_order" 2018-09-15 20:28:36 +02:00
Matthias
4303e86e09 Merge pull request #1210 from freqtrade/pyup-scheduled-update-2018-09-15
Scheduled daily dependency update on saturday
2018-09-15 17:40:49 +02:00
pyup-bot
f4d26961c8 Update ccxt from 1.17.291 to 1.17.294 2018-09-15 14:28:05 +02:00
Matthias
029a6798a4 Merge pull request #1209 from freqtrade/pyup-scheduled-update-2018-09-14
Scheduled daily dependency update on friday
2018-09-14 19:39:08 +02:00
pyup-bot
f5ba34addf Update ccxt from 1.17.283 to 1.17.291 2018-09-14 14:28:05 +02:00
Matthias
bcf47b29ed Merge pull request #1208 from freqtrade/pyup-scheduled-update-2018-09-13
Scheduled daily dependency update on thursday
2018-09-13 19:23:10 +02:00
pyup-bot
91c0e3640f Update ccxt from 1.17.276 to 1.17.283 2018-09-13 14:29:06 +02:00
Samuel Husso
fadf82dd32 Merge pull request #1205 from freqtrade/pyup-scheduled-update-2018-09-12
Scheduled daily dependency update on wednesday
2018-09-12 17:44:27 +03:00
pyup-bot
241b23e5d8 Update ccxt from 1.17.271 to 1.17.276 2018-09-12 14:28:06 +02:00
Matthias
c429eae6e4 Adjust remaining tests to _load_markets refactoring 2018-09-11 19:59:01 +02:00
Matthias
674bad2a4f Add and fix tests for load_markets 2018-09-11 19:46:47 +02:00
Matthias
14b7fc42fa Change returntype for _load_markets to dict 2018-09-11 19:46:31 +02:00
Matthias
14717b1701 Merge pull request #1203 from freqtrade/pyup-scheduled-update-2018-09-11
Scheduled daily dependency update on tuesday
2018-09-11 16:55:16 +02:00
pyup-bot
51ef137981 Update ccxt from 1.17.257 to 1.17.271 2018-09-11 14:27:07 +02:00
Matthias
f954efbd64 Adapt tests to not _load_markets 2018-09-10 20:19:28 +02:00
Matthias
0a29096794 Refactor load_market out of validate_pairs 2018-09-10 20:19:12 +02:00
Matthias
687dc78dbd Merge pull request #1202 from freqtrade/pyup-scheduled-update-2018-09-10
Scheduled daily dependency update on monday
2018-09-10 19:04:23 +02:00
pyup-bot
8aaf174578 Update ccxt from 1.17.250 to 1.17.257 2018-09-10 14:27:08 +02:00
Matthias
2660be9b13 Merge pull request #1201 from freqtrade/pyup-scheduled-update-2018-09-09
Scheduled daily dependency update on sunday
2018-09-09 15:47:09 +02:00
pyup-bot
65ad9cf741 Update ccxt from 1.17.242 to 1.17.250 2018-09-09 14:27:06 +02:00
Matthias
179bcf3907 Merge pull request #1101 from mishaker/ccxt-async
use ccxt async for ticker_history download
2018-09-09 08:39:57 +02:00
Samuel Husso
062eca19b8 Merge pull request #1199 from freqtrade/doc_ratelimit
Document ccxt_rate_limit
2018-09-08 16:06:59 +03:00
Samuel Husso
4692174677 Merge pull request #1200 from freqtrade/pyup-scheduled-update-2018-09-08
Scheduled daily dependency update on saturday
2018-09-08 16:06:35 +03:00
pyup-bot
65699f702e Update ccxt from 1.17.240 to 1.17.242 2018-09-08 14:27:07 +02:00
Matthias
e57be10772 Document ccxt_rate_limit 2018-09-08 13:01:33 +02:00
Samuel Husso
5ba6cfe406 Merge pull request #1195 from freqtrade/update_hyperopt_doc
explicitly ask for more ressources in hyperopt documentation
2018-09-07 15:56:47 +03:00
Samuel Husso
f0c7394bc8 Merge pull request #1197 from freqtrade/pyup-scheduled-update-2018-09-07
Scheduled daily dependency update on friday
2018-09-07 15:56:26 +03:00
pyup-bot
fb4f83b32c Update pytest from 3.7.4 to 3.8.0 2018-09-07 14:28:09 +02:00
pyup-bot
a49a60b4fa Update ccxt from 1.17.233 to 1.17.240 2018-09-07 14:28:07 +02:00
misagh
13ffd88053 merging develop into async. requirement.txt conflict resolved 2018-09-06 20:28:07 +02:00
Matthias
4e847f26bc explicitly ask for more ressources in hyperopt documentation 2018-09-06 20:12:16 +02:00
Matthias
0004b32411 Merge pull request #1194 from freqtrade/pyup-scheduled-update-2018-09-06
Scheduled daily dependency update on thursday
2018-09-06 19:51:42 +02:00
pyup-bot
4f583d61c8 Update ccxt from 1.17.231 to 1.17.233 2018-09-06 14:28:06 +02:00
Samuel Husso
3eb2e92d53 Merge pull request #1191 from freqtrade/pyup-scheduled-update-2018-09-05
Scheduled daily dependency update on wednesday
2018-09-05 16:01:27 +03:00
pyup-bot
a748c0794e Update ccxt from 1.17.229 to 1.17.231 2018-09-05 14:28:06 +02:00
Matthias
1682d6b365 Merge pull request #1188 from freqtrade/pyup-scheduled-update-2018-09-04
Scheduled daily dependency update on tuesday
2018-09-04 19:22:29 +02:00
pyup-bot
27ffce4c3f Update pytest-cov from 2.5.1 to 2.6.0 2018-09-04 14:28:08 +02:00
pyup-bot
d62f97dc3b Update ccxt from 1.17.223 to 1.17.229 2018-09-04 14:28:06 +02:00
Matthias
9c1cd4bee2 Merge pull request #1187 from freqtrade/pyup-scheduled-update-2018-09-03
Scheduled daily dependency update on monday
2018-09-03 19:15:03 +02:00
pyup-bot
754027efed Update ccxt from 1.17.222 to 1.17.223 2018-09-03 14:28:07 +02:00
Matthias
e9deb928f6 Fix bug when exchange result is empty 2018-09-02 19:15:23 +02:00
Matthias
6b74fb0893 Merge pull request #1119 from creslinux/ta_on_candle
ta_on_candle (not loop, with optional flag in config.json) Resubmitting - because GIT.
2018-09-02 17:01:21 +02:00
Samuel Husso
feb14990c2 Merge pull request #1186 from freqtrade/pyup-scheduled-update-2018-09-02
Scheduled daily dependency update on sunday
2018-09-02 16:10:26 +03:00
pyup-bot
3831f198e9 Update python-telegram-bot from 11.0.0 to 11.1.0 2018-09-02 14:28:07 +02:00
pyup-bot
adfd8c7f5c Update ccxt from 1.17.216 to 1.17.222 2018-09-02 14:28:06 +02:00
Matthias
3fd00c9a9c Merge branch 'develop' into ta_on_candle 2018-09-01 20:01:18 +02:00
Matthias
2ec5a536aa Fix comment location 2018-09-01 19:57:12 +02:00
Matthias
d35d3bb38c rename ta_on_candle to process_only_new_candles
be more expressive
2018-09-01 19:52:40 +02:00
Matthias
cb46aeb73c rename variable to be more expressive 2018-09-01 19:50:45 +02:00
Matthias
b8624e5909 Merge pull request #1183 from freqtrade/pyup-scheduled-update-2018-09-01
Scheduled daily dependency update on saturday
2018-09-01 19:27:15 +02:00
pyup-bot
fa5c8e4bb1 Update ccxt from 1.17.210 to 1.17.216 2018-09-01 14:28:06 +02:00
Samuel Husso
9945b97595 Merge pull request #1175 from freqtrade/doc/installation
installation documentation update
2018-08-31 23:05:12 +03:00
Matthias
17d6d92302 Merge pull request #1179 from freqtrade/pyup-scheduled-update-2018-08-30
Scheduled daily dependency update on thursday
2018-08-30 19:10:00 +02:00
pyup-bot
9560cb8056 Update pytest from 3.7.3 to 3.7.4 2018-08-30 14:28:10 +02:00
pyup-bot
3ed97fe5e8 Update python-telegram-bot from 10.1.0 to 11.0.0 2018-08-30 14:28:08 +02:00
pyup-bot
35c5d4f580 Update ccxt from 1.17.205 to 1.17.210 2018-08-30 14:28:07 +02:00
Matthias
a1bd30aa60 Fix documentation string 2018-08-29 19:59:25 +02:00
Matthias
ffd4469c1d fix typo, refresh_tickers does not need a return value 2018-08-29 19:56:38 +02:00
Matthias
54ddd908e6 Merge branch 'develop' into ccxt-async 2018-08-29 19:43:09 +02:00
Matthias
d41f0667b8 Merge pull request #1125 from nullart2/order-book
Order Book with tests
2018-08-29 19:36:01 +02:00
Matthias
9f8e68ce02 Merge branch 'develop' into order-book 2018-08-29 19:32:44 +02:00
Matthias
f7b67cec5b Fix missing docstring 2018-08-29 19:16:41 +02:00
Matthias
e14e7d9b8a Merge pull request #1177 from freqtrade/pyup-scheduled-update-2018-08-29
Scheduled daily dependency update on wednesday
2018-08-29 17:04:41 +02:00
pyup-bot
b659ec00ee Update ccxt from 1.17.199 to 1.17.205 2018-08-29 14:28:07 +02:00
Nullart2
b6b89a464f move order_book config out of experimental 2018-08-29 17:38:43 +08:00
Matthias
c9ee528050 Add section about raspberry / conda to install.md 2018-08-28 22:06:46 +02:00
Matthias
9bce6c5f48 Add error-section for windows 2018-08-28 19:30:26 +02:00
Matthias
cdfff57403 Merge pull request #1174 from freqtrade/pyup-scheduled-update-2018-08-28
Scheduled daily dependency update on tuesday
2018-08-28 19:11:09 +02:00
pyup-bot
19628d317a Update ccxt from 1.17.194 to 1.17.199 2018-08-28 14:28:06 +02:00
Matthias
32ae344e59 Merge pull request #1172 from freqtrade/pyup-scheduled-update-2018-08-27
Scheduled daily dependency update on monday
2018-08-27 15:51:22 +02:00
pyup-bot
c99ff78f2f Update pytest from 3.7.2 to 3.7.3 2018-08-27 14:28:07 +02:00
pyup-bot
188cfc435d Update ccxt from 1.17.188 to 1.17.194 2018-08-27 14:28:05 +02:00
Matthias
1a9c085f10 Restructure install documentation 2018-08-26 20:09:12 +02:00
Samuel Husso
eefc5349c8 Merge pull request #1171 from freqtrade/pyup-scheduled-update-2018-08-26
Scheduled daily dependency update on sunday
2018-08-26 18:55:47 +03:00
pyup-bot
fe169483ed Update ccxt from 1.17.184 to 1.17.188 2018-08-26 14:28:07 +02:00
nullart2
4dfaf1d284 Merge pull request #5 from xmatthias/order_book_xmatt
fix some test mockings in orderbook pr
2018-08-26 20:01:42 +08:00
Matthias
c5efcace47 change pip3.6 to pip3 2018-08-26 12:49:39 +02:00
Samuel Husso
c770eae70b Merge pull request #1168 from freqtrade/pyup-scheduled-update-2018-08-25
Scheduled daily dependency update on saturday
2018-08-25 17:06:58 +03:00
pyup-bot
2ee1a2d851 Update ccxt from 1.17.176 to 1.17.184 2018-08-25 14:28:06 +02:00
Matthias
42587741dd mock exchange to avoid random failures 2018-08-25 13:21:10 +02:00
Matthias
a489a044ad Mock Exchange results to avoid random test-failures 2018-08-25 13:17:07 +02:00
Matthias
1d0802192d Merge pull request #1167 from freqtrade/pyup-scheduled-update-2018-08-24
Scheduled daily dependency update on friday
2018-08-24 15:36:33 +02:00
pyup-bot
ab628c1381 Update ccxt from 1.17.170 to 1.17.176 2018-08-24 14:28:06 +02:00
Matthias
a37802e21c Merge pull request #1165 from freqtrade/pyup-scheduled-update-2018-08-23
Scheduled daily dependency update on thursday
2018-08-23 16:14:14 +02:00
pyup-bot
8c0e33753e Update ccxt from 1.17.163 to 1.17.170 2018-08-23 14:28:07 +02:00
Matthias
cac7e2c745 Merge pull request #1164 from freqtrade/pyup-scheduled-update-2018-08-22
Scheduled daily dependency update on wednesday
2018-08-22 19:29:07 +02:00
pyup-bot
ebc072396b Update numpy from 1.15.0 to 1.15.1 2018-08-22 14:28:09 +02:00
pyup-bot
4508349d07 Update ccxt from 1.17.157 to 1.17.163 2018-08-22 14:28:07 +02:00
Samuel Husso
7376a0d538 Merge pull request #1131 from freqtrade/parametrize_outdated_ticker
parametrize outdated_offset to simplify sandbox usage
2018-08-22 07:02:38 +03:00
Samuel Husso
36e0e652f0 Merge pull request #1135 from freqtrade/fix/rpc_balance_vtho
Fix /balance rpc call if coin is not properly listed
2018-08-22 07:01:40 +03:00
Samuel Husso
5e4ae46b3c Merge pull request #1163 from freqtrade/remove_amount_to_lots
remove amount_to_lots (deprecated / removed)
2018-08-22 07:01:09 +03:00
Misagh
66d52c1236 Merge pull request #4 from xmatthias/ccxt_async_retrier
Add async retrier
2018-08-21 19:55:30 +02:00
Matthias
6e90d482ef remove amount_to_lots (deprecated / removed)
was removed from ccxt in
527f082e59
2018-08-21 19:08:21 +02:00
Samuel Husso
37bb6ac57b Merge pull request #1162 from freqtrade/pyup-scheduled-update-2018-08-21
Scheduled daily dependency update on tuesday
2018-08-21 15:42:57 +03:00
pyup-bot
8a844488d4 Update sqlalchemy from 1.2.10 to 1.2.11 2018-08-21 14:28:08 +02:00
pyup-bot
e5707b8a2c Update ccxt from 1.17.152 to 1.17.157 2018-08-21 14:28:06 +02:00
Matthias
8f41e0e190 Use setting in 'exchange' dict 2018-08-20 20:01:57 +02:00
Samuel Husso
4bf0542204 Merge pull request #1161 from freqtrade/pyup-scheduled-update-2018-08-20
Scheduled daily dependency update on monday
2018-08-20 19:07:03 +03:00
pyup-bot
43f73c5aec Update ccxt from 1.17.146 to 1.17.152 2018-08-20 14:28:06 +02:00
Matthias
a077955efa update json.load to json_load - followup to #1142 2018-08-19 19:58:07 +02:00
Matthias
0674c3e8f0 Merge pull request #1142 from freqtrade/ujson-loader
backtesting: try to load data with ujson if it exists
2018-08-19 19:53:38 +02:00
Matthias
6d1c82a5fa Remove last refreence to get_candle_history 2018-08-19 19:50:14 +02:00
Matthias
de0f3e43bf remove unused mocks 2018-08-19 19:49:39 +02:00
Matthias
694b8be32f Move variables from class to instance 2018-08-19 19:49:02 +02:00
Matthias
9403248e4d have plot-script use async ticker-refresh 2018-08-19 19:48:24 +02:00
Samuel Husso
c955c7c494 Merge pull request #1160 from freqtrade/pyup-scheduled-update-2018-08-19
Scheduled daily dependency update on sunday
2018-08-19 18:14:46 +03:00
pyup-bot
5a0876704a Update pytest from 3.7.1 to 3.7.2 2018-08-19 14:28:07 +02:00
pyup-bot
97e9a44fd2 Update ccxt from 1.17.139 to 1.17.146 2018-08-19 14:28:06 +02:00
Matthias
088c54b88c remove unnecessary function 2018-08-19 09:17:17 +02:00
Matthias
d722c12109 fix bug in async download script 2018-08-18 21:08:59 +02:00
Matthias
d556f669b0 Add async retrier 2018-08-18 21:05:38 +02:00
Matthias
66255b8c61 Merge pull request #1159 from freqtrade/pyup-scheduled-update-2018-08-18
Scheduled daily dependency update on saturday
2018-08-18 17:24:38 +02:00
pyup-bot
bc22320f77 Update ccxt from 1.17.134 to 1.17.139 2018-08-18 14:27:07 +02:00
Samuel Husso
64781643d3 Merge pull request #1157 from freqtrade/pyup-scheduled-update-2018-08-17
Scheduled daily dependency update on friday
2018-08-17 18:55:04 +03:00
pyup-bot
56188f2f67 Update ccxt from 1.17.132 to 1.17.134 2018-08-17 14:27:07 +02:00
Samuel Husso
eb4bc66443 Merge pull request #1156 from freqtrade/add_min_roi_test
Add explicit test on handling min_roi_reached
2018-08-17 09:59:10 +03:00
Matthias
d1c5eebff2 Add explicit test on handling min_roi_reached 2018-08-17 06:50:36 +02:00
Samuel Husso
98240e0e48 Merge pull request #1154 from freqtrade/min_roi_output
Output min-roi setting when overwriting from config
2018-08-16 20:18:49 +03:00
Samuel Husso
0750d356a1 Merge pull request #1141 from freqtrade/fix/python3.7
fix running freqtrade on python3.7
2018-08-16 20:17:24 +03:00
Matthias
f57bf8f269 Merge pull request #1155 from freqtrade/pyup-scheduled-update-2018-08-16
Scheduled daily dependency update on thursday
2018-08-16 14:36:53 +02:00
pyup-bot
dc41a19f99 Update ccxt from 1.17.126 to 1.17.132 2018-08-16 14:27:06 +02:00
Matthias
16fa877b67 Remove verbosity of trying backup tables - properly log if
databasemigration happened
2018-08-16 13:15:46 +02:00
Matthias
ff8ed564f1 Refactor refresh_pairs to exchange and fix tests 2018-08-16 12:15:09 +02:00
misagh
e6e2799f03 Keeping cached Klines only in exchange and renaming _cached_klines to
klines.
2018-08-16 11:37:31 +02:00
Matthias
4a8c120926 Output min-roi setting when overwriting from config 2018-08-16 11:35:41 +02:00
misagh
a2d9126917 Merge branch 'develop' into ccxt-async 2018-08-15 15:09:35 +02:00
Matthias
baeffee80d Replace time.time with arrow.utcnow().timestamp
arrow is imported already
2018-08-15 13:26:01 +02:00
Matthias
76914c2c07 remove todo comment as this is actually done 2018-08-15 12:57:27 +02:00
Matthias
ca6594cd24 remove comment, add docstring 2018-08-15 12:49:39 +02:00
Matthias
d007ac4b96 check version explicitly, use "python" in venv 2018-08-15 08:37:20 +02:00
Matthias
3aa210cf93 Add test for get_history 2018-08-14 20:53:58 +02:00
Matthias
e37cb49dc2 Ad test for async_load_markets 2018-08-14 20:42:13 +02:00
Matthias
67cbbc86f2 Add test for exception 2018-08-14 20:35:12 +02:00
Matthias
37e504610a refactor private method - improve some async tests 2018-08-14 20:33:03 +02:00
Matthias
8528143ffa Properly close async exchange as requested by ccxt 2018-08-14 19:52:09 +02:00
Matthias
69cc6aa958 Add test to async 2018-08-14 16:02:03 +02:00
misagh
a6b69da391 Merge branch 'develop' into ccxt-async 2018-08-14 15:30:34 +02:00
misagh
0b44dda7b7 Merge pull request #3 from xmatthias/ccxt-async_xmatt
ccxt async download
2018-08-14 13:21:13 +02:00
Nullart2
78610bb47f mock order_book and additional test 2018-08-14 18:12:44 +08:00
Matthias
a0bc17d1ef Update dockerfile to 3.7.0 2018-08-12 13:59:50 +02:00
Matthias
2b37c1ff0e Merge branch 'develop' into ujson-loader 2018-08-12 13:11:40 +02:00
Matthias
7d72e364aa Remove broken ujson loading - replace with variable-based fix 2018-08-12 13:08:10 +02:00
creslin
bd61478367 Merge pull request #2 from xmatthias/ta_on_candle_xmatt
Ta on candle xmatt
2018-08-12 10:07:58 +00:00
Matthias
f7afd9a5ff update setup.sh to support 3.7 2018-08-12 10:37:10 +02:00
Matthias
e3e79a55fa Fix _abc_data pickle error in 3.7 2018-08-12 10:16:51 +02:00
Matthias
88e85e8d33 fix tests - move load_async_markets call to validate_pairs 2018-08-10 13:11:04 +02:00
Matthias
fce071843d Move async-load to seperate function 2018-08-10 13:04:43 +02:00
Matthias
a852d2ff32 default since_ms to 30 days if no timerange is given 2018-08-10 11:15:02 +02:00
Matthias
a107c4c7b4 Download using asyncio 2018-08-10 11:08:28 +02:00
Matthias
74d6816a1a Fix some comments 2018-08-10 11:00:07 +02:00
Matthias
e34f2abc3a Add some typehints 2018-08-10 09:58:04 +02:00
Matthias
8a0fc888d6 log if using cached data 2018-08-10 09:48:54 +02:00
Matthias
36f05af79a sort fetch_olvhc result, refactor some
* add exception for since_ms - if this is set it should always download
2018-08-10 09:44:15 +02:00
Matthias
e654b76bc8 Fix async test 2018-08-10 09:44:03 +02:00
Matthias
56768f1a61 Flake8 in tests ... 2018-08-09 20:17:55 +02:00
Matthias
b008649d79 Remove unnecessary quote escaping 2018-08-09 20:13:07 +02:00
Matthias
3b2f161573 Add test for ta_on_candle override 2018-08-09 20:12:45 +02:00
Matthias
df960241bd Add log-message for skipped candle and tests 2018-08-09 20:07:01 +02:00
Matthias
4ece5d6d7a Add tests for ta_on_candle 2018-08-09 20:02:24 +02:00
Matthias
e36067afd3 refactor candle_seen to private 2018-08-09 19:58:47 +02:00
Matthias
c4e43039f2 Allow control from strategy 2018-08-09 19:24:00 +02:00
Matthias
029d61b8c5 Add ta_on_candle descripton to support strategy 2018-08-09 13:12:12 +02:00
misagh
280ead7bdb Merge branch 'develop' into ccxt-async 2018-08-09 13:04:01 +02:00
Matthias
98730939d4 Refactor to use a plain dict
* check config-setting first - avoids any call to "candle_seen"
eventually
2018-08-09 13:02:41 +02:00
Matthias
d1306a2177 Fix failing tests when metadata in analyze_ticker is actually used 2018-08-09 13:01:57 +02:00
misagh
cb26085229 Moving should_not_update logic to async function per pair. if there is
no new candle, async function will just return the last cached candle
locally and doesn’t hit the API
2018-08-09 12:47:26 +02:00
misagh
cef09f49a6 wait for markets to be loaded before looping in symbols. 2018-08-09 11:51:38 +02:00
Matthias
e1921c8849 Fix bug causing /balance to fail 2018-08-08 22:00:39 +02:00
Matthias
3c451e0677 Add test for bugreport #1111 2018-08-08 21:54:52 +02:00
Nullart2
c9c0e108ab refactor 2018-08-07 18:29:37 +08:00
Matthias
c9580b31d0 parametrize outdated_offset to simplify sandbox usage 2018-08-07 09:25:21 +02:00
Matthias
255f303850 Fix tests and flake8 2018-08-07 08:56:06 +02:00
Nullart2
1309c2b14f tests update 2018-08-05 22:56:14 +08:00
Nullart2
7143b64fb7 tests for coverage 2018-08-05 22:41:58 +08:00
Nullart2
26d591ea43 mypy fix 2018-08-05 21:08:07 +08:00
Nullart2
4a9bf78770 Order Book with tests 2018-08-05 12:41:06 +08:00
misagh
3ce4d20ab9 using constants instead of stripping the string 2018-08-04 13:04:16 +02:00
misagh
af93b18475 Do not refresh candles on "process_throttle_secs" but on intervals 2018-08-03 18:10:03 +02:00
misagh
3987a8aeb8 Merge branch 'ccxt-async' of https://github.com/misaghshakeri/freqtrade into ccxt-async 2018-08-03 14:50:11 +02:00
misagh
59b9a6d94d Break the loop as soon as one buy signal is found. 2018-08-03 14:49:55 +02:00
creslin
10ab6c7ffa Removed unneeded property code 2018-08-03 09:14:16 +00:00
creslin
71b0e15182 updated configuration.md 2018-08-03 08:45:24 +00:00
creslin
1fef384bba flake 8 2018-08-03 08:40:16 +00:00
creslin
d2a728cebd flake 8 2018-08-03 08:38:13 +00:00
creslin
6b3e8dcc33 holds a dict of each pair last seen.
to correctly manage the last seen of a pair.
2018-08-03 08:33:37 +00:00
creslin
c38d94df2d Resubmitting - because GIT.
This is the last cut that was in #1117 before i closed that PR

This PR allows a user to set the flag "ta_on_candle" in their config.json

This will change the behaviour of the the bot to only process indicators
when there is a new candle to be processed for that pair.

The test is made up of "last dataframe row date + pair" is different to
last_seen OR  ta_on_candle is not True
2018-08-03 07:33:34 +00:00
Matthias
337d9174d9 Flake8 fixes 2018-08-02 20:11:27 +02:00
misagh
05ca78d2a3 ticker_history changed to candle_history naming 2018-08-02 17:10:38 +02:00
misagh
2ec2f1abce async branch updated to reflect develop branch changes 2018-08-02 16:48:21 +02:00
misagh
7dc440b874 Merge pull request #2 from xmatthias/ccxt-async-xmatt
Ccxt async xmatt
2018-08-02 16:33:02 +02:00
Matthias
9c08cdc81d Fix typehints 2018-08-01 21:58:32 +02:00
Matthias
915160f21f Add tests for tickers-history 2018-08-01 21:44:02 +02:00
Matthias
c466a028e0 Add a first async test 2018-08-01 21:40:54 +02:00
misagh
b47c5f1d9a Merge pull request #1 from xmatthias/ccxt-async-xmatt
some fixes and improvements hopefully
2018-07-31 21:21:45 +02:00
Matthias
136442245c Add todo's and dockstring 2018-07-31 21:02:04 +02:00
Matthias
12417cc303 fix tests 2018-07-31 20:54:51 +02:00
Matthias
52065178e1 use .get all the time 2018-07-31 20:53:32 +02:00
Matthias
b45d465ed8 init _klines properly 2018-07-31 20:50:59 +02:00
Matthias
31870abd25 Refactor async-refresh to it's own function 2018-07-31 20:43:32 +02:00
Matthias
a486b1d01c Use Dict instead of tuplelist, run in _process 2018-07-31 20:25:10 +02:00
misagh
154e4569d7 Merge branch 'develop' into ccxt-async 2018-07-31 12:48:12 +02:00
misagh
c8f125dbb9 ccxt async POC 2018-07-31 12:47:32 +02:00
41 changed files with 1547 additions and 433 deletions

View File

@@ -13,7 +13,7 @@ addons:
install:
- ./install_ta-lib.sh
- export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
- pip install --upgrade flake8 coveralls pytest-random-order mypy
- pip install --upgrade flake8 coveralls pytest-random-order pytest-asyncio mypy
- pip install -r requirements.txt
- pip install -e .
jobs:

View File

@@ -1,4 +1,4 @@
FROM python:3.6.6-slim-stretch
FROM python:3.7.0-slim-stretch
# Install TA-lib
RUN apt-get update && apt-get -y install curl build-essential && apt-get clean
@@ -16,10 +16,10 @@ WORKDIR /freqtrade
# Install dependencies
COPY requirements.txt /freqtrade/
RUN pip install numpy \
&& pip install -r requirements.txt
RUN pip install numpy --no-cache-dir \
&& pip install -r requirements.txt --no-cache-dir
# Install and execute
COPY . /freqtrade/
RUN pip install -e .
RUN pip install -e . --no-cache-dir
ENTRYPOINT ["freqtrade"]

View File

@@ -152,7 +152,7 @@ The project is currently setup in two main branches:
- `develop` - This branch has often new features, but might also cause breaking changes.
- `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 beeing worked on heavily. Please don't use these unless you want to test a specific feature.
- `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
@@ -200,6 +200,10 @@ to understand the requirements before sending your pull-requests.
## Requirements
### Uptodate clock
The clock must be accurate, syncronized to a NTP server very frequently to avoid problems with communication to the exchanges.
### Min hardware required
To run this bot we recommend you a cloud instance with a minimum of:

View File

@@ -11,7 +11,18 @@
"sell": 30
},
"bid_strategy": {
"ask_last_balance": 0.0
"ask_last_balance": 0.0,
"use_order_book": false,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"ask_strategy":{
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 9
},
"exchange": {
"name": "bittrex",

View File

@@ -20,7 +20,18 @@
"sell": 30
},
"bid_strategy": {
"ask_last_balance": 0.0
"ask_last_balance": 0.0,
"use_order_book": false,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": false,
"bids_to_ask_delta": 1
}
},
"ask_strategy":{
"use_order_book": false,
"order_book_min": 1,
"order_book_max": 9
},
"exchange": {
"name": "bittrex",
@@ -41,7 +52,8 @@
],
"pair_blacklist": [
"DOGE/BTC"
]
],
"outdated_offset": 5
},
"experimental": {
"use_sell_signal": false,

View File

@@ -23,19 +23,28 @@ The table below will list all configuration parameters.
| `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_stoploss` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file).
| `trailing_stoploss_positve` | 0 | No | Changes stop-loss once profit has been reached.
| `trailing_stoploss_positve_offset` | 0 | No | Offset on when to apply `trailing_stoploss_positive`. Percentage value which should be positive.
| `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.
| `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 | Have CCXT handle Exchange rate limits. Depending on the exchange, having this to false can lead to temporary bans from the exchange.
| `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`

View File

@@ -5,6 +5,8 @@ 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)

View File

@@ -8,7 +8,6 @@ To understand how to set up the bot please read the [Bot Configuration](https://
* [Table of Contents](#table-of-contents)
* [Easy Installation - Linux Script](#easy-installation---linux-script)
* [Manual installation](#manual-installation)
* [Automatic Installation - Docker](#automatic-installation---docker)
* [Custom Linux MacOS Installation](#custom-installation)
- [Requirements](#requirements)
@@ -56,34 +55,6 @@ Reset parameter will hard reset your branch (only if you are on `master` or `dev
Config parameter is a `config.json` configurator. This script will ask you questions to setup your bot and create your `config.json`.
## Manual installation - Linux/MacOS
The following steps are made for Linux/MacOS environment
### 1. Clone the repo
```bash
git clone git@github.com:freqtrade/freqtrade.git
git checkout develop
cd freqtrade
```
### 2. Create the config file
Switch `"dry_run": true,`
```bash
cp config.json.example config.json
vi config.json
```
### 3. Build your docker image and run it
```bash
docker build -t freqtrade .
docker run --rm -v /etc/localtime:/etc/localtime:ro -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
```
------
## Automatic Installation - Docker
@@ -196,7 +167,7 @@ docker run -d \
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.
*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
@@ -211,14 +182,15 @@ docker stop freqtrade
docker start freqtrade
```
You do not need to rebuild the image for configuration changes, it will suffice to edit `config.json` and restart the container.
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 \
@@ -238,12 +210,13 @@ Head over to the [Backtesting Documentation](https://github.com/freqtrade/freqtr
## Custom Installation
We've included/collected install instructions for Ubuntu 16.04, MacOS, and Windows. These are guidelines and your success may vary with other distros.
OS Specific steps are listed first, the [common](#common) section below is necessary for all systems.
### Requirements
Click each one for install guide:
* [Python 3.6.x](http://docs.python-guide.org/en/latest/starting/installation/), note the bot was not tested on Python >= 3.7.x
* [Python >= 3.6.x](http://docs.python-guide.org/en/latest/starting/installation/)
* [pip](https://pip.pypa.io/en/stable/installing/)
* [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [virtualenv](https://virtualenv.pypa.io/en/stable/installation/) (Recommended)
@@ -251,7 +224,7 @@ Click each one for install guide:
### Linux - Ubuntu 16.04
#### 1. Install Python 3.6, Git, and wget
#### Install Python 3.6, Git, and wget
```bash
sudo add-apt-repository ppa:jonathonf/python-3.6
@@ -259,7 +232,34 @@ sudo apt-get update
sudo apt-get install python3.6 python3.6-venv python3.6-dev build-essential autoconf libtool pkg-config make wget git
```
#### 2. Install TA-Lib
#### Raspberry Pi / Raspbian
Before installing FreqTrade on a Raspberry Pi running the official Raspbian Image, make sure you have at least Python 3.6 installed. The default image only provides Python 3.5. Probably the easiest way to get a recent version of python is [miniconda](https://repo.continuum.io/miniconda/).
The following assumes that miniconda3 is installed and available in your environment, and is installed.
It's recommended to use (mini)conda for this as installation/compilation of `scipy` and `pandas` takes a long time.
``` bash
conda config --add channels rpi
conda install python=3.6
conda create -n freqtrade python=3.6
conda install scipy pandas
pip install -r requirements.txt
pip install -e .
```
### MacOS
#### Install Python 3.6, git, wget and ta-lib
```bash
brew install python3 git wget
```
### common
#### 1. Install TA-Lib
Official webpage: https://mrjbq7.github.io/ta-lib/install.html
@@ -267,7 +267,7 @@ Official webpage: https://mrjbq7.github.io/ta-lib/install.html
wget http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz
tar xvzf ta-lib-0.4.0-src.tar.gz
cd ta-lib
sed -i "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h
sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h
./configure --prefix=/usr
make
make install
@@ -275,15 +275,60 @@ 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.
#### 2. Setup your Python virtual environment (virtualenv)
*Note*: This step is optional but strongly recommended to keep your system organized
```bash
python3 -m venv .env
source .env/bin/activate
```
#### 3. Install FreqTrade
Clone the git repository:
```bash
git clone https://github.com/freqtrade/freqtrade.git
```
#### 4. Configure `freqtrade` as a `systemd` service
Optionally checkout the stable/master branch:
```bash
git checkout master
```
#### 4. Initialize the configuration
```bash
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).*
#### 5. Install python dependencies
``` bash
pip3 install --upgrade pip
pip3 install -r requirements.txt
pip3 install -e .
```
#### 6. Run the Bot
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
```
*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.
#### 7. [Optional] Configure `freqtrade` as a `systemd` service
From the freqtrade repo... copy `freqtrade.service` to your systemd user directory (usually `~/.config/systemd/user`) and update `WorkingDirectory` and `ExecStart` to match your setup.
@@ -299,57 +344,6 @@ For this to be persistent (run when user is logged out) you'll need to enable `l
sudo loginctl enable-linger "$USER"
```
### MacOS
#### 1. Install Python 3.6, git, wget and ta-lib
```bash
brew install python3 git wget ta-lib
```
#### 2. Install FreqTrade
Clone the git repository:
```bash
git clone https://github.com/freqtrade/freqtrade.git
```
Optionally checkout the develop branch:
```bash
git checkout develop
```
### Setup Config and virtual env
#### 1. Initialize the configuration
```bash
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).*
#### 2. Setup your Python virtual environment (virtualenv)
```bash
python3.6 -m venv .env
source .env/bin/activate
pip3.6 install --upgrade pip
pip3.6 install -r requirements.txt
pip3.6 install -e .
```
#### 3. Run the Bot
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
```
------
## Windows
@@ -369,7 +363,7 @@ git clone https://github.com/freqtrade/freqtrade.git
copy paste `config.json` to ``\path\freqtrade-develop\freqtrade`
#### install ta-lib
#### Install ta-lib
Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows).
@@ -390,5 +384,17 @@ REM >pip install TA_Lib0.4.17cp36cp36mwin32.whl
> Thanks [Owdr](https://github.com/Owdr) for the commands. Source: [Issue #222](https://github.com/freqtrade/freqtrade/issues/222)
#### Error during installation under Windows
``` bash
error: Microsoft Visual C++ 14.0 is required. Get it with "Microsoft Visual C++ Build Tools": http://landinghub.visualstudio.com/visual-cpp-build-tools
```
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.
---
Now you have an environment ready, the next step is
[Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)...

View File

@@ -1,4 +1,5 @@
# Sandbox API testing
Where an exchange provides a sandbox for risk-free integration, or end-to-end, testing CCXT provides access to these.
This document is a *light overview of configuring Freqtrade and GDAX sandbox.
@@ -11,8 +12,11 @@ https://public.sandbox.gdax.com
https://api-public.sandbox.gdax.com
---
# Configure a Sandbox account on Gdax
Aim of this document section
- An sanbox account
- create 2FA (needed to create an API)
- Add test 50BTC to account
@@ -30,29 +34,34 @@ After registration and Email confimation you wil be redirected into your sanbox
> https://public.sandbox.pro.coinbase.com/
## Enable 2Fa (a prerequisite to creating sandbox API Keys)
From within sand box site select your profile, top right.
>Or as a direct link: https://public.sandbox.pro.coinbase.com/profile
From the menu panel to the left of the screen select
> Security: "*View or Update*"
In the new site select "enable authenticator" as typical google Authenticator.
- open Google Authenticator on your phone
- scan barcode
- enter your generated 2fa
## Enable API Access
From within sandbox select profile>api>create api-keys
>or as a direct link: https://public.sandbox.pro.coinbase.com/profile/api
Click on "create one" and ensure **view** and **trade** are "checked" and sumbit your 2Fa
Click on "create one" and ensure **view** and **trade** are "checked" and sumbit your 2FA
- **Copy and paste the Passphase** into a notepade this will be needed later
- **Copy and paste the API Secret** popup into a notepad this will needed later
- **Copy and paste the API Key** into a notepad this will needed later
## Add 50 BTC test funds
To add funds, use the web interface deposit and withdraw buttons.
To add funds, use the web interface deposit and withdraw buttons.
To begin select 'Wallets' from the top menu.
> Or as a direct link: https://public.sandbox.pro.coinbase.com/wallets
@@ -64,88 +73,69 @@ To begin select 'Wallets' from the top menu.
- - - - - Deposit
*This process may be repeated for other currencies, ETH as example*
---
# Configure Freqtrade to use Gax Sandbox
The aim of this document section
- Enable sandbox URLs in Freqtrade
- Configure API
- - secret
- - key
- - passphrase
- Enable sandbox URLs in Freqtrade
- Configure API
- - secret
- - key
- - passphrase
## Sandbox URLs
Freqtrade makes use of CCXT which in turn provides a list of URLs to Freqtrade.
These include `['test']` and `['api']`.
- `[Test]` if available will point to an Exchanges sandbox.
- `[Api]` normally used, and resolves to live API target on the exchange
To make use of sandbox / test add "sandbox": true, to your config.json
```
```json
"exchange": {
"name": "gdax",
"sandbox": true,
"key": "5wowfxemogxeowo;heiohgmd",
"secret": "/ZMH1P62rCVmwefewrgcewX8nh4gob+lywxfwfxwwfxwfNsH1ySgvWCUR/w==",
"password": "1bkjfkhfhfu6sr",
"outdated_offset": 5
"pair_whitelist": [
"BTC/USD"
```
Also insert your
- api-key (noted earlier)
- api-secret (noted earlier)
- password (the passphrase - noted earlier)
---
## You should now be ready to test your sandbox!
## You should now be ready to test your sandbox
Ensure Freqtrade logs show the sandbox URL, and trades made are shown in sandbox.
** Typically the BTC/USD has the most activity in sandbox to test against.
## GDAX - Old Candles problem
It is my experience that GDAX sandbox candles may be 20+- minutes out of date. This can cause trades to fail as one of Freqtrades safety checks
To disable this check, edit:
>strategy/interface.py
Look for the following section:
```
# Check if dataframe is out of date
signal_date = arrow.get(latest['date'])
interval_minutes = constants.TICKER_INTERVAL_MINUTES[interval]
if signal_date < (arrow.utcnow().shift(minutes=-(interval_minutes * 2 + 5))):
logger.warning(
'Outdated history for pair %s. Last tick is %s minutes old',
pair,
(arrow.utcnow() - signal_date).seconds // 60
)
return False, False
```
It is my experience that GDAX sandbox candles may be 20+- minutes out of date. This can cause trades to fail as one of Freqtrades safety checks.
You could Hash out the entire check as follows:
```
# # Check if dataframe is out of date
# signal_date = arrow.get(latest['date'])
# interval_minutes = constants.TICKER_INTERVAL_MINUTES[interval]
# if signal_date < (arrow.utcnow().shift(minutes=-(interval_minutes * 2 + 5))):
# logger.warning(
# 'Outdated history for pair %s. Last tick is %s minutes old',
# pair,
# (arrow.utcnow() - signal_date).seconds // 60
# )
# return False, False
```
To disable this check, add / change the `"outdated_offset"` parameter in the exchange section of your configuration to adjust for this delay.
Example based on the above configuration:
Or inrease the timeout to offer a level of protection/alignment of this test to freqtrade in live.
As example, to allow an additional 30 minutes. "(interval_minutes * 2 + 5 + 30)"
```
# Check if dataframe is out of date
signal_date = arrow.get(latest['date'])
interval_minutes = constants.TICKER_INTERVAL_MINUTES[interval]
if signal_date < (arrow.utcnow().shift(minutes=-(interval_minutes * 2 + 5 + 30))):
logger.warning(
'Outdated history for pair %s. Last tick is %s minutes old',
pair,
(arrow.utcnow() - signal_date).seconds // 60
)
return False, False
```json
"exchange": {
"name": "gdax",
"sandbox": true,
"key": "5wowfxemogxeowo;heiohgmd",
"secret": "/ZMH1P62rCVmwefewrgcewX8nh4gob+lywxfwfxwwfxwfNsH1ySgvWCUR/w==",
"password": "1bkjfkhfhfu6sr",
"outdated_offset": 30
"pair_whitelist": [
"BTC/USD"
```

View File

@@ -119,7 +119,6 @@ class Arguments(object):
help='Override trades database URL, this is useful if dry_run is enabled'
' or in custom deployments (default: %(default)s)',
dest='db_url',
default=constants.DEFAULT_DB_PROD_URL,
type=str,
metavar='PATH',
)

View File

@@ -110,9 +110,12 @@ class Configuration(object):
'(not applicable with Backtesting and Hyperopt)'
)
if self.args.db_url != constants.DEFAULT_DB_PROD_URL:
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')

View File

@@ -53,6 +53,7 @@ CONF_SCHEMA = {
},
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
'dry_run': {'type': 'boolean'},
'process_only_new_candles': {'type': 'boolean'},
'minimal_roi': {
'type': 'object',
'patternProperties': {
@@ -78,18 +79,35 @@ CONF_SCHEMA = {
'type': 'number',
'minimum': 0,
'maximum': 1,
'exclusiveMaximum': False
'exclusiveMaximum': False,
'use_order_book': {'type': 'boolean'},
'order_book_top': {'type': 'number', 'maximum': 20, 'minimum': 1},
'check_depth_of_market': {
'type': 'object',
'properties': {
'enabled': {'type': 'boolean'},
'bids_to_ask_delta': {'type': 'number', 'minimum': 0},
}
},
},
},
'required': ['ask_last_balance']
},
'ask_strategy': {
'type': 'object',
'properties': {
'use_order_book': {'type': 'boolean'},
'order_book_min': {'type': 'number', 'minimum': 1},
'order_book_max': {'type': 'number', 'minimum': 1, 'maximum': 50}
}
},
'exchange': {'$ref': '#/definitions/exchange'},
'experimental': {
'type': 'object',
'properties': {
'use_sell_signal': {'type': 'boolean'},
'sell_profit_only': {'type': 'boolean'},
"ignore_roi_if_buy_signal_true": {'type': 'boolean'}
'ignore_roi_if_buy_signal_true': {'type': 'boolean'}
}
},
'telegram': {
@@ -145,7 +163,8 @@ CONF_SCHEMA = {
'pattern': '^[0-9A-Z]+/[0-9A-Z]+$'
},
'uniqueItems': True
}
},
'outdated_offset': {'type': 'integer', 'minimum': 1}
},
'required': ['name', 'key', 'secret', 'pair_whitelist']
}

View File

@@ -1,12 +1,15 @@
# pragma pylint: disable=W0603
""" Cryptocurrency Exchanges support """
import logging
import inspect
from random import randint
from typing import List, Dict, Any, Optional
from typing import List, Dict, Tuple, Any, Optional
from datetime import datetime
from math import floor, ceil
import asyncio
import ccxt
import ccxt.async_support as ccxt_async
import arrow
from freqtrade import constants, OperationalException, DependencyException, TemporaryError
@@ -23,6 +26,24 @@ _EXCHANGE_URLS = {
}
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)
@@ -45,8 +66,8 @@ class Exchange(object):
# Current selected exchange
_api: ccxt.Exchange = None
_api_async: ccxt_async.Exchange = None
_conf: Dict = {}
_cached_ticker: Dict[str, Any] = {}
# Holds all open sell orders for dry_run
_dry_run_open_orders: Dict[str, Any] = {}
@@ -60,14 +81,24 @@ class Exchange(object):
"""
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, Any] = {}
if config['dry_run']:
logger.info('Instance is running with dry_run enabled')
exchange_config = config['exchange']
self._api = self._init_ccxt(exchange_config)
self._api_async = self._init_ccxt(exchange_config, ccxt_async)
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'])
@@ -75,7 +106,15 @@ class Exchange(object):
# Check if timeframe is available
self.validate_timeframes(config['ticker_interval'])
def _init_ccxt(self, exchange_config: dict) -> ccxt.Exchange:
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.Exchange:
"""
Initialize ccxt with given config and return valid
ccxt instance.
@@ -83,15 +122,15 @@ class Exchange(object):
# Find matching class for the given exchange name
name = exchange_config['name']
if name not in ccxt.exchanges:
if name not in ccxt_module.exchanges:
raise OperationalException(f'Exchange {name} is not supported')
try:
api = getattr(ccxt, name.lower())({
api = getattr(ccxt_module, name.lower())({
'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),
'enableRateLimit': exchange_config.get('ccxt_rate_limit', True)
})
except (KeyError, AttributeError):
raise OperationalException(f'Exchange {name} is not supported')
@@ -116,10 +155,29 @@ class Exchange(object):
api.urls['api'] = api.urls['test']
logger.info("Enabled Sandbox API on %s", name)
else:
logger.warning(self._api.name, "No Sandbox URL in CCXT, exiting. "
"Please check your config.json")
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.
@@ -128,11 +186,9 @@ class Exchange(object):
:return: None
"""
try:
markets = self._api.load_markets()
except ccxt.BaseError as e:
logger.warning('Unable to validate pairs (assuming they are correct). Reason: %s', e)
return
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:
@@ -141,7 +197,7 @@ class Exchange(object):
if not pair.endswith(stake_cur):
raise OperationalException(
f'Pair {pair} not compatible with stake_currency: {stake_cur}')
if pair not in markets:
if self.markets and pair not in self.markets:
raise OperationalException(
f'Pair {pair} is not available at {self.name}')
@@ -322,13 +378,109 @@ class Exchange(object):
return data
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}')
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 tick in tickers:
if tick[0] == pair:
data.extend(tick[1])
# 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 return 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 """
input_coroutines = [self._async_get_candle_history(
symbol, tick_interval) for symbol in pairs]
tickers = await asyncio.gather(*input_coroutines, return_exceptions=True)
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)
# Calculating ticker interval in second
interval_in_sec = constants.TICKER_INTERVAL_MINUTES[tick_interval] * 60
# If (last update time) + (interval in second) is greater or equal than now
# that means we don't have to hit the API as there is no new candle
# so we fetch it from local cache
if (not since_ms and
self._pairs_last_refresh_time.get(pair, 0) + interval_in_sec >=
arrow.utcnow().timestamp):
data = self.klines[pair]
logger.debug("Using cached klines data for %s ...", pair)
else:
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)
data = sorted(data, key=lambda x: x[0])
# keeping last candle time as last refreshed time of the pair
if data:
self._pairs_last_refresh_time[pair] = data[-1][0] // 1000
# keeping candles in cache
self.klines[pair] = data
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 get_candle_history(self, pair: str, tick_interval: str,
since_ms: Optional[int] = None) -> List[Dict]:
@@ -409,6 +561,37 @@ class Exchange(object):
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']:
@@ -416,7 +599,8 @@ class Exchange(object):
if not self.exchange_has('fetchMyTrades'):
return []
try:
my_trades = self._api.fetch_my_trades(pair, since.timestamp())
# 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
@@ -462,12 +646,3 @@ class Exchange(object):
f'Could not get fee info due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
def get_amount_lots(self, pair: str, amount: float) -> float:
"""
get buyable amount rounding, ..
"""
# validate that markets are loaded before trying to get fee
if not self._api.markets:
self._api.load_markets()
return self._api.amount_to_lots(pair, amount)

View File

@@ -2,6 +2,7 @@
Functions to analyze ticker data with indicators and produce buy and sell signals
"""
import logging
import pandas as pd
from pandas import DataFrame, to_datetime
logger = logging.getLogger(__name__)
@@ -31,3 +32,27 @@ def parse_ticker_dataframe(ticker: list) -> DataFrame:
})
frame.drop(frame.tail(1).index, inplace=True) # eliminate partial candle
return frame
def order_book_to_dataframe(bids: list, asks: list) -> DataFrame:
"""
Gets order book list, returns dataframe with below format per suggested by creslin
-------------------------------------------------------------------
b_sum b_size bids asks a_size a_sum
-------------------------------------------------------------------
"""
cols = ['bids', 'b_size']
bids_frame = DataFrame(bids, columns=cols)
# add cumulative sum column
bids_frame['b_sum'] = bids_frame['b_size'].cumsum()
cols2 = ['asks', 'a_size']
asks_frame = DataFrame(asks, columns=cols2)
# add cumulative sum column
asks_frame['a_sum'] = asks_frame['a_size'].cumsum()
frame = pd.concat([bids_frame['b_sum'], bids_frame['b_size'], bids_frame['bids'],
asks_frame['asks'], asks_frame['a_size'], asks_frame['a_sum']], axis=1,
keys=['b_sum', 'b_size', 'bids', 'asks', 'a_size', 'a_sum'])
# logger.info('order book %s', frame )
return frame

View File

@@ -10,7 +10,8 @@ from datetime import datetime
from typing import Any, Callable, Dict, List, Optional
import arrow
import requests
from requests.exceptions import RequestException
from cachetools import TTLCache, cached
from freqtrade import (DependencyException, OperationalException,
@@ -21,6 +22,7 @@ from freqtrade.rpc import RPCManager, RPCMessageType
from freqtrade.state import State
from freqtrade.strategy.interface import SellType
from freqtrade.strategy.resolver import IStrategy, StrategyResolver
from freqtrade.exchange.exchange_helpers import order_book_to_dataframe
logger = logging.getLogger(__name__)
@@ -180,6 +182,9 @@ class FreqtradeBot(object):
final_list = sanitized_list[:nb_assets] if nb_assets else sanitized_list
self.config['exchange']['pair_whitelist'] = final_list
# Refreshing candles
self.exchange.refresh_tickers(final_list, self.strategy.ticker_interval)
# Query trades from persistence layer
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
@@ -267,16 +272,40 @@ class FreqtradeBot(object):
return final_list
def get_target_bid(self, ticker: Dict[str, float]) -> float:
def get_target_bid(self, pair: str, ticker: Dict[str, float]) -> 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']:
return ticker['ask']
balance = self.config['bid_strategy']['ask_last_balance']
return ticker['ask'] + balance * (ticker['last'] - ticker['ask'])
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):
logger.info('Getting price from order book')
order_book_top = config_bid_strategy.get('order_book_top', 1)
order_book = self.exchange.get_order_book(pair, order_book_top)
logger.debug('order_book %s', order_book)
# top 1 = index 0
order_book_rate = order_book['bids'][order_book_top - 1][0]
# 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
else:
logger.info('Using Last Ask / Last Price')
used_rate = ticker_rate
return used_rate
def _get_trade_stake_amount(self) -> Optional[float]:
"""
@@ -333,7 +362,7 @@ class FreqtradeBot(object):
amount_reserve_percent += self.strategy.stoploss
# it should not be more than 50%
amount_reserve_percent = max(amount_reserve_percent, 0.5)
return min(min_stake_amounts)/amount_reserve_percent
return min(min_stake_amounts) / amount_reserve_percent
def create_trade(self) -> bool:
"""
@@ -362,13 +391,38 @@ class FreqtradeBot(object):
if not whitelist:
raise DependencyException('No currency pairs in whitelist')
# Pick pair based on buy signals
# running get_signal on historical data fetched
# to find buy signals
for _pair in whitelist:
thistory = self.exchange.get_candle_history(_pair, interval)
(buy, sell) = self.strategy.get_signal(_pair, interval, thistory)
(buy, sell) = self.strategy.get_signal(_pair, interval, self.exchange.klines.get(_pair))
if buy and not sell:
bidstrat_check_depth_of_market = self.config.get('bid_strategy', {}).\
get('check_depth_of_market', {})
if (bidstrat_check_depth_of_market.get('enabled', False)) and\
(bidstrat_check_depth_of_market.get('bids_to_ask_delta', 0) > 0):
if self._check_depth_of_market_buy(_pair, bidstrat_check_depth_of_market):
return self.execute_buy(_pair, stake_amount)
else:
return False
return self.execute_buy(_pair, stake_amount)
return False
def _check_depth_of_market_buy(self, pair: str, conf: Dict) -> bool:
"""
Checks depth of market before executing a buy
"""
conf_bids_to_ask_delta = conf.get('bids_to_ask_delta', 0)
logger.info('checking depth of market for %s', pair)
order_book = self.exchange.get_order_book(pair, 1000)
order_book_data_frame = order_book_to_dataframe(order_book['bids'], order_book['asks'])
order_book_bids = order_book_data_frame['b_size'].sum()
order_book_asks = order_book_data_frame['a_size'].sum()
bids_ask_delta = order_book_bids / order_book_asks
logger.info('bids: %s, asks: %s, delta: %s', order_book_bids,
order_book_asks, bids_ask_delta)
if bids_ask_delta >= conf_bids_to_ask_delta:
return True
return False
def execute_buy(self, pair: str, stake_amount: float) -> bool:
@@ -383,7 +437,7 @@ class FreqtradeBot(object):
fiat_currency = self.config.get('fiat_display_currency', None)
# Calculate amount
buy_limit = self.get_target_bid(self.exchange.get_ticker(pair))
buy_limit = self.get_target_bid(pair, self.exchange.get_ticker(pair))
min_stake_amount = self._get_min_pair_stake_amount(pair_s, buy_limit)
if min_stake_amount is not None and min_stake_amount > stake_amount:
@@ -526,22 +580,52 @@ class FreqtradeBot(object):
raise ValueError(f'attempt to handle closed trade: {trade}')
logger.debug('Handling %s ...', trade)
current_rate = self.exchange.get_ticker(trade.pair)['bid']
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'):
ticker = self.exchange.get_candle_history(trade.pair, self.strategy.ticker_interval)
ticker = self.exchange.klines.get(trade.pair)
(buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.ticker_interval,
ticker)
should_sell = self.strategy.should_sell(trade, current_rate, datetime.utcnow(), buy, sell)
if should_sell.sell_flag:
self.execute_sell(trade, current_rate, should_sell.sell_type)
return True
config_ask_strategy = self.config.get('ask_strategy', {})
if config_ask_strategy.get('use_order_book', False):
logger.info('Using order book for selling...')
# logger.debug('Order book %s',orderBook)
order_book_min = config_ask_strategy.get('order_book_min', 1)
order_book_max = config_ask_strategy.get('order_book_max', 1)
order_book = self.exchange.get_order_book(trade.pair, order_book_max)
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
if self.check_sell(trade, sell_rate, buy, sell):
return True
break
else:
logger.info('checking sell')
if self.check_sell(trade, sell_rate, buy, sell):
return True
logger.info('Found no sell signals for whitelisted currencies. Trying again..')
return False
def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool:
should_sell = self.strategy.should_sell(trade, sell_rate, datetime.utcnow(), buy, sell)
if should_sell.sell_flag:
self.execute_sell(trade, sell_rate, should_sell.sell_type)
logger.info('excuted sell')
return True
return False
def check_handle_timedout(self) -> None:
"""
Check if any orders are timed out and cancel if neccessary
@@ -562,7 +646,7 @@ class FreqtradeBot(object):
if not trade.open_order_id:
continue
order = self.exchange.get_order(trade.open_order_id, trade.pair)
except requests.exceptions.RequestException:
except (RequestException, DependencyException):
logger.info(
'Cannot query order for %s due to %s',
trade,

View File

@@ -1,7 +1,13 @@
# pragma pylint: disable=missing-docstring
import gzip
import json
try:
import ujson as json
_UJSON = True
except ImportError:
# see mypy/issues/1153
import json # type: ignore
_UJSON = False
import logging
import os
from typing import Optional, List, Dict, Tuple, Any
@@ -14,6 +20,14 @@ from freqtrade.arguments import TimeRange
logger = logging.getLogger(__name__)
def json_load(data):
"""Try to load data with ujson"""
if _UJSON:
return json.load(data, precise_float=True)
else:
return json.load(data)
def trim_tickerlist(tickerlist: List[Dict], timerange: TimeRange) -> List[Dict]:
if not tickerlist:
return tickerlist
@@ -163,7 +177,7 @@ def load_cached_data_for_updating(filename: str,
# read the cached file
if os.path.isfile(filename):
with open(filename, "rt") as file:
data = json.load(file)
data = json_load(file)
# remove the last item, because we are not sure if it is correct
# it could be fetched when the candle was incompleted
if data:
@@ -191,19 +205,18 @@ def download_backtesting_testdata(datadir: str,
timerange: Optional[TimeRange] = None) -> None:
"""
Download the latest ticker intervals from the exchange for the pairs passed in parameters
Download the latest ticker intervals from the exchange for the pair passed in parameters
The data is downloaded starting from the last correct ticker interval data that
esists in a cache. If timerange starts earlier than the data in the cache,
exists in a cache. If timerange starts earlier than the data in the cache,
the full data will be redownloaded
Based on @Rybolov work: https://github.com/rybolov/freqtrade-data
:param pairs: list of pairs to download
:param pair: pair to download
:param tick_interval: ticker interval
:param timerange: range of time to download
:return: None
"""
path = make_testdata_path(datadir)
filepair = pair.replace("/", "_")
filename = os.path.join(path, f'{filepair}-{tick_interval}.json')
@@ -219,8 +232,11 @@ def download_backtesting_testdata(datadir: str,
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')
new_data = exchange.get_candle_history(pair=pair, tick_interval=tick_interval,
since_ms=since_ms)
# Default since_ms to 30 days if nothing is given
new_data = exchange.get_history(pair=pair, tick_interval=tick_interval,
since_ms=since_ms if since_ms
else
int(arrow.utcnow().shift(days=-30).float_timestamp) * 1000)
data.extend(new_data)
logger.debug("New Start: %s", misc.format_ms_time(data[0][0]))

View File

@@ -75,8 +75,6 @@ class Backtesting(object):
else:
# only one strategy
strat = StrategyResolver(self.config).strategy
self.strategylist.append(StrategyResolver(self.config).strategy)
# Load one strategy
self._set_strategy(self.strategylist[0])
@@ -108,7 +106,8 @@ class Backtesting(object):
return min(timeframe, key=operator.itemgetter(0))[0], \
max(timeframe, key=operator.itemgetter(1))[1]
def _generate_text_table(self, data: Dict[str, Dict], results: DataFrame) -> str:
def _generate_text_table(self, data: Dict[str, Dict], results: DataFrame,
skip_nan: bool = False) -> str:
"""
Generates and returns a text table for the given backtest data and the results dataframe
:return: pretty printed table with tabulate as str
@@ -121,6 +120,9 @@ class Backtesting(object):
'total profit ' + stake_currency, 'avg duration', 'profit', 'loss']
for pair in data:
result = results[results.pair == pair]
if skip_nan and result.profit_abs.isnull().all():
continue
tabular_data.append([
pair,
len(result.index),
@@ -330,15 +332,15 @@ class Backtesting(object):
Run a backtesting end-to-end
:return: None
"""
data = {}
data: Dict[str, Any] = {}
pairs = self.config['exchange']['pair_whitelist']
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 ...')
for pair in pairs:
data[pair] = self.exchange.get_candle_history(pair, self.ticker_interval)
self.exchange.refresh_tickers(pairs, self.ticker_interval)
data = self.exchange.klines
else:
logger.info('Using local backtesting data (using whitelist in given config) ...')
@@ -404,7 +406,7 @@ class Backtesting(object):
print(self._generate_text_table_sell_reason(data, results))
print(' LEFT OPEN TRADES REPORT '.center(119, '='))
print(self._generate_text_table(data, results.loc[results.open_at_end]))
print(self._generate_text_table(data, results.loc[results.open_at_end], True))
print()
if len(all_results) > 1:
# Print Strategy summary table

View File

@@ -152,7 +152,7 @@ class Hyperopt(Backtesting):
@staticmethod
def generate_roi_table(params: Dict) -> Dict[int, float]:
"""
Generate the ROI table thqt will be used by Hyperopt
Generate the ROI table that will be used by Hyperopt
"""
roi_table = {}
roi_table[0] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
@@ -402,6 +402,13 @@ def start(args: Namespace) -> None:
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

@@ -79,10 +79,12 @@ def check_migrate(engine) -> None:
table_back_name = 'trades_bak'
for i, table_back_name in enumerate(tabs):
table_back_name = f'trades_bak{i}'
logger.info(f'trying {table_back_name}')
logger.debug(f'trying {table_back_name}')
# Check for latest column
if not has_column(cols, 'ticker_interval'):
logger.info(f'Running database migration - backup available as {table_back_name}')
fee_open = get_column_def(cols, 'fee_open', 'fee')
fee_close = get_column_def(cols, 'fee_close', 'fee')
open_rate_requested = get_column_def(cols, 'open_rate_requested', 'null')

View File

@@ -13,6 +13,7 @@ import sqlalchemy as sql
from numpy import mean, nan_to_num
from pandas import DataFrame
from freqtrade import TemporaryError
from freqtrade.fiat_convert import CryptoToFiatConverter
from freqtrade.misc import shorten_date
from freqtrade.persistence import Trade
@@ -273,10 +274,13 @@ class RPC(object):
if coin == 'BTC':
rate = 1.0
else:
if coin == 'USDT':
rate = 1.0 / self._freqtrade.exchange.get_ticker('BTC/USDT', False)['bid']
else:
rate = self._freqtrade.exchange.get_ticker(coin + '/BTC', False)['bid']
try:
if coin == 'USDT':
rate = 1.0 / self._freqtrade.exchange.get_ticker('BTC/USDT', False)['bid']
else:
rate = self._freqtrade.exchange.get_ticker(coin + '/BTC', False)['bid']
except TemporaryError:
continue
est_btc: float = rate * balance['total']
total = total + est_btc
output.append({

View File

@@ -1,8 +1,10 @@
import logging
import sys
from copy import deepcopy
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__)
@@ -12,8 +14,18 @@ def import_strategy(strategy: IStrategy, config: dict) -> IStrategy:
Imports given Strategy instance to global scope
of freqtrade.strategy and returns an instance of it
"""
# Copy all attributes from base class and class
attr = deepcopy({**strategy.__class__.__dict__, **strategy.__dict__})
comb = {**strategy.__class__.__dict__, **strategy.__dict__}
# Delete '_abc_impl' from dict as deepcopy fails on 3.7 with
# `TypeError: can't pickle _abc_data objects``
# This will only apply to python 3.7
if sys.version_info.major == 3 and sys.version_info.minor == 7 and '_abc_impl' in comb:
del comb['_abc_impl']
attr = deepcopy(comb)
# Adjust module name
attr['__module__'] = 'freqtrade.strategy'

View File

@@ -6,7 +6,7 @@ import logging
from abc import ABC, abstractmethod
from datetime import datetime
from enum import Enum
from typing import Dict, List, NamedTuple, Tuple
from typing import Dict, List, NamedTuple, Optional, Tuple
import warnings
import arrow
@@ -70,8 +70,15 @@ class IStrategy(ABC):
# associated ticker interval
ticker_interval: str
# 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] = {}
def __init__(self, config: dict) -> None:
self.config = config
self._last_candle_seen_per_pair = {}
@abstractmethod
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
@@ -112,13 +119,34 @@ class IStrategy(ABC):
add several TA indicators and buy signal to it
:return DataFrame with ticker data and indicator data
"""
dataframe = parse_ticker_dataframe(ticker_history)
dataframe = self.advise_indicators(dataframe, metadata)
dataframe = self.advise_buy(dataframe, metadata)
dataframe = self.advise_sell(dataframe, metadata)
pair = str(metadata.get('pair'))
# Test if seen this pair and last candle before.
# always run if process_only_new_candles is set to true
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")
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")
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")
return dataframe
def get_signal(self, pair: str, interval: str, ticker_hist: List[Dict]) -> Tuple[bool, bool]:
def get_signal(self, pair: str, interval: str,
ticker_hist: Optional[List[Dict]]) -> Tuple[bool, bool]:
"""
Calculates current signal based several technical analysis indicators
:param pair: pair in format ANT/BTC
@@ -155,7 +183,8 @@ class IStrategy(ABC):
# Check if dataframe is out of date
signal_date = arrow.get(latest['date'])
interval_minutes = constants.TICKER_INTERVAL_MINUTES[interval]
if signal_date < (arrow.utcnow().shift(minutes=-(interval_minutes * 2 + 5))):
offset = self.config.get('exchange', {}).get('outdated_offset', 5)
if signal_date < (arrow.utcnow().shift(minutes=-(interval_minutes * 2 + offset))):
logger.warning(
'Outdated history for pair %s. Last tick is %s minutes old',
pair,

View File

@@ -44,14 +44,15 @@ class StrategyResolver(object):
# 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.")
logger.info("Override strategy 'minimal_roi' with value in config file: %s.",
config['minimal_roi'])
else:
config['minimal_roi'] = self.strategy.minimal_roi
if 'stoploss' in config:
self.strategy.stoploss = config['stoploss']
logger.info(
"Override strategy \'stoploss\' with value in config file: %s.", config['stoploss']
"Override strategy 'stoploss' with value in config file: %s.", config['stoploss']
)
else:
config['stoploss'] = self.strategy.stoploss
@@ -59,12 +60,21 @@ class StrategyResolver(object):
if 'ticker_interval' in config:
self.strategy.ticker_interval = config['ticker_interval']
logger.info(
"Override strategy \'ticker_interval\' with value in config file: %s.",
"Override strategy 'ticker_interval' with value in config file: %s.",
config['ticker_interval']
)
else:
config['ticker_interval'] = self.strategy.ticker_interval
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
# Sort and apply type conversions
self.strategy.minimal_roi = OrderedDict(sorted(
{int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(),

View File

@@ -4,7 +4,7 @@ import logging
from datetime import datetime
from functools import reduce
from typing import Dict, Optional
from unittest.mock import MagicMock
from unittest.mock import MagicMock, PropertyMock
import arrow
import pytest
@@ -26,8 +26,10 @@ def log_has(line, logs):
def patch_exchange(mocker, api_mock=None) -> None:
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value="Bittrex"))
mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value="bittrex"))
if api_mock:
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
else:
@@ -102,7 +104,18 @@ def default_conf():
"sell": 30
},
"bid_strategy": {
"ask_last_balance": 0.0
"ask_last_balance": 0.0,
"use_order_book": False,
"order_book_top": 1,
"check_depth_of_market": {
"enabled": False,
"bids_to_ask_delta": 1
}
},
"ask_strategy": {
"use_order_book": False,
"order_book_min": 1,
"order_book_max": 1
},
"exchange": {
"name": "bittrex",
@@ -403,6 +416,39 @@ def limit_sell_order():
}
@pytest.fixture
def order_book_l2():
return MagicMock(return_value={
'bids': [
[0.043936, 10.442],
[0.043935, 31.865],
[0.043933, 11.212],
[0.043928, 0.088],
[0.043925, 10.0],
[0.043921, 10.0],
[0.04392, 37.64],
[0.043899, 0.066],
[0.043885, 0.676],
[0.04387, 22.758]
],
'asks': [
[0.043949, 0.346],
[0.04395, 0.608],
[0.043951, 3.948],
[0.043954, 0.288],
[0.043958, 9.277],
[0.043995, 1.566],
[0.044, 0.588],
[0.044002, 0.992],
[0.044003, 0.095],
[0.04402, 37.64]
],
'timestamp': None,
'datetime': None,
'nonce': 288004540
})
@pytest.fixture
def ticker_history():
return [

View File

@@ -3,8 +3,9 @@
import logging
from datetime import datetime
from random import randint
from unittest.mock import MagicMock, PropertyMock
from unittest.mock import Mock, MagicMock, PropertyMock
import arrow
import ccxt
import pytest
@@ -13,6 +14,14 @@ from freqtrade.exchange import API_RETRY_COUNT, Exchange
from freqtrade.tests.conftest import get_patched_exchange, log_has
# Source: https://stackoverflow.com/questions/29881236/how-to-mock-asyncio-coroutines
def get_mock_coro(return_value):
async def mock_coro(*args, **kwargs):
return return_value
return Mock(wraps=mock_coro)
def ccxt_exceptionhandlers(mocker, default_conf, api_mock, fun, mock_ccxt_fun, **kwargs):
with pytest.raises(TemporaryError):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError)
@@ -27,12 +36,32 @@ def ccxt_exceptionhandlers(mocker, default_conf, api_mock, fun, mock_ccxt_fun, *
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fun, **kwargs):
with pytest.raises(TemporaryError):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
await getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == API_RETRY_COUNT + 1
with pytest.raises(OperationalException):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
await getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
def test_init(default_conf, mocker, caplog):
caplog.set_level(logging.INFO)
get_patched_exchange(mocker, default_conf)
assert log_has('Instance is running with dry_run enabled', caplog.record_tuples)
def test_destroy(default_conf, mocker, caplog):
caplog.set_level(logging.DEBUG)
get_patched_exchange(mocker, default_conf)
assert log_has('Exchange object destroyed, closing async loop', caplog.record_tuples)
def test_init_exception(default_conf, mocker):
default_conf['exchange']['name'] = 'wrong_exchange_name'
@@ -64,6 +93,7 @@ def test_symbol_amount_prec(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
exchange = Exchange(default_conf)
amount = 2.34559
@@ -87,6 +117,7 @@ def test_symbol_price_prec(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
exchange = Exchange(default_conf)
price = 2.34559
@@ -108,6 +139,7 @@ def test_set_sandbox(default_conf, mocker):
type(api_mock).urls = url_mock
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
exchange = Exchange(default_conf)
liveurl = exchange._api.urls['api']
@@ -129,6 +161,7 @@ def test_set_sandbox_exception(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
with pytest.raises(OperationalException, match=r'does not provide a sandbox api'):
exchange = Exchange(default_conf)
@@ -136,6 +169,43 @@ def test_set_sandbox_exception(default_conf, mocker):
exchange.set_sandbox(exchange._api, default_conf['exchange'], 'Logname')
def test__load_async_markets(default_conf, mocker, caplog):
exchange = get_patched_exchange(mocker, default_conf)
exchange._api_async.load_markets = get_mock_coro(None)
exchange._load_async_markets()
assert exchange._api_async.load_markets.call_count == 1
caplog.set_level(logging.DEBUG)
exchange._api_async.load_markets = Mock(side_effect=ccxt.BaseError("deadbeef"))
exchange._load_async_markets()
assert log_has('Could not load async markets. Reason: deadbeef',
caplog.record_tuples)
def test__load_markets(default_conf, mocker, caplog):
caplog.set_level(logging.INFO)
api_mock = MagicMock()
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value='Binance'))
api_mock.load_markets = MagicMock(return_value={})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
expected_return = {'ETH/BTC': 'available'}
api_mock.load_markets = MagicMock(return_value=expected_return)
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
default_conf['exchange']['pair_whitelist'] = ['ETH/BTC']
ex = Exchange(default_conf)
assert ex.markets == expected_return
api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError())
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
Exchange(default_conf)
assert log_has('Unable to initialize markets. Reason: ', caplog.record_tuples)
def test_validate_pairs(default_conf, mocker):
api_mock = MagicMock()
api_mock.load_markets = MagicMock(return_value={
@@ -146,14 +216,16 @@ def test_validate_pairs(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
Exchange(default_conf)
def test_validate_pairs_not_available(default_conf, mocker):
api_mock = MagicMock()
api_mock.load_markets = MagicMock(return_value={})
api_mock.load_markets = MagicMock(return_value={'XRP/BTC': 'inactive'})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
with pytest.raises(OperationalException, match=r'not available'):
Exchange(default_conf)
@@ -167,6 +239,7 @@ def test_validate_pairs_not_compatible(default_conf, mocker):
default_conf['stake_currency'] = 'ETH'
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
with pytest.raises(OperationalException, match=r'not compatible'):
Exchange(default_conf)
@@ -179,15 +252,14 @@ def test_validate_pairs_exception(default_conf, mocker, caplog):
api_mock.load_markets = MagicMock(return_value={})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available at Binance'):
Exchange(default_conf)
api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError())
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
Exchange(default_conf)
assert log_has('Unable to validate pairs (assuming they are correct). Reason: ',
assert log_has('Unable to validate pairs (assuming they are correct).',
caplog.record_tuples)
@@ -198,6 +270,7 @@ def test_validate_pairs_stake_exception(default_conf, mocker, caplog):
api_mock.name = MagicMock(return_value='binance')
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', api_mock)
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
with pytest.raises(
OperationalException,
@@ -218,7 +291,7 @@ def test_validate_timeframes(default_conf, mocker):
type(api_mock).timeframes = timeframes
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
Exchange(default_conf)
@@ -234,7 +307,7 @@ def test_validate_timeframes_failed(default_conf, mocker):
type(api_mock).timeframes = timeframes
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
with pytest.raises(OperationalException, match=r'Invalid ticker 3m, this Exchange supports.*'):
Exchange(default_conf)
@@ -251,7 +324,7 @@ def test_validate_timeframes_not_in_config(default_conf, mocker):
type(api_mock).timeframes = timeframes
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
Exchange(default_conf)
@@ -515,6 +588,189 @@ def test_get_ticker(default_conf, mocker):
exchange.get_ticker(pair='ETH/BTC', refresh=True)
def test_get_history(default_conf, mocker, caplog):
exchange = get_patched_exchange(mocker, default_conf)
tick = [
[
arrow.utcnow().timestamp * 1000, # unix timestamp ms
1, # open
2, # high
3, # low
4, # close
5, # volume (in quote currency)
]
]
pair = 'ETH/BTC'
async def mock_candle_hist(pair, tick_interval, since_ms):
return pair, tick
exchange._async_get_candle_history = Mock(wraps=mock_candle_hist)
# one_call calculation * 1.8 should do 2 calls
since = 5 * 60 * 500 * 1.8
print(f"since = {since}")
ret = exchange.get_history(pair, "5m", int((arrow.utcnow().timestamp - since) * 1000))
assert exchange._async_get_candle_history.call_count == 2
# Returns twice the above tick
assert len(ret) == 2
def test_refresh_tickers(mocker, default_conf, caplog) -> None:
tick = [
[
1511686200000, # unix timestamp ms
1, # open
2, # high
3, # low
4, # close
5, # volume (in quote currency)
]
]
caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf)
exchange._api_async.fetch_ohlcv = get_mock_coro(tick)
pairs = ['IOTA/ETH', 'XRP/ETH']
# empty dicts
assert not exchange.klines
exchange.refresh_tickers(['IOTA/ETH', 'XRP/ETH'], '5m')
assert log_has(f'Refreshing klines for {len(pairs)} pairs', caplog.record_tuples)
assert exchange.klines
for pair in pairs:
assert exchange.klines[pair]
@pytest.mark.asyncio
async def test__async_get_candle_history(default_conf, mocker, caplog):
tick = [
[
arrow.utcnow().timestamp * 1000, # unix timestamp ms
1, # open
2, # high
3, # low
4, # close
5, # volume (in quote currency)
]
]
caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf)
# Monkey-patch async function
exchange._api_async.fetch_ohlcv = get_mock_coro(tick)
exchange = Exchange(default_conf)
pair = 'ETH/BTC'
res = await exchange._async_get_candle_history(pair, "5m")
assert type(res) is tuple
assert len(res) == 2
assert res[0] == pair
assert res[1] == tick
assert exchange._api_async.fetch_ohlcv.call_count == 1
assert not log_has(f"Using cached klines data for {pair} ...", caplog.record_tuples)
# test caching
res = await exchange._async_get_candle_history(pair, "5m")
assert exchange._api_async.fetch_ohlcv.call_count == 1
assert log_has(f"Using cached klines data for {pair} ...", caplog.record_tuples)
# exchange = Exchange(default_conf)
await async_ccxt_exception(mocker, default_conf, MagicMock(),
"_async_get_candle_history", "fetch_ohlcv",
pair='ABCD/BTC', tick_interval=default_conf['ticker_interval'])
api_mock = MagicMock()
with pytest.raises(OperationalException, match=r'Could not fetch ticker data*'):
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
await exchange._async_get_candle_history(pair, "5m",
(arrow.utcnow().timestamp - 2000) * 1000)
@pytest.mark.asyncio
async def test__async_get_candle_history_empty(default_conf, mocker, caplog):
""" Test empty exchange result """
tick = []
caplog.set_level(logging.DEBUG)
exchange = get_patched_exchange(mocker, default_conf)
# Monkey-patch async function
exchange._api_async.fetch_ohlcv = get_mock_coro([])
exchange = Exchange(default_conf)
pair = 'ETH/BTC'
res = await exchange._async_get_candle_history(pair, "5m")
assert type(res) is tuple
assert len(res) == 2
assert res[0] == pair
assert res[1] == tick
assert exchange._api_async.fetch_ohlcv.call_count == 1
@pytest.mark.asyncio
async def test_async_get_candles_history(default_conf, mocker):
tick = [
[
1511686200000, # unix timestamp ms
1, # open
2, # high
3, # low
4, # close
5, # volume (in quote currency)
]
]
async def mock_get_candle_hist(pair, tick_interval, since_ms=None):
return (pair, tick)
exchange = get_patched_exchange(mocker, default_conf)
# Monkey-patch async function
exchange._api_async.fetch_ohlcv = get_mock_coro(tick)
exchange._async_get_candle_history = Mock(wraps=mock_get_candle_hist)
pairs = ['ETH/BTC', 'XRP/BTC']
res = await exchange.async_get_candles_history(pairs, "5m")
assert type(res) is list
assert len(res) == 2
assert type(res[0]) is tuple
assert res[0][0] == pairs[0]
assert res[0][1] == tick
assert res[1][0] == pairs[1]
assert res[1][1] == tick
assert exchange._async_get_candle_history.call_count == 2
def test_get_order_book(default_conf, mocker, order_book_l2):
default_conf['exchange']['name'] = 'binance'
api_mock = MagicMock()
api_mock.fetch_l2_order_book = order_book_l2
exchange = get_patched_exchange(mocker, default_conf, api_mock)
order_book = exchange.get_order_book(pair='ETH/BTC', limit=10)
assert 'bids' in order_book
assert 'asks' in order_book
assert len(order_book['bids']) == 10
assert len(order_book['asks']) == 10
def test_get_order_book_exception(default_conf, mocker):
api_mock = MagicMock()
with pytest.raises(OperationalException):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NotSupported)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_order_book(pair='ETH/BTC', limit=50)
with pytest.raises(TemporaryError):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NetworkError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_order_book(pair='ETH/BTC', limit=50)
with pytest.raises(OperationalException):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.BaseError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_order_book(pair='ETH/BTC', limit=50)
def make_fetch_ohlcv_mock(data):
def fetch_ohlcv_mock(pair, timeframe, since):
if since:
@@ -705,8 +961,7 @@ def test_get_order(default_conf, mocker):
def test_name(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange.validate_pairs',
side_effect=lambda s: True)
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
default_conf['exchange']['name'] = 'binance'
exchange = Exchange(default_conf)
@@ -714,16 +969,14 @@ def test_name(default_conf, mocker):
def test_id(default_conf, mocker):
mocker.patch('freqtrade.exchange.Exchange.validate_pairs',
side_effect=lambda s: True)
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
default_conf['exchange']['name'] = 'binance'
exchange = Exchange(default_conf)
assert exchange.id == 'binance'
def test_get_pair_detail_url(default_conf, mocker, caplog):
mocker.patch('freqtrade.exchange.Exchange.validate_pairs',
side_effect=lambda s: True)
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
default_conf['exchange']['name'] = 'binance'
exchange = Exchange(default_conf)
@@ -823,15 +1076,3 @@ def test_get_fee(default_conf, mocker):
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
'get_fee', 'calculate_fee')
def test_get_amount_lots(default_conf, mocker):
api_mock = MagicMock()
api_mock.amount_to_lots = MagicMock(return_value=1.0)
api_mock.markets = None
marketmock = MagicMock()
api_mock.load_markets = marketmock
exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert exchange.get_amount_lots('LTC/BTC', 1.54) == 1
assert marketmock.call_count == 1

View File

@@ -110,7 +110,7 @@ def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=Fals
return pairdata
# use for mock freqtrade.exchange.get_candle_history'
# use for mock ccxt.fetch_ohlvc'
def _load_pair_as_ticks(pair, tickfreq):
ticks = optimize.load_data(None, ticker_interval=tickfreq, pairs=[pair])
ticks = trim_dictlist(ticks, -201)
@@ -455,7 +455,7 @@ 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.optimize.load_data', mocked_load_data)
mocker.patch('freqtrade.exchange.Exchange.get_candle_history')
mocker.patch('freqtrade.exchange.Exchange.refresh_tickers', MagicMock())
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.optimize.backtesting.Backtesting',
@@ -490,7 +490,7 @@ 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.optimize.load_data', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.get_candle_history')
mocker.patch('freqtrade.exchange.Exchange.refresh_tickers', MagicMock())
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.optimize.backtesting.Backtesting',
@@ -733,9 +733,14 @@ def test_backtest_record(default_conf, fee, mocker):
def test_backtest_start_live(default_conf, mocker, caplog):
default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
mocker.patch('freqtrade.exchange.Exchange.get_candle_history',
new=lambda s, n, i: _load_pair_as_ticks(n, i))
patch_exchange(mocker)
async def load_pairs(pair, timeframe, since):
return _load_pair_as_ticks(pair, timeframe)
api_mock = MagicMock()
api_mock.fetch_ohlcv = load_pairs
patch_exchange(mocker, api_mock)
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock())
mocker.patch('freqtrade.optimize.backtesting.Backtesting._generate_text_table', MagicMock())
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
@@ -776,9 +781,13 @@ def test_backtest_start_live(default_conf, mocker, caplog):
def test_backtest_start_multi_strat(default_conf, mocker, caplog):
default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
mocker.patch('freqtrade.exchange.Exchange.get_candle_history',
new=lambda s, n, i: _load_pair_as_ticks(n, i))
patch_exchange(mocker)
async def load_pairs(pair, timeframe, since):
return _load_pair_as_ticks(pair, timeframe)
api_mock = MagicMock()
api_mock.fetch_ohlcv = load_pairs
patch_exchange(mocker, api_mock)
backtestmock = MagicMock()
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
gen_table_mock = MagicMock()

View File

@@ -65,6 +65,31 @@ def test_start(mocker, default_conf, caplog) -> None:
assert start_mock.call_count == 1
def test_start_failure(mocker, default_conf, caplog) -> None:
start_mock = MagicMock()
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',
'--strategy', 'TestStrategy',
'hyperopt',
'--epochs', '5'
]
args = get_args(args)
StrategyResolver({'strategy': 'DefaultStrategy'})
with pytest.raises(ValueError):
start(args)
assert log_has(
"Please don't use --strategy for hyperopt.",
caplog.record_tuples
)
def test_loss_calculation_prefer_correct_trade_count(hyperopt) -> None:
StrategyResolver({'strategy': 'DefaultStrategy'})

View File

@@ -53,7 +53,7 @@ def _clean_test_file(file: str) -> None:
def test_load_data_30min_ticker(ticker_history, mocker, caplog, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=ticker_history)
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=ticker_history)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-30m.json')
_backup_file(file, copy_file=True)
optimize.load_data(None, pairs=['UNITTEST/BTC'], ticker_interval='30m')
@@ -63,7 +63,7 @@ def test_load_data_30min_ticker(ticker_history, mocker, caplog, default_conf) ->
def test_load_data_5min_ticker(ticker_history, mocker, caplog, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=ticker_history)
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=ticker_history)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-5m.json')
_backup_file(file, copy_file=True)
@@ -74,7 +74,7 @@ def test_load_data_5min_ticker(ticker_history, mocker, caplog, default_conf) ->
def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=ticker_history)
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=ticker_history)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-1m.json')
_backup_file(file, copy_file=True)
optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
@@ -87,7 +87,7 @@ def test_load_data_with_new_pair_1min(ticker_history, mocker, caplog, default_co
"""
Test load_data() with 1 min ticker
"""
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=ticker_history)
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=ticker_history)
exchange = get_patched_exchange(mocker, default_conf)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
@@ -118,7 +118,7 @@ def test_testdata_path() -> None:
def test_download_pairs(ticker_history, mocker, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=ticker_history)
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=ticker_history)
exchange = get_patched_exchange(mocker, default_conf)
file1_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
file1_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-5m.json')
@@ -261,7 +261,7 @@ def test_load_cached_data_for_updating(mocker) -> None:
def test_download_pairs_exception(ticker_history, mocker, caplog, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=ticker_history)
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=ticker_history)
mocker.patch('freqtrade.optimize.__init__.download_backtesting_testdata',
side_effect=BaseException('File Error'))
exchange = get_patched_exchange(mocker, default_conf)
@@ -279,7 +279,7 @@ def test_download_pairs_exception(ticker_history, mocker, caplog, default_conf)
def test_download_backtesting_testdata(ticker_history, mocker, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=ticker_history)
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=ticker_history)
exchange = get_patched_exchange(mocker, default_conf)
# Download a 1 min ticker file
@@ -304,7 +304,7 @@ def test_download_backtesting_testdata2(mocker, default_conf) -> None:
[1509836580000, 0.00161, 0.00161, 0.00161, 0.00161, 82.390199]
]
json_dump_mock = mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=tick)
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=tick)
exchange = get_patched_exchange(mocker, default_conf)
download_backtesting_testdata(None, exchange, pair="UNITTEST/BTC", tick_interval='1m')
download_backtesting_testdata(None, exchange, pair="UNITTEST/BTC", tick_interval='3m')

View File

@@ -6,13 +6,14 @@ from unittest.mock import MagicMock, ANY
import pytest
from freqtrade import TemporaryError
from freqtrade.fiat_convert import CryptoToFiatConverter
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import Trade
from freqtrade.rpc import RPC, RPCException
from freqtrade.state import State
from freqtrade.tests.test_freqtradebot import patch_get_signal
from freqtrade.tests.conftest import patch_coinmarketcap
from freqtrade.tests.conftest import patch_coinmarketcap, patch_exchange
# Functions for recurrent object patching
@@ -29,7 +30,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
_load_markets=MagicMock(return_value={}),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -67,10 +68,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
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',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -98,10 +99,10 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
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',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -151,11 +152,11 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
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())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -181,7 +182,6 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
# Update the ticker with a market going up
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker_sell_up
)
trade.update(limit_sell_order)
@@ -196,7 +196,6 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
# Update the ticker with a market going up
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker_sell_up
)
trade.update(limit_sell_order)
@@ -222,6 +221,7 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
# trade.open_rate (it is set to None)
def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets,
ticker_sell_up, limit_buy_order, limit_sell_order):
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.fiat_convert.Market',
ticker=MagicMock(return_value={'price_usd': 15000.0}),
@@ -230,7 +230,6 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets,
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -251,7 +250,6 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee, markets,
# Update the ticker with a market going up
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker_sell_up,
get_fee=fee
)
@@ -285,23 +283,25 @@ def test_rpc_balance_handle(default_conf, mocker):
'used': 2.0,
},
'ETH': {
'free': 0.0,
'total': 0.0,
'used': 0.0,
'free': 1.0,
'total': 5.0,
'used': 4.0,
}
}
# ETH will be skipped due to mocked Error below
mocker.patch.multiple(
'freqtrade.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())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_balances=MagicMock(return_value=mock_balance)
get_balances=MagicMock(return_value=mock_balance),
get_ticker=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx'))
)
freqtradebot = FreqtradeBot(default_conf)
@@ -320,14 +320,15 @@ def test_rpc_balance_handle(default_conf, mocker):
'pending': 2.0,
'est_btc': 12.0,
}]
assert result['total'] == 12.0
def test_rpc_start(mocker, default_conf) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=MagicMock()
)
@@ -347,10 +348,10 @@ 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(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=MagicMock()
)
@@ -371,12 +372,12 @@ def test_rpc_stop(mocker, default_conf) -> None:
def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
cancel_order_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
cancel_order=cancel_order_mock,
get_order=MagicMock(
@@ -472,10 +473,10 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
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(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
get_fee=fee,
@@ -508,10 +509,10 @@ 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(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
get_fee=fee,

View File

@@ -177,10 +177,9 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
default_conf['telegram']['chat_id'] = 123
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_pair_detail_url=MagicMock(),
get_fee=fee,
@@ -228,9 +227,9 @@ def test_status(default_conf, update, mocker, fee, ticker, markets) -> None:
def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -273,9 +272,9 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No
def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value={'id': 'mocked_order_id'}),
get_fee=fee,
@@ -324,13 +323,13 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker)
def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
limit_sell_order, markets, mocker) -> None:
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
patch_exchange(mocker)
mocker.patch(
'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price',
return_value=15000.0
)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -395,9 +394,9 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker
)
msg_mock = MagicMock()
@@ -431,10 +430,10 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
def test_profit_handle(default_conf, update, ticker, ticker_sell_up, 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.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -678,7 +677,7 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
_load_markets=MagicMock(return_value={}),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -727,7 +726,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
_load_markets=MagicMock(return_value={}),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -743,7 +742,6 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
# Decrease the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker_sell_down
)
@@ -775,13 +773,13 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
patch_exchange(mocker)
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
rpc_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.get_pair_detail_url', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -827,7 +825,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
_init=MagicMock(),
_send_msg=msg_mock
)
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
patch_exchange(mocker)
freqtradebot = FreqtradeBot(default_conf)
patch_get_signal(freqtradebot, (True, False))
@@ -860,6 +858,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
def test_performance_handle(default_conf, update, ticker, fee,
limit_buy_order, limit_sell_order, markets, mocker) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
@@ -868,7 +867,6 @@ def test_performance_handle(default_conf, update, ticker, fee,
)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -899,13 +897,13 @@ def test_performance_handle(default_conf, update, ticker, fee,
def test_performance_handle_invalid(default_conf, update, mocker) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
_send_msg=msg_mock
)
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
freqtradebot = FreqtradeBot(default_conf)
patch_get_signal(freqtradebot, (True, False))
telegram = Telegram(freqtradebot)
@@ -919,6 +917,7 @@ def test_performance_handle_invalid(default_conf, update, mocker) -> None:
def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
@@ -927,7 +926,6 @@ def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> Non
)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value={'id': 'mocked_order_id'}),
get_markets=markets

View File

@@ -8,6 +8,7 @@ from pandas import DataFrame
from freqtrade.arguments import TimeRange
from freqtrade.optimize.__init__ import load_tickerdata_file
from freqtrade.persistence import Trade
from freqtrade.tests.conftest import get_patched_exchange, log_has
from freqtrade.strategy.default_strategy import DefaultStrategy
@@ -88,7 +89,6 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog):
def test_get_signal_handles_exceptions(mocker, default_conf):
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=MagicMock())
exchange = get_patched_exchange(mocker, default_conf)
mocker.patch.object(
_STRATEGY, 'analyze_ticker',
@@ -105,3 +105,98 @@ def test_tickerdata_to_dataframe(default_conf) -> None:
tickerlist = {'UNITTEST/BTC': tick}
data = strategy.tickerdata_to_dataframe(tickerlist)
assert len(data['UNITTEST/BTC']) == 99 # partial candle was removed
def test_min_roi_reached(default_conf, fee) -> None:
strategy = DefaultStrategy(default_conf)
strategy.minimal_roi = {0: 0.1, 20: 0.05, 55: 0.01}
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
open_date=arrow.utcnow().shift(hours=-1).datetime,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='bittrex',
open_rate=1,
)
assert not strategy.min_roi_reached(trade, 0.01, arrow.utcnow().shift(minutes=-55).datetime)
assert strategy.min_roi_reached(trade, 0.12, arrow.utcnow().shift(minutes=-55).datetime)
assert not strategy.min_roi_reached(trade, 0.04, arrow.utcnow().shift(minutes=-39).datetime)
assert strategy.min_roi_reached(trade, 0.06, arrow.utcnow().shift(minutes=-39).datetime)
assert not strategy.min_roi_reached(trade, -0.01, arrow.utcnow().shift(minutes=-1).datetime)
assert strategy.min_roi_reached(trade, 0.02, arrow.utcnow().shift(minutes=-1).datetime)
def test_analyze_ticker_default(ticker_history, mocker, caplog) -> None:
caplog.set_level(logging.DEBUG)
ind_mock = MagicMock(side_effect=lambda x, meta: x)
buy_mock = MagicMock(side_effect=lambda x, meta: x)
sell_mock = MagicMock(side_effect=lambda x, meta: x)
mocker.patch.multiple(
'freqtrade.strategy.interface.IStrategy',
advise_indicators=ind_mock,
advise_buy=buy_mock,
advise_sell=sell_mock,
)
strategy = DefaultStrategy({})
strategy.analyze_ticker(ticker_history, {'pair': 'ETH/BTC'})
assert ind_mock.call_count == 1
assert buy_mock.call_count == 1
assert buy_mock.call_count == 1
assert log_has('TA Analysis Launched', caplog.record_tuples)
assert not log_has('Skippinig TA Analysis for already analyzed candle',
caplog.record_tuples)
caplog.clear()
strategy.analyze_ticker(ticker_history, {'pair': 'ETH/BTC'})
# No analysis happens as process_only_new_candles is true
assert ind_mock.call_count == 2
assert buy_mock.call_count == 2
assert buy_mock.call_count == 2
assert log_has('TA Analysis Launched', caplog.record_tuples)
assert not log_has('Skippinig TA Analysis for already analyzed candle',
caplog.record_tuples)
def test_analyze_ticker_skip_analyze(ticker_history, mocker, caplog) -> None:
caplog.set_level(logging.DEBUG)
ind_mock = MagicMock(side_effect=lambda x, meta: x)
buy_mock = MagicMock(side_effect=lambda x, meta: x)
sell_mock = MagicMock(side_effect=lambda x, meta: x)
mocker.patch.multiple(
'freqtrade.strategy.interface.IStrategy',
advise_indicators=ind_mock,
advise_buy=buy_mock,
advise_sell=sell_mock,
)
strategy = DefaultStrategy({})
strategy.process_only_new_candles = True
ret = strategy.analyze_ticker(ticker_history, {'pair': 'ETH/BTC'})
assert ind_mock.call_count == 1
assert buy_mock.call_count == 1
assert buy_mock.call_count == 1
assert log_has('TA Analysis Launched', caplog.record_tuples)
assert not log_has('Skippinig TA Analysis for already analyzed candle',
caplog.record_tuples)
caplog.clear()
ret = strategy.analyze_ticker(ticker_history, {'pair': 'ETH/BTC'})
# No analysis happens as process_only_new_candles is true
assert ind_mock.call_count == 1
assert buy_mock.call_count == 1
assert buy_mock.call_count == 1
# only skipped analyze adds buy and sell columns, otherwise it's all mocked
assert 'buy' in ret
assert 'sell' in ret
assert ret['buy'].sum() == 0
assert ret['sell'].sum() == 0
assert not log_has('TA Analysis Launched', caplog.record_tuples)
assert log_has('Skippinig TA Analysis for already analyzed candle',
caplog.record_tuples)

View File

@@ -130,7 +130,7 @@ def test_strategy_override_minimal_roi(caplog):
assert resolver.strategy.minimal_roi[0] == 0.5
assert ('freqtrade.strategy.resolver',
logging.INFO,
'Override strategy \'minimal_roi\' with value in config file.'
"Override strategy 'minimal_roi' with value in config file: {'0': 0.5}."
) in caplog.record_tuples
@@ -145,7 +145,7 @@ def test_strategy_override_stoploss(caplog):
assert resolver.strategy.stoploss == -0.5
assert ('freqtrade.strategy.resolver',
logging.INFO,
'Override strategy \'stoploss\' with value in config file: -0.5.'
"Override strategy 'stoploss' with value in config file: -0.5."
) in caplog.record_tuples
@@ -161,7 +161,24 @@ def test_strategy_override_ticker_interval(caplog):
assert resolver.strategy.ticker_interval == 60
assert ('freqtrade.strategy.resolver',
logging.INFO,
'Override strategy \'ticker_interval\' with value in config file: 60.'
"Override strategy 'ticker_interval' with value in config file: 60."
) in caplog.record_tuples
def test_strategy_override_process_only_new_candles(caplog):
caplog.set_level(logging.INFO)
config = {
'strategy': 'DefaultStrategy',
'process_only_new_candles': True
}
resolver = StrategyResolver(config)
assert resolver.strategy.process_only_new_candles
assert ('freqtrade.strategy.resolver',
logging.INFO,
"Override process_only_new_candles 'process_only_new_candles' "
"with value in config file: True."
) in caplog.record_tuples

View File

@@ -2,38 +2,39 @@
from unittest.mock import MagicMock
import freqtrade.tests.conftest as tt # test tools
from freqtrade.tests.conftest import get_patched_freqtradebot
import pytest
# whitelist, blacklist, filtering, all of that will
# eventually become some rules to run on a generic ACL engine
# perhaps try to anticipate that by using some python package
def whitelist_conf():
config = tt.default_conf()
config['stake_currency'] = 'BTC'
config['exchange']['pair_whitelist'] = [
@pytest.fixture(scope="function")
def whitelist_conf(default_conf):
default_conf['stake_currency'] = 'BTC'
default_conf['exchange']['pair_whitelist'] = [
'ETH/BTC',
'TKN/BTC',
'TRST/BTC',
'SWT/BTC',
'BCC/BTC'
]
config['exchange']['pair_blacklist'] = [
default_conf['exchange']['pair_blacklist'] = [
'BLK/BTC'
]
return config
return default_conf
def test_refresh_market_pair_not_in_whitelist(mocker, markets):
conf = whitelist_conf()
def test_refresh_market_pair_not_in_whitelist(mocker, markets, whitelist_conf):
freqtradebot = tt.get_patched_freqtradebot(mocker, conf)
freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf)
mocker.patch('freqtrade.exchange.Exchange.get_markets', markets)
refreshedwhitelist = freqtradebot._refresh_whitelist(
conf['exchange']['pair_whitelist'] + ['XXX/BTC']
whitelist_conf['exchange']['pair_whitelist'] + ['XXX/BTC']
)
# List ordered by BaseVolume
whitelist = ['ETH/BTC', 'TKN/BTC']
@@ -41,12 +42,12 @@ def test_refresh_market_pair_not_in_whitelist(mocker, markets):
assert whitelist == refreshedwhitelist
def test_refresh_whitelist(mocker, markets):
conf = whitelist_conf()
freqtradebot = tt.get_patched_freqtradebot(mocker, conf)
def test_refresh_whitelist(mocker, markets, whitelist_conf):
freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf)
mocker.patch('freqtrade.exchange.Exchange.get_markets', markets)
refreshedwhitelist = freqtradebot._refresh_whitelist(conf['exchange']['pair_whitelist'])
refreshedwhitelist = freqtradebot._refresh_whitelist(
whitelist_conf['exchange']['pair_whitelist'])
# List ordered by BaseVolume
whitelist = ['ETH/BTC', 'TKN/BTC']
@@ -54,9 +55,8 @@ def test_refresh_whitelist(mocker, markets):
assert whitelist == refreshedwhitelist
def test_refresh_whitelist_dynamic(mocker, markets, tickers):
conf = whitelist_conf()
freqtradebot = tt.get_patched_freqtradebot(mocker, conf)
def test_refresh_whitelist_dynamic(mocker, markets, tickers, whitelist_conf):
freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_markets=markets,
@@ -68,21 +68,20 @@ def test_refresh_whitelist_dynamic(mocker, markets, tickers):
whitelist = ['ETH/BTC', 'TKN/BTC']
refreshedwhitelist = freqtradebot._refresh_whitelist(
freqtradebot._gen_pair_whitelist(conf['stake_currency'])
freqtradebot._gen_pair_whitelist(whitelist_conf['stake_currency'])
)
assert whitelist == refreshedwhitelist
def test_refresh_whitelist_dynamic_empty(mocker, markets_empty):
conf = whitelist_conf()
freqtradebot = tt.get_patched_freqtradebot(mocker, conf)
def test_refresh_whitelist_dynamic_empty(mocker, markets_empty, whitelist_conf):
freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf)
mocker.patch('freqtrade.exchange.Exchange.get_markets', markets_empty)
# argument: use the whitelist dynamically by exchange-volume
whitelist = []
conf['exchange']['pair_whitelist'] = []
whitelist_conf['exchange']['pair_whitelist'] = []
freqtradebot._refresh_whitelist(whitelist)
pairslist = conf['exchange']['pair_whitelist']
pairslist = whitelist_conf['exchange']['pair_whitelist']
assert set(whitelist) == set(pairslist)

View File

@@ -43,7 +43,7 @@ def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None:
:return: None
"""
freqtrade.strategy.get_signal = lambda e, s, t: value
freqtrade.exchange.get_candle_history = lambda p, i: None
freqtrade.exchange.refresh_tickers = lambda p, i: None
def patch_RPCManager(mocker) -> MagicMock:
@@ -140,7 +140,6 @@ def test_gen_pair_whitelist(mocker, default_conf, tickers) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers)
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
# mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
# Test to retrieved BTC sorted on quoteVolume (default)
whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC')
@@ -159,6 +158,15 @@ def test_gen_pair_whitelist(mocker, default_conf, tickers) -> None:
assert whitelist == []
def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers)
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=False))
with pytest.raises(OperationalException):
freqtrade._gen_pair_whitelist(base_currency='BTC')
@pytest.mark.skip(reason="Test not implemented")
def test_refresh_whitelist() -> None:
pass
@@ -166,9 +174,9 @@ def test_refresh_whitelist() -> None:
def test_get_trade_stake_amount(default_conf, ticker, limit_buy_order, fee, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 2)
)
@@ -184,9 +192,9 @@ def test_get_trade_stake_amount_no_stake_amount(default_conf,
fee,
mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5)
)
freqtrade = FreqtradeBot(default_conf)
@@ -202,6 +210,7 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf,
markets,
mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
@@ -244,7 +253,7 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf,
def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
patch_RPCManager(mocker)
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
patch_exchange(mocker)
freqtrade = FreqtradeBot(default_conf)
freqtrade.strategy.stoploss = -0.05
# no pair found
@@ -379,9 +388,9 @@ def test_get_min_pair_stake_amount(mocker, default_conf) -> None:
def test_create_trade(default_conf, ticker, limit_buy_order, fee, markets, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
@@ -413,9 +422,9 @@ def test_create_trade(default_conf, ticker, limit_buy_order, fee, markets, mocke
def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order,
fee, markets, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5),
@@ -432,10 +441,10 @@ def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order,
def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order,
fee, markets, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
buy_mock = MagicMock(return_value={'id': limit_buy_order['id']})
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=buy_mock,
get_fee=fee,
@@ -453,10 +462,10 @@ def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order,
def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_order,
fee, markets, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
buy_mock = MagicMock(return_value={'id': limit_buy_order['id']})
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=buy_mock,
get_fee=fee,
@@ -474,9 +483,9 @@ def test_create_trade_too_small_stake_amount(default_conf, ticker, limit_buy_ord
def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order,
fee, markets, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_balance=MagicMock(return_value=default_conf['stake_amount']),
@@ -495,9 +504,9 @@ def test_create_trade_limit_reached(default_conf, ticker, limit_buy_order,
def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, markets, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
@@ -518,9 +527,9 @@ def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, fee, marke
def test_create_trade_no_pairs_after_blacklist(default_conf, ticker,
limit_buy_order, fee, markets, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
@@ -541,10 +550,9 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None:
default_conf['dry_run'] = True
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_candle_history=MagicMock(return_value=20),
get_balance=MagicMock(return_value=20),
get_fee=fee,
)
@@ -560,9 +568,9 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None:
def test_process_trade_creation(default_conf, ticker, limit_buy_order,
markets, fee, mocker, caplog) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_markets=markets,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
@@ -597,9 +605,9 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order,
def test_process_exchange_failures(default_conf, ticker, markets, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_markets=markets,
buy=MagicMock(side_effect=TemporaryError)
@@ -616,9 +624,9 @@ def test_process_exchange_failures(default_conf, ticker, markets, mocker) -> Non
def test_process_operational_exception(default_conf, ticker, markets, mocker) -> None:
msg_mock = patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_markets=markets,
buy=MagicMock(side_effect=OperationalException)
@@ -637,9 +645,9 @@ def test_process_operational_exception(default_conf, ticker, markets, mocker) ->
def test_process_trade_handling(
default_conf, ticker, limit_buy_order, markets, fee, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_markets=markets,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
@@ -664,21 +672,21 @@ def test_balance_fully_ask_side(mocker, default_conf) -> None:
default_conf['bid_strategy']['ask_last_balance'] = 0.0
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.get_target_bid({'ask': 20, 'last': 10}) == 20
assert freqtrade.get_target_bid('ETH/BTC', {'ask': 20, 'last': 10}) == 20
def test_balance_fully_last_side(mocker, default_conf) -> None:
default_conf['bid_strategy']['ask_last_balance'] = 1.0
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.get_target_bid({'ask': 20, 'last': 10}) == 10
assert freqtrade.get_target_bid('ETH/BTC', {'ask': 20, 'last': 10}) == 10
def test_balance_bigger_last_ask(mocker, default_conf) -> None:
default_conf['bid_strategy']['ask_last_balance'] = 1.0
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.get_target_bid({'ask': 5, 'last': 10}) == 5
assert freqtrade.get_target_bid('ETH/BTC', {'ask': 5, 'last': 10}) == 5
def test_process_maybe_execute_buy(mocker, default_conf) -> None:
@@ -763,9 +771,9 @@ def test_process_maybe_execute_sell_exception(mocker, default_conf,
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order,
fee, markets, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=MagicMock(return_value={
'bid': 0.00001172,
'ask': 0.00001173,
@@ -806,9 +814,9 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order,
default_conf.update({'experimental': {'use_sell_signal': True}})
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
@@ -862,9 +870,9 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order,
default_conf.update({'experimental': {'use_sell_signal': True}})
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
@@ -895,9 +903,9 @@ def test_handle_trade_experimental(
caplog.set_level(logging.DEBUG)
default_conf.update({'experimental': {'use_sell_signal': True}})
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
@@ -923,9 +931,9 @@ def test_handle_trade_experimental(
def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order,
fee, markets, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
@@ -951,9 +959,9 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order,
def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, fee, mocker) -> None:
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_order=MagicMock(return_value=limit_buy_order_old),
cancel_order=cancel_order_mock,
@@ -985,13 +993,52 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, fe
assert nb_trades == 0
def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old, mocker) -> None:
def test_check_handle_timedout_buy_exception(default_conf, ticker, limit_buy_order_old,
fee, mocker) -> None:
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_order=MagicMock(side_effect=DependencyException),
cancel_order=cancel_order_mock,
get_fee=fee
)
freqtrade = FreqtradeBot(default_conf)
trade_buy = Trade(
pair='ETH/BTC',
open_rate=0.00001099,
exchange='bittrex',
open_order_id='123456789',
amount=90.99181073,
fee_open=0.0,
fee_close=0.0,
stake_amount=1,
open_date=arrow.utcnow().shift(minutes=-601).datetime,
is_open=True
)
Trade.session.add(trade_buy)
# check it does cancel buy orders over the time limit
freqtrade.check_handle_timedout()
assert cancel_order_mock.call_count == 0
assert rpc_mock.call_count == 0
trades = Trade.query.filter(Trade.open_order_id.is_(trade_buy.open_order_id)).all()
nb_trades = len(trades)
assert nb_trades == 1
def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old, mocker) -> None:
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
get_order=MagicMock(return_value=limit_sell_order_old),
cancel_order=cancel_order_mock
)
@@ -1024,9 +1071,9 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
mocker) -> None:
rpc_mock = patch_RPCManager(mocker)
cancel_order_mock = MagicMock()
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_order=MagicMock(return_value=limit_buy_order_old_partial),
cancel_order=cancel_order_mock
@@ -1061,6 +1108,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
cancel_order_mock = MagicMock()
mocker.patch.multiple(
@@ -1070,7 +1118,6 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -
)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_order=MagicMock(side_effect=requests.exceptions.RequestException('Oh snap')),
cancel_order=cancel_order_mock
@@ -1103,10 +1150,10 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -
def test_handle_timedout_limit_buy(mocker, default_conf) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
cancel_order_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
cancel_order=cancel_order_mock
)
@@ -1125,10 +1172,10 @@ def test_handle_timedout_limit_buy(mocker, default_conf) -> None:
def test_handle_timedout_limit_sell(mocker, default_conf) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
cancel_order_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
cancel_order=cancel_order_mock
)
@@ -1149,7 +1196,7 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, moc
rpc_mock = patch_RPCManager(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
_load_markets=MagicMock(return_value={}),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -1166,7 +1213,6 @@ def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, markets, moc
# Increase the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker_sell_up
)
@@ -1195,7 +1241,7 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets,
rpc_mock = patch_RPCManager(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
_load_markets=MagicMock(return_value={}),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -1212,7 +1258,6 @@ def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, markets,
# Decrease the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker_sell_down
)
@@ -1243,7 +1288,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee,
rpc_mock = patch_RPCManager(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
_load_markets=MagicMock(return_value={}),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -1260,7 +1305,6 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee,
# Increase the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker_sell_up
)
freqtrade.config = {}
@@ -1289,7 +1333,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
rpc_mock = patch_RPCManager(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
_load_markets=MagicMock(return_value={}),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -1306,7 +1350,6 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
# Decrease the price and sell it
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker_sell_down
)
@@ -1334,9 +1377,9 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
def test_sell_profit_only_enable_profit(default_conf, limit_buy_order,
fee, markets, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=MagicMock(return_value={
'bid': 0.00002172,
'ask': 0.00002173,
@@ -1366,9 +1409,9 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order,
def test_sell_profit_only_disable_profit(default_conf, limit_buy_order,
fee, markets, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=MagicMock(return_value={
'bid': 0.00002172,
'ask': 0.00002173,
@@ -1396,9 +1439,9 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order,
def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, markets, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=MagicMock(return_value={
'bid': 0.00000172,
'ask': 0.00000173,
@@ -1427,9 +1470,9 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, fee, market
def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, markets, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=MagicMock(return_value={
'bid': 0.0000172,
'ask': 0.0000173,
@@ -1459,9 +1502,9 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, marke
def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, markets, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=MagicMock(return_value={
'bid': 0.0000172,
'ask': 0.0000173,
@@ -1493,9 +1536,9 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, markets, m
def test_trailing_stop_loss(default_conf, limit_buy_order, fee, markets, caplog, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=MagicMock(return_value={
'bid': 0.00000102,
'ask': 0.00000103,
@@ -1527,9 +1570,9 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, markets
caplog, mocker) -> None:
buy_price = limit_buy_order['price']
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=MagicMock(return_value={
'bid': buy_price - 0.000001,
'ask': buy_price - 0.000001,
@@ -1585,9 +1628,9 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee,
caplog, mocker, markets) -> None:
buy_price = limit_buy_order['price']
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=MagicMock(return_value={
'bid': buy_price - 0.000001,
'ask': buy_price - 0.000001,
@@ -1645,9 +1688,9 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee,
def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order,
fee, markets, mocker) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=MagicMock(return_value={
'bid': 0.00000172,
'ask': 0.00000173,
@@ -1681,7 +1724,7 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order,
def test_get_real_amount_quote(default_conf, trades_for_order, buy_order_fee, caplog, mocker):
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
patch_RPCManager(mocker)
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
patch_exchange(mocker)
amount = sum(x['amount'] for x in trades_for_order)
trade = Trade(
pair='LTC/ETH',
@@ -1704,7 +1747,7 @@ def test_get_real_amount_no_trade(default_conf, buy_order_fee, caplog, mocker):
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
patch_RPCManager(mocker)
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
patch_exchange(mocker)
amount = buy_order_fee['amount']
trade = Trade(
pair='LTC/ETH',
@@ -1727,7 +1770,7 @@ def test_get_real_amount_stake(default_conf, trades_for_order, buy_order_fee, mo
trades_for_order[0]['fee']['currency'] = 'ETH'
patch_RPCManager(mocker)
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
patch_exchange(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
amount = sum(x['amount'] for x in trades_for_order)
trade = Trade(
@@ -1749,7 +1792,7 @@ def test_get_real_amount_BNB(default_conf, trades_for_order, buy_order_fee, mock
trades_for_order[0]['fee']['cost'] = 0.00094518
patch_RPCManager(mocker)
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
patch_exchange(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
amount = sum(x['amount'] for x in trades_for_order)
trade = Trade(
@@ -1768,7 +1811,7 @@ def test_get_real_amount_BNB(default_conf, trades_for_order, buy_order_fee, mock
def test_get_real_amount_multi(default_conf, trades_for_order2, buy_order_fee, caplog, mocker):
patch_RPCManager(mocker)
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
patch_exchange(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order2)
amount = float(sum(x['amount'] for x in trades_for_order2))
trade = Trade(
@@ -1793,7 +1836,7 @@ def test_get_real_amount_fromorder(default_conf, trades_for_order, buy_order_fee
limit_buy_order['fee'] = {'cost': 0.004, 'currency': 'LTC'}
patch_RPCManager(mocker)
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
patch_exchange(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order',
return_value=[trades_for_order])
amount = float(sum(x['amount'] for x in trades_for_order))
@@ -1819,7 +1862,7 @@ def test_get_real_amount_invalid_order(default_conf, trades_for_order, buy_order
limit_buy_order['fee'] = {'cost': 0.004}
patch_RPCManager(mocker)
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
patch_exchange(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=[])
amount = float(sum(x['amount'] for x in trades_for_order))
trade = Trade(
@@ -1841,7 +1884,7 @@ def test_get_real_amount_invalid(default_conf, trades_for_order, buy_order_fee,
trades_for_order[0]['fee'] = {'cost': 0.008}
patch_RPCManager(mocker)
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
patch_exchange(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_trades_for_order', return_value=trades_for_order)
amount = sum(x['amount'] for x in trades_for_order)
trade = Trade(
@@ -1859,7 +1902,7 @@ def test_get_real_amount_invalid(default_conf, trades_for_order, buy_order_fee,
def test_get_real_amount_open_trade(default_conf, mocker):
patch_RPCManager(mocker)
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock(return_value=True))
patch_exchange(mocker)
amount = 12345
trade = Trade(
pair='LTC/ETH',
@@ -1878,6 +1921,191 @@ def test_get_real_amount_open_trade(default_conf, mocker):
assert freqtrade.get_real_amount(trade, order) == amount
def test_order_book_depth_of_market(default_conf, ticker, limit_buy_order, fee, markets, mocker,
order_book_l2):
default_conf['bid_strategy']['check_depth_of_market']['enabled'] = True
default_conf['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = 0.1
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
get_markets=markets
)
# Save state of current whitelist
whitelist = deepcopy(default_conf['exchange']['pair_whitelist'])
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
freqtrade.create_trade()
trade = Trade.query.first()
assert trade is not None
assert trade.stake_amount == 0.001
assert trade.is_open
assert trade.open_date is not None
assert trade.exchange == 'bittrex'
# Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order)
assert trade.open_rate == 0.00001099
assert whitelist == default_conf['exchange']['pair_whitelist']
def test_order_book_depth_of_market_high_delta(default_conf, ticker, limit_buy_order,
fee, markets, mocker, order_book_l2):
default_conf['bid_strategy']['check_depth_of_market']['enabled'] = True
# delta is 100 which is impossible to reach. hence check_depth_of_market will return false
default_conf['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = 100
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=ticker,
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
get_fee=fee,
get_markets=markets
)
# Save state of current whitelist
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
freqtrade.create_trade()
trade = Trade.query.first()
assert trade is None
def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2, markets) -> None:
"""
test if function get_target_bid will return the order book price
instead of the ask rate
"""
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_markets=markets,
get_order_book=order_book_l2
)
default_conf['exchange']['name'] = 'binance'
default_conf['bid_strategy']['use_order_book'] = True
default_conf['bid_strategy']['order_book_top'] = 2
default_conf['bid_strategy']['ask_last_balance'] = 0
default_conf['telegram']['enabled'] = False
freqtrade = FreqtradeBot(default_conf)
assert freqtrade.get_target_bid('ETH/BTC', {'ask': 0.045, 'last': 0.046}) == 0.043935
def test_order_book_bid_strategy2(mocker, default_conf, order_book_l2, markets) -> None:
"""
test if function get_target_bid will return the ask rate (since its value is lower)
instead of the order book rate (even if enabled)
"""
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_markets=markets,
get_order_book=order_book_l2
)
default_conf['exchange']['name'] = 'binance'
default_conf['bid_strategy']['use_order_book'] = True
default_conf['bid_strategy']['order_book_top'] = 2
default_conf['bid_strategy']['ask_last_balance'] = 0
default_conf['telegram']['enabled'] = False
freqtrade = FreqtradeBot(default_conf)
assert freqtrade.get_target_bid('ETH/BTC', {'ask': 0.042, 'last': 0.046}) == 0.042
def test_order_book_bid_strategy3(default_conf, mocker, order_book_l2, markets) -> None:
"""
test if function get_target_bid will return ask rate instead
of the order book rate
"""
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_markets=markets,
get_order_book=order_book_l2
)
default_conf['exchange']['name'] = 'binance'
default_conf['bid_strategy']['use_order_book'] = True
default_conf['bid_strategy']['order_book_top'] = 1
default_conf['bid_strategy']['ask_last_balance'] = 0
default_conf['telegram']['enabled'] = False
freqtrade = FreqtradeBot(default_conf)
assert freqtrade.get_target_bid('ETH/BTC', {'ask': 0.03, 'last': 0.029}) == 0.03
def test_check_depth_of_market_buy(default_conf, mocker, order_book_l2, markets) -> None:
"""
test check depth of market
"""
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_markets=markets,
get_order_book=order_book_l2
)
default_conf['telegram']['enabled'] = False
default_conf['exchange']['name'] = 'binance'
default_conf['bid_strategy']['check_depth_of_market']['enabled'] = True
# delta is 100 which is impossible to reach. hence function will return false
default_conf['bid_strategy']['check_depth_of_market']['bids_to_ask_delta'] = 100
freqtrade = FreqtradeBot(default_conf)
conf = default_conf['bid_strategy']['check_depth_of_market']
assert freqtrade._check_depth_of_market_buy('ETH/BTC', conf) is False
def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order,
fee, markets, mocker, order_book_l2) -> None:
"""
test order book ask strategy
"""
mocker.patch('freqtrade.exchange.Exchange.get_order_book', order_book_l2)
default_conf['exchange']['name'] = 'binance'
default_conf['ask_strategy']['use_order_book'] = True
default_conf['ask_strategy']['order_book_min'] = 1
default_conf['ask_strategy']['order_book_max'] = 2
default_conf['telegram']['enabled'] = False
patch_RPCManager(mocker)
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_ticker=MagicMock(return_value={
'bid': 0.00001172,
'ask': 0.00001173,
'last': 0.00001172
}),
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
get_fee=fee,
get_markets=markets
)
freqtrade = FreqtradeBot(default_conf)
patch_get_signal(freqtrade)
freqtrade.create_trade()
trade = Trade.query.first()
assert trade
time.sleep(0.01) # Race condition fix
trade.update(limit_buy_order)
assert trade.is_open is True
patch_get_signal(freqtrade, value=(False, True))
assert freqtrade.handle_trade(trade) is True
def test_startup_messages(default_conf, mocker):
default_conf['dynamic_whitelist'] = 20
freqtrade = get_patched_freqtradebot(mocker, default_conf)

View File

@@ -1,5 +1,6 @@
# pragma pylint: disable=missing-docstring, C0103
from unittest.mock import MagicMock
import logging
import pytest
from sqlalchemy import create_engine
@@ -403,6 +404,7 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
"""
Test Database migration (starting with new pairformat)
"""
caplog.set_level(logging.DEBUG)
amount = 103.223
# Always create all columns apart from the last!
create_table_old = """CREATE TABLE IF NOT EXISTS "trades" (
@@ -471,12 +473,15 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
assert trade.ticker_interval is None
assert log_has("trying trades_bak1", caplog.record_tuples)
assert log_has("trying trades_bak2", caplog.record_tuples)
assert log_has("Running database migration - backup available as trades_bak2",
caplog.record_tuples)
def test_migrate_mid_state(mocker, default_conf, fee, caplog):
"""
Test Database migration (starting with new pairformat)
"""
caplog.set_level(logging.DEBUG)
amount = 103.223
create_table_old = """CREATE TABLE IF NOT EXISTS "trades" (
id INTEGER NOT NULL,
@@ -530,6 +535,8 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog):
assert trade.stop_loss == 0.0
assert trade.initial_stop_loss == 0.0
assert log_has("trying trades_bak0", caplog.record_tuples)
assert log_has("Running database migration - backup available as trades_bak0",
caplog.record_tuples)
def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee):

View File

@@ -1,6 +1,6 @@
if [ ! -f "ta-lib/CHANGELOG.TXT" ]; then
tar zxvf ta-lib-0.4.0-src.tar.gz
cd ta-lib && sed -i "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h && ./configure && make && sudo make install && cd ..
cd ta-lib && sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h && ./configure && make && sudo make install && cd ..
else
echo "TA-lib already installed, skipping download and build."
cd ta-lib && sudo make install && cd ..

View File

@@ -1,20 +1,21 @@
ccxt==1.17.126
SQLAlchemy==1.2.10
python-telegram-bot==10.1.0
ccxt==1.17.363
SQLAlchemy==1.2.12
python-telegram-bot==11.1.0
arrow==0.12.1
cachetools==2.1.0
requests==2.19.1
urllib3==1.22
wrapt==1.10.11
pandas==0.23.4
scikit-learn==0.19.2
scikit-learn==0.20.0
scipy==1.1.0
jsonschema==2.6.0
numpy==1.15.0
numpy==1.15.2
TA-Lib==0.4.17
pytest==3.7.1
pytest==3.8.1
pytest-mock==1.10.0
pytest-cov==2.5.1
pytest-asyncio==0.9.0
pytest-cov==2.6.0
tabulate==0.8.2
coinmarketcap==5.0.3

View File

@@ -1,6 +1,6 @@
#!/usr/bin/env python3
"""This script generate json data from bittrex"""
"""This script generate json data"""
import json
import sys
from pathlib import Path
@@ -52,9 +52,10 @@ exchange = Exchange({'key': '',
'stake_currency': '',
'dry_run': True,
'exchange': {
'name': args.exchange,
'pair_whitelist': []
}
'name': args.exchange,
'pair_whitelist': [],
'ccxt_rate_limit': False
}
})
pairs_not_available = []

View File

@@ -73,7 +73,7 @@ def load_trades(args: Namespace, pair: str, timerange: TimeRange) -> pd.DataFram
file = Path(args.exportfilename)
# must align with columns in backtest.py
columns = ["pair", "profit", "opents", "closets", "index", "duration",
"open_rate", "close_rate", "open_at_end"]
"open_rate", "close_rate", "open_at_end", "sell_reason"]
with file.open() as f:
data = json.load(f)
trades = pd.DataFrame(data, columns=columns)
@@ -138,7 +138,8 @@ def plot_analyzed_dataframe(args: Namespace) -> None:
tickers = {}
if args.live:
logger.info('Downloading pair.')
tickers[pair] = exchange.get_candle_history(pair, tick_interval)
exchange.refresh_tickers([pair], tick_interval)
tickers[pair] = exchange.klines[pair]
else:
tickers = optimize.load_data(
datadir=_CONF.get("datadir"),

View File

@@ -1,13 +1,31 @@
#!/usr/bin/env bash
#encoding=utf8
# Check which python version is installed
function check_installed_python() {
which python3.7
if [ $? -eq 0 ]; then
echo "using Python 3.7"
PYTHON=python3.7
return
fi
which python3.6
if [ $? -eq 0 ]; then
echo "using Python 3.6"
PYTHON=python3.6
return
fi
}
function updateenv () {
echo "-------------------------"
echo "Update your virtual env"
echo "-------------------------"
source .env/bin/activate
echo "pip3 install in-progress. Please wait..."
pip3.6 install --quiet --upgrade pip
pip3 install --quiet --upgrade pip
pip3 install --quiet -r requirements.txt --upgrade
pip3 install --quiet -r requirements.txt
pip3 install --quiet -e .
@@ -79,7 +97,7 @@ function reset () {
fi
echo
python3.6 -m venv .env
${PYTHON} -m venv .env
updateenv
}
@@ -183,7 +201,7 @@ function install () {
install_debian
else
echo "This script does not support your OS."
echo "If you have Python3.6, pip, virtualenv, ta-lib you can continue."
echo "If you have Python3.6 or Python3.7, pip, virtualenv, ta-lib you can continue."
echo "Wait 10 seconds to continue the next install steps or use ctrl+c to interrupt this shell."
sleep 10
fi
@@ -193,7 +211,7 @@ function install () {
echo "-------------------------"
echo "Run the bot"
echo "-------------------------"
echo "You can now use the bot by executing 'source .env/bin/activate; python3.6 freqtrade/main.py'."
echo "You can now use the bot by executing 'source .env/bin/activate; python freqtrade/main.py'."
}
function plot () {
@@ -214,6 +232,9 @@ function help () {
echo " -p,--plot Install dependencies for Plotting scripts."
}
# Verify if 3.6 or 3.7 is installed
check_installed_python
case $* in
--install|-i)
install

View File

@@ -45,6 +45,9 @@ class TestStrategy(IStrategy):
# Optimal ticker interval for the strategy
ticker_interval = '5m'
# run "populate_indicators" only for new candle
ta_on_candle = False
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Adds several different TA indicators to the given DataFrame