Compare commits

...

1617 Commits

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

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

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

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

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

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

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

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

Other small cosmetic improvements to logging in freqtradebot.py
2019-02-10 21:31:13 +03:00
hroff-1902
585f525879 Cosmetic: fix logging 2019-02-10 21:02:53 +03:00
Samuel Husso
58bd272c0f Merge pull request #1543 from freqtrade/pyup/scheduled-update-2019-02-10
Scheduled daily dependency update on sunday
2019-02-10 14:49:25 +02:00
pyup-bot
fe2f98c802 Update ccxt from 1.18.221 to 1.18.223 2019-02-10 13:33:06 +01:00
Matthias
76187cc3d9 Merge pull request #1541 from freqtrade/pyup/scheduled-update-2019-02-09
Scheduled daily dependency update on saturday
2019-02-09 14:00:49 +01:00
pyup-bot
3bb9e17b0d Update plotly from 3.6.0 to 3.6.1 2019-02-09 13:33:11 +01:00
pyup-bot
f0c9064b77 Update mypy from 0.660 to 0.670 2019-02-09 13:33:09 +01:00
pyup-bot
e0142526e3 Update scipy from 1.2.0 to 1.2.1 2019-02-09 13:33:08 +01:00
pyup-bot
e9aba03981 Update ccxt from 1.18.210 to 1.18.221 2019-02-09 13:33:06 +01:00
Samuel Husso
9a50771a27 Merge pull request #1540 from freqtrade/pyup/scheduled-update-2019-02-08
Scheduled daily dependency update on friday
2019-02-08 14:52:56 +02:00
pyup-bot
92eb951966 Update ccxt from 1.18.208 to 1.18.210 2019-02-08 13:34:26 +01:00
Matthias
e6a8ecbf66 Merge pull request #1539 from freqtrade/pyup/scheduled-update-2019-02-07
Scheduled daily dependency update on thursday
2019-02-07 15:37:18 +01:00
pyup-bot
39626bb520 Update ccxt from 1.18.203 to 1.18.208 2019-02-07 13:34:09 +01:00
Samuel Husso
f48936dcde Merge pull request #1538 from freqtrade/pyup/scheduled-update-2019-02-06
Scheduled daily dependency update on wednesday
2019-02-06 15:29:06 +02:00
pyup-bot
395a7b25be Update ccxt from 1.18.197 to 1.18.203 2019-02-06 13:34:09 +01:00
Misagh
02698c7493 Merge pull request #1537 from freqtrade/pyup/scheduled-update-2019-02-05
Scheduled daily dependency update on tuesday
2019-02-05 13:55:09 +01:00
pyup-bot
6fd932bf7d Update pytest-mock from 1.10.0 to 1.10.1 2019-02-05 13:34:07 +01:00
pyup-bot
fcc7cb9892 Update flake8 from 3.7.4 to 3.7.5 2019-02-05 13:34:06 +01:00
Misagh
21ffdbb3a2 Merge pull request #1535 from freqtrade/fix/cancelled_on_exchange
Fix/cancelled on exchange
2019-02-04 22:47:58 +01:00
Misagh
f847bf0b8d Merge pull request #1536 from freqtrade/pyup/scheduled-update-2019-02-04
Scheduled daily dependency update on monday
2019-02-04 14:44:47 +01:00
pyup-bot
64891df122 Update pandas from 0.24.0 to 0.24.1 2019-02-04 13:34:12 +01:00
pyup-bot
218b501119 Update ccxt from 1.18.195 to 1.18.197 2019-02-04 13:34:10 +01:00
Matthias
0a71ebce68 Remove unneeded pair conversation, add docstring 2019-02-04 06:13:22 +01:00
Matthias
80440f25cf Merge pull request #1534 from freqtrade/pyup/scheduled-update-2019-02-03
Scheduled daily dependency update on sunday
2019-02-03 13:53:32 +01:00
Matthias
1d940041e3 Add log test for cancelled order 2019-02-03 13:52:05 +01:00
Matthias
40fea4593f Add log_has_re 2019-02-03 13:52:05 +01:00
Matthias
02c3552954 Adjust comment 2019-02-03 13:52:05 +01:00
Matthias
91ed02134e Add tests for cancelled on exchnage 2019-02-03 13:52:05 +01:00
Matthias
e8ed8a2ea7 Handle orders cancelled on exchange
fix #1527
2019-02-03 13:51:52 +01:00
pyup-bot
f2dd32e319 Update flake8-tidy-imports from 1.1.0 to 2.0.0 2019-02-03 13:34:07 +01:00
Samuel Husso
5243941c4d Merge pull request #1533 from freqtrade/pyup/scheduled-update-2019-02-02
Scheduled daily dependency update on saturday
2019-02-02 15:25:29 +02:00
pyup-bot
8d5474d4d5 Update plotly from 3.5.0 to 3.6.0 2019-02-02 13:34:08 +01:00
pyup-bot
576d893d95 Update ccxt from 1.18.193 to 1.18.195 2019-02-02 13:34:06 +01:00
Misagh
697e698abc Merge pull request #1530 from freqtrade/pyup/scheduled-update-2019-02-01
Scheduled daily dependency update on friday
2019-02-01 13:56:17 +01:00
pyup-bot
24f779eda7 Update flake8 from 3.7.3 to 3.7.4 2019-02-01 13:34:10 +01:00
pyup-bot
2ce3bd956d Update numpy from 1.16.0 to 1.16.1 2019-02-01 13:34:09 +01:00
pyup-bot
ba4e5cae54 Update ccxt from 1.18.190 to 1.18.193 2019-02-01 13:34:07 +01:00
Matthias
645ec30ec5 Merge pull request #1528 from freqtrade/pyup/scheduled-update-2019-01-31
Scheduled daily dependency update on thursday
2019-01-31 14:41:14 +01:00
pyup-bot
d07cc5929e Update pytest from 4.1.1 to 4.2.0 2019-01-31 13:34:11 +01:00
pyup-bot
8d9114aa79 Update flake8 from 3.7.1 to 3.7.3 2019-01-31 13:34:09 +01:00
pyup-bot
68a4e0426e Update ccxt from 1.18.179 to 1.18.190 2019-01-31 13:34:07 +01:00
Matthias
10e548dcab Merge pull request #1526 from freqtrade/pyup/scheduled-update-2019-01-30
Scheduled daily dependency update on wednesday
2019-01-31 06:58:57 +01:00
Samuel Husso
e3ae8d3f69 flake8 3.7.1 fixes 2019-01-31 07:51:03 +02:00
Samuel Husso
576d9b8f5c requirements: move numpy installation earlier as later packages require it 2019-01-31 07:50:13 +02:00
pyup-bot
0c959c22ec Update flake8 from 3.6.0 to 3.7.1 2019-01-30 13:33:10 +01:00
pyup-bot
6ad1089f45 Update cachetools from 3.0.0 to 3.1.0 2019-01-30 13:33:08 +01:00
pyup-bot
9f87a27465 Update ccxt from 1.18.171 to 1.18.179 2019-01-30 13:33:06 +01:00
Matthias
2b71e8de5c Merge pull request #1525 from mishaker/edge_messages
Edge messages enriched
2019-01-30 11:41:32 +01:00
misagh
84b6b8fe97 edge messages enriched 2019-01-30 11:23:23 +01:00
Misagh
421be5da86 Merge pull request #1511 from freqtrade/fix/more_settings_strategy
add more settings to strategy
2019-01-30 11:22:10 +01:00
Misagh
09cb043b24 Merge pull request #1522 from freqtrade/pyup/scheduled-update-2019-01-29
Scheduled daily dependency update on tuesday
2019-01-29 13:50:11 +01:00
pyup-bot
cf283344de Update ccxt from 1.18.160 to 1.18.171 2019-01-29 13:33:08 +01:00
Matthias
dcfa4d421e Merge pull request #1518 from freqtrade/pyup/scheduled-update-2019-01-28
Scheduled daily dependency update on monday
2019-01-28 14:41:23 +01:00
pyup-bot
1a1123a555 Update ccxt from 1.18.155 to 1.18.160 2019-01-28 13:33:07 +01:00
Misagh
382215eb70 Merge pull request #1517 from freqtrade/pyup/scheduled-update-2019-01-27
Scheduled daily dependency update on sunday
2019-01-27 13:49:36 +01:00
pyup-bot
f095492804 Update ccxt from 1.18.152 to 1.18.155 2019-01-27 13:33:07 +01:00
Misagh
22e82f5e47 Merge pull request #1507 from xmatthias/feat/dataprovider
Data Provider
2019-01-27 12:32:18 +01:00
Misagh
69ef743811 Merge pull request #1515 from freqtrade/pyup/scheduled-update-2019-01-26
Scheduled daily dependency update on saturday
2019-01-27 12:28:32 +01:00
Matthias
38f73dafb3 Fix indexing error 2019-01-27 10:47:24 +01:00
Matthias
e0ad095bc7 Simplify conversation to python dates (pandas offers this "for free" 2019-01-27 10:47:02 +01:00
Matthias
1d08ada939 Fix backtest-test with timestamp-conversion 2019-01-27 10:40:52 +01:00
Matthias
3446dd1792 Add test informative_pairs_added 2019-01-26 20:05:49 +01:00
Matthias
02d13645b0 Merge branch 'develop' into feat/dataprovider 2019-01-26 19:29:41 +01:00
Matthias
ba07348b82 Rename additional_pairs to informative_pairs 2019-01-26 19:22:45 +01:00
Matthias
bfd8609352 Fix comment 2019-01-26 19:16:33 +01:00
pyup-bot
b112f2f315 Update pandas from 0.23.4 to 0.24.0 2019-01-26 13:33:20 +01:00
pyup-bot
d222dd6717 Update sqlalchemy from 1.2.16 to 1.2.17 2019-01-26 13:33:18 +01:00
Matthias
3bc96c16ac Merge pull request #1513 from Axel-CH/feature/plot_df_refactoring_multiple_pairs
Feature/plot df refactoring multiple pairs
2019-01-26 11:07:17 +01:00
Matthias
da5210ef5b Merge branch 'develop' into feature/plot_df_refactoring_multiple_pairs 2019-01-26 11:04:35 +01:00
Matthias
e5b0224050 remove unused import 2019-01-26 11:00:12 +01:00
Axel Cherubin
e43aaaef9c add macd signal as default indicator2 2019-01-26 11:00:10 +01:00
Axel Cherubin
422a0ce114 better Path usage, remove arg parameter in generate_graph 2019-01-26 11:00:09 +01:00
Matthias
22e7ad8ec1 Change back to LF lineendings 2019-01-26 11:00:08 +01:00
pyup-bot
b840b9f53a Update ccxt from 1.18.144 to 1.18.146 2019-01-26 11:00:05 +01:00
AxelCh
eec7276393 fix crash when backtest-result.json not exist 2019-01-26 11:00:04 +01:00
Matthias
5e7ba85dbe Fix typo 2019-01-25 19:17:44 +01:00
Matthias
3c316fe3e4 Fix alignment 2019-01-25 19:14:38 +01:00
Matthias
56a3d78128 Fix typo 2019-01-25 19:14:29 +01:00
Misagh
ab8cc5f586 Merge pull request #1514 from freqtrade/pyup/scheduled-update-2019-01-25
Scheduled daily dependency update on friday
2019-01-25 13:58:36 +01:00
pyup-bot
bd24646822 Update tabulate from 0.8.2 to 0.8.3 2019-01-25 13:33:30 +01:00
pyup-bot
a97b3ab04a Update ccxt from 1.18.146 to 1.18.152 2019-01-25 13:33:29 +01:00
Matthias
3afe54790e Merge pull request #1510 from gianlup/add_totprofit_to_bt
Added total profit column to backtest result
2019-01-25 06:38:39 +01:00
Gianluca Puglia
38d293cc26 Updated doc 2019-01-24 15:24:38 +01:00
Samuel Husso
497a467864 Merge pull request #1512 from freqtrade/pyup/scheduled-update-2019-01-24
Scheduled daily dependency update on thursday
2019-01-24 14:50:52 +02:00
pyup-bot
dcceb40fab Update ccxt from 1.18.144 to 1.18.146 2019-01-24 13:33:07 +01:00
Matthias
9960fe07bc Add experimental settings to sample strategy 2019-01-24 07:08:21 +01:00
Matthias
74b03d0529 Add tests and default values for all experimental features 2019-01-24 07:03:41 +01:00
Matthias
ac199b626a Drop mandatory column 2019-01-24 07:03:25 +01:00
Matthias
8750f1be3f Add strategy-override options 2019-01-24 06:43:28 +01:00
Matthias
05d65b81da Fix typo 2019-01-23 21:05:07 +01:00
Matthias
d136cac181 Merge branch 'develop' into feat/dataprovider 2019-01-23 21:01:19 +01:00
Matthias
97f6a45819 Allow more settings to come from strategy 2019-01-23 20:59:41 +01:00
Matthias
ad8b1bbb79 Change default for positive_offset in sample 2019-01-23 20:57:31 +01:00
Matthias
5ea332e9be fix bug with trailing_stop_offset if it's disabled 2019-01-23 20:57:31 +01:00
AxelCh
06e0616fb0 Merge branch 'develop' of https://github.com/freqtrade/freqtrade into develop 2019-01-23 13:26:05 -04:00
Samuel Husso
372c5d813a Merge pull request #1508 from freqtrade/improve_strategy_docs
Add information about dataframe
2019-01-23 10:15:43 +02:00
Samuel Husso
fd94b322be Merge pull request #1509 from freqtrade/test/flake_mock
Add flake8 plugins
2019-01-23 09:06:24 +02:00
Gianluca Puglia
896c9d34fd Added total profit column do backtest result 2019-01-22 22:41:53 +01:00
Matthias
13e2f71d30 Add flake8 plugins and implement small improvements 2019-01-22 20:01:12 +01:00
Matthias
c412cd9e57 Add information about dataframe
fix #1192
2019-01-22 19:44:56 +01:00
Matthias
86a0863e30 Clarify logmessage Done fetching 2019-01-22 19:26:07 +01:00
Matthias
a06593e6e9 Fix test 2019-01-22 19:17:21 +01:00
Matthias
89ddfe08f4 Add additional-pairs (sample) to defaultstrategy 2019-01-22 19:17:08 +01:00
Samuel Husso
580e9ccaf3 Merge pull request #1506 from freqtrade/pyup/scheduled-update-2019-01-22
Scheduled daily dependency update on tuesday
2019-01-22 14:49:44 +02:00
pyup-bot
7c71b9513c Update ccxt from 1.18.141 to 1.18.144 2019-01-22 13:32:11 +01:00
Misagh
188c391444 Merge pull request #1504 from freqtrade/development_guide
Improve developer docs
2019-01-22 11:20:01 +01:00
Matthias
c77607b997 Fix tests after rebase 2019-01-22 07:38:15 +01:00
Matthias
3221f883d3 Wrap line correctly 2019-01-22 07:07:15 +01:00
Matthias
1e7431a7b8 Blackify 2019-01-22 07:07:15 +01:00
Matthias
e66808bb02 Add additional pairs to refresh call 2019-01-22 07:07:15 +01:00
Matthias
fc92491a47 Add documentation for additional_pairs 2019-01-22 07:07:15 +01:00
Matthias
6e2de75bcb Add additional_pairs to strategy 2019-01-22 07:07:15 +01:00
Matthias
d6cdfc58af Fix mypy hickup after changing list to tuples 2019-01-22 07:07:15 +01:00
Matthias
7b138ef3b4 Add warning about strategy/backtesting 2019-01-22 07:07:15 +01:00
Matthias
27b2021726 Only run once per pair 2019-01-22 07:07:15 +01:00
Matthias
e7800aa88a Import only what's necessary 2019-01-22 07:07:15 +01:00
Matthias
a2bc1da669 Remove private var from class instance
it's overwritten in __init__ anyway
2019-01-22 07:07:15 +01:00
Matthias
1e749a0f9b Rename variable to be clearer 2019-01-22 07:07:15 +01:00
Matthias
d7df5d5715 Keep last_pair_refresh as tuple asw ell 2019-01-22 07:07:15 +01:00
Matthias
6525a838d1 Adjust documentation to tuple use 2019-01-22 07:07:15 +01:00
Matthias
f0af4601f9 Adopt plot_dataframe to work with --live 2019-01-22 07:07:15 +01:00
Matthias
a9abc25785 Improve data-provider tests 2019-01-22 07:07:15 +01:00
Matthias
0aa0b1d4fe Store tickers by pair / ticker_interval 2019-01-22 07:07:15 +01:00
Matthias
5f61da30ed Adjust tests to 3tuple return value from async method 2019-01-22 07:06:30 +01:00
Matthias
d6df3e55c0 Return ticker_interval from async routine
used to identify calls in refresh_latest_ohlcv
2019-01-22 07:06:30 +01:00
Matthias
e503d811bd Change logmessages to match functions called 2019-01-22 07:05:09 +01:00
Matthias
b981cfcaa0 remove comment which proves untrue now 2019-01-22 07:05:09 +01:00
Matthias
a206777fe5 Rename refresh_tickers to refresh_latest_ohlcv 2019-01-22 07:05:09 +01:00
Matthias
06ec106079 simplify refresh_tickers 2019-01-22 07:04:19 +01:00
Matthias
646e98da55 Always return dataframe 2019-01-22 07:04:19 +01:00
Matthias
2b029b2a86 Only return ohlcv if available (Live and dry modes) 2019-01-22 07:04:19 +01:00
Matthias
9edb88051d Add dataprovider documentation 2019-01-22 07:04:19 +01:00
Matthias
35c8d1dcbe Update comment 2019-01-22 07:04:19 +01:00
Matthias
8f3ea3608a some cleanup 2019-01-22 07:04:19 +01:00
Matthias
5ecdecd1eb remove unused local variable persistance 2019-01-22 07:04:19 +01:00
Matthias
58f1abf287 Add dp / wallets to strategy interface 2019-01-22 07:04:19 +01:00
Matthias
d3a37db79a Provide available pairs 2019-01-22 07:04:19 +01:00
Matthias
f034235af4 Tests for RunMode 2019-01-22 07:04:19 +01:00
Matthias
1340b71633 Add RunMode setting to determine bot state 2019-01-22 07:04:19 +01:00
Matthias
fed3ebfb46 Change enum from 0 to 1 according to the documentation
see [here](https://docs.python.org/3/library/enum.html#functional-api)
2019-01-22 07:04:19 +01:00
Matthias
a7db4d74cb Add some simple dataprovider tests 2019-01-22 07:04:19 +01:00
Matthias
84cc4887ce Add copy parameter 2019-01-22 07:04:19 +01:00
Matthias
e38c06afe9 Small fixes 2019-01-22 07:04:19 +01:00
Matthias
f1a5a8e20e provide history 2019-01-22 07:04:19 +01:00
Matthias
4ab7edd3d6 small adaptations 2019-01-22 07:04:19 +01:00
Matthias
05570732c6 add get_runmode 2019-01-22 07:04:19 +01:00
Matthias
7206287b00 Use Dataprovider 2019-01-22 07:04:19 +01:00
Matthias
b119a767de Some more restructuring 2019-01-22 07:04:19 +01:00
Matthias
a6d74a1463 Draft of dataprovider 2019-01-22 07:04:19 +01:00
AxelCh
70881f12d2 Merge branch 'master' of https://github.com/freqtrade/freqtrade into develop 2019-01-21 22:08:59 -04:00
Matthias
1be3d57b60 Improve developer docs 2019-01-21 19:53:14 +01:00
Samuel Husso
07577ac18d Merge pull request #1503 from freqtrade/pyup/scheduled-update-2019-01-21
Scheduled daily dependency update on monday
2019-01-21 14:57:42 +02:00
pyup-bot
e4a399039b Update ccxt from 1.18.137 to 1.18.141 2019-01-21 13:32:07 +01:00
Misagh
34b617065d Merge pull request #1500 from freqtrade/fix/1491_typeerror
Fix typeerror when fetching candles
2019-01-20 16:13:02 +01:00
Matthias
2a16e9b6a0 Merge pull request #1501 from freqtrade/pyup/scheduled-update-2019-01-20
Scheduled daily dependency update on sunday
2019-01-20 15:57:54 +01:00
Matthias
8a3615dea3 Merge pull request #1502 from macd2/patch-2
JSON standard does not allow single quoted strings
2019-01-20 15:57:41 +01:00
macd2
6fb50e35c9 JSON standard does not allow single quoted strings 2019-01-20 14:21:42 +01:00
pyup-bot
a733630083 Update ccxt from 1.18.134 to 1.18.137 2019-01-20 13:32:07 +01:00
Matthias
b48430f922 Return list not None 2019-01-19 20:21:33 +01:00
Matthias
4e760e1a5e Test for errors found in 1491
fixes #1491
2019-01-19 20:03:04 +01:00
Matthias
30e3b52b1e catch errors found in #1491 2019-01-19 20:02:37 +01:00
Matthias
b52da0ad09 Merge pull request #1498 from Axel-CH/patch-1
fix stoploss_on_exchange_interval type
2019-01-19 19:10:11 +01:00
Axel CHERUBIN
797ac71376 fix stoploss_on_exchange_interval type
from boolean to number
2019-01-19 13:26:30 -04:00
Matthias
e8423d8155 Merge pull request #1496 from freqtrade/pyup/scheduled-update-2019-01-19
Scheduled daily dependency update on saturday
2019-01-19 13:57:46 +01:00
pyup-bot
b421e437ab Update wrapt from 1.11.0 to 1.11.1 2019-01-19 13:32:07 +01:00
pyup-bot
2b65e3f35c Update ccxt from 1.18.133 to 1.18.134 2019-01-19 13:32:06 +01:00
Matthias
c2578c7321 Merge pull request #1474 from mishaker/tsl_on_exchange
Making trailing stoploss compatible with stoploss on exchange
2019-01-18 19:29:39 +01:00
misagh
87329a393c adding stop loss last update to test persistence 2019-01-18 12:14:00 +01:00
misagh
a2618208ef wrapping in parantheses instead of line breaks 2019-01-18 12:07:51 +01:00
misagh
70780bb01e using dict.get to fetch interval 2019-01-18 12:02:29 +01:00
misagh
89eddfd349 refactoring english … 2019-01-18 12:00:53 +01:00
misagh
1c4ee35eca using italic for “off exchange” 2019-01-18 12:00:02 +01:00
misagh
e41e45413f adding tailing_stop to docs 2019-01-18 11:58:23 +01:00
Matthias
c7ebd8228e Merge pull request #1495 from freqtrade/fix_mypy_issue
Fix mypy issue 2
2019-01-17 20:48:55 +01:00
Matthias
cc6466388e update mypy 2019-01-17 20:31:00 +01:00
Matthias
27d907e71b Merge pull request #1493 from freqtrade/pyup/scheduled-update-2019-01-17
Scheduled daily dependency update on thursday
2019-01-17 20:30:45 +01:00
Matthias
a2c01916e1 Add type-ignores to floatfmt
tabulate supports this:
30554300d7/tabulate.py (tabulate.py-1291):1294
2019-01-17 20:28:21 +01:00
Misagh
357c28d5ea Merge pull request #1494 from freqtrade/fix_mypy_issue
Remove unversioned install of most dev packages
2019-01-17 19:38:05 +01:00
Matthias
648def69ca Remove unversioned install of most dev packages 2019-01-17 19:05:28 +01:00
pyup-bot
97a8341436 Update ccxt from 1.18.131 to 1.18.133 2019-01-17 13:32:07 +01:00
Matthias
30cdf85ffa Merge pull request #1492 from mishaker/patch_unreachable
unreachable code removed
2019-01-16 21:34:50 +01:00
misagh
75cedfafb8 unreachable code removed 2019-01-16 20:03:34 +01:00
misagh
9d6c54791b added note for only binance 2019-01-16 19:23:55 +01:00
misagh
e682eceae4 stop loss documentation added 2019-01-16 19:22:06 +01:00
misagh
2533112254 added referral to stop loss documentation 2019-01-16 19:12:52 +01:00
misagh
08d98773f3 added SL interval to configuration document 2019-01-16 19:10:13 +01:00
misagh
da51ef40f8 SL interval added to CONF_SCHEMA 2019-01-16 19:04:43 +01:00
misagh
91c714c7d1 stoploss_on_exchange_interval added to full config 2019-01-16 18:58:12 +01:00
misagh
5e2e96acd2 compatibility with edge added 2019-01-16 18:38:20 +01:00
misagh
611b48dbb9 fix return value from info hash: value is in string 2019-01-16 16:15:36 +01:00
misagh
50bc20134f adding whitespace 2019-01-16 15:17:28 +01:00
misagh
baa5cc5b9e logs enriched 2019-01-16 15:10:31 +01:00
misagh
aa03a864f7 comments added for TSL on exchange function 2019-01-16 15:00:35 +01:00
misagh
a44f781284 Merge branch 'develop' into tsl_on_exchange 2019-01-16 14:56:27 +01:00
Misagh
d108138999 Merge pull request #1486 from freqtrade/fix/roi_problem
Fix ROI problem
2019-01-16 14:51:27 +01:00
misagh
cffc9ce890 last step: stop loss on exchange added to trailing 2019-01-16 14:49:47 +01:00
misagh
6d588b3b0b trailing stop loss on exchange extracted to a separate function 2019-01-16 14:28:52 +01:00
misagh
bfb7121583 refactoring handle_stoploss_on_exchange 2019-01-16 12:16:32 +01:00
misagh
e31fa8721f Merge branch 'develop' into tsl_on_exchange 2019-01-16 11:52:23 +01:00
misagh
29439c05d6 adding update beat test 2019-01-16 11:51:54 +01:00
misagh
1cd5abde37 removing unnecessary guard 2019-01-16 11:22:25 +01:00
misagh
12e8108015 checking params of cancel order and stop loss order 2019-01-15 15:36:41 +01:00
Misagh
07b4afedf7 Merge pull request #1490 from gaugau3000/patch-1
Win rate formula was wrong in edge doc (but not the definition)
2019-01-15 15:16:58 +01:00
gautier pialat
399d2d89a3 Win rate formula was wrong (but not the definition) 2019-01-15 15:02:01 +01:00
Misagh
22d3881b6e Merge pull request #1489 from freqtrade/pyup/scheduled-update-2019-01-15
Scheduled daily dependency update on tuesday
2019-01-15 13:45:27 +01:00
pyup-bot
494b905d1e Update ccxt from 1.18.126 to 1.18.131 2019-01-15 13:32:08 +01:00
misagh
f0cfab7940 flaking 8 2019-01-15 11:10:28 +01:00
misagh
cfe00c2f0c initial test added for TSL on exchange 2019-01-15 11:04:32 +01:00
Misagh
678162fada Merge pull request #1488 from freqtrade/pyup/scheduled-update-2019-01-14
Scheduled daily dependency update on monday
2019-01-14 14:06:53 +01:00
pyup-bot
da04182287 Update numpy from 1.15.4 to 1.16.0 2019-01-14 13:32:08 +01:00
pyup-bot
a3897c990d Update ccxt from 1.18.120 to 1.18.126 2019-01-14 13:32:07 +01:00
Misagh
929995117f Merge pull request #1487 from freqtrade/pyup/scheduled-update-2019-01-13
Scheduled daily dependency update on sunday
2019-01-13 14:51:14 +01:00
pyup-bot
04786f09f4 Update pytest from 4.1.0 to 4.1.1 2019-01-13 13:32:06 +01:00
Matthias
9b97e1e8fb Merge pull request #1485 from freqtrade/pyup/scheduled-update-2019-01-12
Scheduled daily dependency update on saturday
2019-01-12 13:49:39 +01:00
Matthias
cd2bccd441 Have backtest use the same logic to get the ROI entry 2019-01-12 13:45:43 +01:00
Matthias
e9d61eb35d Fix ROI calculation problem
Prior to that all ROI entries with a key > trade_duration where active.
This causes a problem if the ROI is not linearly declining
2019-01-12 13:45:03 +01:00
Matthias
9e0902e72f Add test for case for odd ROI dict - #1478 2019-01-12 13:38:49 +01:00
pyup-bot
690fbeb907 Update joblib from 0.13.0 to 0.13.1 2019-01-12 13:32:08 +01:00
pyup-bot
e95351fd04 Update sqlalchemy from 1.2.15 to 1.2.16 2019-01-12 13:32:06 +01:00
Matthias
a095ccd1d6 Merge pull request #1484 from freqtrade/pyup/scheduled-update-2019-01-11
Scheduled daily dependency update on friday
2019-01-12 09:30:15 +01:00
pyup-bot
3867f73c8c Update ccxt from 1.18.119 to 1.18.120 2019-01-11 13:32:07 +01:00
Samuel Husso
d8f2d868c1 Merge pull request #1483 from freqtrade/pyup/scheduled-update-2019-01-10
Scheduled daily dependency update on thursday
2019-01-10 15:01:10 +02:00
pyup-bot
4920ee3455 Update wrapt from 1.10.11 to 1.11.0 2019-01-10 13:32:09 +01:00
pyup-bot
3f8092192e Update ccxt from 1.18.117 to 1.18.119 2019-01-10 13:32:07 +01:00
misagh
e025ad3918 temp test commit 2019-01-09 16:23:13 +01:00
Matthias
5ac5b18e6d Merge pull request #1481 from freqtrade/pyup/scheduled-update-2019-01-09
Scheduled daily dependency update on wednesday
2019-01-09 14:02:39 +01:00
pyup-bot
c1007f95b3 Update ccxt from 1.18.114 to 1.18.117 2019-01-09 13:32:07 +01:00
Misagh
4d52301ee6 Merge pull request #1479 from freqtrade/feat/improve_travis
Fix travis failures
2019-01-09 12:21:34 +01:00
Matthias
a494755449 Update .travis.yml 2019-01-09 08:17:03 +01:00
Matthias
2e530a3e03 Update install_ta-lib.sh 2019-01-09 08:16:33 +01:00
Matthias
e76ed31b08 fix ta-lib cache 2019-01-09 08:15:36 +01:00
Matthias
f4979e0e8a Cache /usr/loca/lib 2019-01-09 06:27:58 +01:00
Matthias
dd7d655a63 remove unwriteable cache-dir 2019-01-08 21:30:11 +01:00
Matthias
f9a99f4ad3 Change caching 2019-01-08 21:27:50 +01:00
Matthias
99e2d795c5 Merge pull request #1475 from freqtrade/feat/hyperopt_sell
Feat/hyperopt sell
2019-01-08 21:19:53 +01:00
Matthias
7a13565efb travis - python - test 2019-01-08 21:13:56 +01:00
Matthias
bb3d78757d Test python 3.7 2019-01-08 21:11:36 +01:00
Matthias
356a17cdaa Build python 3.7 2019-01-08 21:07:08 +01:00
Matthias
da436c920f switch travis to xenial 2019-01-08 20:58:14 +01:00
Matthias
69eed95a54 cd out of the build-helpers dir 2019-01-08 20:39:50 +01:00
Matthias
df97652f6e Try fix ta-lib install 2019-01-08 20:35:08 +01:00
Matthias
64372ea6fb Fix ta-lib installation build helpers 2019-01-08 20:12:39 +01:00
Matthias
b3f67bb8c6 Fix git pull in docker-image 2019-01-08 20:09:32 +01:00
Matthias
d29c294f6a Merge pull request #1477 from freqtrade/pyup/scheduled-update-2019-01-08
Scheduled daily dependency update on tuesday
2019-01-08 19:04:43 +01:00
Matthias
b5adfcf51a Fix documentation typo 2019-01-08 17:07:32 +01:00
misagh
1a27258469 condition fixed 2019-01-08 16:34:23 +01:00
misagh
9e133eb32e adding guard not to cancel the previous stop loss on exchange if market
is falling quickly
2019-01-08 16:31:02 +01:00
misagh
f4ceeca438 Merge branch 'develop' into tsl_on_exchange 2019-01-08 15:33:32 +01:00
misagh
aed855284c comparing with stopPrice instead of price 2019-01-08 13:44:51 +01:00
pyup-bot
f4e0e04462 Update pytest-cov from 2.6.0 to 2.6.1 2019-01-08 13:33:13 +01:00
pyup-bot
4069e2fdfb Update pytest-asyncio from 0.9.0 to 0.10.0 2019-01-08 13:33:11 +01:00
pyup-bot
ec22512fd9 Update pytest from 4.0.2 to 4.1.0 2019-01-08 13:33:10 +01:00
pyup-bot
c3107272d3 Update arrow from 0.12.1 to 0.13.0 2019-01-08 13:33:08 +01:00
pyup-bot
b98526d32c Update ccxt from 1.18.102 to 1.18.114 2019-01-08 13:33:06 +01:00
misagh
4fbb9d4462 adding stoploss_on_exchange_interval to order_types dict. default to 1
minute (60)
2019-01-08 12:39:53 +01:00
misagh
16472535eb adding stoploss_last_update to persistence 2019-01-08 12:39:10 +01:00
Matthias
f620449bec Add test for hyperoptresolver 2019-01-06 19:38:32 +01:00
Matthias
440a7ec9c2 fix pytest 2019-01-06 19:31:25 +01:00
Matthias
40b1d8f067 Fix CI problems 2019-01-06 14:57:14 +01:00
Matthias
5dd1f9b38a improve hyperopt docs 2019-01-06 14:47:53 +01:00
Matthias
dd2af86a41 pprint results 2019-01-06 14:47:38 +01:00
misagh
821e299afb adjusting trailing stoploss on exchange 2019-01-06 14:45:29 +01:00
Matthias
85bca58905 Merge pull request #1473 from gianlup/fix_dburl
Fix custom db_url ignored if provided by conf.json
2019-01-06 14:20:55 +01:00
Matthias
f5fc9e69cf Merge pull request #1472 from freqtrade/pyup/scheduled-update-2019-01-06
Scheduled daily dependency update on sunday
2019-01-06 14:19:24 +01:00
Matthias
167088827a include default buy/sell trends for the hyperopt strategy 2019-01-06 14:13:15 +01:00
Matthias
a0df7b9d7c Use sell/buy trends from hyperopt file if available 2019-01-06 14:12:55 +01:00
Gianluca Puglia
87cbff5d0e Fix warning for max_open_trades when edge is enabled 2019-01-06 13:48:27 +01:00
Gianluca Puglia
13800701ce Fix custom db_url ignored if provided by conf.json 2019-01-06 13:47:36 +01:00
pyup-bot
3f82dd05aa Update ccxt from 1.18.101 to 1.18.102 2019-01-06 13:33:06 +01:00
Matthias
2147bd8847 Fix problem when no experimental dict is available 2019-01-06 13:29:14 +01:00
Matthias
798ae460d8 Add check if trigger is in parameters 2019-01-06 13:29:14 +01:00
Matthias
5e08769366 Update hyperopt documentation with sell-stuff 2019-01-06 13:29:14 +01:00
Matthias
68ba1e1f37 Add sell signal hyperopt 2019-01-06 13:29:14 +01:00
Matthias
b731973c7a Merge pull request #1471 from freqtrade/setup_text
fixes few wordings
2019-01-06 12:53:04 +01:00
Misagh
a07353d3c7 fixes few wordings 2019-01-06 12:02:57 +01:00
Misagh
86023744d9 Merge pull request #1470 from freqtrade/fix/installscript
fix and improve installation script
2019-01-06 11:55:43 +01:00
Misagh
e91be7aff9 Merge pull request #1468 from freqtrade/update_dockerfile
Update dockerfile to python 3.7.2
2019-01-06 11:43:03 +01:00
Misagh
5af656d3ba Merge pull request #1469 from freqtrade/improve_doc_structure
fix typo, improve doc sequence
2019-01-06 11:42:35 +01:00
Misagh
4a0bc8937d Merge pull request #1466 from freqtrade/feat/strategy_trailing
trailing stoploss configuration in strategy
2019-01-06 11:40:33 +01:00
Matthias
506237e3b4 Don't use --quiet on pip install
this hides errors from users and complicates debugging in case of
problmes
2019-01-05 20:06:15 +01:00
Matthias
f088f43b40 Install numpy before py_find_1st 2019-01-05 20:04:54 +01:00
Matthias
01e2dc17b5 Remove whitespace in fucntion definition 2019-01-05 20:00:10 +01:00
Matthias
337ebdeccb Avoid installing ta-lib multiple times 2019-01-05 19:51:51 +01:00
Matthias
9e5e485d0a put --upgrade flag to the same location in subsequent requests 2019-01-05 19:51:28 +01:00
Matthias
31da42a485 Show error when no python is found 2019-01-05 19:50:46 +01:00
Matthias
94aa1aaff3 fix typo, improve doc sequence 2019-01-05 19:34:45 +01:00
Matthias
dbf8ec6a20 Update dockerfile to python 3.7.2 2019-01-05 19:24:07 +01:00
Samuel Husso
c8d40e81f0 Merge pull request #1467 from freqtrade/pyup/scheduled-update-2019-01-05
Scheduled daily dependency update on saturday
2019-01-05 15:43:12 +02:00
pyup-bot
16512d9918 Update plotly from 3.4.2 to 3.5.0 2019-01-05 13:33:07 +01:00
pyup-bot
8505ffbe78 Update ccxt from 1.18.97 to 1.18.101 2019-01-05 13:33:06 +01:00
Matthias
f7b96d839d Add trailing options to sample-strategy 2019-01-05 09:03:26 +01:00
Matthias
f32232ba96 Add documentation for stoploss in strategy 2019-01-05 07:32:35 +01:00
Matthias
cacb9ef3ad Loop twice 2019-01-05 07:25:35 +01:00
Matthias
00c5ac56d4 Print startup strategy summary 2019-01-05 07:24:15 +01:00
Matthias
a7dc6b18aa Overridable attributs as list 2019-01-05 07:22:19 +01:00
Matthias
5e23442032 Simplify StrategyResolver by code deduplication 2019-01-05 07:20:38 +01:00
Matthias
4599c80e79 Add trailing-stop to strategy 2019-01-05 07:10:25 +01:00
Misagh
29db2078d6 Merge pull request #1463 from mishaker/readme
Readme refactoring.
2019-01-04 23:00:36 +01:00
misagh
67cbd5d77f putting back requirements 2019-01-04 22:39:44 +01:00
Misagh
26a77e193e Merge pull request #1454 from freqtrade/feat/interpolate_missing
interpolate missing candles
2019-01-04 22:33:53 +01:00
Matthias
55235ce20d Merge pull request #1464 from freqtrade/pyup/scheduled-update-2019-01-04
Scheduled daily dependency update on friday
2019-01-04 14:10:42 +01:00
pyup-bot
a5ec564fc3 Update ccxt from 1.18.95 to 1.18.97 2019-01-04 13:33:09 +01:00
misagh
f1bb4233c9 telegram documentation link 2019-01-03 14:40:27 +01:00
misagh
afffa2f313 changed to “Free, open source …” 2019-01-03 14:38:38 +01:00
Misagh
221bca0aaa Merge pull request #1462 from freqtrade/pyup/scheduled-update-2019-01-03
Scheduled daily dependency update on thursday
2019-01-03 14:02:03 +01:00
pyup-bot
de278a77d7 Update ccxt from 1.18.94 to 1.18.95 2019-01-03 13:33:08 +01:00
Matthias
56924e6909 Merge pull request #1461 from mishaker/fix_talib
cd build_helpers added before extracting Tar
2019-01-02 19:31:37 +01:00
misagh
2c31fd662c cd build_helpers added before extracting Tar 2019-01-02 19:26:58 +01:00
Misagh
3b5785884f Merge pull request #1460 from freqtrade/fix/stop_loss_result
Stop loss should also be shown when trailing is active
2019-01-02 16:41:15 +01:00
Matthias
3329ffd071 improve comment 2019-01-02 14:44:17 +01:00
misagh
05ce7787d6 readme enriched 2019-01-02 14:41:27 +01:00
misagh
68a9d1b2b8 description 2019-01-02 14:21:02 +01:00
misagh
207daf084e change description 2019-01-02 14:18:34 +01:00
Misagh
dcdd7d7436 Merge pull request #1456 from freqtrade/fix/refresh_pairs_cached
Fix bug on --refresh-pairs-cached
2019-01-02 14:02:11 +01:00
Misagh
138de389e2 Merge pull request #1459 from freqtrade/pyup/scheduled-update-2019-01-02
Scheduled daily dependency update on wednesday
2019-01-02 13:50:00 +01:00
Matthias
41a4621caf Merge pull request #1458 from freqtrade/readthedoc-badge
Readthedoc badge
2019-01-02 13:38:02 +01:00
Matthias
516217b6cb Stop loss should also be shown when trailing is active 2019-01-02 13:34:08 +01:00
pyup-bot
6cc6ce359b Update ccxt from 1.18.89 to 1.18.94 2019-01-02 13:34:06 +01:00
Misagh
848af3755e Update README.md 2019-01-02 13:33:33 +01:00
Misagh
71eba2afba Merge pull request #1457 from freqtrade/fix/minroi
minroi sequence of keys should not matter
2019-01-02 12:32:13 +01:00
Matthias
1b84aa82eb dont use 55 for regular check as that's a key in the dict 2019-01-01 16:54:44 +01:00
Matthias
2bc76771bf Align backtest to interface.py
interface.py roi calculation skips on <= duration
the correct selection is therefore trade_duration > x.
2019-01-01 16:50:10 +01:00
Matthias
1d518885a9 fix roi-reached when list is unsorted 2019-01-01 16:45:52 +01:00
Matthias
b55994cb71 Clarify documentation 2019-01-01 16:33:02 +01:00
Matthias
da6f1a3945 Sequence of minroi dict must be irrelevant 2019-01-01 16:32:45 +01:00
Matthias
2b5e02fae8 Merge pull request #1452 from mishaker/doc_interface
Documentation structure revised and content beautifulized.
2019-01-01 14:13:37 +01:00
Matthias
e3cf838bc6 Merge pull request #1455 from freqtrade/pyup/scheduled-update-2019-01-01
Scheduled daily dependency update on tuesday
2019-01-01 14:08:50 +01:00
Matthias
a54d8f0e16 Create datadir when not exists 2019-01-01 14:07:40 +01:00
Matthias
c337a931c2 Fix bug on --refresh-pairs-cached 2019-01-01 13:42:30 +01:00
pyup-bot
a909322f60 Update ccxt from 1.18.87 to 1.18.89 2019-01-01 13:34:06 +01:00
Matthias
672d115eca Change default value and add docstring 2018-12-31 19:42:14 +01:00
Matthias
dd1d3430b9 Add explicit test for ohlcv fillup 2018-12-31 19:40:14 +01:00
Matthias
fae875f588 Implement missing_data_fillup to tests and operations 2018-12-31 19:15:49 +01:00
Matthias
ef4555735a Fill up missing as part of loading data 2018-12-31 19:13:34 +01:00
Matthias
8b9cc45f41 move test for data completeness
should be done before analyzing strategy
2018-12-31 15:09:50 +01:00
Misagh
0824db03e7 Merge pull request #1453 from freqtrade/pyup/scheduled-update-2018-12-31
Scheduled daily dependency update on monday
2018-12-31 14:35:03 +01:00
misagh
7b1f4aec76 typo 2018-12-31 14:01:55 +01:00
misagh
827a8309d7 more links corrected 2018-12-31 14:00:36 +01:00
misagh
366980fd62 broken link corrected 2018-12-31 13:39:18 +01:00
misagh
42cc3e525e link second try 2018-12-31 13:36:27 +01:00
pyup-bot
5be21fd9d8 Update ccxt from 1.18.86 to 1.18.87 2018-12-31 13:34:07 +01:00
misagh
361b294e43 block “==“ removed 2018-12-31 13:27:59 +01:00
misagh
dd91b5c731 links corrected 2018-12-31 13:26:25 +01:00
misagh
a07a004bb6 test relative link 2018-12-31 13:19:00 +01:00
misagh
a86b34e41c old files removed 2018-12-31 11:52:09 +01:00
misagh
7bf1a92dc3 “inofficial” => unofficial 2018-12-31 11:12:56 +01:00
misagh
db1c9b8edf relative links 2018-12-31 11:04:22 +01:00
misagh
79ac20636f typo + broken link 2018-12-31 10:49:14 +01:00
misagh
04483da8df typo corrected 2018-12-31 10:31:28 +01:00
Matthias
d409211908 add test to verify data does not contain missing data afterwards 2018-12-31 09:24:04 +01:00
Matthias
03389d961f ADd test for data_interpolate 2018-12-31 09:18:22 +01:00
Matthias
a021cd3ae2 Add ohlcv data interpolator 2018-12-31 07:12:54 +01:00
misagh
be1969adc8 added widgets to index documents 2018-12-30 18:22:28 +01:00
misagh
689ca76456 added “ready to try?” 2018-12-30 18:06:09 +01:00
misagh
034bcd64d5 requirements added to “about” 2018-12-30 18:03:09 +01:00
misagh
faad07aa3d changing index to about page 2018-12-30 17:58:06 +01:00
misagh
4d415205d1 about document enriched 2018-12-30 17:53:33 +01:00
misagh
c357483eef typo 2018-12-30 17:47:41 +01:00
misagh
aa542784ab unnecessary config removed 2018-12-30 17:32:03 +01:00
misagh
49c37692f3 content bar completed 2018-12-30 17:29:07 +01:00
misagh
f42df56a88 edge reformatted 2018-12-30 17:21:54 +01:00
misagh
808ce3e7ba edge added to content bar 2018-12-30 17:20:28 +01:00
misagh
87cbf6aaaa Plotting reformatted 2018-12-30 17:20:16 +01:00
misagh
863cf303e3 hyperopt reformatted 2018-12-30 17:17:52 +01:00
misagh
b029a98980 backtesting reformatted 2018-12-30 17:14:56 +01:00
misagh
219e9d9e2b optimization page reformatted 2018-12-30 17:14:03 +01:00
misagh
da380e6a0d start the bot documentation reformatted 2018-12-30 17:08:21 +01:00
misagh
83b9732106 permalink installed 2018-12-30 16:56:27 +01:00
misagh
016522b151 configuration reformatetd 2018-12-30 16:56:13 +01:00
misagh
cb1d9b6200 restructuring docs content 2018-12-30 15:22:19 +01:00
Misagh
e215373ad0 Merge pull request #1450 from freqtrade/pyup/scheduled-update-2018-12-30
Scheduled daily dependency update on sunday
2018-12-30 14:47:46 +01:00
pyup-bot
f32dfc57ca Update ccxt from 1.18.84 to 1.18.86 2018-12-30 13:34:05 +01:00
Misagh
b321c66654 Merge pull request #1448 from freqtrade/wallet_remove_wrong_namedtuple
Remove unused and duplicate datastructure
2018-12-30 12:09:06 +01:00
Matthias
a6e23e6b9b Merge pull request #1449 from freqtrade/pyup/scheduled-update-2018-12-29
Scheduled daily dependency update on saturday
2018-12-29 14:08:16 +01:00
pyup-bot
af85080113 Update ccxt from 1.18.78 to 1.18.84 2018-12-29 13:34:05 +01:00
Matthias
460900ddd7 Remove unused and duplicate datastructure 2018-12-29 09:01:58 +01:00
Matthias
884e1bd9a9 Merge pull request #1447 from mishaker/docs
Readthedocs configuration
2018-12-28 19:46:33 +01:00
misagh
f6ff9b0419 changed to introduction 2018-12-28 16:16:04 +01:00
misagh
0bcadbd854 bigger logo 2018-12-28 15:55:51 +01:00
misagh
78f29a7454 added theme configuration 2018-12-28 15:37:14 +01:00
misagh
71de820adf changing doc theme 2018-12-28 14:52:23 +01:00
misagh
dcf0feefb9 move requirements to docs 2018-12-28 14:28:09 +01:00
misagh
ad696a9d12 removing numpy from docs 2018-12-28 14:22:17 +01:00
misagh
793dd38445 removing unnecessary packages 2018-12-28 14:20:43 +01:00
misagh
ffbe95ef02 adding requirements for docs 2018-12-28 14:18:34 +01:00
misagh
9c442455a2 adding readdoc config 2018-12-28 14:11:22 +01:00
Matthias
a5452d2c75 Merge pull request #1446 from freqtrade/pyup/scheduled-update-2018-12-28
Scheduled daily dependency update on friday
2018-12-28 13:48:58 +01:00
misagh
b45af56199 initial commit for docs 2018-12-28 13:37:44 +01:00
pyup-bot
cf0e31c5a2 Update plotly from 3.4.1 to 3.4.2 2018-12-28 13:34:07 +01:00
pyup-bot
305f2b74e8 Update ccxt from 1.18.74 to 1.18.78 2018-12-28 13:34:05 +01:00
Misagh
b859fa1e42 Merge pull request #1445 from freqtrade/add_plotly_dev
Add plotly requirements file
2018-12-28 11:47:00 +01:00
Misagh
f286e092fb Merge pull request #1444 from freqtrade/replace_ujson
Replace ujson with rapidjson
2018-12-28 11:45:57 +01:00
Matthias
fab7663ab3 Log when dumping to file (instead of print) 2018-12-28 10:46:48 +01:00
Matthias
61f8ce5c0e remove unused imports 2018-12-28 10:44:24 +01:00
Matthias
0f86e218c1 Add plotly requirements file 2018-12-28 10:41:54 +01:00
Matthias
7dc40cdac5 refactor file_load_json to be standalone 2018-12-28 10:25:41 +01:00
Matthias
27abdd9788 Move load_json to misc 2018-12-28 10:04:28 +01:00
Matthias
065b469a10 rename test to avoid naming collision 2018-12-28 10:04:07 +01:00
Matthias
c955415cc3 Switch from ujson to rapidjson 2018-12-28 10:01:16 +01:00
Misagh
98ac2b15ca Merge pull request #1443 from freqtrade/download_data_fix
Reset stake-currency when using config to download pairs
2018-12-27 14:56:10 +01:00
Matthias
1ce8f416ca Reset stake-currency when using config to download pairs 2018-12-27 14:29:26 +01:00
Matthias
29e0a45b5b Merge pull request #1442 from freqtrade/pyup/scheduled-update-2018-12-27
Scheduled daily dependency update on thursday
2018-12-27 14:22:29 +01:00
pyup-bot
1167b24eeb Update ccxt from 1.18.71 to 1.18.74 2018-12-27 13:34:07 +01:00
Misagh
9e735de3c6 Merge pull request #1441 from freqtrade/upd/release_docs
Add release documentation
2018-12-27 13:07:00 +01:00
Matthias
cb654a82db Add release documentation 2018-12-27 12:56:56 +01:00
Matthias
407a978e08 Merge pull request #1437 from freqtrade/after_release
After release
2018-12-27 12:51:05 +01:00
Matthias
24e1de91eb Merge pull request #1438 from freqtrade/release_1804
Release last version for 2018
2018-12-27 12:47:12 +01:00
Matthias
ecb5cdc9e3 Version bump to 0.18.1-dev 2018-12-27 11:47:00 +01:00
Matthias
23d0cea01f Version bump to 0.18.0 2018-12-27 11:45:21 +01:00
Matthias
bb9dd86e77 Merge branch 'master' into release_1804 2018-12-27 11:44:50 +01:00
Misagh
0cbdf10ebe Merge pull request #1440 from freqtrade/fix/market_orders
Fix/market orders
2018-12-27 11:28:40 +01:00
Matthias
9af2fca718 Add handling for market orders
fixes #1427 and #1428
2018-12-27 11:19:26 +01:00
Matthias
20cdabbe9c Add test for market order 2018-12-27 09:31:21 +01:00
Matthias
9d906d013a Merge pull request #1439 from oonid/patch-1
Update installation reference for Raspbian
2018-12-27 08:57:13 +01:00
Oon Arfiandwi
8b38f44da6 Update installation reference for Raspbian
Add `libffi-dev` as an additional package to install before process installation with `pip`.
Add recommendation to use (mini)conda and remove package `scipy`, `pandas`, and `numpy` from `requrements.txt`.
2018-12-27 14:56:35 +08:00
Matthias
3f1248405f Merge pull request #1434 from freqtrade/strategy_explanatin
Enhance strategy explanation
2018-12-27 07:04:30 +01:00
Matthias
5b30815d7b Move "following section" part 2018-12-27 07:03:28 +01:00
Matthias
37cde77e18 Fix typo 2018-12-27 07:01:57 +01:00
Matthias
1fc0dcb9d8 Fix typo in link 2018-12-27 06:58:59 +01:00
Misagh
3411d3d5fa Merge pull request #1436 from freqtrade/remove_convert_dataframe
Remove convert_backtestdata - this is not usefull anymore
2018-12-26 15:53:25 +01:00
Misagh
93a9642abf Merge pull request #1435 from freqtrade/pyup_include_dev_file
Adjust pyup.yml to also "find" requirements-dev
2018-12-26 15:52:40 +01:00
Matthias
b2bc5d9396 Remove convert_backtestdata - this is not usefull anymore 2018-12-26 14:02:17 +01:00
Matthias
32f43d3294 Adjust pyup.yml to also "find" requirements-dev 2018-12-26 13:52:56 +01:00
Matthias
dc4e412e21 Merge pull request #1433 from freqtrade/pyup-scheduled-update-2018-12-26
Scheduled daily dependency update on wednesday
2018-12-26 13:44:47 +01:00
Matthias
5e3e7b6928 correct TOC for bot-optimization.md 2018-12-26 13:42:52 +01:00
Matthias
f2beaf101c Add strategy documentation (fixes #818) 2018-12-26 13:42:46 +01:00
Matthias
d951289862 Refactor strategy documentation 2018-12-26 13:42:37 +01:00
pyup-bot
b28b2369da Update ccxt from 1.18.69 to 1.18.71 2018-12-26 13:33:06 +01:00
Misagh
2f1721a45b Merge pull request #1431 from freqtrade/pyup-scheduled-update-2018-12-25
Scheduled daily dependency update on tuesday
2018-12-25 14:04:53 +01:00
pyup-bot
0e6dbfab5e Update ccxt from 1.18.67 to 1.18.69 2018-12-25 13:33:06 +01:00
Misagh
8e5ea8620b Merge pull request #1430 from freqtrade/download_data_config
Download data config
2018-12-25 13:32:11 +01:00
Matthias
34b93eb952 Load config-file in download_backtest_data - 2018-12-25 13:15:41 +01:00
Matthias
8fbeb700d6 move key/secret in download_backtest_data to correct location 2018-12-25 13:00:48 +01:00
Matthias
22cd84de09 Merge pull request #1429 from freqtrade/pyup-scheduled-update-2018-12-24
Scheduled daily dependency update on monday
2018-12-24 13:46:23 +01:00
pyup-bot
ae51458585 Update ccxt from 1.18.64 to 1.18.67 2018-12-24 13:33:08 +01:00
Matthias
cef1fa8636 Merge pull request #1426 from mishaker/fix_int_remaining
Remaining amount in check_handle_timedout should be treated as float not int.
2018-12-24 13:32:45 +01:00
misagh
a5137e4fa4 comparing float instead of int 2018-12-24 11:39:11 +01:00
Samuel Husso
390f13bbe4 Merge pull request #1423 from freqtrade/pyup-scheduled-update-2018-12-23
Scheduled daily dependency update on sunday
2018-12-24 11:18:02 +02:00
pyup-bot
741e336864 Update ccxt from 1.18.60 to 1.18.64 2018-12-23 13:33:07 +01:00
Matthias
30fe06aa55 Merge pull request #1417 from mishaker/last_candle_close
Adding "copy" as a parameter to klines. default to True
2018-12-22 19:14:18 +01:00
misagh
7243da3afe tests added for klines copy=True 2018-12-22 19:03:42 +01:00
Matthias
f563dec0d6 Merge pull request #1422 from freqtrade/pyup-scheduled-update-2018-12-22
Scheduled daily dependency update on saturday
2018-12-22 14:07:49 +01:00
pyup-bot
7c69dbae30 Update ccxt from 1.18.58 to 1.18.60 2018-12-22 13:33:07 +01:00
Matthias
82a3806015 Merge pull request #1421 from freqtrade/pyup-scheduled-update-2018-12-21
Scheduled daily dependency update on friday
2018-12-21 13:53:40 +01:00
pyup-bot
41ef02a292 Update ccxt from 1.18.50 to 1.18.58 2018-12-21 13:32:06 +01:00
misagh
34e3af6ad4 do not copy DF if copy is false 2018-12-21 10:35:17 +01:00
misagh
a13b30b2de removing test 2018-12-21 10:21:31 +01:00
misagh
a45ec1ed1c adding copy as a parameter to klines 2018-12-21 10:20:01 +01:00
Matthias
7f4b5e43fc Merge pull request #1420 from freqtrade/pyup-scheduled-update-2018-12-20
Scheduled daily dependency update on thursday
2018-12-20 14:45:29 +01:00
pyup-bot
358b5d7e5d Update scikit-learn from 0.20.1 to 0.20.2 2018-12-20 13:32:09 +01:00
pyup-bot
fc4384c96f Update ccxt from 1.18.46 to 1.18.50 2018-12-20 13:32:07 +01:00
Samuel Husso
f54a21ae8f Merge pull request #1419 from freqtrade/pyup-scheduled-update-2018-12-19
Scheduled daily dependency update on wednesday
2018-12-19 16:39:52 +02:00
pyup-bot
ad4952731a Update ccxt from 1.18.40 to 1.18.46 2018-12-19 13:32:09 +01:00
Samuel Husso
ff0fc064c7 Merge pull request #1418 from freqtrade/pyup-scheduled-update-2018-12-18
Scheduled daily dependency update on tuesday
2018-12-18 14:50:38 +02:00
pyup-bot
2e06d52240 Update scipy from 1.1.0 to 1.2.0 2018-12-18 13:32:09 +01:00
pyup-bot
fd4cfefda5 Update ccxt from 1.18.39 to 1.18.40 2018-12-18 13:32:07 +01:00
Samuel Husso
d90a86ddef Merge pull request #1415 from pan-long/requirements-dev
Seperate requirements to run the bot and to develop.
2018-12-18 10:13:21 +02:00
Pan Long
b1e9fa754a Base dev Docker image on freqtradeorg/freqtrade:develop. 2018-12-17 13:53:22 -08:00
misagh
215ded2e0a returning last candle close price for a pair 2018-12-17 21:30:58 +01:00
Pan Long
1483593e65 Fix instructions on building a dev Docker image. 2018-12-17 07:54:28 -08:00
Matthias
a4aa87c21b Merge pull request #1416 from freqtrade/pyup-scheduled-update-2018-12-17
Scheduled daily dependency update on monday
2018-12-17 16:12:21 +01:00
pyup-bot
ac9189ebc0 Update ccxt from 1.18.37 to 1.18.39 2018-12-17 13:32:07 +01:00
Misagh
1dbcab0b09 Merge pull request #1413 from freqtrade/feat/data_helpers
Feat/data helpers
2018-12-17 09:14:10 +01:00
Pan Long
1372095c66 Seperate requirements to run the bot and to develop.
- Add a requirements-dev.txt file which includes additional deps for development.
- Add a Dockerfile.develop which installs all deps for development and also enables dev commands.
- Change related documentations on how to run/dev the bot.
2018-12-16 22:15:45 -08:00
Matthias
5d253f352c Merge pull request #1358 from mishaker/time_in_force
Order Time In Force
2018-12-17 06:38:13 +01:00
Matthias
b3bb98777b Merge branch 'develop' into time_in_force 2018-12-17 06:37:46 +01:00
Matthias
5493d1a7e0 Fix wonrly named module 2018-12-17 06:32:59 +01:00
Matthias
c21bf7d6bb Merge pull request #1414 from mishaker/add_link_order_type
Adding order type explanation link to doc.
2018-12-17 06:09:48 +01:00
misagh
7357d6b089 adding order type explanation link to doc. 2018-12-16 22:13:50 +01:00
misagh
c784b829e5 typo 2018-12-16 22:11:51 +01:00
misagh
213155e6d3 typo 2018-12-16 22:09:46 +01:00
misagh
f756f1ad28 unnecessary explanation removed. 2018-12-16 22:08:50 +01:00
misagh
2e7028442d reformatting 2018-12-16 22:07:03 +01:00
misagh
a967b8918a broken link corrected 2018-12-16 22:05:15 +01:00
misagh
9d8a3b4ec5 docs added 2018-12-16 22:02:29 +01:00
Matthias
806ab3729f Add / fix some comments 2018-12-16 14:14:17 +01:00
Misagh
b6474e4a3c Merge pull request #1411 from freqtrade/xmatthias-readme_link
Add link to contributing for "wanna help"
2018-12-16 13:11:29 +01:00
Matthias
eb7034c7a7 Rename download_backtest_testdata to download_pair_history 2018-12-16 10:33:08 +01:00
Matthias
50938d410a Remove tests for download_pairs 2018-12-16 10:30:13 +01:00
Matthias
8bd4d03e13 remove download_pairs 2018-12-16 10:29:53 +01:00
Matthias
8826a1df5f Add missing tests for trim_tickerlist 2018-12-16 10:19:49 +01:00
Matthias
043cefd60a allow reloading single pair 2018-12-16 10:17:11 +01:00
Matthias
ebb80b6906 remove ujson / json fallback hack as it's now in requirements 2018-12-16 09:58:54 +01:00
Matthias
f5b2430cda Fix docstrings and typo 2018-12-16 09:58:46 +01:00
Matthias
8a3c2a0c63 allow only loading 1 pair if necessary
* simplify tests nad remove unnecessary mocking
2018-12-15 20:32:55 +01:00
Matthias
429f846ad1 Switch load_data to kwargs 2018-12-15 20:31:05 +01:00
Matthias
acd07d40a0 Cleanup some comments and code formatting 2018-12-15 19:52:52 +01:00
Matthias
d421e4e8af update edge description 2018-12-15 19:15:38 +01:00
Matthias
d0c9791ca6 Fix tests to support load_data with dataframe 2018-12-15 15:38:40 +01:00
Matthias
34ea214f7c Fix some tests to use dataframe 2018-12-15 14:42:21 +01:00
Matthias
1c5031b468 load_data to return dataframe 2018-12-15 14:28:37 +01:00
Matthias
c1a32bc6c8 use json_load to load data
- otherwise unforseen problems could appear due to the default beeing ujson
2018-12-15 14:22:49 +01:00
Matthias
b4f1a80dc1 Add edge oneliner to index 2018-12-15 14:21:14 +01:00
Matthias
6c02cc5993 Adjust test to pathlib 2018-12-15 14:14:38 +01:00
Matthias
21aba1620c Replace calls to load_data 2018-12-15 14:10:33 +01:00
Matthias
f261911285 replace os.path with pathlib.Path 2018-12-15 13:54:35 +01:00
Matthias
c82d165713 Merge pull request #1412 from freqtrade/pyup-scheduled-update-2018-12-15
Scheduled daily dependency update on saturday
2018-12-15 13:49:12 +01:00
Matthias
a34c2cf64b Add missing test-module __init__.py 2018-12-15 13:40:02 +01:00
pyup-bot
4ad507f8dd Update ccxt from 1.18.36 to 1.18.37 2018-12-15 13:32:06 +01:00
Matthias
7e463b209c Add link to contributing for "wanna help" 2018-12-15 13:28:00 +01:00
Matthias
df01e8b326 Merge pull request #1410 from freqtrade/pyup-scheduled-update-2018-12-14
Scheduled daily dependency update on friday
2018-12-14 13:54:07 +01:00
pyup-bot
c42d5002a1 Update pytest from 4.0.1 to 4.0.2 2018-12-14 13:32:09 +01:00
pyup-bot
43039aa6ab Update ccxt from 1.18.32 to 1.18.36 2018-12-14 13:32:07 +01:00
Matthias
407139b0e0 remove unused imports 2018-12-14 06:32:49 +01:00
Matthias
17a820e5c0 Move tests from test_optimize to test_history 2018-12-14 06:32:49 +01:00
Matthias
92c800d925 Adjust tests to data.history 2018-12-14 06:32:49 +01:00
Matthias
4ca6aad99a Adjust imports in scripts 2018-12-14 06:32:49 +01:00
Matthias
432cc00283 Adjust imports to data.history 2018-12-14 06:32:49 +01:00
Matthias
0250a96feb Sort imports 2018-12-14 06:32:49 +01:00
Matthias
1a3fcd4771 extract data-handling methods from optimize 2018-12-14 06:32:49 +01:00
Matthias
b38195e9b3 Rename to converter 2018-12-14 06:32:49 +01:00
Matthias
1f29802884 only export what's needed 2018-12-14 06:32:49 +01:00
Matthias
453f62cdfa Adjust imports 2018-12-14 06:32:49 +01:00
Matthias
030ecbfc17 move exchange_helpers to data module 2018-12-14 06:32:49 +01:00
Matthias
04c330f10b Merge pull request #1404 from freqtrade/feat/pass_df
keep DF instead of list
2018-12-13 20:14:32 +01:00
Matthias
aca243086e Fix comment 2018-12-13 19:43:17 +01:00
Samuel Husso
eb7fb2ff0f Merge pull request #1409 from freqtrade/pyup-scheduled-update-2018-12-13
Scheduled daily dependency update on thursday
2018-12-13 14:57:55 +02:00
pyup-bot
6c9c03b3d5 Update ccxt from 1.18.24 to 1.18.32 2018-12-13 13:32:07 +01:00
Misagh
cdd0ef3094 Merge pull request #1407 from freqtrade/edge_docu
Add link to edge documentation in bot-usage
2018-12-12 20:38:44 +01:00
Matthias
6b4bab272f Add link to edge documentation in bot-usage 2018-12-12 20:20:07 +01:00
Matthias
960abeac0a Merge pull request #1406 from mishaker/fix_edge_broken_link
fix edge doc broken link
2018-12-12 20:17:56 +01:00
Misagh
c3af7220f1 Merge pull request #1405 from freqtrade/edge_cli_comments
Fix edge-cli comments
2018-12-12 20:15:14 +01:00
misagh
df5a280169 fix edge doc broken link 2018-12-12 20:11:27 +01:00
Matthias
7e3955b04c Fix edge-cli comments (refer to edge, not backtest 2018-12-12 20:04:14 +01:00
Matthias
5c3dcf3e2b Test for wrong inputs (empty / none-dataframes) in get_signal 2018-12-12 19:35:51 +01:00
Matthias
d6ba4f0e81 Fix last 2 tests to use DF as data container 2018-12-12 19:17:09 +01:00
Matthias
7a533de1a8 Use list ticker history for backtesting 2018-12-12 19:17:09 +01:00
Matthias
fe3990af3d Adjust some tests to dataframe passing 2018-12-12 19:17:09 +01:00
Matthias
627ab9f583 pass around dataframe instead of list 2018-12-12 19:17:09 +01:00
Misagh
a377088421 Merge pull request #1403 from freqtrade/pyup-scheduled-update-2018-12-12
Scheduled daily dependency update on wednesday
2018-12-12 13:46:13 +01:00
misagh
aa1262bea6 typo corrected 2018-12-12 13:33:03 +01:00
pyup-bot
79f5c4adfe Update sqlalchemy from 1.2.14 to 1.2.15 2018-12-12 13:32:09 +01:00
pyup-bot
fd953bab8c Update ccxt from 1.18.18 to 1.18.24 2018-12-12 13:32:07 +01:00
misagh
8d8b53f4d1 added tests for IOC and FOK 2018-12-12 13:05:55 +01:00
Misagh
62f6dd5b17 Merge pull request #1402 from freqtrade/move_fiat
Move fiat to rpc module
2018-12-12 09:32:55 +01:00
Matthias
81b4940eef Adjust tests to new fiat-convert location 2018-12-11 20:27:54 +01:00
Matthias
efc709501a move fiat-convert to rpc - adjust imports 2018-12-11 20:27:30 +01:00
Matthias
0f2c547805 Move fiat-convert to subfolder 2018-12-11 20:26:53 +01:00
Misagh
5a7451a823 Merge pull request #1400 from freqtrade/feat/exchange_styling
Feat/exchange styling
2018-12-11 19:32:49 +01:00
Matthias
0ab8ac1c1d Add test to verify downloading history does not modify
_pairs_last_refresh_time
2018-12-11 19:18:28 +01:00
Misagh
80efef87ab Merge pull request #1401 from freqtrade/pyup-scheduled-update-2018-12-11
Scheduled daily dependency update on tuesday
2018-12-11 13:59:59 +01:00
pyup-bot
70ad8a06c3 Update requests from 2.20.1 to 2.21.0 2018-12-11 13:33:08 +01:00
pyup-bot
97e7b0d9f6 Update ccxt from 1.18.17 to 1.18.18 2018-12-11 13:33:06 +01:00
Matthias
8c1901ad1e Extract caching logic from lowestlevel fetch_ohlcv function 2018-12-11 07:14:39 +01:00
Matthias
523dea4a04 remove hacky workaround not needed anymore 2018-12-10 20:22:41 +01:00
Matthias
e2bff9d5cb Remove assigning klines from download method 2018-12-10 20:22:21 +01:00
Matthias
36de451809 Remove class-level variables 2018-12-10 19:55:21 +01:00
misagh
adcaa8439e test_strategy_override_order_tif added 2018-12-10 19:17:56 +01:00
misagh
e6fd7da43f adding test: create order should consider TIF 2018-12-10 19:09:20 +01:00
misagh
6018f2d252 order status handled in case of IOC and FOK 2018-12-10 18:52:24 +01:00
Misagh
3e479d045d Merge pull request #1399 from freqtrade/pyup-scheduled-update-2018-12-10
Scheduled daily dependency update on monday
2018-12-10 15:04:37 +01:00
pyup-bot
d904667c87 Update ccxt from 1.18.13 to 1.18.17 2018-12-10 13:33:07 +01:00
misagh
866b7aee8e tests fixed 2018-12-09 16:22:21 +01:00
misagh
663e33d2ef if condition refactored 2018-12-09 16:06:00 +01:00
misagh
20d794e265 mistake in previous commit 2018-12-09 16:04:28 +01:00
misagh
2f5c8941eb removing unnecessary default value 2018-12-09 16:00:04 +01:00
misagh
b35199a772 intermediary commit before extracting the logic 2018-12-09 15:59:05 +01:00
misagh
510f78079b conflict with develop resolved 2018-12-09 15:16:38 +01:00
Misagh
c15231d1b9 Merge pull request #1398 from freqtrade/pyup-scheduled-update-2018-12-09
Scheduled daily dependency update on sunday
2018-12-09 15:09:02 +01:00
pyup-bot
acb96eb501 Update ccxt from 1.18.11 to 1.18.13 2018-12-09 13:33:06 +01:00
Misagh
7e476e6144 Merge pull request #1397 from freqtrade/fix/db_migration
Drop indexes on renamed table during migration
2018-12-09 11:15:02 +01:00
Matthias
3b951c3817 Drop indexes on renamed table
avoid naming conflicts on recreate (indexes are not renamed, and keeping
them on backup tables does not really make sense).

fixes #1396
2018-12-09 09:03:17 +01:00
Matthias
2c27736dfe Merge pull request #1390 from freqtrade/feat/dynamic_provider
Dynamic Pairlist provider
2018-12-09 08:39:53 +01:00
Misagh
4e5fb6afd4 Merge pull request #1395 from freqtrade/pyup-scheduled-update-2018-12-08
Scheduled daily dependency update on saturday
2018-12-08 15:35:47 +01:00
pyup-bot
b3b6eda2ba Update ccxt from 1.18.10 to 1.18.11 2018-12-08 13:33:06 +01:00
Misagh
e5a51456ef Merge pull request #1394 from freqtrade/pyup-scheduled-update-2018-12-07
Scheduled daily dependency update on friday
2018-12-07 18:28:22 +01:00
pyup-bot
ac9f19aee5 Update ccxt from 1.18.2 to 1.18.10 2018-12-07 13:34:07 +01:00
Matthias
c38a1d0324 Merge pull request #1393 from freqtrade/pyup-scheduled-update-2018-12-06
Scheduled daily dependency update on thursday
2018-12-06 20:04:24 +01:00
Matthias
aa579bafa4 Merge pull request #1365 from mishaker/edge_position
Fix edge position sizing.
2018-12-06 20:02:31 +01:00
Matthias
8f19c83f6b Refrase documentation 2018-12-06 19:39:25 +01:00
Matthias
a63f123b6d Check if number_assets is defined, as it's required by VolumePairList 2018-12-06 19:36:33 +01:00
Matthias
40376c1e74 Merge pull request #1392 from freqtrade/fix/jsonschema
Specify JsonValidatorversion explicitly
2018-12-06 19:09:44 +01:00
misagh
0ea7dc9272 test added for total open trade stake amount from schalchemy 2018-12-06 13:51:06 +01:00
pyup-bot
bf1841d2a8 Update ccxt from 1.17.583 to 1.18.2 2018-12-06 13:34:06 +01:00
Matthias
0c10719037 Specify JsonValidatorversion explicitly
without doing that, it exclusiveMaximum raises an exception
as jsonschema defaults to the latest version (Draft6)
which changes behaviour of this property.

fixes #1233
2018-12-06 06:57:07 +01:00
Matthias
2f0d7a1aea Add specific test 2018-12-05 20:45:11 +01:00
Matthias
3e2fa58029 load pairlists via resolver 2018-12-05 20:44:56 +01:00
Matthias
43031aa3bb Add missing path-error handler for hyperopt 2018-12-05 20:44:41 +01:00
Matthias
1a10e12861 Documentation and developer documentation 2018-12-05 19:48:59 +01:00
Matthias
21906e4892 Remove duplicate code 2018-12-05 19:48:50 +01:00
Matthias
616ca0237e Merge pull request #1385 from freqtrade/feat/improve_travis
Add commit and message to container
2018-12-05 19:16:37 +01:00
Samuel Husso
ed22419b32 Merge pull request #1391 from freqtrade/pyup-scheduled-update-2018-12-05
Scheduled daily dependency update on wednesday
2018-12-05 15:22:56 +02:00
pyup-bot
37ebe05c6d Update ccxt from 1.17.581 to 1.17.583 2018-12-05 13:34:06 +01:00
misagh
ee26b6bcff Merge branch 'develop' into time_in_force 2018-12-05 10:57:23 +01:00
misagh
d12cc39a5e some visual happyness 2018-12-04 20:59:55 +01:00
misagh
910601ba1d in case exchange doesn’t return order info … 2018-12-04 20:50:35 +01:00
misagh
e3876bcf0f removing AON as it is not supported in binance. will be added once TIF
is added for other exchanges
2018-12-04 20:36:44 +01:00
misagh
b7aa77acdd conflict resolved 2018-12-04 20:28:07 +01:00
Matthias
369a609f61 Merge pull request #1389 from freqtrade/feat/sellreason
publish sellreason in rpc message
2018-12-04 20:27:13 +01:00
Matthias
1c3ce265f1 documentation for pairlists 2018-12-04 20:24:52 +01:00
Matthias
6ab907bef1 Rename config whitelist to pairlist 2018-12-04 20:24:45 +01:00
Matthias
4143e2c032 adapt tests to send sell-reason in sell-message 2018-12-04 19:58:43 +01:00
Matthias
33e9ed5a5e Print sellreason in sell-message 2018-12-04 19:58:26 +01:00
misagh
24f9ea29c6 tests fixed 2018-12-04 17:13:46 +01:00
misagh
e7684b446b capital in trade extracted to a separated argument 2018-12-04 17:05:35 +01:00
Samuel Husso
cc3b84a8de Merge pull request #1388 from freqtrade/pyup-scheduled-update-2018-12-04
Scheduled daily dependency update on tuesday
2018-12-04 14:55:23 +02:00
pyup-bot
32b6cd9dff Update ccxt from 1.17.574 to 1.17.581 2018-12-04 13:34:07 +01:00
Samuel Husso
4a6cec752d Merge pull request #1383 from freqtrade/remove_unnecessary_test
Remove unnecessary test-file
2018-12-04 12:06:47 +02:00
Matthias
bf678164c7 remove default param - fix tests 2018-12-04 07:16:34 +01:00
Matthias
ba3218a87d Support multiple sorting variants 2018-12-04 07:12:56 +01:00
Matthias
ab60571ac7 Add sample config 2018-12-04 06:13:39 +01:00
Matthias
0929f59680 Refactor pairlist-tests 2018-12-03 20:48:51 +01:00
Matthias
18ad3388b4 Some more tests adapted to pairlists 2018-12-03 20:38:15 +01:00
Matthias
ef1208b366 Fix rpc messages 2018-12-03 20:31:25 +01:00
Matthias
1b3ecb8343 Deprecate --dynamic-whitelist 2018-12-03 20:00:18 +01:00
misagh
108d9a1117 function name refactored 2018-12-03 19:55:37 +01:00
misagh
43bafc391f static method added 2018-12-03 19:46:22 +01:00
misagh
b5192193fd total amount passed to edge should consider open trades too 2018-12-03 19:45:00 +01:00
Matthias
3360e777a1 Fix flake adn mypy 2018-12-03 19:29:35 +01:00
Matthias
49a6581dfe Merge pull request #1387 from freqtrade/pyup-scheduled-update-2018-12-03
Scheduled daily dependency update on monday
2018-12-03 14:41:20 +01:00
pyup-bot
11da297c25 Update ccxt from 1.17.572 to 1.17.574 2018-12-03 13:34:08 +01:00
Matthias
f748a63df2 Merge pull request #1386 from pan-long/patch-2
Correct Edge links
2018-12-03 06:38:44 +01:00
Pan Long
99f7c3752a Correct Edge links
It was pointing to a fork instead of freqtrade/freqtrade
2018-12-03 07:21:01 +08:00
Matthias
3a086aac58 Add commit and message to container 2018-12-02 22:49:30 +01:00
Matthias
26187ef6c7 patch exchange_has 2018-12-02 22:18:14 +01:00
Matthias
d09dbfe2e6 Add volumePairList - refactor tests to correct file 2018-12-02 22:07:09 +01:00
Matthias
58c7adae0a Test for blacklist 2018-12-02 22:07:09 +01:00
Matthias
8fd713f3ae validate_whitelist should return the list again 2018-12-02 22:07:09 +01:00
Matthias
1738633efc Fix refresh_whitelist tests 2018-12-02 22:07:09 +01:00
Matthias
e8fbe77ebc Refactor static whitelist to module 2018-12-02 22:07:09 +01:00
Matthias
bb828c308f Remove unnecessary test-file 2018-12-02 16:03:34 +01:00
Matthias
dee6249977 Merge pull request #1381 from freqtrade/pyup-scheduled-update-2018-12-02
Scheduled daily dependency update on sunday
2018-12-02 15:40:35 +01:00
pyup-bot
35d678c505 Update ccxt from 1.17.566 to 1.17.572 2018-12-02 13:34:06 +01:00
Matthias
27c2e80cff Merge pull request #1357 from mishaker/fix_dry_run_stop_price
Fix dry run stop price in case of stoploss on exchange
2018-12-02 09:07:25 +01:00
Matthias
0f4a3365ad Merge pull request #1379 from freqtrade/pyup-scheduled-update-2018-12-01
Scheduled daily dependency update on saturday
2018-12-01 19:12:00 +01:00
pyup-bot
b594bc7ccc Update ccxt from 1.17.563 to 1.17.566 2018-12-01 13:34:06 +01:00
misagh
a5414b8437 flake8 2018-12-01 13:02:45 +01:00
misagh
2d17346b0e explaining arbitrary stake amount in comment 2018-12-01 13:01:51 +01:00
misagh
7ddbaa70ad USDT to ETH conversion. 1 USDT = 1 ETH 2018-12-01 12:06:48 +01:00
misagh
237dc8290f conflict resolved0 2018-12-01 12:00:03 +01:00
misagh
bd673178ce constants removed 2018-12-01 11:56:53 +01:00
misagh
33f1cc13b3 fixing tests 2018-12-01 11:56:16 +01:00
misagh
1d41a91788 stake_amount in case it doesn’t exist 2018-12-01 11:48:41 +01:00
misagh
ee62adf4f7 highlight 2018-12-01 11:41:30 +01:00
misagh
4431e3bdb6 position size explanation enriched 2018-12-01 11:40:13 +01:00
misagh
88d277ea55 adding required config for edge 2018-12-01 11:08:18 +01:00
misagh
9c0be99ff7 rounding float at the end 2018-12-01 11:00:33 +01:00
misagh
c4f17f1c45 config json updated 2018-12-01 10:58:47 +01:00
misagh
86d9457ea1 removing unnecessary variable before returning the result 2018-12-01 10:58:05 +01:00
misagh
9c987fdedd variable name changed (_final_pairs) 2018-12-01 10:56:33 +01:00
misagh
b1c81acfcb another futile one 2018-12-01 10:53:21 +01:00
misagh
042e631f87 rollback on futile change 2018-12-01 10:52:36 +01:00
misagh
bf990ec599 test fixed and flake 2018-12-01 10:50:41 +01:00
misagh
f100432fe8 conflict resolved0 2018-12-01 10:43:26 +01:00
Misagh
24f573f3b0 log "Found no sell signal for whitelisted ..." changed (#1378)
* sell log enriched and put modify on debug
2018-12-01 10:01:11 +01:00
Matthias
e31963f6e1 Merge pull request #1341 from mishaker/stoploss_on_exchange
Stoploss on exchange
2018-12-01 09:46:37 +01:00
Matthias
d4f83a7516 Fix missing mock in test_add_stoploss_on_exchange 2018-11-30 20:15:56 +01:00
Matthias
f04655c012 Test exceptions in sell-stoploss 2018-11-30 20:13:50 +01:00
Matthias
3ac2106a16 Merge pull request #1290 from freqtrade/fix/backtest_toomanyopen
fix backtesting not respecting max_open_trades
2018-11-30 19:17:09 +01:00
Matthias
8effcc2de5 Merge pull request #1374 from freqtrade/refactor_startupmessges
refactor startup_messages to rpc_manger
2018-11-30 19:15:00 +01:00
misagh
4a2d60370c adding dots at the end of sentences 2018-11-30 18:28:18 +01:00
misagh
7e86ec31be tests added for wallet additional functions 2018-11-30 18:23:16 +01:00
misagh
c61ede4182 documentation updated 2018-11-30 18:20:29 +01:00
misagh
aadc9f052a conf schema 2018-11-30 18:10:22 +01:00
misagh
11101e6668 config full aded 2018-11-30 18:07:45 +01:00
misagh
12471e012e added tests for position sizing 2018-11-30 17:59:51 +01:00
misagh
abd88767f8 Merge branch 'develop' into edge_position 2018-11-30 17:50:06 +01:00
misagh
7767470af8 return stake amount of strategy if edge doesn’t have any 2018-11-30 17:50:03 +01:00
misagh
9d005678c3 Merge branch 'develop' into stoploss_on_exchange 2018-11-30 15:13:43 +01:00
Matthias
eedc790b53 Merge pull request #1375 from freqtrade/pyup-scheduled-update-2018-11-30
Scheduled daily dependency update on friday
2018-11-30 14:56:10 +01:00
Matthias
7570a0d0a4 Merge pull request #1376 from mishaker/info_to_debug
"checking sell" INFO log message pollutes logs unnecessarily.
2018-11-30 14:55:46 +01:00
misagh
f554647efd “checking sell” message removed to debug 2018-11-30 14:14:31 +01:00
pyup-bot
42c8888fa1 Update ccxt from 1.17.556 to 1.17.563 2018-11-30 13:34:08 +01:00
misagh
8ff82e3dac Merge branch 'develop' into fix_dry_run_stop_price 2018-11-30 10:37:58 +01:00
Matthias
efcec736b5 refactor startup_messages to rpc_manger
this cleans up freqtradebot slightly
2018-11-29 20:02:12 +01:00
Matthias
49e44d5481 Merge pull request #1373 from mishaker/fix_edge_stoploss
Should fallback to strategy stoploss if Edge cannot provide.
2018-11-29 19:09:25 +01:00
misagh
74ca34f2de flaking8 2018-11-29 18:45:37 +01:00
misagh
3d37c5d767 edge non existing stoploss fixed. solves #1370 2018-11-29 18:31:08 +01:00
Samuel Husso
6cf897a17a Merge pull request #1369 from freqtrade/add_binance_sampleconfig
Add binance config sample, improve invalid pair message
2018-11-29 18:17:16 +02:00
Matthias
a6eb3328d2 Merge pull request #1372 from freqtrade/pyup-scheduled-update-2018-11-29
Scheduled daily dependency update on thursday
2018-11-29 16:17:23 +01:00
pyup-bot
bc2f6d3b71 Update ccxt from 1.17.545 to 1.17.556 2018-11-29 13:34:07 +01:00
misagh
6bedcc5d79 log enriched for time in force 2018-11-29 13:22:41 +01:00
misagh
a61daed8e9 logs enriched 2018-11-29 12:24:04 +01:00
Matthias
cb9104fd8a Add BNB as blacklist to align to documentation 2018-11-29 07:36:37 +01:00
Matthias
38592c6fa6 Add binance config sample, improve invalid pair message 2018-11-29 07:07:47 +01:00
misagh
e698590bb2 avoid generating logs on each iteration 2018-11-28 20:04:56 +01:00
misagh
1a5465fb50 logs enriched in case of stop loss on exchange, test fixed 2018-11-28 19:35:10 +01:00
Samuel Husso
b090b7f4f0 Merge pull request #1368 from freqtrade/pyup-scheduled-update-2018-11-28
Scheduled daily dependency update on wednesday
2018-11-28 18:07:22 +02:00
misagh
c913fef80c stop loss limit when hit, the close price is “average” 2018-11-28 15:45:11 +01:00
misagh
e9305b6592 position size fixed 2018-11-28 15:36:32 +01:00
misagh
fb755880fa logs added in case stop loss on exchange is hit 2018-11-28 14:16:50 +01:00
misagh
da94e97c60 in case trade is not open, then handle_stoploss_on_exchange should not
be called
2018-11-28 13:58:53 +01:00
pyup-bot
50a384130f Update ccxt from 1.17.543 to 1.17.545 2018-11-28 13:34:07 +01:00
misagh
4ffc74d5fa if buy order is rejected or expired the bot should exit the buy loop 2018-11-27 19:05:59 +01:00
Matthias
ff8987f517 Merge pull request #1367 from freqtrade/pyup-scheduled-update-2018-11-27
Scheduled daily dependency update on tuesday
2018-11-27 19:04:10 +01:00
misagh
29f680ec5d fix order type test 2018-11-27 17:26:06 +01:00
misagh
7dbf0fed68 stop loss limit order type corrected 2018-11-27 17:09:51 +01:00
misagh
159ac6e657 edge tests fixed for position sizing 2018-11-27 14:02:34 +01:00
pyup-bot
7832fe7074 Update ccxt from 1.17.539 to 1.17.543 2018-11-27 13:34:08 +01:00
Matthias
5fa3548dbe Merge pull request #1145 from freqtrade/feat/improve_travis
improve travis integration, add test for Docker
2018-11-27 07:04:38 +01:00
misagh
f5a70750f0 edge real position sizing drafted 2018-11-26 21:06:32 +01:00
misagh
6351fe7a7f test added: stoploss_order_id should be null after migration 2018-11-26 20:24:13 +01:00
misagh
3131788639 Merge branch 'develop' into time_in_force 2018-11-26 19:20:01 +01:00
misagh
7f6fc7e90f Lost in git ! 2018-11-26 19:13:36 +01:00
misagh
86354ed258 conflict resolved 2018-11-26 19:08:58 +01:00
misagh
2135976cb8 Merge branch 'develop' of https://github.com/freqtrade/freqtrade into develop 2018-11-26 19:01:29 +01:00
misagh
b63535083e flake8 2018-11-26 18:47:32 +01:00
misagh
1f1770ad5a migration script and and error handling on stop loss order 2018-11-26 18:46:59 +01:00
misagh
17004a5a72 documentation corrected 2018-11-26 18:29:41 +01:00
misagh
b2634e8e08 typo corrected 2018-11-26 18:28:13 +01:00
Samuel Husso
823bc3abb6 Merge pull request #1361 from freqtrade/wallets/add_live_test
Test live mode of get_free
2018-11-26 15:15:42 +02:00
Samuel Husso
a584327d2f Merge pull request #1363 from freqtrade/pyup-scheduled-update-2018-11-26
Scheduled daily dependency update on monday
2018-11-26 15:13:36 +02:00
pyup-bot
d3712c6e40 Update ccxt from 1.17.536 to 1.17.539 2018-11-26 13:34:05 +01:00
Matthias
854af9c124 Merge pull request #1355 from freqtrade/fix/async_followup
async followup PR
2018-11-26 06:55:10 +01:00
Matthias
ad8592f316 Test live mode of get_free 2018-11-26 06:40:20 +01:00
Matthias
797a0e8fd0 Merge pull request #1354 from freqtrade/fix/lambda_test
replace  lambda with Magicmock in test
2018-11-26 06:28:01 +01:00
Matthias
c38f8b8ae2 Merge pull request #1360 from pan-long/patch-1
Use dot to access attribute in NamedTuple
2018-11-26 06:18:16 +01:00
Pan Long
16eec078d7 Use dot to access attribute in NamedTuple
This should fix the crash in #1359
2018-11-26 09:18:29 +08:00
misagh
9f26022ce5 copy/paste corrected 2018-11-25 22:08:42 +01:00
misagh
962b02b079 one last step before tests 2018-11-25 22:02:59 +01:00
misagh
29c23e3136 added time in force in buy and sell functions 2018-11-25 21:38:11 +01:00
misagh
181424e8ea time in force validator added 2018-11-25 21:09:35 +01:00
misagh
ba20b1b5c7 TIF added to constants and json full 2018-11-25 21:05:25 +01:00
misagh
890cef88ab oops, lost in git :/ 2018-11-25 21:02:58 +01:00
misagh
fb7b65c909 time in force drafted
time in force drafted
2018-11-25 20:44:40 +01:00
misagh
6c38bde24a some formatting fixed 2018-11-25 20:21:50 +01:00
misagh
b579768618 dry run set explicitly to False for live stop loss 2018-11-25 20:20:11 +01:00
misagh
5c257730a8 test added for dry run stop loss sell 2018-11-25 20:16:53 +01:00
misagh
59fc67f85b Merge branch 'develop' of https://github.com/freqtrade/freqtrade into develop 2018-11-25 19:48:51 +01:00
misagh
1ad5ccdfb0 dry run condition when sell occurs 2018-11-25 19:48:46 +01:00
misagh
a80c984323 flake8 2018-11-25 19:09:11 +01:00
misagh
92930b2343 test fixed 2018-11-25 19:03:28 +01:00
misagh
5e1fb11124 documentation added for stop loss on exchange 2018-11-25 17:30:06 +01:00
misagh
3e29fbb17a stoploss on exchange added as a parameter to order_types 2018-11-25 17:22:56 +01:00
Matthias
ebaf58b0fe Only sort data if necessary 2018-11-25 15:00:50 +01:00
Matthias
8a43611992 Remove get_candle_history (it's now async)
convert sort-test to async
2018-11-25 14:48:15 +01:00
Matthias
745a517795 Fix comment pointing to wrong column 2018-11-25 14:40:21 +01:00
Matthias
317eba2139 Remove dual instanciation of pairinfo named tuple 2018-11-25 14:38:06 +01:00
Matthias
fd7184718b replace lambda with Magicmock in test 2018-11-25 14:31:46 +01:00
Matthias
200484ab8b Merge pull request #1352 from freqtrade/combine_resolvers
Combine resolvers
2018-11-25 13:52:48 +01:00
Matthias
5a36dd5d5b Merge pull request #1353 from freqtrade/pyup-scheduled-update-2018-11-25
Scheduled daily dependency update on sunday
2018-11-25 13:51:59 +01:00
pyup-bot
e89df448e8 Update ccxt from 1.17.535 to 1.17.536 2018-11-25 13:34:08 +01:00
Matthias
0aa74b8d72 Merge pull request #1348 from mishaker/walletizer
Getting available balance from wallet instead of API call.
2018-11-25 13:25:18 +01:00
misagh
e4744c1ba4 stop loss on exchanged removed from doc 2018-11-25 11:31:30 +01:00
misagh
dcae3a2644 test of check_consistency added 2018-11-25 11:29:04 +01:00
misagh
664b96173e removing NotImplementedError from stoploss_limit 2018-11-25 10:54:36 +01:00
Matthias
1d35428c8d Rename get_valid_objects to get_valid object
it only ever returns one object ...
2018-11-25 10:08:27 +01:00
Matthias
a3477e07eb Remove constructor, it's not needed in the baseclass 2018-11-25 09:55:36 +01:00
misagh
266bd7b9b6 error message improved 2018-11-24 21:42:15 +01:00
Matthias
20de8c82e4 Convert to Pathlib 2018-11-24 20:39:16 +01:00
Matthias
cc7b820978 Move hyperoptresolver to resolvers package 2018-11-24 20:14:08 +01:00
misagh
519b1f00e2 adding strategy config consistency function 2018-11-24 20:12:50 +01:00
Matthias
2c0d0946e6 Small stylistic improvements to strategyresolver 2018-11-24 20:02:29 +01:00
Matthias
21a093bcdb extract resolvers to IResolvers and it's own package 2018-11-24 20:00:02 +01:00
misagh
c8a0956e1b fixed test handle_stoploss_on_exchange 2018-11-24 19:12:00 +01:00
Matthias
e442390b1b Merge pull request #1350 from freqtrade/update_ordertype_docs
Add Note about order types support
2018-11-24 19:09:23 +01:00
misagh
b5192880df [WIP] adding tests for handle_stoploss_on_exchange. 2018-11-24 19:00:59 +01:00
misagh
fe8927136c typo 2018-11-24 18:36:07 +01:00
misagh
b2c0b20a58 added real tests for stop on exchange in dry-run 2018-11-24 18:26:04 +01:00
misagh
000711b025 added stoploss_limit_order for dry-run 2018-11-24 18:08:11 +01:00
misagh
870631f324 1) comments added to handle_sl 2) dry-run force price removed 2018-11-24 17:32:25 +01:00
misagh
531d9ecd0c docstring added 2018-11-24 17:10:51 +01:00
misagh
afd0a054b2 typo corrected 2018-11-24 17:08:12 +01:00
misagh
a9ec5c6699 simplifying if conditions 2018-11-24 17:07:35 +01:00
misagh
1a8e9ebc0f stoploss_order_id added to migration script 2018-11-24 16:53:10 +01:00
misagh
63c2ea110a Not sure why those arguments were there ! 2018-11-24 16:41:17 +01:00
misagh
29347a6931 adding get_free to wallet 2018-11-24 16:37:28 +01:00
Matthias
2b0b7ffa5e Merge pull request #1351 from freqtrade/pyup-scheduled-update-2018-11-24
Scheduled daily dependency update on saturday
2018-11-24 14:09:15 +01:00
pyup-bot
29a4c99d1d Update pytest from 4.0.0 to 4.0.1 2018-11-24 13:34:07 +01:00
pyup-bot
412a627d9e Update ccxt from 1.17.533 to 1.17.535 2018-11-24 13:34:05 +01:00
Matthias
3e8de28b51 Add Note about order types support 2018-11-24 13:26:36 +01:00
Matthias
805f509498 Merge branch 'develop' into fix/backtest_toomanyopen 2018-11-24 10:39:16 +01:00
Matthias
f88a113109 Merge pull request #1349 from freqtrade/pyup-scheduled-update-2018-11-23
Scheduled daily dependency update on friday
2018-11-24 09:39:12 +01:00
misagh
dedf1ff703 refactoring 2018-11-23 20:51:23 +01:00
misagh
89eb3d9f36 blank line removed 2018-11-23 20:49:00 +01:00
misagh
1c2c19b12c the complex in the life of flake8 resolved 2018-11-23 20:47:17 +01:00
misagh
9144a8f79d tests fixed 2018-11-23 20:28:01 +01:00
misagh
5ee2faa182 adding stop loss on exchange after the buy order is fulfilled not
before.
2018-11-23 19:17:36 +01:00
misagh
fea77824d0 handle stop loss on exchange added 2018-11-23 15:17:36 +01:00
pyup-bot
605211dbaf Update scikit-learn from 0.20.0 to 0.20.1 2018-11-23 13:34:09 +01:00
pyup-bot
270624c0c5 Update ccxt from 1.17.529 to 1.17.533 2018-11-23 13:34:08 +01:00
misagh
a9f04609d3 tests fixed 2018-11-23 10:17:10 +01:00
misagh
27a6dcf3fc getting available balance from wallet instead of API call. 2018-11-22 21:23:35 +01:00
misagh
1dde56790c final broken test fixed 2018-11-22 21:12:49 +01:00
misagh
6f0025c6de documentation written 2018-11-22 21:07:33 +01:00
misagh
7faafea8a2 added test for cancelling stop loss before sell 2018-11-22 21:01:39 +01:00
misagh
07ac902451 test exchange added 2018-11-22 20:30:31 +01:00
misagh
ecb2c4dca3 bloody flake8 2018-11-22 19:38:20 +01:00
misagh
cc1422d448 flake8 2018-11-22 19:27:32 +01:00
misagh
3418592908 freqtradebot test added for orders on exchange 2018-11-22 19:25:26 +01:00
misagh
24df093a85 test: only implemented for binance 2018-11-22 17:41:01 +01:00
misagh
2461d86c8d dry run should consider stop loss is hit on limit price 2018-11-22 17:24:45 +01:00
misagh
3a1c378325 typing bugs 2018-11-22 17:14:22 +01:00
Matthias
e4d9d72ff1 Merge pull request #1347 from freqtrade/pyup-scheduled-update-2018-11-22
Scheduled daily dependency update on thursday
2018-11-22 17:08:32 +01:00
misagh
bbe8e4e494 flake8 2018-11-22 17:07:37 +01:00
misagh
da5617624c cancelling stop loss order before selling 2018-11-22 17:02:02 +01:00
misagh
fad7593935 doesn’t have to create another Trade for SL. can be cumulated into the
same.
2018-11-22 16:53:50 +01:00
misagh
bb37b56dea adding stop loss order id to Trade 2018-11-22 16:47:52 +01:00
misagh
3b7e05e07b stop loss order added right after a buy order is executued 2018-11-22 16:26:24 +01:00
misagh
bfbdddff26 stoploss limit order added to exchange 2018-11-22 16:24:40 +01:00
pyup-bot
f73a18c56c Update ccxt from 1.17.522 to 1.17.529 2018-11-22 13:34:06 +01:00
misagh
238dd6413c Merge branch 'develop' into stoploss_on_exchange 2018-11-22 09:39:01 +01:00
Matthias
1810fc9efa Merge pull request #1346 from mishaker/fix_python_beginner_mistake
Refactoring a bit ...
2018-11-22 06:02:15 +01:00
Matthias
8e62fc1c03 Merge pull request #1337 from mishaker/wallet
Wallet data structure added. it is initialized on boot then updated right after any trade happens on the exchange.
2018-11-22 06:00:49 +01:00
misagh
eb53281434 python beginner problem resolved 2018-11-22 00:04:20 +01:00
misagh
4b86b2b7e3 Happy flake8 ! 2018-11-21 23:36:48 +01:00
misagh
3a2134db24 removed Optional 2018-11-21 23:35:44 +01:00
misagh
4d75e9059c None ripped off for optional as wallet must have exchange and currency 2018-11-21 21:05:20 +01:00
misagh
b129750f4d adding “optional” in str 2018-11-21 19:58:28 +01:00
misagh
88f61581d9 1) NamedTuple refactored 2) Missing data handled 2018-11-21 19:47:51 +01:00
misagh
cb3cf960d7 tests added in case of missing data 2018-11-21 19:47:28 +01:00
Matthias
64028647a0 Merge pull request #571 from stephendade/userhyper
Separated out custom hyperopts
2018-11-21 19:14:30 +01:00
misagh
aeb372c2f0 test wallet when api return changes 2018-11-21 17:54:14 +01:00
misagh
5b68940213 update wallet in casse order remaining is zero 2018-11-21 17:48:53 +01:00
misagh
68f81aa2af test wallets moved to tests folder 2018-11-21 17:27:45 +01:00
Matthias
d2ae5e9201 Merge pull request #1343 from freqtrade/pyup-scheduled-update-2018-11-21
Scheduled daily dependency update on wednesday
2018-11-21 16:08:45 +01:00
Matthias
0f21c80335 Merge pull request #1344 from mishaker/fix_twice_refresh_ticker_call
Fix twice refresh ticker call
2018-11-21 16:08:24 +01:00
misagh
c1673aaba3 Merge branch 'develop' into fix_twice_refresh_ticker_call 2018-11-21 14:01:08 +01:00
misagh
64129897f9 refresh_ticker should be called just once per iteration. 2018-11-21 14:00:15 +01:00
pyup-bot
d745e577b4 Update ccxt from 1.17.518 to 1.17.522 2018-11-21 13:34:06 +01:00
Matthias
a3b6004115 IHyperopt: all methods static, somef ixes for mypy 2018-11-20 19:41:07 +01:00
Matthias
7757c53b06 Small fixes 2018-11-20 17:43:49 +01:00
Matthias
5dd013c3b1 Rename hyperopt interface and resolver 2018-11-20 17:40:45 +01:00
Matthias
5a550ef2af Fix docs typo in hyperopt 2018-11-20 17:36:17 +01:00
Matthias
3d006b6cf9 Merge pull request #1342 from freqtrade/pyup-scheduled-update-2018-11-20
Scheduled daily dependency update on tuesday
2018-11-20 17:29:22 +01:00
pyup-bot
ce092742da Update ccxt from 1.17.513 to 1.17.518 2018-11-20 13:34:07 +01:00
Matthias
e69f943911 Add missing semicolon 2018-11-19 20:07:35 +01:00
misagh
b50250139e Drafting stoploss on exchange 2018-11-19 20:02:26 +01:00
Matthias
d72e605cb7 Merge pull request #1330 from freqtrade/feat/diff_order_types
Add support for different order types
2018-11-19 19:55:30 +01:00
Matthias
2ce13713fb Merge pull request #1340 from freqtrade/pyup-scheduled-update-2018-11-19
Scheduled daily dependency update on monday
2018-11-19 19:20:07 +01:00
pyup-bot
cf2d68501c Update ccxt from 1.17.502 to 1.17.513 2018-11-19 13:34:07 +01:00
misagh
003480ad90 flake indentation 2018-11-19 13:01:17 +01:00
misagh
b680681b34 updating wallet at handle timeout functions too 2018-11-19 11:16:07 +01:00
misagh
c033378048 change dict type to Any 2018-11-18 14:57:03 +01:00
misagh
9c549f4513 removing unnecessary private function 2018-11-18 14:39:31 +01:00
misagh
608ce98e1a moving wallets to root 2018-11-18 14:38:31 +01:00
misagh
a92619f18c Added empty lines related to last commit removed 2018-11-18 14:34:31 +01:00
misagh
7cb8b28f58 wallet sync added 2018-11-17 23:03:07 +01:00
misagh
606e41d574 wallet tests added 2018-11-17 22:58:27 +01:00
misagh
f4bb203782 removing persistence update 2018-11-17 21:59:21 +01:00
misagh
d5b47abe98 Wallet table removed 2018-11-17 21:31:06 +01:00
misagh
a0658bb504 comments added 2018-11-17 21:27:42 +01:00
misagh
12f07ee126 space removed 2018-11-17 21:26:41 +01:00
misagh
b815c8fe2d updating wallets whenever a trade happens 2018-11-17 21:22:54 +01:00
misagh
afe52efc8a removing wallet from freq 2018-11-17 21:17:39 +01:00
misagh
82cb0e4d95 putting wallets into a class (doesn’t need to be in persistence) 2018-11-17 21:16:32 +01:00
Matthias
b3e08831f7 Remove rate for market orders 2018-11-17 20:14:50 +01:00
Matthias
c11984d943 Check if exchange supports all configured market types 2018-11-17 19:54:55 +01:00
Matthias
968184ef0d Swap default mode to all limit (defaults to how it was before) 2018-11-17 19:40:53 +01:00
misagh
69dd56b237 wallet sync drafted 2018-11-17 18:47:13 +01:00
Matthias
2799994098 Merge pull request #1336 from freqtrade/pyup-scheduled-update-2018-11-17
Scheduled daily dependency update on saturday
2018-11-17 13:48:38 +01:00
Matthias
492868a966 Seperate different tests within one test clearer 2018-11-17 13:34:23 +01:00
pyup-bot
681659f2d2 Update ccxt from 1.17.500 to 1.17.502 2018-11-17 13:34:06 +01:00
Matthias
a9a157af0f Align tests and test if ordertype is passed to ccxt correctly 2018-11-17 13:29:42 +01:00
Matthias
ef1e20bfe8 Don't add default value for ordertype
sort parameters to align with ccxt
2018-11-17 13:29:24 +01:00
Matthias
543873263a remove need for escaping quote 2018-11-17 13:13:16 +01:00
Matthias
e485aff597 Test failed load on invalid ordertypes 2018-11-17 13:12:11 +01:00
Matthias
9ba281c141 add supported limit values 2018-11-17 13:05:35 +01:00
Matthias
54a86d72f2 Raise error if one of the required ordertypes is not present 2018-11-17 12:59:16 +01:00
Matthias
3ab0cf49af Add order_types to sample strategy 2018-11-17 10:26:15 +01:00
Matthias
6e78efd971 Document "order_types" setting 2018-11-17 10:24:42 +01:00
Matthias
24ed9a8b7d Add loading order_types from config file 2018-11-17 10:14:18 +01:00
Matthias
797de3e0c3 Merge pull request #1333 from freqtrade/pyup-scheduled-update-2018-11-16
Scheduled daily dependency update on friday
2018-11-17 09:37:48 +01:00
pyup-bot
b7abf7dda9 Update ccxt from 1.17.498 to 1.17.500 2018-11-16 13:34:08 +01:00
Matthias
de57da3249 Merge pull request #1328 from mishaker/edge_cli
Edge cli
2018-11-15 20:14:03 +01:00
Matthias
cb1ab0aa49 Merge pull request #1332 from freqtrade/fix_missing_json_mock
Fix missing mock in backtesting
2018-11-15 20:13:47 +01:00
Matthias
98df3c8103 Fix missing mock in backtesting 2018-11-15 20:02:48 +01:00
misagh
db8c8ea4a4 added a space in help 2018-11-15 20:02:07 +01:00
misagh
d05c671a7e adding edge args to bot-usage 2018-11-15 19:54:17 +01:00
Matthias
f1340142f0 Merge pull request #1331 from freqtrade/pyup-scheduled-update-2018-11-15
Scheduled daily dependency update on thursday
2018-11-15 19:51:31 +01:00
Matthias
44c682724d Merge pull request #1327 from mishaker/max_open_trades
Ignoring max_open_trades if it is -1 in config.
2018-11-15 19:40:05 +01:00
Matthias
dcf9930858 improve hyperopt documentation (links) 2018-11-15 19:36:04 +01:00
Matthias
e6baa9ccf2 Switch tests to kwarguments 2018-11-15 19:31:24 +01:00
pyup-bot
52f4d700ca Update pytest from 3.10.1 to 4.0.0 2018-11-15 13:34:08 +01:00
pyup-bot
23295514f6 Update ccxt from 1.17.494 to 1.17.498 2018-11-15 13:34:07 +01:00
misagh
69619030f3 removing unnecessary args from config 2018-11-15 10:50:40 +01:00
misagh
03e6caa501 adding notice about Edge ignoring ROI and TSL in doc 2018-11-15 10:46:36 +01:00
misagh
1cfd19aee3 removing unnecessary args for edge 2018-11-15 10:44:33 +01:00
misagh
f666d1596b renaming edge to edge_cli for command line version 2018-11-15 10:31:56 +01:00
Matthias
6a71f80a9e Add support for different order types 2018-11-15 06:58:24 +01:00
Matthias
4f800bfbc8 Fix pickling-error 2018-11-14 20:25:43 +01:00
misagh
bb9a1e5f9f edge cli tests added 2018-11-14 19:14:34 +01:00
Matthias
23958ba96a Merge pull request #1322 from freqtrade/feat/add_whitelist_rpc
Feat/add whitelist rpc
2018-11-14 19:13:00 +01:00
misagh
9698eee934 documentation added 2018-11-14 17:14:44 +01:00
misagh
ca22a116ad timerange added to args 2018-11-14 17:14:37 +01:00
misagh
5d73b303fe unnecessary libraries removed + arg help enriched 2018-11-14 16:49:16 +01:00
misagh
0767718a17 clear help added to stop losses arg 2018-11-14 16:38:55 +01:00
misagh
dd47d7adb4 cli blank line added to readability 2018-11-14 16:37:26 +01:00
misagh
b0e4aa8eff stop loss range added to args 2018-11-14 16:31:23 +01:00
Matthias
454fba2328 Merge pull request #1329 from freqtrade/pyup-scheduled-update-2018-11-14
Scheduled daily dependency update on wednesday
2018-11-14 14:52:06 +01:00
misagh
36030176bb nb_trades and avg_trade_duration added to cli 2018-11-14 13:38:23 +01:00
misagh
ac0c931492 adding number of trades + average trade duration to edge info 2018-11-14 13:38:04 +01:00
pyup-bot
7fb8ae3e1b Update py_find_1st from 1.1.2 to 1.1.3 2018-11-14 13:34:09 +01:00
pyup-bot
9baf228e8d Update ccxt from 1.17.492 to 1.17.494 2018-11-14 13:34:08 +01:00
misagh
5de3f1d9dd showing result in tabular 2018-11-14 13:25:44 +01:00
misagh
95cbbf1cb5 adding edge configuration to cli 2018-11-14 12:53:20 +01:00
misagh
cf974168e9 Edge cli drafted 2018-11-14 12:37:15 +01:00
misagh
51dfd2bf47 If max_open_trade=-1 means it should be ignored. 2018-11-14 11:37:53 +01:00
Matthias
5c8544a425 Merge pull request #1229 from mishaker/money_mgt
Edge Positioning
2018-11-13 19:33:59 +01:00
Matthias
7fff389f34 Merge pull request #1325 from freqtrade/pyup-scheduled-update-2018-11-13
Scheduled daily dependency update on tuesday
2018-11-13 15:14:15 +01:00
pyup-bot
4e64bc3d29 Update ccxt from 1.17.491 to 1.17.492 2018-11-13 13:34:07 +01:00
Matthias
bdba6186d8 Fix doc-typos 2018-11-12 19:43:20 +01:00
Matthias
79b255179d Merge pull request #1324 from freqtrade/pyup-scheduled-update-2018-11-12
Scheduled daily dependency update on monday
2018-11-12 19:20:26 +01:00
pyup-bot
028139fa3a Update pytest from 3.10.0 to 3.10.1 2018-11-12 13:34:08 +01:00
pyup-bot
c29543dd6c Update ccxt from 1.17.489 to 1.17.491 2018-11-12 13:34:07 +01:00
Matthias
6e1bbb5c88 Merge pull request #1323 from freqtrade/pyup-scheduled-update-2018-11-11
Scheduled daily dependency update on sunday
2018-11-11 14:36:05 +01:00
pyup-bot
261cd7746b Update sqlalchemy from 1.2.13 to 1.2.14 2018-11-11 13:34:07 +01:00
pyup-bot
ef2c31b543 Update ccxt from 1.17.488 to 1.17.489 2018-11-11 13:34:06 +01:00
Matthias
060a1b3fbc Add /whitelist to help message 2018-11-10 20:16:20 +01:00
Matthias
08ef2730a9 Add /whitelist call to telegram 2018-11-10 20:15:06 +01:00
Matthias
62402351b3 Clarify volume selection for dynamic whitelist 2018-11-10 20:14:46 +01:00
Matthias
02527eeea4 Add rpc_whitelist call 2018-11-10 20:07:09 +01:00
Matthias
b3157fc499 Merge pull request #1321 from freqtrade/pyup-scheduled-update-2018-11-10
Scheduled daily dependency update on saturday
2018-11-10 19:37:09 +01:00
misagh
94f56af77d Merge branch 'develop' into money_mgt 2018-11-10 18:45:27 +01:00
misagh
9bbaeb4e6f mypy expression 2018-11-10 18:39:49 +01:00
misagh
aacc1d5004 removing total capital in favour of stake amount 2018-11-10 18:28:05 +01:00
misagh
d613553306 base position on stake amount instead of total capital 2018-11-10 18:22:34 +01:00
misagh
7dd74c374a flake happiness provided 2018-11-10 18:09:32 +01:00
misagh
97fd33d752 adding test for process 2018-11-10 18:03:46 +01:00
misagh
523a9a603c fix tests 2018-11-10 17:20:11 +01:00
pyup-bot
0f2ddbbef2 Update ccxt from 1.17.485 to 1.17.488 2018-11-10 13:34:06 +01:00
Matthias
2e4e5c86da Merge pull request #1320 from freqtrade/contribute_simplify
point out "good first issue" label
2018-11-10 12:45:30 +01:00
misagh
4dcd15da1d improving documentation for positioning 2018-11-09 20:59:28 +01:00
misagh
617a58402f putting edge adjust function in _process not in create_trade 2018-11-09 20:52:03 +01:00
misagh
12e735e831 1) extracting edge_conf to a fixture
2) test cased adjusted to Backtesting
3) Formatted backtesting_details a bit
2018-11-09 20:51:15 +01:00
Matthias
b41633cfe3 point out "good first issue" label 2018-11-09 20:26:10 +01:00
Matthias
59cd4fe0ef Remove boilerplate comments 2018-11-09 19:34:46 +01:00
Matthias
292962d64d Fix tests 2018-11-09 19:34:18 +01:00
Matthias
610d5210ce Merge pull request #1318 from freqtrade/pyup-scheduled-update-2018-11-09
Scheduled daily dependency update on friday
2018-11-09 14:38:48 +01:00
pyup-bot
1840695a1c Update requests from 2.20.0 to 2.20.1 2018-11-09 13:34:08 +01:00
pyup-bot
1db9169cfc Update ccxt from 1.17.481 to 1.17.485 2018-11-09 13:34:07 +01:00
Matthias
5c5fe4c13a Fix test 2018-11-09 07:14:43 +01:00
Matthias
272ff51d51 correctly patch exchange 2018-11-09 07:13:20 +01:00
Matthias
56dcf080a9 Add explicit test for parallel trades 2018-11-09 07:13:20 +01:00
Matthias
93429a58b2 remove TODO 2018-11-09 07:13:20 +01:00
Matthias
9cd2ed5a16 fix hyperopt get_timeframe mock 2018-11-09 07:13:20 +01:00
Matthias
fa4c199aa6 fix some mismatches after rebase 2018-11-09 07:13:20 +01:00
Matthias
2371d1e696 Fix backtest test (don't use 8m file if we use 1m tickers) 2018-11-09 07:13:20 +01:00
Matthias
66487f2a13 require start/end-date argument in backtest 2018-11-09 07:13:20 +01:00
Matthias
83a8d79115 Fix alternate buy/sell (this should respect the sell signal!) 2018-11-09 07:13:20 +01:00
Matthias
db17ccef2b Adapt backtesting-tests to new backtest-logic 2018-11-09 07:13:20 +01:00
Matthias
03cda8e23e remove meaningless backtesting test 2018-11-09 07:12:41 +01:00
Matthias
6729dfa6d3 Add get_timeframe mock for hyperopt 2018-11-09 07:12:41 +01:00
Matthias
96efd12a31 add new options to hyperopt 2018-11-09 07:12:41 +01:00
Matthias
e94da7ca41 inverse backtest logic to loop over time - not pairs (more realistic) 2018-11-09 07:12:41 +01:00
Matthias
cc3d05488b Merge pull request #1206 from xmatthias/contributing
Improve Contributing documentation
2018-11-09 06:56:30 +01:00
Matthias
d8c224c212 Merge pull request #1317 from freqtrade/pyup-scheduled-update-2018-11-08
Scheduled daily dependency update on thursday
2018-11-08 14:36:24 +01:00
misagh
aefc20738a adding dot to the end of the phrase. 2018-11-08 14:18:07 +01:00
misagh
a7dc8f5f4f adding edge configuration to configuration.md and removed whitespaces 2018-11-08 14:16:46 +01:00
misagh
5d850825f5 adding a notice about the incompatibility of Edge with Dynamic whitelist 2018-11-08 14:10:52 +01:00
pyup-bot
cca371c573 Update ccxt from 1.17.480 to 1.17.481 2018-11-08 13:34:06 +01:00
misagh
6d80c03877 removing raise KeyError in test 2018-11-08 00:28:20 +01:00
misagh
e5c6499706 assigning strategy to edge from FreqtradeBot 2018-11-08 00:22:46 +01:00
Matthias
7b62e71f23 Fix some tests and rebase issues 2018-11-07 20:45:52 +01:00
misagh
866da8aaa1 reinitializing Edge calculated data in case of inability to download
backtesting data
2018-11-07 19:24:53 +01:00
misagh
3330d327ed removing reserve keyword “filter”: replaced by “adjust” 2018-11-07 19:03:08 +01:00
misagh
7b80985533 comments on recursive function + indentation of function declaration 2018-11-07 19:00:18 +01:00
misagh
934dd97eb2 adding init for edge test folder 2018-11-07 18:54:21 +01:00
misagh
96a43327ca _pair_info moved to class header for reusibility 2018-11-07 18:52:15 +01:00
misagh
b425cc3e3b adding explanation regarding max trade duration and interval 2018-11-07 18:33:35 +01:00
misagh
f75606d295 formulas markdown style 2018-11-07 18:27:10 +01:00
misagh
5bd3bae5af unifying default value explanations 2018-11-07 18:24:13 +01:00
misagh
bd1b05828e typos in documentation corrected 2018-11-07 18:19:58 +01:00
misagh
6d63de1932 removing unnecessary lib 2018-11-07 18:15:04 +01:00
misagh
553e5656ac forcestoploss refactored 2018-11-07 18:12:46 +01:00
misagh
6838ae0591 conflict resolved => new backtest low and high params 2018-11-07 17:56:21 +01:00
Matthias
a96112f631 Merge pull request #1316 from freqtrade/pyup-scheduled-update-2018-11-07
Scheduled daily dependency update on wednesday
2018-11-07 15:20:25 +01:00
pyup-bot
f4b626eda3 Update ccxt from 1.17.476 to 1.17.480 2018-11-07 13:34:07 +01:00
Matthias
8044846d37 Fix some refactoring problems 2018-11-07 07:05:40 +01:00
Stephen Dade
477515c4b5 Now using resolver for custom hyperopts 2018-11-07 06:58:20 +01:00
Stephen Dade
e0f420983e Updated logger in custom_hyperopt 2018-11-07 06:55:28 +01:00
Stephen Dade
40368bd1b2 Added more hyperopt documentation 2018-11-07 06:55:27 +01:00
Stephen Dade
5816d1c1bd Updated documentation for new hyperopt 2018-11-07 06:52:25 +01:00
Stephen Dade
469db0d434 Decoupled custom hyperopts from hyperopt.py 2018-11-07 06:26:16 +01:00
misagh
23d3a7f31e capital after dots and default values corrected 2018-11-06 20:11:15 +01:00
misagh
1b457e902c config initializer refactored 2018-11-06 19:45:41 +01:00
misagh
bcecaa69d4 removing global variable modification 2018-11-06 19:41:46 +01:00
Matthias
e0489878d8 Merge pull request #1306 from xmatthias/feat/functional_tests
Funcional tests / backtest stoploss alignment
2018-11-06 19:25:31 +01:00
misagh
133ba5d6a1 moving stop loss range to init as it doesn’t need to be called on each
iteration
2018-11-06 19:16:20 +01:00
misagh
5c38b92a75 simplifying calculations to be more readable 2018-11-06 19:05:42 +01:00
Samuel Husso
8adaaf37e0 Merge pull request #1315 from freqtrade/pyup-scheduled-update-2018-11-06
Scheduled daily dependency update on tuesday
2018-11-06 14:56:58 +02:00
pyup-bot
7278cdc7d5 Update ccxt from 1.17.469 to 1.17.476 2018-11-06 13:34:06 +01:00
Matthias
d95ae135a8 Merge pull request #1314 from freqtrade/pyup-scheduled-update-2018-11-05
Scheduled daily dependency update on monday
2018-11-05 20:07:56 +01:00
misagh
5754e51960 more typos 2018-11-05 17:32:12 +01:00
misagh
8a25490146 Typo corrected 2018-11-05 17:28:07 +01:00
misagh
4fbabd3b99 Doc for Edge WIP 3 2018-11-05 17:24:11 +01:00
misagh
49d30ad0e2 doc WIP 2 2018-11-05 15:55:35 +01:00
misagh
cc41317670 documentation WIP 1 2018-11-05 15:49:10 +01:00
misagh
2c0fc3c735 Test latex images 2018-11-05 14:36:10 +01:00
pyup-bot
f92d229f2e Update pytest from 3.9.3 to 3.10.0 2018-11-05 13:34:12 +01:00
pyup-bot
9f03c26c9a Update numpy from 1.15.3 to 1.15.4 2018-11-05 13:34:10 +01:00
pyup-bot
8a5e4c3f30 Update cachetools from 2.1.0 to 3.0.0 2018-11-05 13:34:09 +01:00
pyup-bot
3666e01396 Update ccxt from 1.17.464 to 1.17.469 2018-11-05 13:34:07 +01:00
misagh
ed24d96a79 some formatting for flake8 2018-11-04 18:57:57 +01:00
misagh
8ea9b3746b passing pair to get_trade_stake_amount 2018-11-04 18:51:54 +01:00
misagh
714ac6dd08 Merge branch 'develop' into money_mgt 2018-11-04 18:49:21 +01:00
misagh
120655d262 fixing tests for namedtuple 2018-11-04 18:43:57 +01:00
misagh
14bfd4b7ee using named tuples for keeping pairs data 2018-11-04 18:11:58 +01:00
Matthias
67618e5db5 Merge pull request #1313 from freqtrade/pyup-scheduled-update-2018-11-04
Scheduled daily dependency update on sunday
2018-11-04 13:43:54 +01:00
pyup-bot
81f971f13e Update ccxt from 1.17.459 to 1.17.464 2018-11-04 13:34:07 +01:00
Matthias
7e5fd82f25 Merge pull request #1269 from freqtrade/feat/force_buy
add /forcebuy to telgram handler
2018-11-04 09:25:13 +01:00
Matthias
31a51bd96c Merge pull request #1312 from freqtrade/pyup-scheduled-update-2018-11-03
Scheduled daily dependency update on saturday
2018-11-04 09:17:24 +01:00
misagh
d7821acbf0 refreshing pairs on each iteration 2018-11-03 14:33:17 +01:00
misagh
b6d4e11e88 added minimum win rate to config 2018-11-03 14:31:34 +01:00
pyup-bot
4ab7a0fb5c Update urllib3 from 1.24 to 1.24.1 2018-11-03 13:34:07 +01:00
pyup-bot
7155e5cfeb Update ccxt from 1.17.455 to 1.17.459 2018-11-03 13:34:05 +01:00
misagh
f6498bf5f7 beginning 2018-11-02 22:13:38 +01:00
misagh
9cb660776c money_mgt added 2018-11-02 20:52:46 +01:00
misagh
f77fa6b592 misharizing temporarily for doc 2018-11-02 20:51:55 +01:00
misagh
b57ae20af4 edge doc file added 2018-11-02 20:49:31 +01:00
misagh
83b3323c56 formating md 2018-11-02 20:48:35 +01:00
misagh
85768fcc51 beginning of doc 2018-11-02 20:35:46 +01:00
misagh
7e7af07c04 Merge branch 'develop' into money_mgt 2018-11-02 20:27:56 +01:00
misagh
ece1c8a702 flake8 again and again and again and again
https://www.youtube.com/watch?v=MuSK3pDDYD4
2018-11-02 20:12:48 +01:00
misagh
d1ba994e54 expectancy test completed 2018-11-02 20:07:45 +01:00
misagh
237233c300 renaming tests 2018-11-02 19:59:06 +01:00
misagh
2ef2754ffd flake8 happiness satisfied 2018-11-02 19:55:41 +01:00
misagh
3eeaa50fe5 stoploss and sell signal tests done 2018-11-02 19:54:32 +01:00
Matthias
62b546b180 Merge pull request #1311 from freqtrade/reinstate_plotdf
Reinstate df - which was removed in #1287
2018-11-02 19:21:05 +01:00
misagh
bb791eac7e backtesting remove from import + whitespace removed 2018-11-02 19:19:28 +01:00
Matthias
7f3b4a97dd Reinstate df - which was removed in #1287 2018-11-02 19:14:50 +01:00
misagh
333d505b66 OHLC validation corrected 2018-11-02 19:01:37 +01:00
misagh
080ecae332 whitelist conflict resolved with develop branch 2018-11-02 18:59:31 +01:00
misagh
05b8010460 removing unnecessary test cases 2018-11-02 18:10:03 +01:00
misagh
2f6aafe66c Edge calculation refactored: removing redundant calculations 2018-11-02 18:07:38 +01:00
Matthias
9cadb188d7 Merge pull request #1276 from freqtrade/fix/1272
solve /balance crashes
2018-11-02 16:05:42 +01:00
Matthias
efd59ed9ad Merge pull request #1302 from freqtrade/fix/whitelistprobs
Fix no tickerdata for pair for open trades
2018-11-02 16:03:09 +01:00
Matthias
509b1901b3 Merge pull request #1310 from freqtrade/pyup-scheduled-update-2018-11-02
Scheduled daily dependency update on friday
2018-11-02 14:34:53 +01:00
pyup-bot
17895282a1 Update ccxt from 1.17.448 to 1.17.455 2018-11-02 13:34:06 +01:00
Matthias
b3e144f317 Merge pull request #1308 from freqtrade/pyup-scheduled-update-2018-11-01
Scheduled daily dependency update on thursday
2018-11-01 13:56:23 +01:00
pyup-bot
afc1329126 Update sqlalchemy from 1.2.12 to 1.2.13 2018-11-01 13:34:08 +01:00
pyup-bot
92f9c828e6 Update ccxt from 1.17.439 to 1.17.448 2018-11-01 13:34:07 +01:00
Matthias
8316acfa78 Add column description to test-cases 2018-11-01 13:16:10 +01:00
Matthias
95d271ca5d Fix ROI close-rate calculation to work with fees - adjust tests 2018-11-01 13:14:59 +01:00
Matthias
c21b45647d Fix smoe comments in persistence 2018-11-01 13:05:57 +01:00
Samuel Husso
dedb91645c Merge pull request #1307 from freqtrade/pyup-scheduled-update-2018-10-31
Scheduled daily dependency update on wednesday
2018-10-31 15:56:39 +02:00
pyup-bot
eab15e09f5 Update ccxt from 1.17.436 to 1.17.439 2018-10-31 13:34:07 +01:00
Matthias
a321d0a820 Short descriptors 2018-10-30 20:49:12 +01:00
Matthias
daa9863d0b Try adding headers 2018-10-30 20:45:32 +01:00
Matthias
79d1d63e6f Align data (by halfing all data) 2018-10-30 20:42:34 +01:00
Matthias
8c93760a6d simplify some code 2018-10-30 20:23:31 +01:00
Matthias
fe2c158e59 Adjust sell-rate to new backtesting (respects roi/stoploss) 2018-10-30 20:13:56 +01:00
Matthias
f96f0cdea7 Add additional comment 2018-10-30 20:02:31 +01:00
Matthias
9e921d4410 refactor General bt-utils out of detailed backtest file 2018-10-30 20:02:01 +01:00
Matthias
e442e22a20 refactorign 2018-10-30 19:58:06 +01:00
Matthias
9798e881cb refactor sell_r to sell_reason 2018-10-30 19:44:31 +01:00
Matthias
3679b0948a cleanup interface 2018-10-30 19:37:45 +01:00
Matthias
fc3f8b436d some more cleanup 2018-10-30 19:36:19 +01:00
Matthias
b383113d6c Test open / close time - small refactorings 2018-10-30 19:33:32 +01:00
Samuel Husso
8559b2dc23 Merge pull request #1305 from freqtrade/pyup-scheduled-update-2018-10-30
Scheduled daily dependency update on tuesday
2018-10-30 17:57:55 +02:00
pyup-bot
936441a853 Update ccxt from 1.17.432 to 1.17.436 2018-10-30 13:33:07 +01:00
Matthias
9065e79f53 Cleanup and add some comments on what's happening in the sample snippets 2018-10-29 20:33:27 +01:00
Matthias
6096f3ca47 Simplify functional tests 2018-10-29 20:17:15 +01:00
Matthias
98050ff594 use all min_roi entries 2018-10-29 19:27:23 +01:00
Matthias
233c442af9 Adjust backtest so sell uses stop-loss or roi value as closerate 2018-10-29 19:27:23 +01:00
Matthias
a0e8bfbd77 shift buy-signal to one earlier (backtest shifts it forward to avoid
lookahead)
2018-10-29 19:27:23 +01:00
Matthias
409465ac8e adapt functional tests for new version after rebase 2018-10-29 19:27:23 +01:00
Matthias
30a6e684a6 update with new comments and new data for tc5 2018-10-29 19:27:23 +01:00
Matthias
b8f78cb187 Refactor tests, implement @creslinux's data 2018-10-29 19:27:23 +01:00
Matthias
e0fda7a5dd Add tests validating backtest details 2018-10-29 19:27:23 +01:00
Matthias
2f55cbde35 fix #1298 2018-10-29 19:23:56 +01:00
Samuel Husso
d66ff78e79 Merge pull request #1303 from freqtrade/pyup-scheduled-update-2018-10-29
Scheduled daily dependency update on monday
2018-10-29 14:41:59 +02:00
pyup-bot
35759b372d Update ccxt from 1.17.429 to 1.17.432 2018-10-29 13:33:10 +01:00
Samuel Husso
d733657db5 Merge pull request #1300 from freqtrade/doc/hyperopt_roi
Add hyperopt ROI documentation, add note on methology for hyperopt
2018-10-29 11:59:57 +02:00
Matthias
1121ec0724 don't have nb_assets as parameter - it's a config setting as any other 2018-10-28 14:43:35 +01:00
Matthias
f9fefc14c9 Merge pull request #1301 from freqtrade/pyup-scheduled-update-2018-10-28
Scheduled daily dependency update on sunday
2018-10-28 09:36:06 -04:00
pyup-bot
86ad0c047c Update pytest from 3.9.2 to 3.9.3 2018-10-28 13:33:10 +01:00
pyup-bot
d3387dec45 Update ccxt from 1.17.427 to 1.17.429 2018-10-28 13:33:09 +01:00
Matthias
551dc79cf7 Don't overwrite pair_whitelist in config dict
Doing that will result in an empty whitelist after a short Exchange
downtime
2018-10-28 13:15:49 +01:00
Matthias
7e4a0baef2 improve hyperopt.md 2018-10-27 17:38:15 +02:00
Matthias
a4fc5afb66 Add hyperopt ROI documentation, add note on methology for hyperopt 2018-10-27 17:35:08 +02:00
Matthias
db9a85f4a2 Merge pull request #1282 from freqtrade/feat/add_missingdata_warning
Show warning if part of backtest data is missing
2018-10-27 11:16:10 -04:00
Samuel Husso
20c48fb351 Merge pull request #1299 from freqtrade/pyup-scheduled-update-2018-10-27
Scheduled daily dependency update on saturday
2018-10-27 16:49:15 +03:00
pyup-bot
57d3a6f7a7 Update ccxt from 1.17.421 to 1.17.427 2018-10-27 14:33:06 +02:00
Samuel Husso
ae13f3db17 Merge pull request #1297 from freqtrade/pyup-scheduled-update-2018-10-26
Scheduled daily dependency update on friday
2018-10-27 10:13:02 +03:00
pyup-bot
f860aab094 Update ccxt from 1.17.411 to 1.17.421 2018-10-26 14:33:07 +02:00
Matthias
7e1a30f9bf Merge pull request #1287 from freqtrade/backtest_data_validation
Backtest data validation
2018-10-26 07:21:24 -04:00
misagh
8a316aba35 Merge branch 'develop' into money_mgt 2018-10-25 17:37:46 +02:00
misagh
426db72126 removing test line 2018-10-25 17:24:33 +02:00
misagh
dfeabcf7e5 Edge tests template refactored to be more readable 2018-10-25 16:59:05 +02:00
misagh
c5474794d1 1) open_trade_index refactored 2) sell index is shifted by 1 2018-10-25 16:57:49 +02:00
Matthias
92e2a3c0ea Merge pull request #1296 from freqtrade/pyup-scheduled-update-2018-10-24
Scheduled daily dependency update on wednesday
2018-10-25 06:25:45 -04:00
pyup-bot
5c77dc6b3b Update ccxt from 1.17.402 to 1.17.411 2018-10-24 14:33:06 +02:00
Matthias
1fe066e4ad Merge pull request #1278 from mishaker/mac_install_talib
Mac ta-lib installation + setup.sh script bug resolved
2018-10-23 22:40:53 -04:00
misagh
b09a1d1abe 1) do not download ta-lib as we have it offline. 2) removing ta-lib
directory but not the file
2018-10-23 19:36:57 +02:00
misagh
346e155dd9 Merge branch 'develop' into mac_install_talib 2018-10-23 19:34:34 +02:00
misagh
25daf3a0f7 Merge branch 'develop' into money_mgt 2018-10-23 19:33:13 +02:00
misagh
67ace0a76c trade open time bug resolved (was behind of the market) 2018-10-23 19:32:20 +02:00
Samuel Husso
a063397447 Merge pull request #1295 from freqtrade/pyup-scheduled-update-2018-10-23
Scheduled daily dependency update on tuesday
2018-10-23 16:05:41 +03:00
pyup-bot
b90392f9be Update pytest from 3.9.1 to 3.9.2 2018-10-23 14:33:12 +02:00
pyup-bot
59545013c1 Update numpy from 1.15.2 to 1.15.3 2018-10-23 14:33:10 +02:00
pyup-bot
49d1687229 Update ccxt from 1.17.399 to 1.17.402 2018-10-23 14:33:09 +02:00
Samuel Husso
7da127d28e Merge pull request #1293 from wingsgb/patch-1
Update hyperopt.md
2018-10-23 11:50:49 +03:00
Matthias
11900eff39 Merge pull request #1294 from freqtrade/pyup-scheduled-update-2018-10-22
Scheduled daily dependency update on monday
2018-10-23 04:45:28 +02:00
pyup-bot
764aed2c37 Update ccxt from 1.17.395 to 1.17.399 2018-10-22 14:34:08 +02:00
wingsgb
91dc8644bf Update hyperopt.md 2018-10-22 14:30:01 +13:00
Matthias
130a6f42c5 Merge pull request #1291 from freqtrade/fix/1289_decimals
don't mess with decimals (fixes #1289)
2018-10-21 19:45:54 +02:00
Matthias
7fdd23a29d Merge pull request #1292 from freqtrade/pyup-scheduled-update-2018-10-21
Scheduled daily dependency update on sunday
2018-10-21 19:45:29 +02:00
pyup-bot
ee697d609c Update ccxt from 1.17.393 to 1.17.395 2018-10-21 14:34:06 +02:00
Matthias
530d521d78 Rebuild complete image on "cron" events 2018-10-21 14:00:01 +02:00
Matthias
39efda19f4 Add freqtradeorg/freqtrade docker images to the documentation 2018-10-21 13:33:23 +02:00
Matthias
7301d76cff Remove autobuild for technical as it's not versioned
as it's not versioned and installed from github, we cannot guarantee
which version is in the image.
2018-10-21 13:20:51 +02:00
Matthias
0535660db7 build technical image 2018-10-21 13:15:14 +02:00
Matthias
af7283017b modify travis to build and push docker
* name steps
* only build for master / develop and this branch (for now)
2018-10-21 13:15:14 +02:00
Matthias
907761f994 Install ta-lib in Docker with script
works for travis, works for Docker
2018-10-21 13:15:14 +02:00
Matthias
98738c482a modify install-ta-lib script to support running in docker 2018-10-21 13:14:21 +02:00
Matthias
184b5ca3fc cleanup root dir and create build_helpers 2018-10-21 13:13:18 +02:00
Matthias
677a9e56af remove skipped test (refresh_whitelist is tested in test_acl_pair) 2018-10-21 09:23:07 +02:00
Matthias
202b1d1f0b fix #1289 - we should not modify decimal context 2018-10-21 09:21:32 +02:00
Matthias
2f81dc8ff4 Merge pull request #1288 from freqtrade/pyup-scheduled-update-2018-10-19
Scheduled daily dependency update on friday
2018-10-19 19:19:40 +02:00
pyup-bot
71814ae2d6 Update requests from 2.19.1 to 2.20.0 2018-10-19 14:35:09 +02:00
pyup-bot
c69b87914d Update ccxt from 1.17.392 to 1.17.393 2018-10-19 14:35:07 +02:00
Matthias
7f9f53248c Add validate_backtest_data script 2018-10-18 20:25:21 +02:00
Matthias
3c6d10f03e Print missing value count too 2018-10-18 20:05:57 +02:00
Matthias
bc356c4d65 Return true/false for validation function 2018-10-18 19:48:54 +02:00
Matthias
518dcf5209 Cleanup some tests 8m is not a valid ticker value
not in constants.TICKER_INTERVAL_MINUTES map
2018-10-18 19:43:04 +02:00
Matthias
fb52d32296 Add validate_backtest_data function 2018-10-18 19:42:54 +02:00
misagh
57bc4a866a average trade duration added 2018-10-18 11:09:10 +02:00
Matthias
d7459bbbf3 refactor get_timeframe out of backtesting class 2018-10-17 19:59:33 +02:00
Matthias
8a3272e7c5 don't copy tickerdata_to_dataframe into backtesting
it's used only once, so this does not make sense and hides the origin of
the function
2018-10-17 19:47:19 +02:00
Samuel Husso
f9bbeb79fa Merge pull request #1286 from freqtrade/pyup-scheduled-update-2018-10-17
Scheduled daily dependency update on wednesday
2018-10-17 15:47:50 +03:00
pyup-bot
d953190ca5 Update pytest from 3.8.2 to 3.9.1 2018-10-17 14:34:10 +02:00
pyup-bot
14e5816975 Update urllib3 from 1.23 to 1.24 2018-10-17 14:34:09 +02:00
pyup-bot
5134736c61 Update ccxt from 1.17.388 to 1.17.392 2018-10-17 14:34:07 +02:00
Samuel Husso
ca2ffaa201 Merge pull request #1275 from freqtrade/telegram_enable_stopped
Enable analytical telegram commands when stopped
2018-10-16 19:48:23 +03:00
Matthias
b10d41c28a Merge pull request #1284 from freqtrade/pyup-scheduled-update-2018-10-16
Scheduled daily dependency update on tuesday
2018-10-16 17:06:32 +02:00
pyup-bot
b546f0302e Update ccxt from 1.17.383 to 1.17.388 2018-10-16 14:33:06 +02:00
Matthias
80bd4129f1 Merge pull request #1283 from freqtrade/pyup-scheduled-update-2018-10-15
Scheduled daily dependency update on monday
2018-10-15 14:59:34 +02:00
pyup-bot
b278dcd6db Update ccxt from 1.17.381 to 1.17.383 2018-10-15 14:33:06 +02:00
Matthias
a9642dbcdb Merge pull request #1281 from freqtrade/pyup-scheduled-update-2018-10-14
Scheduled daily dependency update on sunday
2018-10-14 14:43:02 +02:00
Matthias
631ba464f3 Show warning if part of backtest data is missing 2018-10-14 14:40:03 +02:00
pyup-bot
4b9d04a2ca Update ccxt from 1.17.376 to 1.17.381 2018-10-14 14:33:06 +02:00
Matthias
e0081bcb53 Merge pull request #1279 from freqtrade/pyup-scheduled-update-2018-10-13
Scheduled daily dependency update on saturday
2018-10-13 19:35:56 +02:00
pyup-bot
93503d6051 Update ccxt from 1.17.375 to 1.17.376 2018-10-13 14:33:06 +02:00
misagh
6aa9cd1060 removing outliers per pair and not across all pairs 2018-10-12 19:37:23 +02:00
Matthias
cda3ddffac Merge pull request #1277 from freqtrade/feat/hide_dust
Hide low value balances from `/balance`
2018-10-12 19:15:46 +02:00
misagh
fb3fd7cb15 setup script and documentation fixed for TA-Lib and MacOS 2018-10-11 19:26:19 +02:00
misagh
912e9bd15c mac installation path 2018-10-11 19:12:12 +02:00
Matthias
138c8152c2 remove unused import 2018-10-10 22:03:54 +02:00
Matthias
701978a4b1 Add test for dust hiding 2018-10-10 22:01:22 +02:00
Matthias
3628659810 Add tests to check if no failure occurs when pair is not available 2018-10-10 21:50:59 +02:00
Matthias
792d2dbe32 Hide "dust" from /balance 2018-10-10 21:29:40 +02:00
Matthias
a4d2bb6f29 Fix "No market symbol" exception in telegram calls 2018-10-10 21:28:48 +02:00
Matthias
3e8e8a55fa Enable analytical telegram commands when stopped 2018-10-10 20:58:21 +02:00
Matthias
3de3c246b4 add warning-message when forcebuy_enable is true 2018-10-10 20:23:25 +02:00
Matthias
6ff4c9b888 Update docs for /forcesell 2018-10-10 20:08:29 +02:00
Samuel Husso
bb057408b0 Merge pull request #1273 from freqtrade/pyup-scheduled-update-2018-10-10
Scheduled daily dependency update on wednesday
2018-10-10 15:42:30 +03:00
pyup-bot
03fb188555 Update urllib3 from 1.22 to 1.23 2018-10-10 14:29:06 +02:00
Samuel Husso
8cf435f0ba Merge pull request #1267 from freqtrade/telegram_fix
flush session for /forcesell all
2018-10-10 08:44:44 +03:00
Samuel Husso
5b7279793c Merge pull request #1268 from freqtrade/tests_as_packages
convert tests to packages
2018-10-10 08:43:48 +03:00
Matthias
a541d0a931 convert tests to packages
source: https://docs.pytest.org/en/latest/goodpractices.html

If you need to have test modules with the same name, you might add __init__.py files to your tests folder and subfolders, changing them to packages:
2018-10-09 21:13:43 +02:00
Matthias
44c275c801 flush session for /forcesell all 2018-10-09 21:08:56 +02:00
Matthias
8c6d7c48ad Add tests for /forcebuy 2018-10-09 20:04:53 +02:00
Matthias
fbe69cee3f Add /forcebuy to telegram 2018-10-09 19:25:43 +02:00
Matthias
eee0958a58 Merge pull request #1265 from fapaydin/patch-1
Update hyperopt.md
2018-10-09 19:17:06 +02:00
Matthias
29b38bdcbe Merge pull request #1266 from freqtrade/pyup-scheduled-update-2018-10-09
Scheduled daily dependency update on tuesday
2018-10-09 16:59:46 +02:00
pyup-bot
98c1706cdd Update ccxt from 1.17.373 to 1.17.375 2018-10-09 14:29:07 +02:00
fapaydin
b1f016b9c0 Update hyperopt.md
Invalid argument in description. 
Replace --timeperiod with --timerange correct format.
2018-10-09 11:49:25 +03:00
Matthias
5029003957 Allow passing price to buy function 2018-10-09 07:06:11 +02:00
Samuel Husso
2126b00dce Merge pull request #1264 from freqtrade/telegram_help
Add reload_conf to telegram help
2018-10-09 08:01:09 +03:00
Matthias
a20ceb9e31 Add reload_conf to telegram help 2018-10-08 19:43:37 +02:00
Samuel Husso
d23dc3ec41 Merge pull request #1263 from freqtrade/doc_stoploss
Fix spelling in stoploss.md
2018-10-08 20:04:51 +03:00
Matthias
21480d4219 be more expressive on what "this value" is 2018-10-08 15:41:07 +02:00
Matthias
2cd7b40b38 Fix spelling in stoploss.md 2018-10-08 15:39:21 +02:00
Matthias
d7994cf9a3 Merge pull request #1262 from freqtrade/pyup-scheduled-update-2018-10-08
Scheduled daily dependency update on monday
2018-10-08 15:37:54 +02:00
pyup-bot
3af655d170 Update ccxt from 1.17.371 to 1.17.373 2018-10-08 14:29:07 +02:00
Matthias
55a6cac966 Merge pull request #1261 from freqtrade/pyup-scheduled-update-2018-10-06
Scheduled daily dependency update on saturday
2018-10-06 14:50:07 +02:00
pyup-bot
d5409287e0 Update ccxt from 1.17.369 to 1.17.371 2018-10-06 14:29:05 +02:00
Matthias
6dec05b2a5 Merge pull request #1257 from freqtrade/add_general_params
Add general ccxt configuration options
2018-10-06 13:17:21 +02:00
Matthias
1d38c35e6a Fix typo / word repetition 2018-10-06 09:27:49 +02:00
misagh
6d4f68fcdb unnecessary variables removed 2018-10-05 17:25:56 +02:00
misagh
9e44b260e2 BacktestResult removed as it is not used 2018-10-05 17:19:20 +02:00
misagh
bd25212bd6 test case added: edge calculate function 2018-10-05 17:07:20 +02:00
misagh
36d928d411 unnecessary if removed 2018-10-05 17:06:17 +02:00
Matthias
f56bd5f5b7 Merge pull request #1259 from freqtrade/pyup-scheduled-update-2018-10-05
Scheduled daily dependency update on friday
2018-10-05 15:14:47 +02:00
pyup-bot
18c04ab4e2 Update ccxt from 1.17.368 to 1.17.369 2018-10-05 14:29:06 +02:00
Matthias
ce4f0696e1 Add logging to download script and enable ccxt_async_config 2018-10-04 20:38:30 +02:00
Matthias
3973d3697c deprecate ccxt_rate_limt 2018-10-04 20:35:28 +02:00
Matthias
37088cfb39 add to constants 2018-10-04 20:34:48 +02:00
Matthias
ddc1513286 Add ccxt_config to both config_samples 2018-10-04 20:34:33 +02:00
Matthias
d1edcf9dcd Add documentation for ccxt_config 2018-10-04 20:17:19 +02:00
Matthias
e7d5cf9d9d Allow loading of any additional configuration to ccxt
seperated by async and non-async
2018-10-04 20:16:43 +02:00
Matthias
939aa6009a Merge pull request #1256 from freqtrade/pyup-scheduled-update-2018-10-04
Scheduled daily dependency update on thursday
2018-10-04 19:18:44 +02:00
misagh
d3078d7564 test case added: edge stop loss for pair 2018-10-04 18:51:59 +02:00
misagh
77cac9e562 autopep8 applied 2018-10-04 18:07:47 +02:00
misagh
06d75a8bad test cases added: force_stoploss by Edge 2018-10-04 18:05:46 +02:00
pyup-bot
9723300a07 Update ccxt from 1.17.365 to 1.17.368 2018-10-04 14:29:06 +02:00
Matthias
73efe52aea Merge pull request #1255 from freqtrade/update_raspi
update documentation for raspberry
2018-10-04 06:10:17 +02:00
Matthias
3ed486f3a0 update documentation for raspberry
to match as shown in #1236
2018-10-03 19:32:14 +02:00
Matthias
8532e66982 Merge pull request #1254 from freqtrade/pyup-scheduled-update-2018-10-03
Scheduled daily dependency update on wednesday
2018-10-03 19:04:26 +02:00
pyup-bot
fa38772942 Update pytest from 3.8.1 to 3.8.2 2018-10-03 14:29:07 +02:00
pyup-bot
1e669c7228 Update ccxt from 1.17.363 to 1.17.365 2018-10-03 14:29:06 +02:00
misagh
b57d9edda8 Edge test expectancy function (round 1) 2018-10-03 14:23:10 +02:00
misagh
6f79b55845 - function renamed to be more readable
- expectancy bug resolved
2018-10-03 14:22:27 +02:00
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
misagh
a46b3ec9e7 first test completed 2018-10-03 10:37:36 +02:00
Matthias
e1ffc11f00 Merge pull request #1253 from freqtrade/dev-version-bump
develop to version 0.17.3
2018-10-03 08:36:52 +02:00
misagh
de20e142a0 added 9 use cased for testing Edge 2018-10-02 18:05:24 +02:00
misagh
a364a1e40d Edge package test cases drafted 2018-10-02 16:32:57 +02:00
misagh
697493bd01 test cases for Edge package drafted 2018-10-02 16:07:33 +02:00
misagh
23f8980973 edge config added to CONF_SCHEMA and config_full.json.example 2018-10-02 12:42:59 +02:00
misagh
8741a63783 return type of stake_amount set to float 2018-10-02 12:20:48 +02:00
misagh
26b3c3f7a8 removing unnecessary typing 2018-10-02 12:20:30 +02:00
misagh
3b57aef168 config name refactored 2018-10-02 12:16:09 +02:00
misagh
9c4fdc1bc5 initializing Edge in Freqtradebot only if it is enabled 2018-10-02 12:15:54 +02:00
misagh
d634a03455 adding DataFrame type 2018-10-02 11:55:14 +02:00
misagh
e4fc298bd6 typo corrected 2018-10-02 11:53:59 +02:00
misagh
11c3b3fdb9 trade_df unnecessary type removed 2018-10-02 11:53:16 +02:00
misagh
a6c2e40bd4 moving time range to initializer as we have to calculate it once 2018-10-02 11:49:49 +02:00
Samuel Husso
d549fe351c Prepare master for release 0.17.2 2018-10-02 09:24:22 +03:00
Samuel Husso
4a9ed02b9b develop to version 0.17.3 2018-10-02 09:18:54 +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
misagh
f306abb3ee No need for Exchange class in Edge 2018-10-01 17:52:07 +02:00
misagh
8b3631d1ac make “if condition” more readable 2018-10-01 17:49:27 +02:00
misagh
2056b6f5f1 no need to initialize a variable with None 2018-10-01 17:35:27 +02:00
misagh
ad666ac65c autopep8 corrected 2018-10-01 17:33:18 +02:00
misagh
f72fb0ad04 exchange “None” condition removed as Edge is after Exchange anyway 2018-10-01 17:29:33 +02:00
misagh
114fd7feef declaring local variables. using get for configuration 2018-10-01 17:21:40 +02:00
misagh
aa1948750f removing unnecessary constructor docstring 2018-10-01 17:11:48 +02:00
misagh
2a9ca9a3dc Removing future from travis and dockerfile 2018-10-01 17:09:08 +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
misagh
cff83d3e6f bloody autopep8 again 2018-09-28 16:46:42 +02:00
misagh
c8d06e2b0e filter pairs according to expectancy + bug at the end of array resolved 2018-09-28 16:40:34 +02:00
misagh
f15825e3a7 long line broken to two 2018-09-28 14:28:05 +02:00
misagh
e822d5d721 upgrading py_first_1st to 1.1.2: ez_setup.py removed 2018-09-28 14:23:39 +02:00
misagh
96a0fc88cb Moving Edge before refresh_pairs
see comments on edge (line 129)
2018-09-28 14:19:22 +02:00
Matthias
6e66763e5f Only load strategy once during backtesting 2018-09-27 19:23:55 +02:00
misagh
d6415f3499 Merge branch 'develop' into money_mgt 2018-09-27 14:54:18 +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
misagh
21f5a94eca using autopep8 for formatting file 2018-09-27 12:23:46 +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
misagh
25d6ed319a whitespace removed 2018-09-26 17:09:20 +02:00
misagh
24364a56ea keeping mypy happy 2018-09-26 17:03:10 +02:00
misagh
0594deafc6 removing whitespaces and long lines 2018-09-26 16:50:17 +02:00
misagh
75ba6578a3 unused library + trailing whitespaces removed. 2018-09-26 16:36:41 +02:00
misagh
abb398786e Merge branch 'develop' into money_mgt 2018-09-26 16:06:38 +02:00
misagh
fcf837bfda refactoring variable declaration 2018-09-26 16:03:51 +02:00
misagh
87df4e4556 refactoring backslap (round 2) 2018-09-26 15:20:53 +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
misagh
40d73de357 refactoring backslap (round one) 2018-09-24 19:22:30 +02:00
misagh
e8716f16ad calculating expectancy and sort pairs accordingly instead of delta 2018-09-24 17:47:50 +02:00
misagh
a806dd45f2 lost in branches ! typo for some magical unknown reasons 2018-09-24 16:02:29 +02:00
misagh
027ec4d98e test_sell_profit_only_enable_loss and test_create_trade_limit_reached
fixed
2018-09-24 15:47:07 +02:00
misagh
308428644b test_process_trade_creation log message changed: in reality the buy
signal is actually triggered
2018-09-24 15:27:26 +02:00
misagh
76dd754963 test_get_trade_stake_amount and
test_get_trade_stake_amount_no_stake_amount fixed: “pair” arg added to
_get_trade_stake_amount
2018-09-24 15:02:50 +02:00
misagh
303eefda76 test_get_trade_stake_amount_unlimited_amount fixed: “pair” argument
added to _get_trade_stake_amount
2018-09-24 14:55:49 +02:00
misagh
1366783517 Dockerfile: installing future before requirements.txt 2018-09-24 14:28:16 +02:00
misagh
a26131cea3 .travis: install future before requirements.txt 2018-09-24 14:21:37 +02:00
misagh
56050e5afe Merge branch 'develop' into money_mgt 2018-09-24 14:20:05 +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
misagh
29459d7d30 import libraries organized. 2018-09-23 04:51:53 +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
misagh
f1b4e4b36c stop loss range “start, end, step” configurable for Edge 2018-09-22 15:43:41 +02:00
pyup-bot
54b714ba3f Update ccxt from 1.17.327 to 1.17.335 2018-09-22 14:28:05 +02:00
misagh
cf37093e5a empty dict default removed 2018-09-21 22:07:12 +02:00
misagh
d6d3dfdcc2 removing “if ujson_found is not None:” as “json” refers to “ujson” if
it exists
2018-09-21 22:06:09 +02:00
misagh
c11e97caf6 Merge branch 'money_mgt' of https://github.com/mishaker/freqtrade into money_mgt 2018-09-21 21:59:57 +02:00
misagh
66b1eac1db removing unnecessary ujson import 2018-09-21 21:59:35 +02:00
misagh
e1ca80734d removing unnecessary ujson import 2018-09-21 21:58:37 +02:00
misagh
fbc77c1f28 moving stake_currency line back to its initial place 2018-09-21 21:55:36 +02:00
misagh
3b925e46be removing default pair value of _get_trade_stake_amount 2018-09-21 21:48:27 +02:00
misagh
3e3ed947cc added “max_trade_duration” config + using “remove_dumps” config 2018-09-21 21:46:18 +02:00
misagh
61095db071 edge config enriched 2018-09-21 21:36:26 +02:00
misagh
4fd037f83f removing pdb 2018-09-21 18:00:37 +02:00
misagh
4bd956d5b1 test file removed 2018-09-21 17:58:20 +02:00
misagh
74979943ba backslap removed from arguments 2018-09-21 17:57:29 +02:00
misagh
2d432bfa95 backtesting rollbacked to develop branch 2018-09-21 17:54:37 +02:00
misagh
21f4b85c7f Merge branch 'develop' into money_mgt 2018-09-21 17:42:42 +02:00
misagh
4746aea05c test file for edge (will be removed) 2018-09-21 17:42:04 +02:00
misagh
ef52c7b510 edge positioning put into package 2018-09-21 17:41:31 +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
misagh
decaf6c42e Backslap bug on “stop loss triggered” indexes resolved 2018-09-20 16:15:53 +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
misagh
88854cba2d removing only pumps from dataset 2018-09-15 15:53:42 +02:00
misagh
07ba14d1ea backslap bug resolved 2018-09-15 15:52:10 +02:00
pyup-bot
f4d26961c8 Update ccxt from 1.17.291 to 1.17.294 2018-09-15 14:28:05 +02:00
Matthias
5d9c7fa82d add point about malicious code 2018-09-14 19:56:04 +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
misagh
5f68834ccc merging develop into money_mgt (updated to async) 2018-09-14 19:20:49 +02:00
misagh
e30d23cf23 [draft] First version of edge positioning 2018-09-14 19:04:54 +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
Matthias
3f890335c5 Introduce Commiter guide 2018-09-12 20:40:52 +02:00
Matthias
601ae05459 formatting for contributing.md 2018-09-12 20:25:15 +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
misagh
a74953cb4d Draft of money management 2018-09-06 16:59:51 +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
misagh
bc6b80ff38 Edge functionality drafted 2018-08-24 11:59:10 +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
721fb3e326 remove unused profile import 2018-08-14 10:12:57 +02: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
4f5b530dcb Merge pull request #1113 from berlinguyinca/backslap_develop
Backslap develop
2018-08-02 10:30:36 +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
Gert
04d5e857e2 added option to easily switch between backtesting and backslapping from the commandline option 2018-07-31 18:10:23 -07:00
Gert
3428b6666b Merge branch 'develop_current' into backslap_develop 2018-07-31 17:07:30 -07: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
Gert
bf0b1af878 merged latest development branch 2018-07-30 13:43:25 -07:00
Gert
ab66fe1b72 prepared for tracking signals 2018-07-28 19:45:33 -07:00
Gert
ed47240b6e working on develop backslap 2018-07-28 18:30:12 -07:00
Gert
1a673c6ac9 working on moving backslap 2018-07-28 14:23:18 -07:00
creslinux
0372485cf0 Some reason did not push this...
vector calcs redone.
2018-07-26 19:17:00 +00:00
creslinux
93ba80b5a9 Merge remote-tracking branch 'origin/backslap_numpy_poc' into backslap_numpy_poc 2018-07-26 19:15:53 +00:00
creslin
2dc3d6a6b8 Update backtesting.py 2018-07-26 18:42:20 +00:00
creslinux
e39ae45d2f Some reason did not push this...
vector calcs redone.
2018-07-26 18:40:45 +00:00
creslin
482b85182a Update setup.py 2018-07-26 06:53:43 +00:00
creslin
79f931f296 Update backtesting.py 2018-07-25 19:49:25 +00:00
creslinux
3184c85dca default settings to trigger low, take stop 2018-07-17 21:33:11 +00:00
creslinux
8cea0517eb Added stop_stops
stop_stops is an int value
when number of stops in a pair reached the int the pair is stopped
trading.

This allows backtest to align with my pre_trade_mgt that does the same
in dry and live operations
2018-07-17 11:22:38 +00:00
creslinux
ed4bf32f2a Fixed Stop closing in Index 0
when buy opening on Index 1
2018-07-17 10:59:17 +00:00
creslinux
baaf0a5b21 Handle when 0 trades are found in any pairs being tested. 2018-07-17 08:12:21 +00:00
creslinux
a313917347 Handle a buy on the last candle
We will never see this, as buy is on close which is the end of backtest
e.g there is no next candle OPEN to buy at, or on
2018-07-16 18:59:48 +00:00
creslinux
357c8c0ba0 sensible defaults 2018-07-16 18:32:41 +00:00
creslinux
3b0cb7bc33 Added ujson and py_find_1st to setup.py 2018-07-16 18:06:31 +00:00
creslinux
8d5da4e6ad changed defaults
Seperated save trades and print trades options.
2018-07-16 17:48:11 +00:00
creslinux
ec1960530b Added Show trades option
If true, prints trades ordered by date after summary.
Useful for spotting trends.
2018-07-16 17:06:06 +00:00
creslinux
99d16e82c0 disable time calcs output on vector displaying in debug. Excessive. 2018-07-16 16:30:11 +00:00
creslinux
885a653439 Disabled full debug on in last commit
Switched Stops to trigger on Low
Switched Stops to pay stop-rate not close.
2018-07-16 16:18:54 +00:00
creslinux
059aceb582 Disabled full debug on in last commit
Switched Stops to trigger on Low
Switched Stops to pay stop-rate not close.
2018-07-16 16:12:33 +00:00
creslinux
0f3339f74f use ujson to load ticker files 30% faster from disk. 2018-07-16 16:09:42 +00:00
creslinux
4a39a754f4 Fixed: self.use_backslap = Bool on line97
If self.use_backslap = True   Backslap executes
If self.use_backslap = False  Original Backtest Code executes
2018-07-16 15:57:15 +00:00
creslinux
5aaf454f12 GAS trades verified from candle data to excel by hand
All pass
3 sells 1 stop loss
2018-07-16 15:48:06 +00:00
creslinux
fb0edd71ff in tech test 2018-07-16 14:16:35 +00:00
creslinux
eed29a6b8a update 2018-07-16 13:16:18 +00:00
creslinux
7174f27eb8 Rewrite to used algned numpy/dataframes
updated logic
added vector fill for abs/profit/duration in single hit on results.
2018-07-16 12:01:02 +00:00
creslinux
a8b62a21cc hmm 2018-07-15 17:03:47 +00:00
creslinux
4e68362d46 Works with reporting output
Bugs
Calculating % prof ok, but abs wrong

BAT/BTC DF is very broken all OHLC are the same - but exposes a
buy after stop on last row "oddness" to be investigated / handled
2018-07-15 10:33:00 +00:00
creslinux
71c3106f8f Added ABS and Fees
Fixed Index Alignment that was off moving from scratch to FT
Fixed Stoploss,
  its a negative in FT, had been using positve stop -1 in scratch
2018-07-15 09:30:01 +00:00
creslinux
07175ebc5a up 2018-07-14 23:45:06 +00:00
creslinux
90e3c38757 First cut, Bslap
science project replacement for freqtrade backtest analysis

- appprox 300-500x quicker to execute

- fixes stop on close take close price bug in FT
Bslap is configurable but by default stops are triggerd on
low and pay stop price

Not implimented dynamic stops or roi
2018-07-14 22:54:23 +00:00
124 changed files with 11068 additions and 3337 deletions

View File

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

8
.readthedocs.yml Normal file
View File

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

View File

@@ -1,9 +1,15 @@
sudo: true
os:
- linux
dist: xenial
language: python
python:
- 3.6
services:
- docker
env:
global:
- IMAGE_NAME=freqtradeorg/freqtrade
addons:
apt:
packages:
@@ -11,28 +17,43 @@ addons:
- libdw-dev
- binutils-dev
install:
- ./install_ta-lib.sh
- cd build_helpers && ./install_ta-lib.sh; cd ..
- export LD_LIBRARY_PATH=/usr/local/lib:$LD_LIBRARY_PATH
- pip install --upgrade flake8 coveralls pytest-random-order mypy
- pip install -r requirements.txt
- pip install --upgrade pytest-random-order
- pip install -r requirements-dev.txt
- pip install -e .
jobs:
include:
- script:
- stage: tests
script:
- pytest --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/
- coveralls
name: pytest
- script:
- cp config.json.example config.json
- python freqtrade/main.py --datadir freqtrade/tests/testdata backtesting
name: backtest
- script:
- cp config.json.example config.json
- python freqtrade/main.py --datadir freqtrade/tests/testdata hyperopt -e 5
name: hyperopt
- script: flake8 freqtrade
name: flake8
- script: mypy freqtrade
name: mypy
- stage: docker
if: branch in (master, develop, feat/improve_travis) AND (type in (push, cron))
script:
- build_helpers/publish_docker.sh
name: "Build and test and push docker image"
after_success:
- coveralls
notifications:
slack:
secure: bKLXmOrx8e2aPZl7W8DA5BdPAXWGpI5UzST33oc1G/thegXcDVmHBTJrBs4sZak6bgAclQQrdZIsRd2eFYzHLalJEaw6pk7hoAw8SvLnZO0ZurWboz7qg2+aZZXfK4eKl/VUe4sM9M4e/qxjkK+yWG7Marg69c4v1ypF7ezUi1fPYILYw8u0paaiX0N5UX8XNlXy+PBlga2MxDjUY70MuajSZhPsY2pDUvYnMY1D/7XN3cFW0g+3O8zXjF0IF4q1Z/1ASQe+eYjKwPQacE+O8KDD+ZJYoTOFBAPllrtpO1jnOPFjNGf3JIbVMZw4bFjIL0mSQaiSUaUErbU3sFZ5Or79rF93XZ81V7uEZ55vD8KMfR2CB1cQJcZcj0v50BxLo0InkFqa0Y8Nra3sbpV4fV5Oe8pDmomPJrNFJnX6ULQhQ1gTCe0M5beKgVms5SITEpt4/Y0CmLUr6iHDT0CUiyMIRWAXdIgbGh1jfaWOMksybeRevlgDsIsNBjXmYI1Sw2ZZR2Eo2u4R6zyfyjOMLwYJ3vgq9IrACv2w5nmf0+oguMWHf6iWi2hiOqhlAN1W74+3HsYQcqnuM3LGOmuCnPprV1oGBqkPXjIFGpy21gNx4vHfO1noLUyJnMnlu2L7SSuN1CdLsnjJ1hVjpJjPfqB4nn8g12x87TqM1bOm+3Q=
cache:
pip: True
directories:
- $HOME/.cache/pip
- ta-lib
- /usr/local/lib

View File

@@ -1,43 +1,52 @@
# Contribute to freqtrade
# Contributing
Feel like our bot is missing a feature? We welcome your pull requests! Few pointers for contributions:
## Contribute to freqtrade
Feel like our bot is missing a feature? We welcome your pull requests!
Issues labeled [good first issue](https://github.com/freqtrade/freqtrade/labels/good%20first%20issue) can be good first contributions, and will help get you familiar with the codebase.
Few pointers for contributions:
- Create your PR against the `develop` branch, not `master`.
- New features need to contain unit tests and must be PEP8
conformant (max-line-length = 100).
- New features need to contain unit tests and must be PEP8 conformant (max-line-length = 100).
If you are unsure, discuss the feature on our [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE)
or in a [issue](https://github.com/freqtrade/freqtrade/issues) before a PR.
## Getting started
**Before sending the PR:**
Best start by reading the [documentation](https://www.freqtrade.io/) to get a feel for what is possible with the bot, or head straight to the [Developer-documentation](https://www.freqtrade.io/en/latest/developer/) (WIP) which should help you getting started.
## 1. Run unit tests
## Before sending the PR:
### 1. Run unit tests
All unit tests must pass. If a unit test is broken, change your code to
make it pass. It means you have introduced a regression.
**Test the whole project**
#### Test the whole project
```bash
pytest freqtrade
```
**Test only one file**
#### Test only one file
```bash
pytest freqtrade/tests/test_<file_name>.py
```
**Test only one method from one file**
#### Test only one method from one file
```bash
pytest freqtrade/tests/test_<file_name>.py::test_<method_name>
```
## 2. Test if your code is PEP8 compliant
**Install packages** (If not already installed)
```bash
pip3.6 install flake8 coveralls
```
**Run Flake8**
### 2. Test if your code is PEP8 compliant
#### Run Flake8
```bash
flake8 freqtrade
```
@@ -47,16 +56,64 @@ To help with that, we encourage you to install the git pre-commit
hook that will warn you when you try to commit code that fails these checks.
Guide for installing them is [here](http://flake8.pycqa.org/en/latest/user/using-hooks.html).
## 3. Test if all type-hints are correct
### 3. Test if all type-hints are correct
**Install packages** (If not already installed)
``` bash
pip3.6 install mypy
```
**Run mypy**
#### Run mypy
``` bash
mypy freqtrade
```
## (Core)-Committer Guide
### Process: Pull Requests
How to prioritize pull requests, from most to least important:
1. Fixes for broken tests. Broken means broken on any supported platform or Python version.
1. Extra tests to cover corner cases.
1. Minor edits to docs.
1. Bug fixes.
1. Major edits to docs.
1. Features.
Ensure that each pull request meets all requirements in the Contributing document.
### Process: Issues
If an issue is a bug that needs an urgent fix, mark it for the next patch release.
Then either fix it or mark as please-help.
For other issues: encourage friendly discussion, moderate debate, offer your thoughts.
### Process: Your own code changes
All code changes, regardless of who does them, need to be reviewed and merged by someone else.
This rule applies to all the core committers.
Exceptions:
- Minor corrections and fixes to pull requests submitted by others.
- While making a formal release, the release manager can make necessary, appropriate changes.
- Small documentation changes that reinforce existing subject matter. Most commonly being, but not limited to spelling and grammar corrections.
### Responsibilities
- Ensure cross-platform compatibility for every change that's accepted. Windows, Mac & Linux.
- Ensure no malicious code is introduced into the core code.
- Create issues for any major changes and enhancements that you wish to make. Discuss things transparently and get community feedback.
- Keep feature versions as small as possible, preferably one new feature per version.
- Be welcoming to newcomers and encourage diverse new contributors from all backgrounds. See the Python Community Code of Conduct (https://www.python.org/psf/codeofconduct/).
### Becoming a Committer
Contributors may be given commit privileges. Preference will be given to those with:
1. Past contributions to FreqTrade and other related open-source projects. Contributions to FreqTrade include both code (both accepted and pending) and friendly participation in the issue tracker and Pull request reviews. Quantity and quality are considered.
1. A coding style that the other core committers find simple, minimal, and clean.
1. Access to resources for cross-platform development and testing.
1. Time to devote to the project regularly.
Beeing a Committer does not grant write permission on `develop` or `master` for security reasons (Users trust FreqTrade with their Exchange API keys).
After beeing Committer for some time, a Committer may be named Core Committer and given full repository access.

View File

@@ -1,25 +1,26 @@
FROM python:3.6.6-slim-stretch
FROM python:3.7.2-slim-stretch
# Install TA-lib
RUN apt-get update && apt-get -y install curl build-essential && apt-get clean
RUN curl -L http://prdownloads.sourceforge.net/ta-lib/ta-lib-0.4.0-src.tar.gz | \
tar xzvf - && \
cd ta-lib && \
sed -i "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h && \
./configure && make && make install && \
cd .. && rm -rf ta-lib
ENV LD_LIBRARY_PATH /usr/local/lib
RUN apt-get update \
&& apt-get -y install curl build-essential \
&& apt-get clean \
&& pip install --upgrade pip
# Prepare environment
RUN mkdir /freqtrade
WORKDIR /freqtrade
# Install TA-lib
COPY build_helpers/* /tmp/
RUN cd /tmp && /tmp/install_ta-lib.sh && rm -r /tmp/*ta-lib*
ENV LD_LIBRARY_PATH /usr/local/lib
# Install dependencies
COPY requirements.txt /freqtrade/
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"]

9
Dockerfile.develop Normal file
View File

@@ -0,0 +1,9 @@
FROM freqtradeorg/freqtrade:develop
# Install dependencies
COPY requirements-dev.txt /freqtrade/
RUN pip install numpy --no-cache-dir \
&& pip install -r requirements-dev.txt --no-cache-dir
# Empty the ENTRYPOINT to allow all commands
ENTRYPOINT []

6
Dockerfile.technical Normal file
View File

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

102
README.md
View File

@@ -1,10 +1,11 @@
# freqtrade
# Freqtrade
[![Build Status](https://travis-ci.org/freqtrade/freqtrade.svg?branch=develop)](https://travis-ci.org/freqtrade/freqtrade)
[![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
[![Documentation](https://readthedocs.org/projects/freqtrade/badge/)](https://www.freqtrade.io)
[![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](https://codeclimate.com/github/freqtrade/freqtrade/maintainability)
Simple High frequency trading bot for crypto currencies designed to support multi exchanges and be controlled via Telegram.
Freqtrade is a free and open source crypto trading bot written in Python. It is designed to support all major exchanges and be controlled via Telegram. It contains backtesting, plotting and money management tools as well as strategy optimization by machine learning.
![freqtrade](https://raw.githubusercontent.com/freqtrade/freqtrade/develop/docs/assets/freqtrade-screenshot.png)
@@ -27,43 +28,27 @@ hesitate to read the source code and understand the mechanism of this bot.
- [X] [Binance](https://www.binance.com/) ([*Note for binance users](#a-note-on-binance))
- [ ] [113 others to tests](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
## Documentation
We invite you to read the bot documentation to ensure you understand how the bot is working.
Please find the complete documentation on our [website](https://www.freqtrade.io).
## Features
- [x] **Based on Python 3.6+**: For botting on any operating system - Windows, macOS and Linux
- [x] **Persistence**: Persistence is achieved through sqlite
- [x] **Based on Python 3.6+**: For botting on any operating system - Windows, macOS and Linux.
- [x] **Persistence**: Persistence is achieved through sqlite.
- [x] **Dry-run**: Run the bot without playing money.
- [x] **Backtesting**: Run a simulation of your buy/sell strategy.
- [x] **Strategy Optimization by machine learning**: Use machine learning to optimize your buy/sell strategy parameters with real exchange data.
- [x] **Whitelist crypto-currencies**: Select which crypto-currency you want to trade.
- [x] **Edge position sizing** Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. [Learn more](https://www.freqtrade.io/en/latest/edge/).
- [x] **Whitelist crypto-currencies**: Select which crypto-currency you want to trade or use dynamic whitelists.
- [x] **Blacklist crypto-currencies**: Select which crypto-currency you want to avoid.
- [x] **Manageable via Telegram**: Manage the bot with Telegram
- [x] **Manageable via Telegram**: Manage the bot with Telegram.
- [x] **Display profit/loss in fiat**: Display your profit/loss in 33 fiat.
- [x] **Daily summary of profit/loss**: Provide a daily summary of your profit/loss.
- [x] **Performance status report**: Provide a performance status of your current trades.
## Table of Contents
- [Quick start](#quick-start)
- [Documentations](https://github.com/freqtrade/freqtrade/blob/develop/docs/index.md)
- [Installation](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md)
- [Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)
- [Strategy Optimization](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md)
- [Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md)
- [Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
- [Sandbox Testing](https://github.com/freqtrade/freqtrade/blob/develop/docs/sandbox-testing.md)
- [Basic Usage](#basic-usage)
- [Bot commands](#bot-commands)
- [Telegram RPC commands](#telegram-rpc-commands)
- [Support](#support)
- [Help](#help--slack)
- [Bugs](#bugs--issues)
- [Feature Requests](#feature-requests)
- [Pull Requests](#pull-requests)
- [Requirements](#requirements)
- [Min hardware required](#min-hardware-required)
- [Software requirements](#software-requirements)
## Quick start
Freqtrade provides a Linux/macOS script to install all dependencies and help you to configure the bot.
@@ -75,63 +60,52 @@ git checkout develop
./setup.sh --install
```
_Windows installation is explained in [Installation doc](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md)_
For any other type of installation please refer to [Installation doc](https://www.freqtrade.io/en/latest/installation/).
## Documentation
We invite you to read the bot documentation to ensure you understand how the bot is working.
- [Index](https://github.com/freqtrade/freqtrade/blob/develop/docs/index.md)
- [Installation](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md)
- [Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)
- [Bot usage](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md)
- [How to run the bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#bot-commands)
- [How to use Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#backtesting-commands)
- [How to use Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands)
- [Strategy Optimization](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md)
- [Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md)
- [Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
## Basic Usage
### Bot commands
```bash
```
usage: main.py [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
[--strategy-path PATH] [--dynamic-whitelist [INT]]
[--dry-run-db]
{backtesting,hyperopt} ...
[--strategy-path PATH] [--customhyperopt NAME]
[--dynamic-whitelist [INT]] [--db-url PATH]
{backtesting,edge,hyperopt} ...
Simple High Frequency Trading Bot for crypto currencies
Free, open source crypto trading bot
positional arguments:
{backtesting,hyperopt}
{backtesting,edge,hyperopt}
backtesting backtesting module
edge edge module
hyperopt hyperopt module
optional arguments:
-h, --help show this help message and exit
-v, --verbose be verbose
--version show program's version number and exit
-v, --verbose verbose mode (-vv for more, -vvv to get all messages)
--version show program\'s version number and exit
-c PATH, --config PATH
specify configuration file (default: config.json)
-d PATH, --datadir PATH
path to backtest data (default:
freqtrade/tests/testdata
path to backtest data
-s NAME, --strategy NAME
specify strategy class name (default: DefaultStrategy)
--strategy-path PATH specify additional strategy lookup path
--customhyperopt NAME
specify hyperopt class name (default:
DefaultHyperOpts)
--dynamic-whitelist [INT]
dynamically generate and update whitelist based on 24h
BaseVolume (Default 20 currencies)
--dry-run-db Force dry run to use a local DB
"tradesv3.dry_run.sqlite" instead of memory DB. Work
only if dry_run is enabled.
BaseVolume (default: 20) DEPRECATED.
--db-url PATH Override trades database URL, this is useful if
dry_run is enabled or in custom deployments (default:
None)
```
### Telegram RPC commands
Telegram is not mandatory. However, this is a great way to control your bot. More details on our [documentation](https://github.com/freqtrade/freqtrade/blob/develop/docs/index.md)
Telegram is not mandatory. However, this is a great way to control your bot. More details on our [documentation](https://www.freqtrade.io/en/latest/telegram-usage/)
- `/start`: Starts the trader
- `/stop`: Stops the trader
@@ -152,7 +126,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
@@ -190,16 +164,24 @@ in the bug reports.
### [Pull Requests](https://github.com/freqtrade/freqtrade/pulls)
Feel like our bot is missing a feature? We welcome your pull requests!
Please read our
[Contributing document](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
to understand the requirements before sending your pull-requests.
Coding is not a neccessity to contribute - maybe start with improving our documentation?
Issues labeled [good first issue](https://github.com/freqtrade/freqtrade/labels/good%20first%20issue) can be good first contributions, and will help get you familiar with the codebase.
**Note** before starting any major new feature work, *please open an issue describing what you are planning to do* or talk to us on [Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE). This will ensure that interested parties can give valuable feedback on the feature, and let others know that you are working on it.
**Important:** Always create your PR against the `develop` branch, not `master`.
## 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:

11
build_helpers/install_ta-lib.sh Executable file
View File

@@ -0,0 +1,11 @@
if [ ! -f "/usr/local/lib/libta_lib.a" ]; then
tar zxvf ta-lib-0.4.0-src.tar.gz
cd ta-lib \
&& sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h \
&& ./configure \
&& make \
&& which sudo && sudo make install || make install \
&& cd ..
else
echo "TA-lib already installed, skipping installation"
fi

60
build_helpers/publish_docker.sh Executable file
View File

@@ -0,0 +1,60 @@
#!/bin/sh
# - export TAG=`if [ "$TRAVIS_BRANCH" == "develop" ]; then echo "latest"; else echo $TRAVIS_BRANCH ; fi`
# Replace / with _ to create a valid tag
TAG=$(echo "${TRAVIS_BRANCH}" | sed -e "s/\//_/")
# Add commit and commit_message to docker container
echo "${TRAVIS_COMMIT} ${TRAVIS_COMMIT_MESSAGE}" > freqtrade_commit
if [ "${TRAVIS_EVENT_TYPE}" = "cron" ]; then
echo "event ${TRAVIS_EVENT_TYPE}: full rebuild - skipping cache"
docker build -t freqtrade:${TAG} .
else
echo "event ${TRAVIS_EVENT_TYPE}: building with cache"
# Pull last build to avoid rebuilding the whole image
docker pull ${IMAGE_NAME}:${TAG}
docker build --cache-from ${IMAGE_NAME}:${TAG} -t freqtrade:${TAG} .
fi
if [ $? -ne 0 ]; then
echo "failed building image"
return 1
fi
# Run backtest
docker run --rm -it -v $(pwd)/config.json.example:/freqtrade/config.json:ro freqtrade:${TAG} --datadir freqtrade/tests/testdata backtesting
if [ $? -ne 0 ]; then
echo "failed running backtest"
return 1
fi
# Tag image for upload
docker tag freqtrade:$TAG ${IMAGE_NAME}:$TAG
if [ $? -ne 0 ]; then
echo "failed tagging image"
return 1
fi
# Tag as latest for develop builds
if [ "${TRAVIS_BRANCH}" = "develop" ]; then
docker tag freqtrade:$TAG ${IMAGE_NAME}:latest
fi
# Login
echo "$DOCKER_PASS" | docker login -u $DOCKER_USER --password-stdin
if [ $? -ne 0 ]; then
echo "failed login"
return 1
fi
# Show all available images
docker images
docker push ${IMAGE_NAME}
if [ $? -ne 0 ]; then
echo "failed pushing repo"
return 1
fi

View File

@@ -11,13 +11,27 @@
"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",
"key": "your_exchange_key",
"secret": "your_exchange_secret",
"ccxt_rate_limit": true,
"ccxt_config": {"enableRateLimit": true},
"ccxt_async_config": {
"enableRateLimit": false
},
"pair_whitelist": [
"ETH/BTC",
"LTC/BTC",
@@ -39,12 +53,28 @@
"sell_profit_only": false,
"ignore_roi_if_buy_signal": false
},
"edge": {
"enabled": false,
"process_throttle_secs": 3600,
"calculate_since_number_of_days": 7,
"capital_available_percentage": 0.5,
"allowed_risk": 0.01,
"stoploss_range_min": -0.01,
"stoploss_range_max": -0.1,
"stoploss_range_step": -0.01,
"minimum_winrate": 0.60,
"minimum_expectancy": 0.20,
"min_trade_number": 10,
"max_trade_duration_minute": 1440,
"remove_pumps": false
},
"telegram": {
"enabled": true,
"token": "your_telegram_token",
"chat_id": "your_telegram_chat_id"
},
"initial_state": "running",
"forcebuy_enable": false,
"internals": {
"process_throttle_secs": 5
}

View File

@@ -0,0 +1,83 @@
{
"max_open_trades": 3,
"stake_currency": "BTC",
"stake_amount": 0.05,
"fiat_display_currency": "USD",
"ticker_interval" : "5m",
"dry_run": true,
"trailing_stop": false,
"unfilledtimeout": {
"buy": 10,
"sell": 30
},
"bid_strategy": {
"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": "binance",
"key": "your_exchange_key",
"secret": "your_exchange_secret",
"ccxt_config": {"enableRateLimit": true},
"ccxt_async_config": {
"enableRateLimit": false
},
"pair_whitelist": [
"AST/BTC",
"ETC/BTC",
"ETH/BTC",
"EOS/BTC",
"IOTA/BTC",
"LTC/BTC",
"MTH/BTC",
"NCASH/BTC",
"TNT/BTC",
"XMR/BTC",
"XLM/BTC",
"XRP/BTC"
],
"pair_blacklist": [
"BNB/BTC"
]
},
"experimental": {
"use_sell_signal": false,
"sell_profit_only": false,
"ignore_roi_if_buy_signal": false
},
"edge": {
"enabled": false,
"process_throttle_secs": 3600,
"calculate_since_number_of_days": 7,
"capital_available_percentage": 0.5,
"allowed_risk": 0.01,
"stoploss_range_min": -0.01,
"stoploss_range_max": -0.1,
"stoploss_range_step": -0.01,
"minimum_winrate": 0.60,
"minimum_expectancy": 0.20,
"min_trade_number": 10,
"max_trade_duration_minute": 1440,
"remove_pumps": false
},
"telegram": {
"enabled": false,
"token": "your_telegram_token",
"chat_id": "your_telegram_chat_id"
},
"initial_state": "running",
"forcebuy_enable": false,
"internals": {
"process_throttle_secs": 5
}
}

View File

@@ -3,6 +3,7 @@
"stake_currency": "BTC",
"stake_amount": 0.05,
"fiat_display_currency": "USD",
"amount_reserve_percent" : 0.05,
"dry_run": false,
"ticker_interval": "5m",
"trailing_stop": false,
@@ -20,13 +21,46 @@
"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
},
"order_types": {
"buy": "limit",
"sell": "limit",
"stoploss": "market",
"stoploss_on_exchange": "false",
"stoploss_on_exchange_interval": 60
},
"order_time_in_force": {
"buy": "gtc",
"sell": "gtc",
},
"pairlist": {
"method": "VolumePairList",
"config": {
"number_assets": 20,
"sort_key": "quoteVolume"
}
},
"exchange": {
"name": "bittrex",
"key": "your_exchange_key",
"secret": "your_exchange_secret",
"ccxt_rate_limit": true,
"ccxt_config": {"enableRateLimit": true},
"ccxt_async_config": {
"enableRateLimit": false,
"aiohttp_trust_env": false
},
"pair_whitelist": [
"ETH/BTC",
"LTC/BTC",
@@ -41,7 +75,23 @@
],
"pair_blacklist": [
"DOGE/BTC"
]
],
"outdated_offset": 5
},
"edge": {
"enabled": false,
"process_throttle_secs": 3600,
"calculate_since_number_of_days": 7,
"capital_available_percentage": 0.5,
"allowed_risk": 0.01,
"stoploss_range_min": -0.01,
"stoploss_range_max": -0.1,
"stoploss_range_step": -0.01,
"minimum_winrate": 0.60,
"minimum_expectancy": 0.20,
"min_trade_number": 10,
"max_trade_duration_minute": 1440,
"remove_pumps": false
},
"experimental": {
"use_sell_signal": false,
@@ -55,6 +105,7 @@
},
"db_url": "sqlite:///tradesv3.sqlite",
"initial_state": "running",
"forcebuy_enable": false,
"internals": {
"process_throttle_secs": 5
},

View File

@@ -3,11 +3,6 @@
This page explains how to validate your strategy performance by using
Backtesting.
## Table of Contents
- [Test your strategy with Backtesting](#test-your-strategy-with-backtesting)
- [Understand the backtesting result](#understand-the-backtesting-result)
## Test your strategy with Backtesting
Now you have good Buy and Sell strategies, you want to test it against
@@ -171,53 +166,65 @@ The most important in the backtesting is to understand the result.
A backtesting result will look like that:
```
======================================== BACKTESTING REPORT =========================================
| pair | buy count | avg profit % | total profit BTC | avg duration | profit | loss |
|:---------|------------:|---------------:|-------------------:|---------------:|---------:|-------:|
| ETH/BTC | 44 | 0.18 | 0.00159118 | 50.9 | 44 | 0 |
| LTC/BTC | 27 | 0.10 | 0.00051931 | 103.1 | 26 | 1 |
| ETC/BTC | 24 | 0.05 | 0.00022434 | 166.0 | 22 | 2 |
| DASH/BTC | 29 | 0.18 | 0.00103223 | 192.2 | 29 | 0 |
| ZEC/BTC | 65 | -0.02 | -0.00020621 | 202.7 | 62 | 3 |
| XLM/BTC | 35 | 0.02 | 0.00012877 | 242.4 | 32 | 3 |
| BCH/BTC | 12 | 0.62 | 0.00149284 | 50.0 | 12 | 0 |
| POWR/BTC | 21 | 0.26 | 0.00108215 | 134.8 | 21 | 0 |
| ADA/BTC | 54 | -0.19 | -0.00205202 | 191.3 | 47 | 7 |
| XMR/BTC | 24 | -0.43 | -0.00206013 | 120.6 | 20 | 4 |
| TOTAL | 335 | 0.03 | 0.00175246 | 157.9 | 315 | 20 |
2018-06-13 06:57:27,347 - freqtrade.optimize.backtesting - INFO -
====================================== LEFT OPEN TRADES REPORT ======================================
| pair | buy count | avg profit % | total profit BTC | avg duration | profit | loss |
|:---------|------------:|---------------:|-------------------:|---------------:|---------:|-------:|
| ETH/BTC | 3 | 0.16 | 0.00009619 | 25.0 | 3 | 0 |
| LTC/BTC | 1 | -1.00 | -0.00020118 | 1085.0 | 0 | 1 |
| ETC/BTC | 2 | -1.80 | -0.00071933 | 1092.5 | 0 | 2 |
| DASH/BTC | 0 | nan | 0.00000000 | nan | 0 | 0 |
| ZEC/BTC | 3 | -4.27 | -0.00256826 | 1301.7 | 0 | 3 |
| XLM/BTC | 3 | -1.11 | -0.00066744 | 965.0 | 0 | 3 |
| BCH/BTC | 0 | nan | 0.00000000 | nan | 0 | 0 |
| POWR/BTC | 0 | nan | 0.00000000 | nan | 0 | 0 |
| ADA/BTC | 7 | -3.58 | -0.00503604 | 850.0 | 0 | 7 |
| XMR/BTC | 4 | -3.79 | -0.00303456 | 291.2 | 0 | 4 |
| TOTAL | 23 | -2.63 | -0.01213062 | 750.4 | 3 | 20 |
========================================================= BACKTESTING REPORT ========================================================
| pair | buy count | avg profit % | cum profit % | tot profit BTC | tot profit % | avg duration | profit | loss |
|:---------|------------:|---------------:|---------------:|-----------------:|---------------:|:---------------|---------:|-------:|
| ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 | 21 |
| ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 | 8 |
| BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 | 14 |
| DASH/BTC | 13 | -0.08 | -1.07 | -0.00005343 | -0.53 | 4:39:00 | 6 | 7 |
| ENG/BTC | 18 | 1.36 | 24.54 | 0.00122807 | 12.27 | 2:50:00 | 8 | 10 |
| EOS/BTC | 36 | 0.08 | 3.06 | 0.00015304 | 1.53 | 3:34:00 | 16 | 20 |
| ETC/BTC | 26 | 0.37 | 9.51 | 0.00047576 | 4.75 | 6:14:00 | 11 | 15 |
| ETH/BTC | 33 | 0.30 | 9.96 | 0.00049856 | 4.98 | 7:31:00 | 16 | 17 |
| IOTA/BTC | 32 | 0.03 | 1.09 | 0.00005444 | 0.54 | 3:12:00 | 14 | 18 |
| LSK/BTC | 15 | 1.75 | 26.26 | 0.00131413 | 13.13 | 2:58:00 | 6 | 9 |
| LTC/BTC | 32 | -0.04 | -1.38 | -0.00006886 | -0.69 | 4:49:00 | 11 | 21 |
| NANO/BTC | 17 | 1.26 | 21.39 | 0.00107058 | 10.70 | 1:55:00 | 10 | 7 |
| NEO/BTC | 23 | 0.82 | 18.97 | 0.00094936 | 9.48 | 2:59:00 | 10 | 13 |
| REQ/BTC | 9 | 1.17 | 10.54 | 0.00052734 | 5.27 | 3:47:00 | 4 | 5 |
| XLM/BTC | 16 | 1.22 | 19.54 | 0.00097800 | 9.77 | 3:15:00 | 7 | 9 |
| XMR/BTC | 23 | -0.18 | -4.13 | -0.00020696 | -2.07 | 5:30:00 | 12 | 11 |
| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 | 23 |
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 | 15 |
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 243 |
========================================================= SELL REASON STATS =========================================================
| Sell Reason | Count |
|:-------------------|--------:|
| trailing_stop_loss | 205 |
| stop_loss | 166 |
| sell_signal | 56 |
| force_sell | 2 |
====================================================== LEFT OPEN TRADES REPORT ======================================================
| pair | buy count | avg profit % | cum profit % | tot profit BTC | tot profit % | avg duration | profit | loss |
|:---------|------------:|---------------:|---------------:|-----------------:|---------------:|:---------------|---------:|-------:|
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 | 0 |
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 | 0 |
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 | 0 |
```
The 1st table will contain all trades the bot made.
The 2nd table will contain all trades the bot had to `forcesell` at the end of the backtest period to prsent a full picture.
The 2nd table will contain a recap of sell reasons.
The 3rd table will contain all trades the bot had to `forcesell` at the end of the backtest period to present a full picture.
These trades are also included in the first table, but are extracted separately for clarity.
The last line will give you the overall performance of your strategy,
here:
```
TOTAL 419 -0.41 -0.00348593 52.9
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 243 |
```
We understand the bot has made `419` trades for an average duration of
`52.9` min, with a performance of `-0.41%` (loss), that means it has
lost a total of `-0.00348593 BTC`.
We understand the bot has made `429` trades for an average duration of
`4:12:00`, with a performance of `76.20%` (profit), that means it has
earned a total of `0.00762792 BTC` starting with a capital of 0.01 BTC.
The column `avg profit %` shows the average profit for all trades made while the column `cum profit %` sums all the profits/losses.
The column `tot profit %` shows instead the total profit % in relation to allocated capital
(`max_open_trades * stake_amount`). In the above results we have `max_open_trades=2 stake_amount=0.005` in config
so `(76.20/100) * (0.005 * 2) =~ 0.00762792 BTC`.
As you will see your strategy performance will be influenced by your buy
strategy, your sell strategy, and also by the `minimal_roi` and
@@ -256,15 +263,15 @@ There will be an additional table comparing win/losses of the different strategi
Detailed output for all strategies one after the other will be available, so make sure to scroll up.
```
=================================================== Strategy Summary ====================================================
| Strategy | buy count | avg profit % | cum profit % | total profit ETH | avg duration | profit | loss |
|:-----------|------------:|---------------:|---------------:|-------------------:|:----------------|---------:|-------:|
| Strategy1 | 19 | -0.76 | -14.39 | -0.01440287 | 15:48:00 | 15 | 4 |
| Strategy2 | 6 | -2.73 | -16.40 | -0.01641299 | 1 day, 14:12:00 | 3 | 3 |
=========================================================== Strategy Summary ===========================================================
| Strategy | buy count | avg profit % | cum profit % | tot profit BTC | tot profit % | avg duration | profit | loss |
|:------------|------------:|---------------:|---------------:|-----------------:|---------------:|:---------------|---------:|-------:|
| Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 243 |
| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 825 |
```
## Next step
Great, your strategy is profitable. What if the bot can give your the
optimal parameters to use for your strategy?
Your next step is to learn [how to find optimal parameters with Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
Your next step is to learn [how to find optimal parameters with Hyperopt](hyperopt.md)

View File

@@ -1,17 +1,8 @@
# Bot Optimization
# Optimization
This page explains where to customize your strategies, and add new
indicators.
## Table of Contents
- [Install a custom strategy file](#install-a-custom-strategy-file)
- [Customize your strategy](#change-your-strategy)
- [Add more Indicator](#add-more-indicator)
- [Where is the default strategy](#where-is-the-default-strategy)
Since the version `0.16.0` the bot allows using custom strategy file.
## Install a custom strategy file
This is very simple. Copy paste your strategy file into the folder
@@ -33,87 +24,43 @@ use your own file to not have to lose your parameters every time the default
strategy file will be updated on Github. Put your custom strategy file
into the folder `user_data/strategies`.
Best copy the test-strategy and modify this copy to avoid having bot-updates override your changes.
`cp user_data/strategies/test_strategy.py user_data/strategies/awesome-strategy.py`
### Anatomy of a strategy
A strategy file contains all the information needed to build a good strategy:
- Indicators
- Buy strategy rules
- Sell strategy rules
- Minimal ROI recommended
- Stoploss recommended
- Stoploss strongly recommended
The bot also include a sample strategy called `TestStrategy` you can update: `user_data/strategies/test_strategy.py`.
You can test it with the parameter: `--strategy TestStrategy`
``` bash
python3 ./freqtrade/main.py --strategy AwesomeStrategy
```
### Specify custom strategy location
If you want to use a strategy from a different folder you can pass `--strategy-path`
```bash
python3 ./freqtrade/main.py --strategy AwesomeStrategy --strategy-path /some/folder
python3 ./freqtrade/main.py --strategy AwesomeStrategy
```
**For the following section we will use the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py)
file as reference.**
### Buy strategy
!!! Note: Strategies and Backtesting
To avoid problems and unexpected differences between Backtesting and dry/live modes, please be aware
that during backtesting the full time-interval is passed to the `populate_*()` methods at once.
It is therefore best to use vectorized operations (across the whole dataframe, not loops) and
avoid index referencing (`df.iloc[-1]`), but instead use `df.shift()` to get to the previous candle.
Edit the method `populate_buy_trend()` into your strategy file to update your buy strategy.
### Customize Indicators
Sample from `user_data/strategies/test_strategy.py`:
```python
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame populated with indicators
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
dataframe.loc[
(
(dataframe['adx'] > 30) &
(dataframe['tema'] <= dataframe['bb_middleband']) &
(dataframe['tema'] > dataframe['tema'].shift(1))
),
'buy'] = 1
return dataframe
```
### Sell strategy
Edit the method `populate_sell_trend()` into your strategy file to update your sell strategy.
Please note that the sell-signal is only used if `use_sell_signal` is set to true in the configuration.
Sample from `user_data/strategies/test_strategy.py`:
```python
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame populated with indicators
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
dataframe.loc[
(
(dataframe['adx'] > 70) &
(dataframe['tema'] > dataframe['bb_middleband']) &
(dataframe['tema'] < dataframe['tema'].shift(1))
),
'sell'] = 1
return dataframe
```
## Add more Indicators
As you have seen, buy and sell strategies need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file.
Buy and sell strategies need indicators. You can add more indicators by extending the list contained in the method `populate_indicators()` from your strategy file.
You should only add the indicators used in either `populate_buy_trend()`, `populate_sell_trend()`, or to populate another indicator, otherwise performance may suffer.
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected.
Sample:
```python
@@ -157,21 +104,260 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame
return dataframe
```
!!! Note "Want more indicator examples?"
Look into the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py).<br/>
Then uncomment indicators you need.
### Buy signal rules
Edit the method `populate_buy_trend()` in your strategy file to update your buy strategy.
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected.
This will method will also define a new column, `"buy"`, which needs to contain 1 for buys, and 0 for "no action".
Sample from `user_data/strategies/test_strategy.py`:
```python
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the buy signal for the given dataframe
:param dataframe: DataFrame populated with indicators
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
dataframe.loc[
(
(dataframe['adx'] > 30) &
(dataframe['tema'] <= dataframe['bb_middleband']) &
(dataframe['tema'] > dataframe['tema'].shift(1))
),
'buy'] = 1
return dataframe
```
### Sell signal rules
Edit the method `populate_sell_trend()` into your strategy file to update your sell strategy.
Please note that the sell-signal is only used if `use_sell_signal` is set to true in the configuration.
It's important to always return the dataframe without removing/modifying the columns `"open", "high", "low", "close", "volume"`, otherwise these fields would contain something unexpected.
This will method will also define a new column, `"sell"`, which needs to contain 1 for sells, and 0 for "no action".
Sample from `user_data/strategies/test_strategy.py`:
```python
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators, populates the sell signal for the given dataframe
:param dataframe: DataFrame populated with indicators
:param metadata: Additional information, like the currently traded pair
:return: DataFrame with buy column
"""
dataframe.loc[
(
(dataframe['adx'] > 70) &
(dataframe['tema'] > dataframe['bb_middleband']) &
(dataframe['tema'] < dataframe['tema'].shift(1))
),
'sell'] = 1
return dataframe
```
### Minimal ROI
This dict defines the minimal Return On Investment (ROI) a trade should reach before selling, independent from the sell signal.
It is of the following format, with the dict key (left side of the colon) being the minutes passed since the trade opened, and the value (right side of the colon) being the percentage.
```python
minimal_roi = {
"40": 0.0,
"30": 0.01,
"20": 0.02,
"0": 0.04
}
```
The above configuration would therefore mean:
- Sell whenever 4% profit was reached
- Sell when 2% profit was reached (in effect after 20 minutes)
- Sell when 1% profit was reached (in effect after 30 minutes)
- Sell when trade is non-loosing (in effect after 40 minutes)
The calculation does include fees.
To disable ROI completely, set it to an insanely high number:
```python
minimal_roi = {
"0": 100
}
```
While technically not completely disabled, this would sell once the trade reaches 10000% Profit.
### Stoploss
Setting a stoploss is highly recommended to protect your capital from strong moves against you.
Sample:
``` python
stoploss = -0.10
```
This would signify a stoploss of -10%.
If your exchange supports it, it's recommended to also set `"stoploss_on_exchange"` in the order dict, so your stoploss is on the exchange and cannot be missed for network-problems (or other problems).
For more information on order_types please look [here](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md#understand-order_types).
### Ticker interval
This is the set of candles the bot should download and use for the analysis.
Common values are `"1m"`, `"5m"`, `"15m"`, `"1h"`, however all values supported by your exchange should work.
Please note that the same buy/sell signals may work with one interval, but not the other.
This setting is accessible within the strategy by using `self.ticker_interval`.
### Metadata dict
The metadata-dict (available for `populate_buy_trend`, `populate_sell_trend`, `populate_indicators`) contains additional information.
Currently this is `pair`, which can be accessed using `metadata['pair']` - and will return a pair in the format `XRP/BTC`.
### Want more indicator examples
The Metadata-dict should not be modified and does not persist information across multiple calls.
Instead, have a look at the section [Storing information](#Storing-information)
Look into the [user_data/strategies/test_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/user_data/strategies/test_strategy.py).
Then uncomment indicators you need.
### Storing information
Storing information can be accomplished by crating a new dictionary within the strategy class.
The name of the variable can be choosen at will, but should be prefixed with `cust_` to avoid naming collisions with predefined strategy variables.
```python
class Awesomestrategy(IStrategy):
# Create custom dictionary
cust_info = {}
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
# Check if the entry already exists
if "crosstime" in self.cust_info[metadata["pair"]:
self.cust_info[metadata["pair"]["crosstime"] += 1
else:
self.cust_info[metadata["pair"]["crosstime"] = 1
```
!!! Warning:
The data is not persisted after a bot-restart (or config-reload). Also, the amount of data should be kept smallish (no DataFrames and such), otherwise the bot will start to consume a lot of memory and eventually run out of memory and crash.
!!! Note:
If the data is pair-specific, make sure to use pair as one of the keys in the dictionary.
### Additional data (DataProvider)
The strategy provides access to the `DataProvider`. This allows you to get additional data to use in your strategy.
!!!Note:
The DataProvier is currently not available during backtesting / hyperopt, but this is planned for the future.
All methods return `None` in case of failure (do not raise an exception).
Please always check if the `DataProvider` is available to avoid failures during backtesting.
#### Possible options for DataProvider
- `available_pairs` - Property with tuples listing cached pairs with their intervals. (pair, interval)
- `ohlcv(pair, ticker_interval)` - Currently cached ticker data for all pairs in the whitelist, returns DataFrame or empty DataFrame
- `historic_ohlcv(pair, ticker_interval)` - Data stored on disk
- `runmode` - Property containing the current runmode.
#### ohlcv / historic_ohlcv
``` python
if self.dp:
if dp.runmode == 'live':
if ('ETH/BTC', ticker_interval) in self.dp.available_pairs:
data_eth = self.dp.ohlcv(pair='ETH/BTC',
ticker_interval=ticker_interval)
else:
# Get historic ohlcv data (cached on disk).
history_eth = self.dp.historic_ohlcv(pair='ETH/BTC',
ticker_interval='1h')
```
!!! Warning: Warning about backtesting
Be carefull when using dataprovider in backtesting. `historic_ohlcv()` provides the full time-range in one go,
so please be aware of it and make sure to not "look into the future" to avoid surprises when running in dry/live mode).
#### Available Pairs
``` python
if self.dp:
for pair, ticker in self.dp.available_pairs:
print(f"available {pair}, {ticker}")
```
#### Get data for non-tradeable pairs
Data for additional, informative pairs (reference pairs) can be beneficial for some strategies.
Ohlcv data for these pairs will be downloaded as part of the regular whitelist refresh process and is available via `DataProvider` just as other pairs (see above).
These parts will **not** be traded unless they are also specified in the pair whitelist, or have been selected by Dynamic Whitelisting.
The pairs need to be specified as tuples in the format `("pair", "interval")`, with pair as the first and time interval as the second argument.
Sample:
``` python
def informative_pairs(self):
return [("ETH/USDT", "5m"),
("BTC/TUSD", "15m"),
]
```
!!! Warning:
As these pairs will be refreshed as part of the regular whitelist refresh, it's best to keep this list short.
All intervals and all pairs can be specified as long as they are available (and active) on the used exchange.
It is however better to use resampling to longer time-intervals when possible
to avoid hammering the exchange with too many requests and risk beeing blocked.
### Additional data - Wallets
The strategy provides access to the `Wallets` object. This contains the current balances on the exchange.
!!!NOTE:
Wallets is not available during backtesting / hyperopt.
Please always check if `Wallets` is available to avoid failures during backtesting.
``` python
if self.wallets:
free_eth = self.wallets.get_free('ETH')
used_eth = self.wallets.get_used('ETH')
total_eth = self.wallets.get_total('ETH')
```
#### Possible options for Wallets
- `get_free(asset)` - currently available balance to trade
- `get_used(asset)` - currently tied up balance (open orders)
- `get_total(asset)` - total available balance - sum of the 2 above
### Where is the default strategy?
The default buy strategy is located in the file
[freqtrade/default_strategy.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/strategy/default_strategy.py).
### Specify custom strategy location
If you want to use a strategy from a different folder you can pass `--strategy-path`
```bash
python3 ./freqtrade/main.py --strategy AwesomeStrategy --strategy-path /some/folder
```
### Further strategy ideas
To get additional Ideas for strategies, head over to our [strategy repository](https://github.com/freqtrade/freqtrade-strategies). Feel free to use them as they are - but results will depend on the current market situation, pairs used etc. - therefore please backtest the strategy for your exchange/desired pairs first, evaluate carefully, use at your own risk.
@@ -183,4 +369,4 @@ We also got a *strategy-sharing* channel in our [Slack community](https://join.s
## Next step
Now you have a perfect strategy you probably want to backtest it.
Your next step is to learn [How to use the Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md).
Your next step is to learn [How to use the Backtesting](backtesting.md).

View File

@@ -1,32 +1,28 @@
# Bot usage
# Start the bot
This page explains the difference parameters of the bot and how to run it.
This page explains the different parameters of the bot and how to run it.
## Table of Contents
- [Bot commands](#bot-commands)
- [Backtesting commands](#backtesting-commands)
- [Hyperopt commands](#hyperopt-commands)
## Bot commands
```
usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
[--strategy-path PATH] [--dynamic-whitelist [INT]]
[--db-url PATH]
{backtesting,hyperopt} ...
usage: main.py [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
[--strategy-path PATH] [--customhyperopt NAME]
[--dynamic-whitelist [INT]] [--db-url PATH]
{backtesting,edge,hyperopt} ...
Simple High Frequency Trading Bot for crypto currencies
Free, open source crypto trading bot
positional arguments:
{backtesting,hyperopt}
{backtesting,edge,hyperopt}
backtesting backtesting module
edge edge module
hyperopt hyperopt module
optional arguments:
-h, --help show this help message and exit
-v, --verbose be verbose
--version show program's version number and exit
-v, --verbose verbose mode (-vv for more, -vvv to get all messages)
--version show program\'s version number and exit
-c PATH, --config PATH
specify configuration file (default: config.json)
-d PATH, --datadir PATH
@@ -34,12 +30,15 @@ optional arguments:
-s NAME, --strategy NAME
specify strategy class name (default: DefaultStrategy)
--strategy-path PATH specify additional strategy lookup path
--customhyperopt NAME
specify hyperopt class name (default:
DefaultHyperOpts)
--dynamic-whitelist [INT]
dynamically generate and update whitelist based on 24h
BaseVolume (default: 20)
BaseVolume (default: 20) DEPRECATED.
--db-url PATH Override trades database URL, this is useful if
dry_run is enabled or in custom deployments (default:
sqlite:///tradesv3.sqlite)
None)
```
### How to use a different config file?
@@ -51,7 +50,7 @@ default, the bot will load the file `./config.json`
python3 ./freqtrade/main.py -c path/far/far/away/config.json
```
### How to use --strategy?
### How to use **--strategy**?
This parameter will allow you to load your custom strategy class.
Per default without `--strategy` or `-s` the bot will load the
@@ -74,7 +73,7 @@ message the reason (File not found, or errors in your code).
Learn more about strategy file in [optimize your bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md).
### How to use --strategy-path?
### How to use **--strategy-path**?
This parameter allows you to add an additional strategy lookup path, which gets
checked before the default locations (The passed path must be a folder!):
@@ -87,7 +86,10 @@ python3 ./freqtrade/main.py --strategy AwesomeStrategy --strategy-path /some/fol
This is very simple. Copy paste your strategy file into the folder
`user_data/strategies` or use `--strategy-path`. And voila, the bot is ready to use it.
### How to use --dynamic-whitelist?
### How to use **--dynamic-whitelist**?
!!! danger "DEPRECATED"
Dynamic-whitelist is deprecated. Please move your configurations to the configuration as outlined [here](/configuration/#dynamic-pairlists)
Per default `--dynamic-whitelist` will retrieve the 20 currencies based
on BaseVolume. This value can be changed when you run the script.
@@ -111,7 +113,7 @@ python3 ./freqtrade/main.py --dynamic-whitelist 30
negative value (e.g -2), `--dynamic-whitelist` will use the default
value (20).
### How to use --db-url?
### How to use **--db-url**?
When you run the bot in Dry-run mode, per default no transactions are
stored in a database. If you want to store your bot actions in a DB
@@ -127,15 +129,17 @@ python3 ./freqtrade/main.py -c config.json --db-url sqlite:///tradesv3.dry_run.s
Backtesting also uses the config specified via `-c/--config`.
```
usage: freqtrade backtesting [-h] [-i TICKER_INTERVAL] [--eps] [--dmmp]
[--timerange TIMERANGE] [-l] [-r]
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
[--export EXPORT] [--export-filename PATH]
usage: main.py backtesting [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE]
[--eps] [--dmmp] [-l] [-r]
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
[--export EXPORT] [--export-filename PATH]
optional arguments:
-h, --help show this help message and exit
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL
specify ticker interval (1m, 5m, 30m, 1h, 1d)
--timerange TIMERANGE
specify what timerange of data to use.
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking)
@@ -143,8 +147,6 @@ optional arguments:
Disable applying `max_open_trades` during backtest
(same as setting `max_open_trades` to a very high
number)
--timerange TIMERANGE
specify what timerange of data to use.
-l, --live using live data
-r, --refresh-pairs-cached
refresh the pairs files in tests/testdata with the
@@ -165,18 +167,18 @@ optional arguments:
filename=user_data/backtest_data/backtest_today.json
(default: user_data/backtest_data/backtest-
result.json)
```
### How to use --refresh-pairs-cached parameter?
### How to use **--refresh-pairs-cached** parameter?
The first time your run Backtesting, it will take the pairs you have
set in your config file and download data from Bittrex.
If for any reason you want to update your data set, you use
`--refresh-pairs-cached` to force Backtesting to update the data it has.
**Use it only if you want to update your data set. You will not be able
to come back to the previous version.**
!!! Note
Use it only if you want to update your data set. You will not be able to come back to the previous version.
To test your strategy with latest data, we recommend continuing using
the parameter `-l` or `--live`.
@@ -204,6 +206,8 @@ optional arguments:
number)
--timerange TIMERANGE
specify what timerange of data to use.
--hyperopt PATH specify hyperopt file (default:
freqtrade/optimize/default_hyperopt.py)
-e INT, --epochs INT specify number of epochs (default: 100)
-s {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...], --spaces {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...]
Specify which parameters to hyperopt. Space separate
@@ -211,6 +215,33 @@ optional arguments:
```
## Edge commands
To know your trade expectacny and winrate against historical data, you can use Edge.
```
usage: main.py edge [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE] [-r]
[--stoplosses STOPLOSS_RANGE]
optional arguments:
-h, --help show this help message and exit
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL
specify ticker interval (1m, 5m, 30m, 1h, 1d)
--timerange TIMERANGE
specify what timerange of data to use.
-r, --refresh-pairs-cached
refresh the pairs files in tests/testdata with the
latest data from the exchange. Use it if you want to
run your edge with up-to-date data.
--stoplosses STOPLOSS_RANGE
defines a range of stoploss against which edge will
assess the strategythe format is "min,max,step"
(without any space).example:
--stoplosses=-0.01,-0.1,-0.001
```
To understand edge and how to read the results, please read the [edge documentation](edge.md).
## A parameter missing in the configuration?
All parameters for `main.py`, `backtesting`, `hyperopt` are referenced
@@ -219,4 +250,4 @@ in [misc.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/misc.
## Next step
The optimal strategy of the bot will change with time depending of the market trends. The next step is to
[optimize your bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md).
[optimize your bot](bot-optimization.md).

View File

@@ -2,12 +2,6 @@
This page explains how to configure your `config.json` file.
## Table of Contents
- [Bot commands](#bot-commands)
- [Backtesting commands](#backtesting-commands)
- [Hyperopt commands](#hyperopt-commands)
## Setup config.json
We recommend to copy and use the `config.json.example` as a template
@@ -15,53 +9,98 @@ for your bot configuration.
The table below will list all configuration parameters.
| Command | Default | Mandatory | Description |
|----------|---------|----------|-------------|
| `max_open_trades` | 3 | Yes | Number of trades open your bot will have.
| `stake_currency` | BTC | Yes | Crypto-currency used for trading.
| `stake_amount` | 0.05 | Yes | Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to 'unlimited' to allow the bot to use all avaliable balance.
| `ticker_interval` | [1m, 5m, 30m, 1h, 1d] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Default is 5 minutes
| `fiat_display_currency` | USD | Yes | Fiat currency used to show your profits. More information below.
| `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode.
| `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.
| `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.
| `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.
| `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`
| `telegram.enabled` | true | Yes | Enable or not the usage of Telegram.
| `telegram.token` | token | No | Your Telegram bot token. Only required if `telegram.enabled` is `true`.
| `telegram.chat_id` | chat_id | No | Your personal Telegram account id. Only required if `telegram.enabled` is `true`.
| `webhook.enabled` | false | No | Enable useage of Webhook notifications
| `webhook.url` | false | No | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
| `webhook.webhookbuy` | false | No | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `webhook.webhooksell` | false | No | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `webhook.webhookstatus` | false | No | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `db_url` | `sqlite:///tradesv3.sqlite` | No | Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `True`.
| `initial_state` | running | No | Defines the initial application state. More information below.
| `strategy` | DefaultStrategy | No | Defines Strategy class to use.
| `strategy_path` | null | No | Adds an additional strategy lookup path (must be a folder).
| `internals.process_throttle_secs` | 5 | Yes | Set the process throttle. Value in second.
Mandatory Parameters are marked as **Required**.
The definition of each config parameters is in [misc.py](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/misc.py#L205).
| Command | Default | Description |
|----------|---------|-------------|
| `max_open_trades` | 3 | **Required.** Number of trades open your bot will have. If -1 then it is ignored (i.e. potentially unlimited open trades)
| `stake_currency` | BTC | **Required.** Crypto-currency used for trading.
| `stake_amount` | 0.05 | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance.
| `amount_reserve_percent` | 0.05 | Reserve some amount in min pair stake amount. Default is 5%. The bot will reserve `amount_reserve_percent` + stop-loss value when calculating min pair stake amount in order to avoid possible trade refusals.
| `ticker_interval` | [1m, 5m, 30m, 1h, 1d] | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-strategy).
| `fiat_display_currency` | USD | **Required.** Fiat currency used to show your profits. More information below.
| `dry_run` | true | **Required.** Define if the bot must be in Dry-run or production mode.
| `process_only_new_candles` | false | If set to true indicators are processed only once a new candle arrives. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-strategy).
| `minimal_roi` | See below | Set the threshold in percent the bot will use to sell a trade. More information below. [Strategy Override](#parameters-in-strategy).
| `stoploss` | -0.10 | Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-strategy).
| `trailing_stop` | false | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-strategy).
| `trailing_stop_positive` | 0 | Changes stop-loss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-strategy).
| `trailing_stop_positive_offset` | 0 | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-strategy).
| `unfilledtimeout.buy` | 10 | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled.
| `unfilledtimeout.sell` | 10 | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled.
| `bid_strategy.ask_last_balance` | 0.0 | **Required.** Set the bidding price. More information [below](#understand-ask_last_balance).
| `bid_strategy.use_order_book` | false | Allows buying of pair using the rates in Order Book Bids.
| `bid_strategy.order_book_top` | 0 | Bot will use the top N rate in Order Book Bids. Ie. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids.
| `bid_strategy. check_depth_of_market.enabled` | false | Does not buy if the % difference of buy orders and sell orders is met in Order Book.
| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | 0 | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher.
| `ask_strategy.use_order_book` | false | Allows selling of open traded pair using the rates in Order Book Asks.
| `ask_strategy.order_book_min` | 0 | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
| `ask_strategy.order_book_max` | 0 | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
| `order_types` | None | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-strategy).
| `order_time_in_force` | None | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-strategy).
| `exchange.name` | bittrex | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
| `exchange.key` | key | API key to use for the exchange. Only required when you are in production mode.
| `exchange.secret` | secret | API secret to use for the exchange. Only required when you are in production mode.
| `exchange.pair_whitelist` | [] | List of currency to use by the bot. Can be overrided with `--dynamic-whitelist` param.
| `exchange.pair_blacklist` | [] | List of currency the bot must avoid. Useful when using `--dynamic-whitelist` param.
| `exchange.ccxt_rate_limit` | True | DEPRECATED!! Have CCXT handle Exchange rate limits. Depending on the exchange, having this to false can lead to temporary bans from the exchange.
| `exchange.ccxt_config` | None | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
| `exchange.ccxt_async_config` | None | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
| `edge` | false | Please refer to [edge configuration document](edge.md) for detailed explanation.
| `experimental.use_sell_signal` | false | Use your sell strategy in addition of the `minimal_roi`. [Strategy Override](#parameters-in-strategy).
| `experimental.sell_profit_only` | false | Waits until you have made a positive profit before taking a sell decision. [Strategy Override](#parameters-in-strategy).
| `experimental.ignore_roi_if_buy_signal` | false | Does not sell if the buy-signal is still active. Takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-strategy).
| `pairlist.method` | StaticPairList | Use Static whitelist. [More information below](#dynamic-pairlists).
| `pairlist.config` | None | Additional configuration for dynamic pairlists. [More information below](#dynamic-pairlists).
| `telegram.enabled` | true | **Required.** Enable or not the usage of Telegram.
| `telegram.token` | token | Your Telegram bot token. Only required if `telegram.enabled` is `true`.
| `telegram.chat_id` | chat_id | Your personal Telegram account id. Only required if `telegram.enabled` is `true`.
| `webhook.enabled` | false | Enable usage of Webhook notifications
| `webhook.url` | false | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
| `webhook.webhookbuy` | false | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `webhook.webhooksell` | false | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `webhook.webhookstatus` | false | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `db_url` | `sqlite:///tradesv3.sqlite`| Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `True`.
| `initial_state` | running | Defines the initial application state. More information below.
| `forcebuy_enable` | false | Enables the RPC Commands to force a buy. More information below.
| `strategy` | DefaultStrategy | Defines Strategy class to use.
| `strategy_path` | null | Adds an additional strategy lookup path (must be a folder).
| `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second.
### Parameters in strategy
The following parameters can be set in either configuration or strategy.
Values in the configuration are always overwriting values set in the strategy.
* `minimal_roi`
* `ticker_interval`
* `stoploss`
* `trailing_stop`
* `trailing_stop_positive`
* `trailing_stop_positive_offset`
* `process_only_new_candles`
* `order_types`
* `order_time_in_force`
* `use_sell_signal` (experimental)
* `sell_profit_only` (experimental)
* `ignore_roi_if_buy_signal` (experimental)
### Understand stake_amount
`stake_amount` is an amount of crypto-currency your bot will use for each trade.
The minimal value is 0.0005. If there is not enough crypto-currency in
the account an exception is generated.
To allow the bot to trade all the avaliable `stake_currency` in your account set `stake_amount` = `unlimited`.
In this case a trade amount is calclulated as `currency_balanse / (max_open_trades - current_open_trades)`.
To allow the bot to trade all the available `stake_currency` in your account set
```json
"stake_amount" : "unlimited",
```
In this case a trade amount is calclulated as:
```python
currency_balanse / (max_open_trades - current_open_trades)
```
### Understand minimal_roi
@@ -69,7 +108,7 @@ In this case a trade amount is calclulated as `currency_balanse / (max_open_trad
in minutes and the value is the minimum ROI in percent.
See the example below:
```
```json
"minimal_roi": {
"40": 0.0, # Sell after 40 minutes if the profit is not negative
"30": 0.01, # Sell after 30 minutes if there is at least 1% profit
@@ -102,6 +141,15 @@ Go to the [trailing stoploss Documentation](stoploss.md) for details on trailing
Possible values are `running` or `stopped`. (default=`running`)
If the value is `stopped` the bot has to be started with `/start` first.
### Understand forcebuy_enable
`forcebuy_enable` enables the usage of forcebuy commands via Telegram.
This is disabled for security reasons by default, and will show a warning message on startup if enabled.
You send `/forcebuy ETH/BTC` to the bot, who buys the pair and holds it until a regular sell-signal appears (ROI, stoploss, /forcesell).
Can be dangerous with some strategies, so use with care
See [the telegram documentation](telegram-usage.md) for details on usage.
### Understand process_throttle_secs
`process_throttle_secs` is an optional field that defines in seconds how long the bot should wait
@@ -116,6 +164,52 @@ use the `last` price and values between those interpolate between ask and last
price. Using `ask` price will guarantee quick success in bid, but bot will also
end up paying more then would probably have been necessary.
### Understand order_types
`order_types` contains a dict mapping order-types to market-types as well as stoploss on or off exchange type and stoploss on exchange update interval in seconds. This allows to buy using limit orders, sell using limit-orders, and create stoploss orders using market. It also allows to set the stoploss "on exchange" which means stoploss order would be placed immediately once the buy order is fulfilled. In case stoploss on exchange and `trailing_stop` are both set, then the bot will use `stoploss_on_exchange_interval` to check it periodically and update it if necessary (e.x. in case of trailing stoploss).
This can be set in the configuration or in the strategy. Configuration overwrites strategy configurations.
If this is configured, all 4 values (`"buy"`, `"sell"`, `"stoploss"` and `"stoploss_on_exchange"`) need to be present, otherwise the bot warn about it and will fail to start.
The below is the default which is used if this is not configured in either Strategy or configuration.
```python
"order_types": {
"buy": "limit",
"sell": "limit",
"stoploss": "market",
"stoploss_on_exchange": False,
"stoploss_on_exchange_interval": 60
},
```
!!! Note
Not all exchanges support "market" orders.
The following message will be shown if your exchange does not support market orders: `"Exchange <yourexchange> does not support market orders."`
!!! Note
stoploss on exchange interval is not mandatory. Do not change it's value if you are unsure of what you are doing. For more information about how stoploss works please read [the stoploss documentation](stoploss.md).
### Understand order_time_in_force
`order_time_in_force` defines the policy by which the order is executed on the exchange. Three commonly used time in force are:<br/>
**GTC (Goog Till Canceled):**
This is most of the time the default time in force. It means the order will remain on exchange till it is canceled by user. It can be fully or partially fulfilled. If partially fulfilled, the remaining will stay on the exchange till cancelled.<br/>
**FOK (Full Or Kill):**
It means if the order is not executed immediately AND fully then it is canceled by the exchange.<br/>
**IOC (Immediate Or Canceled):**
It is the same as FOK (above) except it can be partially fulfilled. The remaining part is automatically cancelled by the exchange.
<br/>
`order_time_in_force` contains a dict buy and sell time in force policy. This can be set in the configuration or in the strategy. Configuration overwrites strategy configurations.<br/>
possible values are: `gtc` (default), `fok` or `ioc`.<br/>
``` python
"order_time_in_force": {
"buy": "gtc",
"sell": "gtc"
},
```
!!! Warning
This is an ongoing work. For now it is supported only for binance and only for buy orders. Please don't change the default value unless you know what you are doing.
### What values for exchange.name?
Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports 115 cryptocurrency
@@ -133,9 +227,15 @@ Feel free to test other exchanges and submit your PR to improve the bot.
### What values for fiat_display_currency?
`fiat_display_currency` set the base currency to use for the conversion from coin to fiat in Telegram.
The valid values are: "AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD".
In addition to central bank currencies, a range of cryto currencies are supported.
The valid values are: "BTC", "ETH", "XRP", "LTC", "BCH", "USDT".
The valid values are:<br/>
```json
"AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD"
```
In addition to FIAT currencies, a range of cryto currencies are supported.
The valid values are:
```json
"BTC", "ETH", "XRP", "LTC", "BCH", "USDT"
```
## Switch to dry-run mode
@@ -144,14 +244,12 @@ behave and how is the performance of your strategy. In Dry-run mode the
bot does not engage your money. It only runs a live simulation without
creating trades.
### To switch your bot in Dry-run mode:
1. Edit your `config.json` file
2. Switch dry-run to true and specify db_url for a persistent db
```json
"dry_run": true,
"db_url": "sqlite///tradesv3.dryrun.sqlite",
"db_url": "sqlite:///tradesv3.dryrun.sqlite",
```
3. Remove your Exchange API key (change them by fake api credentials)
@@ -168,23 +266,48 @@ creating trades.
Once you will be happy with your bot performance, you can switch it to
production mode.
### Dynamic Pairlists
Dynamic pairlists select pairs for you based on the logic configured.
The bot runs against all pairs (with that stake) on the exchange, and a number of assets (`number_assets`) is selected based on the selected criteria.
By default, a Static Pairlist is used (configured as `"pair_whitelist"` under the `"exchange"` section of this configuration).
**Available Pairlist methods:**
* `"StaticPairList"`
* uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklist`
* `"VolumePairList"`
* Formerly available as `--dynamic-whitelist [<number_assets>]`
* Selects `number_assets` top pairs based on `sort_key`, which can be one of `askVolume`, `bidVolume` and `quoteVolume`, defaults to `quoteVolume`.
```json
"pairlist": {
"method": "VolumePairList",
"config": {
"number_assets": 20,
"sort_key": "quoteVolume"
}
},
```
## Switch to production mode
In production mode, the bot will engage your money. Be careful a wrong
strategy can lose all your money. Be aware of what you are doing when
you run it in production mode.
### To switch your bot in production mode:
### To switch your bot in production mode
1. Edit your `config.json` file
**Edit your `config.json` file.**
2. Switch dry-run to false and don't forget to adapt your database URL if set
**Switch dry-run to false and don't forget to adapt your database URL if set:**
```json
"dry_run": false,
```
3. Insert your Exchange API key (change them by fake api keys)
**Insert your Exchange API key (change them by fake api keys):**
```json
"exchange": {
@@ -195,7 +318,28 @@ you run it in production mode.
}
```
If you have not your Bittrex API key yet, [see our tutorial](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md).
!!! Note
If you have an exchange API key yet, [see our tutorial](/pre-requisite).
### Using proxy with FreqTrade
To use a proxy with freqtrade, add the kwarg `"aiohttp_trust_env"=true` to the `"ccxt_async_kwargs"` dict in the exchange section of the configuration.
An example for this can be found in `config_full.json.example`
``` json
"ccxt_async_config": {
"aiohttp_trust_env": true
}
```
Then, export your proxy settings using the variables `"HTTP_PROXY"` and `"HTTPS_PROXY"` set to the appropriate values
``` bash
export HTTP_PROXY="http://addr:port"
export HTTPS_PROXY="http://addr:port"
freqtrade
```
### Embedding Strategies
@@ -204,7 +348,7 @@ FreqTrade provides you with with an easy way to embed the strategy into your con
This is done by utilizing BASE64 encoding and providing this string at the strategy configuration field,
in your chosen config file.
##### Encoding a string as BASE64
#### Encoding a string as BASE64
This is a quick example, how to generate the BASE64 string in python
@@ -226,4 +370,4 @@ Please ensure that 'NameOfStrategy' is identical to the strategy name!
## Next step
Now you have configured your config.json, the next step is to [start your bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md).
Now you have configured your config.json, the next step is to [start your bot](bot-usage.md).

117
docs/developer.md Normal file
View File

@@ -0,0 +1,117 @@
# Development Help
This page is intended for developers of FreqTrade, people who want to contribute to the FreqTrade codebase or documentation, or people who want to understand the source code of the application they're running.
All contributions, bug reports, bug fixes, documentation improvements, enhancements and ideas are welcome. We [track issues](https://github.com/freqtrade/freqtrade/issues) on [GitHub](https://github.com) and also have a dev channel in [slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE) where you can ask questions.
## Documentation
Documentation is available at [https://freqtrade.io](https://www.freqtrade.io/) and needs to be provided with every new feature PR.
Special fields for the documentation (like Note boxes, ...) can be found [here](https://squidfunk.github.io/mkdocs-material/extensions/admonition/).
## Developer setup
To configure a development environment, use best use the `setup.sh` script and answer "y" when asked "Do you want to install dependencies for dev [y/N]? ".
Alternatively (if your system is not supported by the setup.sh script), follow the manual installation process and run `pip3 install -r requirements-dev.txt`.
This will install all required tools for development, including `pytest`, `flake8`, `mypy`, and `coveralls`.
## Modules
### Dynamic Pairlist
You have a great idea for a new pair selection algorithm you would like to try out? Great.
Hopefully you also want to contribute this back upstream.
Whatever your motivations are - This should get you off the ground in trying to develop a new Pairlist provider.
First of all, have a look at the [VolumePairList](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/pairlist/VolumePairList.py) provider, and best copy this file with a name of your new Pairlist Provider.
This is a simple provider, which however serves as a good example on how to start developing.
Next, modify the classname of the provider (ideally align this with the Filename).
The base-class provides the an instance of the bot (`self._freqtrade`), as well as the configuration (`self._config`), and initiates both `_blacklist` and `_whitelist`.
```python
self._freqtrade = freqtrade
self._config = config
self._whitelist = self._config['exchange']['pair_whitelist']
self._blacklist = self._config['exchange'].get('pair_blacklist', [])
```
Now, let's step through the methods which require actions:
#### configuration
Configuration for PairListProvider is done in the bot configuration file in the element `"pairlist"`.
This Pairlist-object may contain a `"config"` dict with additional configurations for the configured pairlist.
By convention, `"number_assets"` is used to specify the maximum number of pairs to keep in the whitelist. Please follow this to ensure a consistent user experience.
Additional elements can be configured as needed. `VolumePairList` uses `"sort_key"` to specify the sorting value - however feel free to specify whatever is necessary for your great algorithm to be successfull and dynamic.
#### short_desc
Returns a description used for Telegram messages.
This should contain the name of the Provider, as well as a short description containing the number of assets. Please follow the format `"PairlistName - top/bottom X pairs"`.
#### refresh_pairlist
Override this method and run all calculations needed in this method.
This is called with each iteration of the bot - so consider implementing caching for compute/network heavy calculations.
Assign the resulting whiteslist to `self._whitelist` and `self._blacklist` respectively. These will then be used to run the bot in this iteration. Pairs with open trades will be added to the whitelist to have the sell-methods run correctly.
Please also run `self._validate_whitelist(pairs)` and to check and remove pairs with inactive markets. This function is available in the Parent class (`StaticPairList`) and should ideally not be overwritten.
##### sample
``` python
def refresh_pairlist(self) -> None:
# Generate dynamic whitelist
pairs = self._gen_pair_whitelist(self._config['stake_currency'], self._sort_key)
# Validate whitelist to only have active market pairs
self._whitelist = self._validate_whitelist(pairs)[:self._number_pairs]
```
#### _gen_pair_whitelist
This is a simple method used by `VolumePairList` - however serves as a good example.
It implements caching (`@cached(TTLCache(maxsize=1, ttl=1800))`) as well as a configuration option to allow different (but similar) strategies to work with the same PairListProvider.
## Creating a release
This part of the documentation is aimed at maintainers, and shows how to create a release.
### create release branch
``` bash
# make sure you're in develop branch
git checkout develop
# create new branch
git checkout -b new_release
```
* edit `freqtrade/__init__.py` and add the desired version (for example `0.18.0`)
* Commit this part
* push that branch to the remote and create a PR
### create changelog from git commits
``` bash
# Needs to be done before merging / pulling that branch.
git log --oneline --no-decorate --no-merges master..develop
```
### Create github release / tag
* Use the version-number specified as tag.
* Use "master" as reference (this step comes after the above PR is merged).
* use the above changelog as release comment (as codeblock)
### After-release
* update version in develop to next valid version and postfix that with `-dev` (`0.18.0 -> 0.18.1-dev`)

212
docs/edge.md Normal file
View File

@@ -0,0 +1,212 @@
# Edge positioning
This page explains how to use Edge Positioning module in your bot in order to enter into a trade only if the trade has a reasonable win rate and risk reward ratio, and consequently adjust your position size and stoploss.
!!! Warning
Edge positioning is not compatible with dynamic whitelist. it overrides dynamic whitelist.
!!! Note
Edge won't consider anything else than buy/sell/stoploss signals. So trailing stoploss, ROI, and everything else will be ignored in its calculation.
## Introduction
Trading is all about probability. No one can claim that he has a strategy working all the time. You have to assume that sometimes you lose.<br/><br/>
But it doesn't mean there is no rule, it only means rules should work "most of the time". Let's play a game: we toss a coin, heads: I give you 10$, tails: You give me 10$. Is it an interesting game ? no, it is quite boring, isn't it?<br/><br/>
But let's say the probability that we have heads is 80%, and the probability that we have tails is 20%. Now it is becoming interesting ...
That means 10$ x 80% versus 10$ x 20%. 8$ versus 2$. That means over time you will win 8$ risking only 2$ on each toss of coin.<br/><br/>
Let's complicate it more: you win 80% of the time but only 2$, I win 20% of the time but 8$. The calculation is: 80% * 2$ versus 20% * 8$. It is becoming boring again because overtime you win $1.6$ (80% x 2$) and me $1.6 (20% * 8$) too.<br/><br/>
The question is: How do you calculate that? how do you know if you wanna play?
The answer comes to two factors:
- Win Rate
- Risk Reward Ratio
### Win Rate
Means over X trades what is the percentage of winning trades to total number of trades (note that we don't consider how much you gained but only If you won or not).
`W = (Number of winning trades) / (Total number of trades)`
### Risk Reward Ratio
Risk Reward Ratio is a formula used to measure the expected gains of a given investment against the risk of loss. It is basically what you potentially win divided by what you potentially lose:
`R = Profit / Loss`
Over time, on many trades, you can calculate your risk reward by dividing your average profit on winning trades by your average loss on losing trades:
`Average profit = (Sum of profits) / (Number of winning trades)`
`Average loss = (Sum of losses) / (Number of losing trades)`
`R = (Average profit) / (Average loss)`
### Expectancy
At this point we can combine W and R to create an expectancy ratio. This is a simple process of multiplying the risk reward ratio by the percentage of winning trades, and subtracting the percentage of losing trades, which is calculated as follows:
Expectancy Ratio = (Risk Reward Ratio x Win Rate) Loss Rate
So lets say your Win rate is 28% and your Risk Reward Ratio is 5:
`Expectancy = (5 * 0.28) - 0.72 = 0.68`
Superficially, this means that on average you expect this strategys trades to return .68 times the size of your losers. This is important for two reasons: First, it may seem obvious, but you know right away that you have a positive return. Second, you now have a number you can compare to other candidate systems to make decisions about which ones you employ.
It is important to remember that any system with an expectancy greater than 0 is profitable using past data. The key is finding one that will be profitable in the future.
You can also use this number to evaluate the effectiveness of modifications to this system.
**NOTICE:** It's important to keep in mind that Edge is testing your expectancy using historical data , there's no guarantee that you will have a similar edge in the future. It's still vital to do this testing in order to build confidence in your methodology, but be wary of "curve-fitting" your approach to the historical data as things are unlikely to play out the exact same way for future trades.
## How does it work?
If enabled in config, Edge will go through historical data with a range of stoplosses in order to find buy and sell/stoploss signals. It then calculates win rate and expectancy over X trades for each stoploss. Here is an example:
| Pair | Stoploss | Win Rate | Risk Reward Ratio | Expectancy |
|----------|:-------------:|-------------:|------------------:|-----------:|
| XZC/ETH | -0.03 | 0.52 |1.359670 | 0.228 |
| XZC/ETH | -0.01 | 0.50 |1.176384 | 0.088 |
| XZC/ETH | -0.02 | 0.51 |1.115941 | 0.079 |
The goal here is to find the best stoploss for the strategy in order to have the maximum expectancy. In the above example stoploss at 3% leads to the maximum expectancy according to historical data.
Edge then forces stoploss to your strategy dynamically.
### Position size
Edge dictates the stake amount for each trade to the bot according to the following factors:
- Allowed capital at risk
- Stoploss
Allowed capital at risk is calculated as follows:
**allowed capital at risk** = **capital_available_percentage** X **allowed risk per trade**
**Stoploss** is calculated as described above against historical data.
Your position size then will be:
**position size** = **allowed capital at risk** / **stoploss**
Example:<br/>
Let's say the stake currency is ETH and you have 10 ETH on the exchange, your **capital_available_percentage** is 50% and you would allow 1% of risk for each trade. thus your available capital for trading is **10 x 0.5 = 5 ETH** and allowed capital at risk would be **5 x 0.01 = 0.05 ETH**. <br/>
Let's assume Edge has calculated that for **XLM/ETH** market your stoploss should be at 2%. So your position size will be **0.05 / 0.02 = 2.5ETH**.<br/>
Bot takes a position of 2.5ETH on XLM/ETH (call it trade 1). Up next, you receive another buy signal while trade 1 is still open. This time on BTC/ETH market. Edge calculated stoploss for this market at 4%. So your position size would be 0.05 / 0.04 = 1.25ETH (call it trade 2).<br/>
Note that available capital for trading didnt change for trade 2 even if you had already trade 1. The available capital doesnt mean the free amount on your wallet.<br/>
Now you have two trades open. The Bot receives yet another buy signal for another market: **ADA/ETH**. This time the stoploss is calculated at 1%. So your position size is **0.05 / 0.01 = 5ETH**. But there are already 4ETH blocked in two previous trades. So the position size for this third trade would be 1ETH.<br/>
Available capital doesnt change before a position is sold. Lets assume that trade 1 receives a sell signal and it is sold with a profit of 1ETH. Your total capital on exchange would be 11 ETH and the available capital for trading becomes 5.5ETH. <br/>
So the Bot receives another buy signal for trade 4 with a stoploss at 2% then your position size would be **0.055 / 0.02 = 2.75**.
## Configurations
Edge has following configurations:
#### enabled
If true, then Edge will run periodically.<br/>
(default to false)
#### process_throttle_secs
How often should Edge run in seconds? <br/>
(default to 3600 so one hour)
#### calculate_since_number_of_days
Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy
Note that it downloads historical data so increasing this number would lead to slowing down the bot.<br/>
(default to 7)
#### capital_available_percentage
This is the percentage of the total capital on exchange in stake currency. <br/>
As an example if you have 10 ETH available in your wallet on the exchange and this value is 0.5 (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers it as available capital.<br/>
(default to 0.5)
#### allowed_risk
Percentage of allowed risk per trade.<br/>
(default to 0.01 [1%])
#### stoploss_range_min
Minimum stoploss.<br/>
(default to -0.01)
#### stoploss_range_max
Maximum stoploss.<br/>
(default to -0.10)
#### stoploss_range_step
As an example if this is set to -0.01 then Edge will test the strategy for [-0.01, -0,02, -0,03 ..., -0.09, -0.10] ranges.
Note than having a smaller step means having a bigger range which could lead to slow calculation. <br/>
if you set this parameter to -0.001, you then slow down the Edge calculation by a factor of 10. <br/>
(default to -0.01)
#### minimum_winrate
It filters pairs which don't have at least minimum_winrate.
This comes handy if you want to be conservative and don't comprise win rate in favor of risk reward ratio.<br/>
(default to 0.60)
#### minimum_expectancy
It filters paris which have an expectancy lower than this number .
Having an expectancy of 0.20 means if you put 10$ on a trade you expect a 12$ return.<br/>
(default to 0.20)
#### min_trade_number
When calculating W and R and E (expectancy) against historical data, you always want to have a minimum number of trades. The more this number is the more Edge is reliable. Having a win rate of 100% on a single trade doesn't mean anything at all. But having a win rate of 70% over past 100 trades means clearly something. <br/>
(default to 10, it is highly recommended not to decrease this number)
#### max_trade_duration_minute
Edge will filter out trades with long duration. If a trade is profitable after 1 month, it is hard to evaluate the strategy based on it. But if most of trades are profitable and they have maximum duration of 30 minutes, then it is clearly a good sign.<br/>
**NOTICE:** While configuring this value, you should take into consideration your ticker interval. as an example filtering out trades having duration less than one day for a strategy which has 4h interval does not make sense. default value is set assuming your strategy interval is relatively small (1m or 5m, etc).<br/>
(default to 1 day, 1440 = 60 * 24)
#### remove_pumps
Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off.<br/>
(default to false)
## Running Edge independently
You can run Edge independently in order to see in details the result. Here is an example:
```bash
python3 ./freqtrade/main.py edge
```
An example of its output:
| pair | stoploss | win rate | risk reward ratio | required risk reward | expectancy | total number of trades | average duration (min) |
|:----------|-----------:|-----------:|--------------------:|-----------------------:|-------------:|-------------------------:|-------------------------:|
| AGI/BTC | -0.02 | 0.64 | 5.86 | 0.56 | 3.41 | 14 | 54 |
| NXS/BTC | -0.03 | 0.64 | 2.99 | 0.57 | 1.54 | 11 | 26 |
| LEND/BTC | -0.02 | 0.82 | 2.05 | 0.22 | 1.50 | 11 | 36 |
| VIA/BTC | -0.01 | 0.55 | 3.01 | 0.83 | 1.19 | 11 | 48 |
| MTH/BTC | -0.09 | 0.56 | 2.82 | 0.80 | 1.12 | 18 | 52 |
| ARDR/BTC | -0.04 | 0.42 | 3.14 | 1.40 | 0.73 | 12 | 42 |
| BCPT/BTC | -0.01 | 0.71 | 1.34 | 0.40 | 0.67 | 14 | 30 |
| WINGS/BTC | -0.02 | 0.56 | 1.97 | 0.80 | 0.65 | 27 | 42 |
| VIBE/BTC | -0.02 | 0.83 | 0.91 | 0.20 | 0.59 | 12 | 35 |
| MCO/BTC | -0.02 | 0.79 | 0.97 | 0.27 | 0.55 | 14 | 31 |
| GNT/BTC | -0.02 | 0.50 | 2.06 | 1.00 | 0.53 | 18 | 24 |
| HOT/BTC | -0.01 | 0.17 | 7.72 | 4.81 | 0.50 | 209 | 7 |
| SNM/BTC | -0.03 | 0.71 | 1.06 | 0.42 | 0.45 | 17 | 38 |
| APPC/BTC | -0.02 | 0.44 | 2.28 | 1.27 | 0.44 | 25 | 43 |
| NEBL/BTC | -0.03 | 0.63 | 1.29 | 0.58 | 0.44 | 19 | 59 |
### Update cached pairs with the latest data
```bash
python3 ./freqtrade/main.py edge --refresh-pairs-cached
```
### Precising stoploss range
```bash
python3 ./freqtrade/main.py edge --stoplosses=-0.01,-0.1,-0.001 #min,max,step
```
### Advanced use of timerange
```bash
python3 ./freqtrade/main.py edge --timerange=20181110-20181113
```
Doing --timerange=-200 will get the last 200 timeframes from your inputdata. You can also specify specific dates, or a range span indexed by start and stop.
The full timerange specification:
* Use last 123 tickframes of data: --timerange=-123
* Use first 123 tickframes of data: --timerange=123-
* Use tickframes from line 123 through 456: --timerange=123-456
* Use tickframes till 2018/01/31: --timerange=-20180131
* Use tickframes since 2018/01/31: --timerange=20180131-
* Use tickframes since 2018/01/31 till 2018/03/01 : --timerange=20180131-20180301
* Use tickframes between POSIX timestamps 1527595200 1527618600: --timerange=1527595200-1527618600

View File

@@ -27,7 +27,7 @@ like pauses. You can stop your bot, adjust settings and start it again.
#### I want to improve the bot with a new strategy
That's great. We have a nice backtesting and hyperoptimizing setup. See
the tutorial [here|Testing-new-strategies-with-Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands).
the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-commands).
#### Is there a setting to only SELL the coins being held and not
perform anymore BUYS?
@@ -68,4 +68,3 @@ but it will give the idea. With only these triggers and guards there is
already 8*10^9*10 evaluations. A roughly total of 80 billion evals.
Did you run 100 000 evals? Congrats, you've done roughly 1 / 100 000 th
of the search space.

View File

@@ -1,33 +1,55 @@
# Hyperopt
This page explains how to tune your strategy by finding the optimal
parameters, a process called hyperparameter optimization. The bot uses several
algorithms included in the `scikit-optimize` package to accomplish this. The
search will burn all your CPU cores, make your laptop sound like a fighter jet
and still take a long time.
## Table of Contents
- [Prepare your Hyperopt](#prepare-hyperopt)
- [Configure your Guards and Triggers](#configure-your-guards-and-triggers)
- [Solving a Mystery](#solving-a-mystery)
- [Adding New Indicators](#adding-new-indicators)
- [Execute Hyperopt](#execute-hyperopt)
- [Understand the hyperopts result](#understand-the-backtesting-result)
!!! Bug
Hyperopt will crash when used with only 1 CPU Core as found out in [Issue #1133](https://github.com/freqtrade/freqtrade/issues/1133)
## Prepare Hyperopting
We recommend you start by taking a look at `hyperopt.py` file located in [freqtrade/optimize](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py)
### Configure your Guards and Triggers
There are two places you need to change to add a new buy strategy for testing:
- Inside [populate_buy_trend()](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L278-L294).
- Inside [hyperopt_space()](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L218-L229)
and the associated methods `indicator_space`, `roi_space`, `stoploss_space`.
Before we start digging into Hyperopt, we recommend you to take a look at
an example hyperopt file located into [user_data/hyperopts/](https://github.com/freqtrade/freqtrade/blob/develop/user_data/hyperopts/test_hyperopt.py)
There you have two different type of indicators: 1. `guards` and 2. `triggers`.
1. Guards are conditions like "never buy if ADX < 10", or "never buy if
current price is over EMA10".
2. Triggers are ones that actually trigger buy in specific moment, like
"buy when EMA5 crosses over EMA10" or "buy when close price touches lower
bollinger band".
Configuring hyperopt is similar to writing your own strategy, and many tasks will be similar and a lot of code can be copied across from the strategy.
### Checklist on all tasks / possibilities in hyperopt
Depending on the space you want to optimize, only some of the below are required.
* fill `populate_indicators` - probably a copy from your strategy
* fill `buy_strategy_generator` - for buy signal optimization
* fill `indicator_space` - for buy signal optimzation
* fill `sell_strategy_generator` - for sell signal optimization
* fill `sell_indicator_space` - for sell signal optimzation
* fill `roi_space` - for ROI optimization
* fill `generate_roi_table` - for ROI optimization (if you need more than 3 entries)
* fill `stoploss_space` - stoploss optimization
* Optional but recommended
* copy `populate_buy_trend` from your strategy - otherwise default-strategy will be used
* copy `populate_sell_trend` from your strategy - otherwise default-strategy will be used
### 1. Install a Custom Hyperopt File
Put your hyperopt file into the folder`user_data/hyperopts`.
Let assume you want a hyperopt file `awesome_hyperopt.py`:
Copy the file `user_data/hyperopts/sample_hyperopt.py` into `user_data/hyperopts/awesome_hyperopt.py`
### 2. Configure your Guards and Triggers
There are two places you need to change in your hyperopt file to add a new buy hyperopt for testing:
- Inside `indicator_space()` - the parameters hyperopt shall be optimizing.
- Inside `populate_buy_trend()` - applying the parameters.
There you have two different types of indicators: 1. `guards` and 2. `triggers`.
1. Guards are conditions like "never buy if ADX < 10", or never buy if current price is over EMA10.
2. Triggers are ones that actually trigger buy in specific moment, like "buy when EMA5 crosses over EMA10" or "buy when close price touches lower bollinger band".
Hyperoptimization will, for each eval round, pick one trigger and possibly
multiple guards. The constructed strategy will be something like
@@ -38,6 +60,17 @@ If you have updated the buy strategy, ie. changed the contents of
`populate_buy_trend()` method you have to update the `guards` and
`triggers` hyperopts must use.
#### Sell optimization
Similar to the buy-signal above, sell-signals can also be optimized.
Place the corresponding settings into the following methods
* Inside `sell_indicator_space()` - the parameters hyperopt shall be optimizing.
* Inside `populate_sell_trend()` - applying the parameters.
The configuration and rules are the same than for buy signals.
To avoid naming collisions in the search-space, please prefix all sell-spaces with `sell-`.
## Solving a Mystery
Let's say you are curious: should you use MACD crossings or lower Bollinger
@@ -48,7 +81,7 @@ mystery.
We will start by defining a search space:
```
```python
def indicator_space() -> List[Dimension]:
"""
Define your Hyperopt space for searching strategy parameters
@@ -71,7 +104,7 @@ one we call `trigger` and use it to decide which buy trigger we want to use.
So let's write the buy strategy using these values:
```
``` python
def populate_buy_trend(dataframe: DataFrame) -> DataFrame:
conditions = []
# GUARDS AND TRENDS
@@ -81,12 +114,13 @@ So let's write the buy strategy using these values:
conditions.append(dataframe['rsi'] < params['rsi-value'])
# TRIGGERS
if params['trigger'] == 'bb_lower':
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
if params['trigger'] == 'macd_cross_signal':
conditions.append(qtpylib.crossed_above(
dataframe['macd'], dataframe['macdsignal']
))
if 'trigger' in params:
if params['trigger'] == 'bb_lower':
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
if params['trigger'] == 'macd_cross_signal':
conditions.append(qtpylib.crossed_above(
dataframe['macd'], dataframe['macdsignal']
))
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
@@ -111,33 +145,44 @@ When you want to test an indicator that isn't used by the bot currently, remembe
add it to the `populate_indicators()` method in `hyperopt.py`.
## Execute Hyperopt
Once you have updated your hyperopt configuration you can run it.
Because hyperopt tries a lot of combination to find the best parameters
it will take time you will have the result (more than 30 mins).
We strongly recommend to use `screen` to prevent any connection loss.
Once you have updated your hyperopt configuration you can run it.
Because hyperopt tries a lot of combinations to find the best parameters it will take time you will have the result (more than 30 mins).
We strongly recommend to use `screen` or `tmux` to prevent any connection loss.
```bash
python3 ./freqtrade/main.py -c config.json hyperopt -e 5000
python3 ./freqtrade/main.py --hyperopt <hyperoptname> -c config.json hyperopt -e 5000 --spaces all
```
Use `<hyperoptname>` as the name of the custom hyperopt used.
The `-e` flag will set how many evaluations hyperopt will do. We recommend
running at least several thousand evaluations.
The `--spaces all` flag determines that all possible parameters should be optimized. Possibilities are listed below.
!!! Warning
When switching parameters or changing configuration options, the file `user_data/hyperopt_results.pickle` should be removed. It's used to be able to continue interrupted calculations, but does not detect changes to settings or the hyperopt file.
### Execute Hyperopt with Different Ticker-Data Source
If you would like to hyperopt parameters using an alternate ticker data that
you have on-disk, use the `--datadir PATH` option. Default hyperopt will
use data from directory `user_data/data`.
### Running Hyperopt with Smaller Testset
Use the `--timeperiod` argument to change how much of the testset
Use the `--timerange` argument to change how much of the testset
you want to use. The last N ticks/timeframes will be used.
Example:
```bash
python3 ./freqtrade/main.py hyperopt --timeperiod -200
python3 ./freqtrade/main.py hyperopt --timerange -200
```
### Running Hyperopt with Smaller Search Space
Use the `--spaces` argument to limit the search space used by hyperopt.
Letting Hyperopt optimize everything is a huuuuge search space. Often it
might make more sense to start by just searching for initial buy algorithm.
@@ -148,11 +193,13 @@ Legal values are:
- `all`: optimize everything
- `buy`: just search for a new buy strategy
- `sell`: just search for a new sell strategy
- `roi`: just optimize the minimal profit table for your strategy
- `stoploss`: search for the best stoploss value
- space-separated list of any of the above values for example `--spaces roi stoploss`
## Understand the Hyperopts Result
## Understand the Hyperopt Result
Once Hyperopt is completed you can use the result to create a new strategy.
Given the following result from hyperopt:
@@ -160,10 +207,15 @@ Given the following result from hyperopt:
Best result:
135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins.
with values:
{'adx-value': 44, 'rsi-value': 29, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'bb_lower'}
{ 'adx-value': 44,
'rsi-value': 29,
'adx-enabled': False,
'rsi-enabled': True,
'trigger': 'bb_lower'}
```
You should understand this result like:
- The buy trigger that worked best was `bb_lower`.
- You should not use ADX because `adx-enabled: False`)
- You should **consider** using the RSI indicator (`rsi-enabled: True` and the best value is `29.0` (`rsi-value: 29.0`)
@@ -171,15 +223,16 @@ You should understand this result like:
You have to look inside your strategy file into `buy_strategy_generator()`
method, what those values match to.
So for example you had `rsi-value: 29.0` so we would look
at `rsi`-block, that translates to the following code block:
So for example you had `rsi-value: 29.0` so we would look at `rsi`-block, that translates to the following code block:
```
(dataframe['rsi'] < 29.0)
```
Translating your whole hyperopt result as the new buy-signal
would then look like:
```
```python
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
dataframe.loc[
(
@@ -190,6 +243,55 @@ def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
return dataframe
```
### Understand Hyperopt ROI results
If you are optimizing ROI, you're result will look as follows and include a ROI table.
```
Best result:
135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins.
with values:
{ 'adx-value': 44,
'rsi-value': 29,
'adx-enabled': false,
'rsi-enabled': True,
'trigger': 'bb_lower',
'roi_t1': 40,
'roi_t2': 57,
'roi_t3': 21,
'roi_p1': 0.03634636907306948,
'roi_p2': 0.055237357937802885,
'roi_p3': 0.015163796015548354,
'stoploss': -0.37996664668703606
}
ROI table:
{ 0: 0.10674752302642071,
21: 0.09158372701087236,
78: 0.03634636907306948,
118: 0}
```
This would translate to the following ROI table:
``` python
minimal_roi = {
"118": 0,
"78": 0.0363463,
"21": 0.0915,
"0": 0.106
}
```
### Validate backtest result
Once the optimized strategy has been implemented into your strategy, you should backtest this strategy to make sure everything is working as expected.
To archive the same results (number of trades, ...) than during hyperopt, please use the command line flag `--disable-max-market-positions`.
This setting is the default for hyperopt for speed reasons. You can overwrite this in the configuration by setting `"position_stacking"=false` or by changing the relevant line in your hyperopt file [here](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L283).
!!! Note:
Dry/live runs will **NOT** use position stacking - therefore it does make sense to also validate the strategy without this as it's closer to reality.
## Next Step
Now you have a perfect bot and want to control it from Telegram. Your
next step is to learn the [Telegram usage](https://github.com/freqtrade/freqtrade/blob/develop/docs/telegram-usage.md).
next step is to learn the [Telegram usage](telegram-usage.md).

BIN
docs/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1,36 +1,67 @@
# freqtrade documentation
# Freqtrade
[![Build Status](https://travis-ci.org/freqtrade/freqtrade.svg?branch=develop)](https://travis-ci.org/freqtrade/freqtrade)
[![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
[![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](https://codeclimate.com/github/freqtrade/freqtrade/maintainability)
Welcome to freqtrade documentation. Please feel free to contribute to
this documentation if you see it became outdated by sending us a
Pull-request. Do not hesitate to reach us on
[Slack](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE)
if you do not find the answer to your questions.
<!-- Place this tag where you want the button to render. -->
<a class="github-button" href="https://github.com/freqtrade/freqtrade" data-icon="octicon-star" data-size="large" aria-label="Star freqtrade/freqtrade on GitHub">Star</a>
<!-- Place this tag where you want the button to render. -->
<a class="github-button" href="https://github.com/freqtrade/freqtrade/fork" data-icon="octicon-repo-forked" data-size="large" aria-label="Fork freqtrade/freqtrade on GitHub">Fork</a>
<!-- Place this tag where you want the button to render. -->
<a class="github-button" href="https://github.com/freqtrade/freqtrade/archive/master.zip" data-icon="octicon-cloud-download" data-size="large" aria-label="Download freqtrade/freqtrade on GitHub">Download</a>
<!-- Place this tag where you want the button to render. -->
<a class="github-button" href="https://github.com/freqtrade" data-size="large" aria-label="Follow @freqtrade on GitHub">Follow @freqtrade</a>
## Introduction
Freqtrade is a cryptocurrency trading bot written in Python.
## Table of Contents
!!! Danger "DISCLAIMER"
This software is for educational purposes only. Do not risk money which you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.
- [Pre-requisite](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md)
- [Setup your Bittrex account](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md#setup-your-bittrex-account)
- [Setup your Telegram bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md#setup-your-telegram-bot)
- [Bot Installation](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md)
- [Install with Docker (all platforms)](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md#docker)
- [Install on Linux Ubuntu](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md#21-linux---ubuntu-1604)
- [Install on MacOS](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md#23-macos-installation)
- [Install on Windows](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md#windows)
- [Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)
- [Bot usage (Start your bot)](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md)
- [Bot commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#bot-commands)
- [Backtesting commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#backtesting-commands)
- [Hyperopt commands](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands)
- [Bot Optimization](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md)
- [Change your strategy](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md#change-your-strategy)
- [Add more Indicator](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md#add-more-indicator)
- [Test your strategy with Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md)
- [Find optimal parameters with Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
- [Control the bot with telegram](https://github.com/freqtrade/freqtrade/blob/develop/docs/telegram-usage.md)
- [Receive notifications via webhook](https://github.com/freqtrade/freqtrade/blob/develop/docs/webhook-config.md)
- [Contribute to the project](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
- [How to contribute](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
- [Run tests & Check PEP8 compliance](https://github.com/freqtrade/freqtrade/blob/develop/CONTRIBUTING.md)
- [FAQ](https://github.com/freqtrade/freqtrade/blob/develop/docs/faq.md)
- [SQL cheatsheet](https://github.com/freqtrade/freqtrade/blob/develop/docs/sql_cheatsheet.md)
- [Sandbox Testing](https://github.com/freqtrade/freqtrade/blob/develop/docs/sandbox-testing.md))
Always start by running a trading bot in Dry-run and do not engage money before you understand how it works and what profit/loss you should expect.
We strongly recommend you to have coding and Python knowledge. Do not hesitate to read the source code and understand the mechanism of this bot.
## Features
- Based on Python 3.6+: For botting on any operating system - Windows, macOS and Linux
- Persistence: Persistence is achieved through sqlite
- Dry-run: Run the bot without playing money.
- Backtesting: Run a simulation of your buy/sell strategy.
- Strategy Optimization by machine learning: Use machine learning to optimize your buy/sell strategy parameters with real exchange data.
- Edge position sizing Calculate your win rate, risk reward ratio, the best stoploss and adjust your position size before taking a position for each specific market. Learn more
- Whitelist crypto-currencies: Select which crypto-currency you want to trade or use dynamic whitelists.
- Blacklist crypto-currencies: Select which crypto-currency you want to avoid.
- Manageable via Telegram: Manage the bot with Telegram
- Display profit/loss in fiat: Display your profit/loss in 33 fiat.
- Daily summary of profit/loss: Provide a daily summary of your profit/loss.
- Performance status report: Provide a performance status of your current trades.
## Requirements
### Uptodate clock
The clock must be accurate, syncronized to a NTP server very frequently to avoid problems with communication to the exchanges.
### Hardware requirements
To run this bot we recommend you a cloud instance with a minimum of:
- 2GB RAM
- 1GB disk space
- 2vCPU
### Software requirements
- Python 3.6.x
- pip
- git
- TA-Lib
- virtualenv (Recommended)
- Docker (Recommended)
## Support
Help / Slack
For any questions not covered by the documentation or for further information about the bot, we encourage you to join our slack channel.
Click [here](https://join.slack.com/t/highfrequencybot/shared_invite/enQtMjQ5NTM0OTYzMzY3LWMxYzE3M2MxNDdjMGM3ZTYwNzFjMGIwZGRjNTc3ZGU3MGE3NzdmZGMwNmU3NDM5ZTNmM2Y3NjRiNzk4NmM4OGE) to join Slack channel.
## Ready to try?
Begin by reading our installation guide [here](installation).

View File

@@ -1,26 +1,69 @@
# Installation
This page explains how to prepare your environment for running the bot.
To understand how to set up the bot please read the [Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md) page.
## Prerequisite
Before running your bot in production you will need to setup few
external API. In production mode, the bot required valid Bittrex API
credentials and a Telegram bot (optional but recommended).
## Table of Contents
- [Setup your exchange account](#setup-your-exchange-account)
- [Backtesting commands](#setup-your-telegram-bot)
* [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)
- [Linux - Ubuntu 16.04](#linux---ubuntu-1604)
- [MacOS](#macos)
- [Setup Config and virtual env](#setup-config-and-virtual-env)
* [Windows](#windows)
### Setup your exchange account
*To be completed, please feel free to complete this section.*
<!-- /TOC -->
### Setup your Telegram bot
The only things you need is a working Telegram bot and its API token.
Below we explain how to create your Telegram Bot, and how to get your
Telegram user id.
------
### 1. Create your Telegram bot
**1.1. Start a chat with https://telegram.me/BotFather**
**1.2. Send the message `/newbot`. ** *BotFather response:*
```
Alright, a new bot. How are we going to call it? Please choose a name for your bot.
```
**1.3. Choose the public name of your bot (e.x. `Freqtrade bot`)**
*BotFather response:*
```
Good. Now let's choose a username for your bot. It must end in `bot`. Like this, for example: TetrisBot or tetris_bot.
```
**1.4. Choose the name id of your bot (e.x "`My_own_freqtrade_bot`")**
**1.5. Father bot will return you the token (API key)**<br/>
Copy it and keep it you will use it for the config parameter `token`.
*BotFather response:*
```hl_lines="4"
Done! Congratulations on your new bot. You will find it at t.me/My_own_freqtrade_bot. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.
Use this token to access the HTTP API:
521095879:AAEcEZEL7ADJ56FtG_qD0bQJSKETbXCBCi0
For a description of the Bot API, see this page: https://core.telegram.org/bots/api
```
**1.6. Don't forget to start the conversation with your bot, by clicking /START button**
### 2. Get your user id
**2.1. Talk to https://telegram.me/userinfobot**
**2.2. Get your "Id", you will use it for the config parameter
`chat_id`.**
<hr/>
## Quick start
Freqtrade provides a Linux/MacOS script to install all dependencies and help you to configure the bot.
```bash
git clone git@github.com:freqtrade/freqtrade.git
cd freqtrade
git checkout develop
./setup.sh --install
```
!!! Note
Windows installation is explained [here](#windows).
<hr/>
## Easy Installation - Linux Script
If you are on Debian, Ubuntu or MacOS a freqtrade provides a script to Install, Update, Configure, and Reset your bot.
@@ -34,7 +77,7 @@ usage:
-c,--config Easy config generator (Will override your existing file).
```
### --install
** --install **
This script will install everything you need to run the bot:
@@ -44,46 +87,18 @@ This script will install everything you need to run the bot:
This script is a combination of `install script` `--reset`, `--config`
### --update
** --update **
Update parameter will pull the last version of your current branch and update your virtualenv.
### --reset
** --reset **
Reset parameter will hard reset your branch (only if you are on `master` or `develop`) and recreate your virtualenv.
### --config
** --config **
Config parameter is a `config.json` configurator. This script will ask you questions to setup your bot and create your `config.json`.
## 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
@@ -98,33 +113,39 @@ Once you have Docker installed, simply create the config file (e.g. `config.json
### 1. Prepare the Bot
#### 1.1. Clone the git repository
**1.1. Clone the git repository**
Linux/Mac/Windows with WSL
```bash
git clone https://github.com/freqtrade/freqtrade.git
```
#### 1.2. (Optional) Checkout the develop branch
Windows with docker
```bash
git clone --config core.autocrlf=input https://github.com/freqtrade/freqtrade.git
```
**1.2. (Optional) Checkout the develop branch**
```bash
git checkout develop
```
#### 1.3. Go into the new directory
**1.3. Go into the new directory**
```bash
cd freqtrade
```
#### 1.4. Copy `config.json.example` to `config.json`
**1.4. Copy `config.json.example` to `config.json`**
```bash
cp -n config.json.example config.json
```
> To edit the config please refer to the [Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md) page.
> To edit the config please refer to the [Bot Configuration](configuration.md) page.
#### 1.5. Create your database file *(optional - the bot will create it if it is missing)*
**1.5. Create your database file *(optional - the bot will create it if it is missing)**
Production
@@ -138,13 +159,37 @@ Dry-Run
touch tradesv3.dryrun.sqlite
```
### 2. Build the Docker image
### 2. Download or build the docker image
Either use the prebuilt image from docker hub - or build the image yourself if you would like more control on which version is used.
Branches / tags available can be checked out on [Dockerhub](https://hub.docker.com/r/freqtradeorg/freqtrade/tags/).
**2.1. Download the docker image**
Pull the image from docker hub and (optionally) change the name of the image
```bash
docker pull freqtradeorg/freqtrade:develop
# Optionally tag the repository so the run-commands remain shorter
docker tag freqtradeorg/freqtrade:develop freqtrade
```
To update the image, simply run the above commands again and restart your running container.
**2.2. Build the Docker image**
```bash
cd freqtrade
docker build -t freqtrade .
```
If you are developing using Docker, use `Dockerfile.develop` to build a dev Docker image, which will also set up develop dependencies:
```bash
docker build -f ./Dockerfile.develop -t freqtrade-dev .
```
For security reasons, your configuration file will not be included in the image, you will need to bind mount it. It is also advised to bind mount an SQLite database file (see the "5. Run a restartable docker image" section) to keep it between updates.
### 3. Verify the Docker image
@@ -169,7 +214,7 @@ There is known issue in OSX Docker versions after 17.09.1, whereby /etc/localtim
docker run --rm -e TZ=`ls -la /etc/localtime | cut -d/ -f8-9` -v `pwd`/config.json:/freqtrade/config.json -it freqtrade
```
More information on this docker issue and work-around can be read [here](https://github.com/docker/for-mac/issues/2396)
More information on this docker issue and work-around can be read [here](https://github.com/docker/for-mac/issues/2396).
In this example, the database will be created inside the docker instance and will be lost when you will refresh your image.
@@ -177,7 +222,7 @@ In this example, the database will be created inside the docker instance and wil
To run a restartable instance in the background (feel free to place your configuration and database files wherever it feels comfortable on your filesystem).
#### 5.1. Move your config file and database
**5.1. Move your config file and database**
```bash
mkdir ~/.freqtrade
@@ -185,7 +230,7 @@ mv config.json ~/.freqtrade
mv tradesv3.sqlite ~/.freqtrade
```
#### 5.2. Run the docker image
**5.2. Run the docker image**
```bash
docker run -d \
@@ -196,8 +241,9 @@ 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.
To override this behaviour use a custom db-url value: i.e.: `--db-url sqlite:///tradesv3.dryrun.sqlite`
!!! Note
db-url defaults to `sqlite:///tradesv3.sqlite` but it defaults to `sqlite://` if `dry_run=True` is being used.
To override this behaviour use a custom db-url value: i.e.: `--db-url sqlite:///tradesv3.dryrun.sqlite`
### 6. Monitor your Docker instance
@@ -211,15 +257,17 @@ 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
```bash
docker run -d \
--name freqtrade \
-v /etc/localtime:/etc/localtime:ro \
@@ -229,21 +277,23 @@ docker run -d \
freqtrade --strategy AwsomelyProfitableStrategy backtesting
```
Head over to the [Backtesting Documentation](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md) for more details.
Head over to the [Backtesting Documentation](backtesting.md) for more details.
*Note*: Additional parameters can be appended after the image name (`freqtrade` in the above example).
!!! Note
Additional parameters can be appended after the image name (`freqtrade` in the above example).
------
## Custom Installation
We've included/collected install instructions for Ubuntu 16.04, MacOS, and Windows. These are guidelines and your success may vary with other distros.
OS Specific steps are listed first, the [Common](#common) section below is necessary for all systems.
### Requirements
Click each one for install guide:
* [Python 3.6.x](http://docs.python-guide.org/en/latest/starting/installation/), 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 +301,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 +309,39 @@ 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. Last miniconda3 installation file use python 3.4, we will update to python 3.6 on this installation.
It's recommended to use (mini)conda for this as installation/compilation of `numpy`, `scipy` and `pandas` takes a long time.
If you have installed it from (mini)conda, you can remove `numpy`, `scipy`, and `pandas` from `requirements.txt` before you install it with `pip`.
Additional package to install on your Raspbian, `libffi-dev` required by cryptography (from python-telegram-bot).
``` bash
conda config --add channels rpi
conda install python=3.6
conda create -n freqtrade python=3.6
conda activate freqtrade
conda install scipy pandas numpy
sudo apt install libffi-dev
python3 -m pip install -r requirements.txt
python3 -m pip install -e .
```
### MacOS
#### Install Python 3.6, git and wget
```bash
brew install python3 git wget
```
### Common
#### 1. Install TA-Lib
Official webpage: https://mrjbq7.github.io/ta-lib/install.html
@@ -267,23 +349,70 @@ 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
./configure --prefix=/usr
sed -i.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h
./configure --prefix=/usr/local
make
make install
sudo make install
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](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 +428,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,11 +447,11 @@ 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).
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of inofficial precompiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which needs to be downloaded and installed using `pip install TA_Lib0.4.17cp36cp36mwin32.whl` (make sure to use the version matching your python version)
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial precompiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which needs to be downloaded and installed using `pip install TA_Lib0.4.17cp36cp36mwin32.whl` (make sure to use the version matching your python version)
```cmd
>cd \path\freqtrade-develop
@@ -390,5 +468,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)...
[Bot Configuration](configuration.md).

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

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

View File

@@ -1,10 +1,6 @@
# Plotting
This page explains how to plot prices, indicator, profits.
## Table of Contents
- [Plot price and indicators](#plot-price-and-indicators)
- [Plot profit](#plot-profit)
## Installation
Plotting scripts use Plotly library. Install/upgrade it with:
@@ -19,7 +15,7 @@ At least version 2.3.0 is required.
Usage for the price plotter:
```
script/plot_dataframe.py [-h] [-p pair] [--live]
script/plot_dataframe.py [-h] [-p pairs] [--live]
```
Example
@@ -27,11 +23,16 @@ Example
python scripts/plot_dataframe.py -p BTC/ETH
```
The `-p` pair argument, can be used to specify what
pair you would like to plot.
The `-p` pairs argument, can be used to specify
pairs you would like to plot.
**Advanced use**
To plot multiple pairs, separate them with a comma:
```
python scripts/plot_dataframe.py -p BTC/ETH,XRP/ETH
```
To plot the current live price use the `--live` flag:
```
python scripts/plot_dataframe.py -p BTC/ETH --live

View File

@@ -1,48 +0,0 @@
# Pre-requisite
Before running your bot in production you will need to setup few
external API. In production mode, the bot required valid Bittrex API
credentials and a Telegram bot (optional but recommended).
## Table of Contents
- [Setup your Bittrex account](#setup-your-bittrex-account)
- [Backtesting commands](#setup-your-telegram-bot)
## Setup your Bittrex account
*To be completed, please feel free to complete this section.*
## Setup your Telegram bot
The only things you need is a working Telegram bot and its API token.
Below we explain how to create your Telegram Bot, and how to get your
Telegram user id.
### 1. Create your Telegram bot
**1.1. Start a chat with https://telegram.me/BotFather**
**1.2. Send the message** `/newbot`
*BotFather response:*
```
Alright, a new bot. How are we going to call it? Please choose a name for your bot.
```
**1.3. Choose the public name of your bot (e.g "`Freqtrade bot`")**
*BotFather response:*
```
Good. Now let's choose a username for your bot. It must end in `bot`. Like this, for example: TetrisBot or tetris_bot.
```
**1.4. Choose the name id of your bot (e.g "`My_own_freqtrade_bot`")**
**1.5. Father bot will return you the token (API key)**
Copy it and keep it you will use it for the config parameter `token`.
*BotFather response:*
```
Done! Congratulations on your new bot. You will find it at t.me/My_own_freqtrade_bot. You can now add a description, about section and profile picture for your bot, see /help for a list of commands. By the way, when you've finished creating your cool bot, ping our Bot Support if you want a better username for it. Just make sure the bot is fully operational before you do this.
Use this token to access the HTTP API:
521095879:AAEcEZEL7ADJ56FtG_qD0bQJSKETbXCBCi0
For a description of the Bot API, see this page: https://core.telegram.org/bots/api
```
**1.6. Don't forget to start the conversation with your bot, by clicking /START button**
### 2. Get your user id
**2.1. Talk to https://telegram.me/userinfobot**
**2.2. Get your "Id", you will use it for the config parameter
`chat_id`.**

View File

@@ -0,0 +1 @@
mkdocs-material==3.1.0

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

@@ -2,9 +2,20 @@
At this stage the bot contains the following stoploss support modes:
1. static stop loss, defined in either the strategy or configuration
2. trailing stop loss, defined in the configuration
3. trailing stop loss, custom positive loss, defined in configuration
1. static stop loss, defined in either the strategy or configuration.
2. trailing stop loss, defined in the configuration.
3. trailing stop loss, custom positive loss, defined in configuration.
!!! Note
All stoploss properties can be configured in either Strategy or configuration. Configuration values override strategy values.
Those stoploss modes can be *on exchange* or *off exchange*. If the stoploss is *on exchange* it means a stoploss limit order is placed on the exchange immediately after buy order happens successfuly. This will protect you against sudden crashes in market as the order will be in the queue immediately and if market goes down then the order has more chance of being fulfilled.
In case of stoploss on exchange there is another parameter called `stoploss_on_exchange_interval`. This configures the interval in seconds at which the bot will check the stoploss and update it if necessary. As an example in case of trailing stoploss if the order is on the exchange and the market is going up then the bot automatically cancels the previous stoploss order and put a new one with a stop value higher than previous one. It is clear that the bot cannot do it every 5 seconds otherwise it gets banned. So this parameter will tell the bot how often it should update the stoploss order. The default value is 60 (1 minute).
!!! Note
Stoploss on exchange is only supported for Binance as of now.
## Static Stop Loss
@@ -48,4 +59,4 @@ Both values can be configured in the main configuration file and requires `"trai
The 0.01 would translate to a 1% stop loss, once you hit 1.1% profit.
You should also make sure to have this value higher than your minimal ROI, otherwise minimal ROI will apply first and sell your trade.
You should also make sure to have this value (`trailing_stop_positive_offset`) lower than your minimal ROI, otherwise minimal ROI will apply first and sell your trade.

View File

@@ -2,9 +2,9 @@
This page explains how to command your bot with Telegram.
## Pre-requisite
## Prerequisite
To control your bot with Telegram, you need first to
[set up a Telegram bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md)
[set up a Telegram bot](installation.md)
and add your Telegram API keys into your config file.
## Telegram commands
@@ -23,6 +23,7 @@ official commands. You can ask at any moment for help with `/help`.
| `/profit` | | Display a summary of your profit/loss from close trades and some stats about your performance
| `/forcesell <trade_id>` | | Instantly sells the given trade (Ignoring `minimum_roi`).
| `/forcesell all` | | Instantly sells all open trades (Ignoring `minimum_roi`).
| `/forcebuy <pair> [rate]` | | Instantly buys the given pair. Rate is optional. (`forcebuy_enable` must be set to True)
| `/performance` | | Show performance of each finished trade grouped by pair
| `/balance` | | Show account balance per currency
| `/daily <n>` | 7 | Shows profit or loss per day, over the last n days
@@ -30,16 +31,20 @@ official commands. You can ask at any moment for help with `/help`.
| `/version` | | Show version
## Telegram commands in action
Below, example of Telegram message you will receive for each command.
### /start
> **Status:** `running`
### /stop
> `Stopping trader ...`
> **Status:** `stopped`
## /status
For each open trade, the bot will send you the following message.
> **Trade ID:** `123`
@@ -54,6 +59,7 @@ For each open trade, the bot will send you the following message.
> **Open Order:** `None`
## /status table
Return the status of all open trades in a table format.
```
ID Pair Since Profit
@@ -63,6 +69,7 @@ Return the status of all open trades in a table format.
```
## /count
Return the number of trades used and available.
```
current max
@@ -71,6 +78,7 @@ current max
```
## /profit
Return a summary of your profit/loss and performance.
> **ROI:** Close trades
@@ -90,7 +98,14 @@ Return a summary of your profit/loss and performance.
> **BITTREX:** Selling BTC/LTC with limit `0.01650000 (profit: ~-4.07%, -0.00008168)`
## /forcebuy <pair>
> **BITTREX**: Buying ETH/BTC with limit `0.03400000` (`1.000000 ETH`, `225.290 USD`)
Note that for this to work, `forcebuy_enable` needs to be set to true.
## /performance
Return the performance of each crypto-currency the bot has sold.
> Performance:
> 1. `RCN/BTC 57.77%`
@@ -101,6 +116,7 @@ Return the performance of each crypto-currency the bot has sold.
> ...
## /balance
Return the balance of all crypto-currency your have on the exchange.
> **Currency:** BTC
@@ -114,6 +130,7 @@ Return the balance of all crypto-currency your have on the exchange.
> **Pending:** 0.0
## /daily <n>
Per default `/daily` will return the 7 last days.
The example below if for `/daily 3`:
@@ -127,11 +144,5 @@ Day Profit BTC Profit USD
```
## /version
> **Version:** `0.14.3`
### using proxy with telegram
```
$ export HTTP_PROXY="http://addr:port"
$ export HTTPS_PROXY="http://addr:port"
$ freqtrade
```
> **Version:** `0.14.3`

View File

@@ -66,6 +66,7 @@ Possible parameters are:
* profit_fiat
* stake_currency
* fiat_currency
* sell_reason
### Webhookstatus

View File

@@ -1,5 +1,5 @@
""" FreqTrade bot """
__version__ = '0.17.1'
__version__ = '0.18.1'
class DependencyException(BaseException):

View File

@@ -104,10 +104,19 @@ class Arguments(object):
type=str,
metavar='PATH',
)
self.parser.add_argument(
'--customhyperopt',
help='specify hyperopt class name (default: %(default)s)',
dest='hyperopt',
default=constants.DEFAULT_HYPEROPT,
type=str,
metavar='NAME',
)
self.parser.add_argument(
'--dynamic-whitelist',
help='dynamically generate and update whitelist'
' based on 24h BaseVolume (default: %(const)s)',
' based on 24h BaseVolume (default: %(const)s)'
' DEPRECATED.',
dest='dynamic_whitelist',
const=constants.DYNAMIC_WHITELIST,
type=int,
@@ -119,7 +128,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',
)
@@ -129,6 +137,22 @@ class Arguments(object):
"""
Parses given arguments for Backtesting scripts.
"""
parser.add_argument(
'--eps', '--enable-position-stacking',
help='Allow buying the same pair multiple times (position stacking)',
action='store_true',
dest='position_stacking',
default=False
)
parser.add_argument(
'--dmmp', '--disable-max-market-positions',
help='Disable applying `max_open_trades` during backtest '
'(same as setting `max_open_trades` to a very high number)',
action='store_false',
dest='use_max_market_positions',
default=True
)
parser.add_argument(
'-l', '--live',
help='using live data',
@@ -172,6 +196,27 @@ class Arguments(object):
metavar='PATH',
)
@staticmethod
def edge_options(parser: argparse.ArgumentParser) -> None:
"""
Parses given arguments for Backtesting scripts.
"""
parser.add_argument(
'-r', '--refresh-pairs-cached',
help='refresh the pairs files in tests/testdata with the latest data from the '
'exchange. Use it if you want to run your edge with up-to-date data.',
action='store_true',
dest='refresh_pairs',
)
parser.add_argument(
'--stoplosses',
help='defines a range of stoploss against which edge will assess the strategy '
'the format is "min,max,step" (without any space).'
'example: --stoplosses=-0.01,-0.1,-0.001',
type=str,
dest='stoploss_range',
)
@staticmethod
def optimizer_shared_options(parser: argparse.ArgumentParser) -> None:
"""
@@ -185,22 +230,6 @@ class Arguments(object):
dest='ticker_interval',
type=str,
)
parser.add_argument(
'--eps', '--enable-position-stacking',
help='Allow buying the same pair multiple times (position stacking)',
action='store_true',
dest='position_stacking',
default=False
)
parser.add_argument(
'--dmmp', '--disable-max-market-positions',
help='Disable applying `max_open_trades` during backtest '
'(same as setting `max_open_trades` to a very high number)',
action='store_false',
dest='use_max_market_positions',
default=True
)
parser.add_argument(
'--timerange',
@@ -215,6 +244,22 @@ class Arguments(object):
"""
Parses given arguments for Hyperopt scripts.
"""
parser.add_argument(
'--eps', '--enable-position-stacking',
help='Allow buying the same pair multiple times (position stacking)',
action='store_true',
dest='position_stacking',
default=False
)
parser.add_argument(
'--dmmp', '--disable-max-market-positions',
help='Disable applying `max_open_trades` during backtest '
'(same as setting `max_open_trades` to a very high number)',
action='store_false',
dest='use_max_market_positions',
default=True
)
parser.add_argument(
'-e', '--epochs',
help='specify number of epochs (default: %(default)d)',
@@ -227,7 +272,7 @@ class Arguments(object):
'-s', '--spaces',
help='Specify which parameters to hyperopt. Space separate list. \
Default: %(default)s',
choices=['all', 'buy', 'roi', 'stoploss'],
choices=['all', 'buy', 'sell', 'roi', 'stoploss'],
default='all',
nargs='+',
dest='spaces',
@@ -238,7 +283,7 @@ class Arguments(object):
Builds and attaches all subcommands
:return: None
"""
from freqtrade.optimize import backtesting, hyperopt
from freqtrade.optimize import backtesting, hyperopt, edge_cli
subparsers = self.parser.add_subparsers(dest='subparser')
@@ -248,6 +293,12 @@ class Arguments(object):
self.optimizer_shared_options(backtesting_cmd)
self.backtesting_options(backtesting_cmd)
# Add edge subcommand
edge_cmd = subparsers.add_parser('edge', help='edge module')
edge_cmd.set_defaults(func=edge_cli.start)
self.optimizer_shared_options(edge_cmd)
self.edge_options(edge_cmd)
# Add hyperopt subcommand
hyperopt_cmd = subparsers.add_parser('hyperopt', help='hyperopt module')
hyperopt_cmd.set_defaults(func=hyperopt.start)
@@ -301,9 +352,9 @@ class Arguments(object):
Parses given arguments for scripts.
"""
self.parser.add_argument(
'-p', '--pair',
'-p', '--pairs',
help='Show profits for only this pairs. Pairs are comma-separated.',
dest='pair',
dest='pairs',
default=None
)
@@ -327,6 +378,15 @@ class Arguments(object):
metavar='PATH',
)
self.parser.add_argument(
'-c', '--config',
help='specify configuration file, used for additional exchange parameters',
dest='config',
default=None,
type=str,
metavar='PATH',
)
self.parser.add_argument(
'--days',
help='Download data for number of days',
@@ -338,7 +398,7 @@ class Arguments(object):
self.parser.add_argument(
'--exchange',
help='Exchange name (default: %(default)s)',
help='Exchange name (default: %(default)s). Only valid if no config is provided',
dest='exchange',
type=str,
default='bittrex'

View File

@@ -12,6 +12,7 @@ from jsonschema import Draft4Validator, validate
from jsonschema.exceptions import ValidationError, best_match
from freqtrade import OperationalException, constants
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@@ -33,9 +34,11 @@ class Configuration(object):
Class to read and init the bot configuration
Reuse this class for the bot, backtesting, hyperopt and every script that required configuration
"""
def __init__(self, args: Namespace) -> None:
def __init__(self, args: Namespace, runmode: RunMode = None) -> None:
self.args = args
self.config: Optional[Dict[str, Any]] = None
self.runmode = runmode
def load_config(self) -> Dict[str, Any]:
"""
@@ -52,15 +55,28 @@ class Configuration(object):
if self.args.strategy_path:
config.update({'strategy_path': self.args.strategy_path})
# Add the hyperopt file to use
config.update({'hyperopt': self.args.hyperopt})
# Load Common configuration
config = self._load_common_config(config)
# Load Backtesting
config = self._load_backtesting_config(config)
# Load Edge
config = self._load_edge_config(config)
# Load Hyperopt
config = self._load_hyperopt_config(config)
# Set runmode
if not self.runmode:
# Handle real mode, infer dry/live from config
self.runmode = RunMode.DRY_RUN if config.get('dry_run', True) else RunMode.LIVE
config.update({'runmode': self.runmode})
return config
def _load_config_file(self, path: str) -> Dict[str, Any]:
@@ -103,14 +119,18 @@ class Configuration(object):
# Add dynamic_whitelist if found
if 'dynamic_whitelist' in self.args and self.args.dynamic_whitelist:
config.update({'dynamic_whitelist': self.args.dynamic_whitelist})
logger.info(
'Parameter --dynamic-whitelist detected. '
'Using dynamically generated whitelist. '
# Update to volumePairList (the previous default)
config['pairlist'] = {'method': 'VolumePairList',
'config': {'number_assets': self.args.dynamic_whitelist}
}
logger.warning(
'Parameter --dynamic-whitelist has been deprecated, '
'and will be completely replaced by the whitelist dict in the future. '
'For now: using dynamically generated whitelist based on VolumePairList. '
'(not applicable with Backtesting and Hyperopt)'
)
if self.args.db_url != 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 ...')
@@ -124,6 +144,13 @@ class Configuration(object):
config['db_url'] = constants.DEFAULT_DB_PROD_URL
logger.info('Dry run is disabled')
if config.get('forcebuy_enable', False):
logger.warning('`forcebuy` RPC message enabled.')
# Setting max_open_trades to infinite if -1
if config.get('max_open_trades') == -1:
config['max_open_trades'] = float('inf')
logger.info(f'Using DB: "{config["db_url"]}"')
# Check if the exchange set by the user is supported
@@ -131,13 +158,16 @@ class Configuration(object):
return config
def _create_default_datadir(self, config: Dict[str, Any]) -> str:
exchange_name = config.get('exchange', {}).get('name').lower()
default_path = os.path.join('user_data', 'data', exchange_name)
if not os.path.isdir(default_path):
os.makedirs(default_path)
logger.info(f'Created data directory: {default_path}')
return default_path
def _create_datadir(self, config: Dict[str, Any], datadir: Optional[str] = None) -> str:
if not datadir:
# set datadir
exchange_name = config.get('exchange', {}).get('name').lower()
datadir = os.path.join('user_data', 'data', exchange_name)
if not os.path.isdir(datadir):
os.makedirs(datadir)
logger.info(f'Created data directory: {datadir}')
return datadir
def _load_backtesting_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
"""
@@ -177,9 +207,9 @@ class Configuration(object):
# If --datadir is used we add it to the configuration
if 'datadir' in self.args and self.args.datadir:
config.update({'datadir': self.args.datadir})
config.update({'datadir': self._create_datadir(config, self.args.datadir)})
else:
config.update({'datadir': self._create_default_datadir(config)})
config.update({'datadir': self._create_datadir(config, None)})
logger.info('Using data folder: %s ...', config.get('datadir'))
# If -r/--refresh-pairs-cached is used we add it to the configuration
@@ -207,6 +237,32 @@ class Configuration(object):
return config
def _load_edge_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
"""
Extract information for sys.argv and load Edge configuration
:return: configuration as dictionary
"""
# If --timerange is used we add it to the configuration
if 'timerange' in self.args and self.args.timerange:
config.update({'timerange': self.args.timerange})
logger.info('Parameter --timerange detected: %s ...', self.args.timerange)
# If --timerange is used we add it to the configuration
if 'stoploss_range' in self.args and self.args.stoploss_range:
txt_range = eval(self.args.stoploss_range)
config['edge'].update({'stoploss_range_min': txt_range[0]})
config['edge'].update({'stoploss_range_max': txt_range[1]})
config['edge'].update({'stoploss_range_step': txt_range[2]})
logger.info('Parameter --stoplosses detected: %s ...', self.args.stoploss_range)
# If -r/--refresh-pairs-cached is used we add it to the configuration
if 'refresh_pairs' in self.args and self.args.refresh_pairs:
config.update({'refresh_pairs': True})
logger.info('Parameter -r/--refresh-pairs-cached detected ...')
return config
def _load_hyperopt_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
"""
Extract information for sys.argv and load Hyperopt configuration
@@ -232,7 +288,7 @@ class Configuration(object):
:return: Returns the config if valid, otherwise throw an exception
"""
try:
validate(conf, constants.CONF_SCHEMA)
validate(conf, constants.CONF_SCHEMA, Draft4Validator)
return conf
except ValidationError as exception:
logger.critical(
@@ -268,6 +324,11 @@ class Configuration(object):
raise OperationalException(
exception_msg
)
# Depreciation warning
if 'ccxt_rate_limit' in config.get('exchange', {}):
logger.warning("`ccxt_rate_limit` has been deprecated in favor of "
"`ccxt_config` and `ccxt_async_config` and will be removed "
"in a future version.")
logger.debug('Exchange "%s" supported', exchange)
return True

View File

@@ -9,10 +9,16 @@ TICKER_INTERVAL = 5 # min
HYPEROPT_EPOCH = 100 # epochs
RETRY_TIMEOUT = 30 # sec
DEFAULT_STRATEGY = 'DefaultStrategy'
DEFAULT_HYPEROPT = 'DefaultHyperOpts'
DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite'
DEFAULT_DB_DRYRUN_URL = 'sqlite://'
UNLIMITED_STAKE_AMOUNT = 'unlimited'
DEFAULT_AMOUNT_RESERVE_PERCENT = 0.05
REQUIRED_ORDERTIF = ['buy', 'sell']
REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']
ORDERTYPE_POSSIBILITIES = ['limit', 'market']
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList']
TICKER_INTERVAL_MINUTES = {
'1m': 1,
@@ -37,13 +43,13 @@ SUPPORTED_FIAT = [
"KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN",
"RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD",
"BTC", "XBT", "ETH", "XRP", "LTC", "BCH", "USDT"
]
]
# Required json-schema for user specified config
CONF_SCHEMA = {
'type': 'object',
'properties': {
'max_open_trades': {'type': 'integer', 'minimum': 0},
'max_open_trades': {'type': 'integer', 'minimum': -1},
'ticker_interval': {'type': 'string', 'enum': list(TICKER_INTERVAL_MINUTES.keys())},
'stake_currency': {'type': 'string', 'enum': ['BTC', 'XBT', 'ETH', 'USDT', 'EUR', 'USD']},
'stake_amount': {
@@ -53,6 +59,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,20 +85,65 @@ 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}
}
},
'order_types': {
'type': 'object',
'properties': {
'buy': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
'sell': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
'stoploss': {'type': 'string', 'enum': ORDERTYPE_POSSIBILITIES},
'stoploss_on_exchange': {'type': 'boolean'},
'stoploss_on_exchange_interval': {'type': 'number'}
},
'required': ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']
},
'order_time_in_force': {
'type': 'object',
'properties': {
'buy': {'type': 'string', 'enum': ORDERTIF_POSSIBILITIES},
'sell': {'type': 'string', 'enum': ORDERTIF_POSSIBILITIES}
},
'required': ['buy', 'sell']
},
'exchange': {'$ref': '#/definitions/exchange'},
'edge': {'$ref': '#/definitions/edge'},
'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'}
}
},
'pairlist': {
'type': 'object',
'properties': {
'method': {'type': 'string', 'enum': AVAILABLE_PAIRLISTS},
'config': {'type': 'object'}
},
'required': ['method']
},
'telegram': {
'type': 'object',
'properties': {
@@ -112,6 +164,7 @@ CONF_SCHEMA = {
},
'db_url': {'type': 'string'},
'initial_state': {'type': 'string', 'enum': ['running', 'stopped']},
'forcebuy_enable': {'type': 'boolean'},
'internals': {
'type': 'object',
'properties': {
@@ -145,9 +198,31 @@ CONF_SCHEMA = {
'pattern': '^[0-9A-Z]+/[0-9A-Z]+$'
},
'uniqueItems': True
}
},
'outdated_offset': {'type': 'integer', 'minimum': 1},
'ccxt_config': {'type': 'object'},
'ccxt_async_config': {'type': 'object'}
},
'required': ['name', 'key', 'secret', 'pair_whitelist']
},
'edge': {
'type': 'object',
'properties': {
"enabled": {'type': 'boolean'},
"process_throttle_secs": {'type': 'integer', 'minimum': 600},
"calculate_since_number_of_days": {'type': 'integer'},
"allowed_risk": {'type': 'number'},
"capital_available_percentage": {'type': 'number'},
"stoploss_range_min": {'type': 'number'},
"stoploss_range_max": {'type': 'number'},
"stoploss_range_step": {'type': 'number'},
"minimum_winrate": {'type': 'number'},
"minimum_expectancy": {'type': 'number'},
"min_trade_number": {'type': 'number'},
"max_trade_duration_minute": {'type': 'integer'},
"remove_pumps": {'type': 'boolean'}
},
'required': ['process_throttle_secs', 'allowed_risk', 'capital_available_percentage']
}
},
'anyOf': [

View File

@@ -0,0 +1,8 @@
"""
Module to handle data operations for freqtrade
"""
# limit what's imported when using `from freqtrad.data import *``
__all__ = [
'converter'
]

106
freqtrade/data/converter.py Normal file
View File

@@ -0,0 +1,106 @@
"""
Functions to convert data from one format to another
"""
import logging
import pandas as pd
from pandas import DataFrame, to_datetime
from freqtrade.constants import TICKER_INTERVAL_MINUTES
logger = logging.getLogger(__name__)
def parse_ticker_dataframe(ticker: list, ticker_interval: str,
fill_missing: bool = True) -> DataFrame:
"""
Converts a ticker-list (format ccxt.fetch_ohlcv) to a Dataframe
:param ticker: ticker list, as returned by exchange.async_get_candle_history
:param ticker_interval: ticker_interval (e.g. 5m). Used to fill up eventual missing data
:param fill_missing: fill up missing candles with 0 candles
(see ohlcv_fill_up_missing_data for details)
:return: DataFrame
"""
logger.debug("Parsing tickerlist to dataframe")
cols = ['date', 'open', 'high', 'low', 'close', 'volume']
frame = DataFrame(ticker, columns=cols)
frame['date'] = to_datetime(frame['date'],
unit='ms',
utc=True,
infer_datetime_format=True)
# Some exchanges return int values for volume and even for ohlc.
# Convert them since TA-LIB indicators used in the strategy assume floats
# and fail with exception...
frame = frame.astype(dtype={'open': 'float', 'high': 'float', 'low': 'float', 'close': 'float',
'volume': 'float'})
# group by index and aggregate results to eliminate duplicate ticks
frame = frame.groupby(by='date', as_index=False, sort=True).agg({
'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last',
'volume': 'max',
})
frame.drop(frame.tail(1).index, inplace=True) # eliminate partial candle
logger.debug('Dropping last candle')
if fill_missing:
return ohlcv_fill_up_missing_data(frame, ticker_interval)
else:
return frame
def ohlcv_fill_up_missing_data(dataframe: DataFrame, ticker_interval: str) -> DataFrame:
"""
Fills up missing data with 0 volume rows,
using the previous close as price for "open", "high" "low" and "close", volume is set to 0
"""
ohlc_dict = {
'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last',
'volume': 'sum'
}
tick_mins = TICKER_INTERVAL_MINUTES[ticker_interval]
# Resample to create "NAN" values
df = dataframe.resample(f'{tick_mins}min', on='date').agg(ohlc_dict)
# Forwardfill close for missing columns
df['close'] = df['close'].fillna(method='ffill')
# Use close for "open, high, low"
df.loc[:, ['open', 'high', 'low']] = df[['open', 'high', 'low']].fillna(
value={'open': df['close'],
'high': df['close'],
'low': df['close'],
})
df.reset_index(inplace=True)
logger.debug(f"Missing data fillup: before: {len(dataframe)} - after: {len(df)}")
return df
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

@@ -0,0 +1,97 @@
"""
Dataprovider
Responsible to provide data to the bot
including Klines, tickers, historic data
Common Interface for bot and strategy to access data.
"""
import logging
from pathlib import Path
from typing import List, Tuple
from pandas import DataFrame
from freqtrade.data.history import load_pair_history
from freqtrade.exchange import Exchange
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
class DataProvider(object):
def __init__(self, config: dict, exchange: Exchange) -> None:
self._config = config
self._exchange = exchange
def refresh(self,
pairlist: List[Tuple[str, str]],
helping_pairs: List[Tuple[str, str]] = None) -> None:
"""
Refresh data, called with each cycle
"""
if helping_pairs:
self._exchange.refresh_latest_ohlcv(pairlist + helping_pairs)
else:
self._exchange.refresh_latest_ohlcv(pairlist)
@property
def available_pairs(self) -> List[Tuple[str, str]]:
"""
Return a list of tuples containing pair, tick_interval for which data is currently cached.
Should be whitelist + open trades.
"""
return list(self._exchange._klines.keys())
def ohlcv(self, pair: str, tick_interval: str = None, copy: bool = True) -> DataFrame:
"""
get ohlcv data for the given pair as DataFrame
Please check `available_pairs` to verify which pairs are currently cached.
:param pair: pair to get the data for
:param tick_interval: ticker_interval to get pair for
:param copy: copy dataframe before returning.
Use false only for RO operations (where the dataframe is not modified)
"""
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
if tick_interval:
pairtick = (pair, tick_interval)
else:
pairtick = (pair, self._config['ticker_interval'])
return self._exchange.klines(pairtick, copy=copy)
else:
return DataFrame()
def historic_ohlcv(self, pair: str, ticker_interval: str) -> DataFrame:
"""
get stored historic ohlcv data
:param pair: pair to get the data for
:param tick_interval: ticker_interval to get pair for
"""
return load_pair_history(pair=pair,
ticker_interval=ticker_interval,
refresh_pairs=False,
datadir=Path(self._config['datadir']) if self._config.get(
'datadir') else None
)
def ticker(self, pair: str):
"""
Return last ticker data
"""
# TODO: Implement me
pass
def orderbook(self, pair: str, max: int):
"""
return latest orderbook data
"""
# TODO: Implement me
pass
@property
def runmode(self) -> RunMode:
"""
Get runmode of the bot
can be "live", "dry-run", "backtest", "edgecli", "hyperopt" or "other".
"""
return RunMode(self._config.get('runmode', RunMode.OTHER))

235
freqtrade/data/history.py Normal file
View File

@@ -0,0 +1,235 @@
"""
Handle historic data (ohlcv).
includes:
* load data for a pair (or a list of pairs) from disk
* download data from exchange and store to disk
"""
import logging
from pathlib import Path
from typing import Optional, List, Dict, Tuple, Any
import arrow
from pandas import DataFrame
from freqtrade import misc, constants, OperationalException
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.exchange import Exchange
from freqtrade.arguments import TimeRange
logger = logging.getLogger(__name__)
def trim_tickerlist(tickerlist: List[Dict], timerange: TimeRange) -> List[Dict]:
"""
Trim tickerlist based on given timerange
"""
if not tickerlist:
return tickerlist
start_index = 0
stop_index = len(tickerlist)
if timerange.starttype == 'line':
stop_index = timerange.startts
if timerange.starttype == 'index':
start_index = timerange.startts
elif timerange.starttype == 'date':
while (start_index < len(tickerlist) and
tickerlist[start_index][0] < timerange.startts * 1000):
start_index += 1
if timerange.stoptype == 'line':
start_index = len(tickerlist) + timerange.stopts
if timerange.stoptype == 'index':
stop_index = timerange.stopts
elif timerange.stoptype == 'date':
while (stop_index > 0 and
tickerlist[stop_index-1][0] > timerange.stopts * 1000):
stop_index -= 1
if start_index > stop_index:
raise ValueError(f'The timerange [{timerange.startts},{timerange.stopts}] is incorrect')
return tickerlist[start_index:stop_index]
def load_tickerdata_file(
datadir: Optional[Path], pair: str,
ticker_interval: str,
timerange: Optional[TimeRange] = None) -> Optional[list]:
"""
Load a pair from file, either .json.gz or .json
:return tickerlist or None if unsuccesful
"""
path = make_testdata_path(datadir)
pair_s = pair.replace('/', '_')
file = path.joinpath(f'{pair_s}-{ticker_interval}.json')
pairdata = misc.file_load_json(file)
if not pairdata:
return None
if timerange:
pairdata = trim_tickerlist(pairdata, timerange)
return pairdata
def load_pair_history(pair: str,
ticker_interval: str,
datadir: Optional[Path],
timerange: TimeRange = TimeRange(None, None, 0, 0),
refresh_pairs: bool = False,
exchange: Optional[Exchange] = None,
fill_up_missing: bool = True
) -> DataFrame:
"""
Loads cached ticker history for the given pair.
:return: DataFrame with ohlcv data
"""
# If the user force the refresh of pairs
if refresh_pairs:
if not exchange:
raise OperationalException("Exchange needs to be initialized when "
"calling load_data with refresh_pairs=True")
logger.info('Download data for pair and store them in %s', datadir)
download_pair_history(datadir=datadir,
exchange=exchange,
pair=pair,
tick_interval=ticker_interval,
timerange=timerange)
pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
if pairdata:
if timerange.starttype == 'date' and pairdata[0][0] > timerange.startts * 1000:
logger.warning('Missing data at start for pair %s, data starts at %s',
pair, arrow.get(pairdata[0][0] // 1000).strftime('%Y-%m-%d %H:%M:%S'))
if timerange.stoptype == 'date' and pairdata[-1][0] < timerange.stopts * 1000:
logger.warning('Missing data at end for pair %s, data ends at %s',
pair,
arrow.get(pairdata[-1][0] // 1000).strftime('%Y-%m-%d %H:%M:%S'))
return parse_ticker_dataframe(pairdata, ticker_interval, fill_up_missing)
else:
logger.warning('No data for pair: "%s", Interval: %s. '
'Use --refresh-pairs-cached to download the data',
pair, ticker_interval)
return None
def load_data(datadir: Optional[Path],
ticker_interval: str,
pairs: List[str],
refresh_pairs: bool = False,
exchange: Optional[Exchange] = None,
timerange: TimeRange = TimeRange(None, None, 0, 0),
fill_up_missing: bool = True) -> Dict[str, DataFrame]:
"""
Loads ticker history data for a list of pairs the given parameters
:return: dict(<pair>:<tickerlist>)
"""
result = {}
for pair in pairs:
hist = load_pair_history(pair=pair, ticker_interval=ticker_interval,
datadir=datadir, timerange=timerange,
refresh_pairs=refresh_pairs,
exchange=exchange,
fill_up_missing=fill_up_missing)
if hist is not None:
result[pair] = hist
return result
def make_testdata_path(datadir: Optional[Path]) -> Path:
"""Return the path where testdata files are stored"""
return datadir or (Path(__file__).parent.parent / "tests" / "testdata").resolve()
def load_cached_data_for_updating(filename: Path, tick_interval: str,
timerange: Optional[TimeRange]) -> Tuple[List[Any],
Optional[int]]:
"""
Load cached data and choose what part of the data should be updated
"""
since_ms = None
# user sets timerange, so find the start time
if timerange:
if timerange.starttype == 'date':
since_ms = timerange.startts * 1000
elif timerange.stoptype == 'line':
num_minutes = timerange.stopts * constants.TICKER_INTERVAL_MINUTES[tick_interval]
since_ms = arrow.utcnow().shift(minutes=num_minutes).timestamp * 1000
# read the cached file
if filename.is_file():
with open(filename, "rt") as file:
data = misc.json_load(file)
# remove the last item, could be incomplete candle
if data:
data.pop()
else:
data = []
if data:
if since_ms and since_ms < data[0][0]:
# Earlier data than existing data requested, redownload all
data = []
else:
# a part of the data was already downloaded, so download unexist data only
since_ms = data[-1][0] + 1
return (data, since_ms)
def download_pair_history(datadir: Optional[Path],
exchange: Exchange,
pair: str,
tick_interval: str = '5m',
timerange: Optional[TimeRange] = None) -> bool:
"""
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
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 pair: pair to download
:param tick_interval: ticker interval
:param timerange: range of time to download
:return: bool with success state
"""
try:
path = make_testdata_path(datadir)
filepair = pair.replace("/", "_")
filename = path.joinpath(f'{filepair}-{tick_interval}.json')
logger.info('Download the pair: "%s", Interval: %s', pair, tick_interval)
data, since_ms = load_cached_data_for_updating(filename, tick_interval, timerange)
logger.debug("Current Start: %s", misc.format_ms_time(data[1][0]) if data else 'None')
logger.debug("Current End: %s", misc.format_ms_time(data[-1][0]) if data else 'None')
# Default since_ms to 30 days if nothing is given
new_data = exchange.get_history(pair=pair, tick_interval=tick_interval,
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]))
logger.debug("New End: %s", misc.format_ms_time(data[-1][0]))
misc.file_dump_json(filename, data)
return True
except BaseException:
logger.info('Failed to download the pair: "%s", Interval: %s',
pair, tick_interval)
return False

441
freqtrade/edge/__init__.py Normal file
View File

@@ -0,0 +1,441 @@
# pragma pylint: disable=W0603
""" Edge positioning package """
import logging
from pathlib import Path
from typing import Any, Dict, NamedTuple
import arrow
import numpy as np
import utils_find_1st as utf1st
from pandas import DataFrame
from freqtrade import constants, OperationalException
from freqtrade.arguments import Arguments
from freqtrade.arguments import TimeRange
from freqtrade.data import history
from freqtrade.optimize import get_timeframe
from freqtrade.strategy.interface import SellType
logger = logging.getLogger(__name__)
class PairInfo(NamedTuple):
stoploss: float
winrate: float
risk_reward_ratio: float
required_risk_reward: float
expectancy: float
nb_trades: int
avg_trade_duration: float
class Edge():
"""
Calculates Win Rate, Risk Reward Ratio, Expectancy
against historical data for a give set of markets and a strategy
it then adjusts stoploss and position size accordingly
and force it into the strategy
Author: https://github.com/mishaker
"""
config: Dict = {}
_cached_pairs: Dict[str, Any] = {} # Keeps a list of pairs
def __init__(self, config: Dict[str, Any], exchange, strategy) -> None:
self.config = config
self.exchange = exchange
self.strategy = strategy
self.ticker_interval = self.strategy.ticker_interval
self.tickerdata_to_dataframe = self.strategy.tickerdata_to_dataframe
self.get_timeframe = get_timeframe
self.advise_sell = self.strategy.advise_sell
self.advise_buy = self.strategy.advise_buy
self.edge_config = self.config.get('edge', {})
self._cached_pairs: Dict[str, Any] = {} # Keeps a list of pairs
self._final_pairs: list = []
# checking max_open_trades. it should be -1 as with Edge
# the number of trades is determined by position size
if self.config['max_open_trades'] != float('inf'):
logger.critical('max_open_trades should be -1 in config !')
if self.config['stake_amount'] != constants.UNLIMITED_STAKE_AMOUNT:
raise OperationalException('Edge works only with unlimited stake amount')
self._capital_percentage: float = self.edge_config.get('capital_available_percentage')
self._allowed_risk: float = self.edge_config.get('allowed_risk')
self._since_number_of_days: int = self.edge_config.get('calculate_since_number_of_days', 14)
self._last_updated: int = 0 # Timestamp of pairs last updated time
self._refresh_pairs = True
self._stoploss_range_min = float(self.edge_config.get('stoploss_range_min', -0.01))
self._stoploss_range_max = float(self.edge_config.get('stoploss_range_max', -0.05))
self._stoploss_range_step = float(self.edge_config.get('stoploss_range_step', -0.001))
# calculating stoploss range
self._stoploss_range = np.arange(
self._stoploss_range_min,
self._stoploss_range_max,
self._stoploss_range_step
)
self._timerange: TimeRange = Arguments.parse_timerange("%s-" % arrow.now().shift(
days=-1 * self._since_number_of_days).format('YYYYMMDD'))
self.fee = self.exchange.get_fee()
def calculate(self) -> bool:
pairs = self.config['exchange']['pair_whitelist']
heartbeat = self.edge_config.get('process_throttle_secs')
if (self._last_updated > 0) and (
self._last_updated + heartbeat > arrow.utcnow().timestamp):
return False
data: Dict[str, Any] = {}
logger.info('Using stake_currency: %s ...', self.config['stake_currency'])
logger.info('Using local backtesting data (using whitelist in given config) ...')
data = history.load_data(
datadir=Path(self.config['datadir']) if self.config.get('datadir') else None,
pairs=pairs,
ticker_interval=self.ticker_interval,
refresh_pairs=self._refresh_pairs,
exchange=self.exchange,
timerange=self._timerange
)
if not data:
# Reinitializing cached pairs
self._cached_pairs = {}
logger.critical("No data found. Edge is stopped ...")
return False
preprocessed = self.tickerdata_to_dataframe(data)
# Print timeframe
min_date, max_date = self.get_timeframe(preprocessed)
logger.info(
'Measuring data from %s up to %s (%s days) ...',
min_date.isoformat(),
max_date.isoformat(),
(max_date - min_date).days
)
headers = ['date', 'buy', 'open', 'close', 'sell', 'high', 'low']
trades: list = []
for pair, pair_data in preprocessed.items():
# Sorting dataframe by date and reset index
pair_data = pair_data.sort_values(by=['date'])
pair_data = pair_data.reset_index(drop=True)
ticker_data = self.advise_sell(
self.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy()
trades += self._find_trades_for_stoploss_range(ticker_data, pair, self._stoploss_range)
# If no trade found then exit
if len(trades) == 0:
return False
# Fill missing, calculable columns, profit, duration , abs etc.
trades_df = self._fill_calculable_fields(DataFrame(trades))
self._cached_pairs = self._process_expectancy(trades_df)
self._last_updated = arrow.utcnow().timestamp
return True
def stake_amount(self, pair: str, free_capital: float,
total_capital: float, capital_in_trade: float) -> float:
stoploss = self.stoploss(pair)
available_capital = (total_capital + capital_in_trade) * self._capital_percentage
allowed_capital_at_risk = available_capital * self._allowed_risk
max_position_size = abs(allowed_capital_at_risk / stoploss)
position_size = min(max_position_size, free_capital)
if pair in self._cached_pairs:
logger.info(
'winrate: %s, expectancy: %s, position size: %s, pair: %s,'
' capital in trade: %s, free capital: %s, total capital: %s,'
' stoploss: %s, available capital: %s.',
self._cached_pairs[pair].winrate,
self._cached_pairs[pair].expectancy,
position_size, pair,
capital_in_trade, free_capital, total_capital,
stoploss, available_capital
)
return round(position_size, 15)
def stoploss(self, pair: str) -> float:
if pair in self._cached_pairs:
return self._cached_pairs[pair].stoploss
else:
logger.warning('tried to access stoploss of a non-existing pair, '
'strategy stoploss is returned instead.')
return self.strategy.stoploss
def adjust(self, pairs) -> list:
"""
Filters out and sorts "pairs" according to Edge calculated pairs
"""
final = []
for pair, info in self._cached_pairs.items():
if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \
info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)) and \
pair in pairs:
final.append(pair)
if self._final_pairs != final:
self._final_pairs = final
if self._final_pairs:
logger.info(
'Minimum expectancy and minimum winrate are met only for %s,'
' so other pairs are filtered out.',
self._final_pairs
)
else:
logger.info(
'Edge removed all pairs as no pair with minimum expectancy '
'and minimum winrate was found !'
)
return self._final_pairs
def _fill_calculable_fields(self, result: DataFrame) -> DataFrame:
"""
The result frame contains a number of columns that are calculable
from other columns. These are left blank till all rows are added,
to be populated in single vector calls.
Columns to be populated are:
- Profit
- trade duration
- profit abs
:param result Dataframe
:return: result Dataframe
"""
# stake and fees
# stake = 0.015
# 0.05% is 0.0005
# fee = 0.001
# we set stake amount to an arbitrary amount.
# as it doesn't change the calculation.
# all returned values are relative. they are percentages.
stake = 0.015
fee = self.fee
open_fee = fee / 2
close_fee = fee / 2
result['trade_duration'] = result['close_time'] - result['open_time']
result['trade_duration'] = result['trade_duration'].map(
lambda x: int(x.total_seconds() / 60))
# Spends, Takes, Profit, Absolute Profit
# Buy Price
result['buy_vol'] = stake / result['open_rate'] # How many target are we buying
result['buy_fee'] = stake * open_fee
result['buy_spend'] = stake + result['buy_fee'] # How much we're spending
# Sell price
result['sell_sum'] = result['buy_vol'] * result['close_rate']
result['sell_fee'] = result['sell_sum'] * close_fee
result['sell_take'] = result['sell_sum'] - result['sell_fee']
# profit_percent
result['profit_percent'] = (result['sell_take'] - result['buy_spend']) / result['buy_spend']
# Absolute profit
result['profit_abs'] = result['sell_take'] - result['buy_spend']
return result
def _process_expectancy(self, results: DataFrame) -> Dict[str, Any]:
"""
This calculates WinRate, Required Risk Reward, Risk Reward and Expectancy of all pairs
The calulation will be done per pair and per strategy.
"""
# Removing pairs having less than min_trades_number
min_trades_number = self.edge_config.get('min_trade_number', 10)
results = results.groupby(['pair', 'stoploss']).filter(lambda x: len(x) > min_trades_number)
###################################
# Removing outliers (Only Pumps) from the dataset
# The method to detect outliers is to calculate standard deviation
# Then every value more than (standard deviation + 2*average) is out (pump)
#
# Removing Pumps
if self.edge_config.get('remove_pumps', False):
results = results.groupby(['pair', 'stoploss']).apply(
lambda x: x[x['profit_abs'] < 2 * x['profit_abs'].std() + x['profit_abs'].mean()])
##########################################################################
# Removing trades having a duration more than X minutes (set in config)
max_trade_duration = self.edge_config.get('max_trade_duration_minute', 1440)
results = results[results.trade_duration < max_trade_duration]
#######################################################################
if results.empty:
return {}
groupby_aggregator = {
'profit_abs': [
('nb_trades', 'count'), # number of all trades
('profit_sum', lambda x: x[x > 0].sum()), # cumulative profit of all winning trades
('loss_sum', lambda x: abs(x[x < 0].sum())), # cumulative loss of all losing trades
('nb_win_trades', lambda x: x[x > 0].count()) # number of winning trades
],
'trade_duration': [('avg_trade_duration', 'mean')]
}
# Group by (pair and stoploss) by applying above aggregator
df = results.groupby(['pair', 'stoploss'])['profit_abs', 'trade_duration'].agg(
groupby_aggregator).reset_index(col_level=1)
# Dropping level 0 as we don't need it
df.columns = df.columns.droplevel(0)
# Calculating number of losing trades, average win and average loss
df['nb_loss_trades'] = df['nb_trades'] - df['nb_win_trades']
df['average_win'] = df['profit_sum'] / df['nb_win_trades']
df['average_loss'] = df['loss_sum'] / df['nb_loss_trades']
# Win rate = number of profitable trades / number of trades
df['winrate'] = df['nb_win_trades'] / df['nb_trades']
# risk_reward_ratio = average win / average loss
df['risk_reward_ratio'] = df['average_win'] / df['average_loss']
# required_risk_reward = (1 / winrate) - 1
df['required_risk_reward'] = (1 / df['winrate']) - 1
# expectancy = (risk_reward_ratio * winrate) - (lossrate)
df['expectancy'] = (df['risk_reward_ratio'] * df['winrate']) - (1 - df['winrate'])
# sort by expectancy and stoploss
df = df.sort_values(by=['expectancy', 'stoploss'], ascending=False).groupby(
'pair').first().sort_values(by=['expectancy'], ascending=False).reset_index()
final = {}
for x in df.itertuples():
final[x.pair] = PairInfo(
x.stoploss,
x.winrate,
x.risk_reward_ratio,
x.required_risk_reward,
x.expectancy,
x.nb_trades,
x.avg_trade_duration
)
# Returning a list of pairs in order of "expectancy"
return final
def _find_trades_for_stoploss_range(self, ticker_data, pair, stoploss_range):
buy_column = ticker_data['buy'].values
sell_column = ticker_data['sell'].values
date_column = ticker_data['date'].values
ohlc_columns = ticker_data[['open', 'high', 'low', 'close']].values
result: list = []
for stoploss in stoploss_range:
result += self._detect_next_stop_or_sell_point(
buy_column, sell_column, date_column, ohlc_columns, round(stoploss, 6), pair
)
return result
def _detect_next_stop_or_sell_point(self, buy_column, sell_column, date_column,
ohlc_columns, stoploss, pair, start_point=0):
"""
Iterate through ohlc_columns recursively in order to find the next trade
Next trade opens from the first buy signal noticed to
The sell or stoploss signal after it.
It then calls itself cutting OHLC, buy_column, sell_colum and date_column
Cut from (the exit trade index) + 1
Author: https://github.com/mishaker
"""
result: list = []
open_trade_index = utf1st.find_1st(buy_column, 1, utf1st.cmp_equal)
# return empty if we don't find trade entry (i.e. buy==1) or
# we find a buy but at the of array
if open_trade_index == -1 or open_trade_index == len(buy_column) - 1:
return []
else:
open_trade_index += 1 # when a buy signal is seen,
# trade opens in reality on the next candle
stop_price_percentage = stoploss + 1
open_price = ohlc_columns[open_trade_index, 0]
stop_price = (open_price * stop_price_percentage)
# Searching for the index where stoploss is hit
stop_index = utf1st.find_1st(
ohlc_columns[open_trade_index:, 2], stop_price, utf1st.cmp_smaller)
# If we don't find it then we assume stop_index will be far in future (infinite number)
if stop_index == -1:
stop_index = float('inf')
# Searching for the index where sell is hit
sell_index = utf1st.find_1st(sell_column[open_trade_index:], 1, utf1st.cmp_equal)
# If we don't find it then we assume sell_index will be far in future (infinite number)
if sell_index == -1:
sell_index = float('inf')
# Check if we don't find any stop or sell point (in that case trade remains open)
# It is not interesting for Edge to consider it so we simply ignore the trade
# And stop iterating there is no more entry
if stop_index == sell_index == float('inf'):
return []
if stop_index <= sell_index:
exit_index = open_trade_index + stop_index
exit_type = SellType.STOP_LOSS
exit_price = stop_price
elif stop_index > sell_index:
# if exit is SELL then we exit at the next candle
exit_index = open_trade_index + sell_index + 1
# check if we have the next candle
if len(ohlc_columns) - 1 < exit_index:
return []
exit_type = SellType.SELL_SIGNAL
exit_price = ohlc_columns[exit_index, 0]
trade = {'pair': pair,
'stoploss': stoploss,
'profit_percent': '',
'profit_abs': '',
'open_time': date_column[open_trade_index],
'close_time': date_column[exit_index],
'open_index': start_point + open_trade_index,
'close_index': start_point + exit_index,
'trade_duration': '',
'open_rate': round(open_price, 15),
'close_rate': round(exit_price, 15),
'exit_type': exit_type
}
result.append(trade)
# Calling again the same function recursively but giving
# it a view of exit_index till the end of array
return result + self._detect_next_stop_or_sell_point(
buy_column[exit_index:],
sell_column[exit_index:],
date_column[exit_index:],
ohlc_columns[exit_index:],
stoploss,
pair,
(start_point + exit_index)
)

View File

@@ -1,15 +1,20 @@
# 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 ccxt
import arrow
import asyncio
import ccxt
import ccxt.async_support as ccxt_async
from pandas import DataFrame
from freqtrade import constants, OperationalException, DependencyException, TemporaryError
from freqtrade.data.converter import parse_ticker_dataframe
logger = logging.getLogger(__name__)
@@ -23,6 +28,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)
@@ -43,13 +66,7 @@ def retrier(f):
class Exchange(object):
# Current selected exchange
_api: ccxt.Exchange = None
_conf: Dict = {}
_cached_ticker: Dict[str, Any] = {}
# Holds all open sell orders for dry_run
_dry_run_open_orders: Dict[str, Any] = {}
def __init__(self, config: dict) -> None:
"""
@@ -60,22 +77,47 @@ 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[Tuple[str, str], int] = {}
# Holds candles
self._klines: Dict[Tuple[str, str], DataFrame] = {}
# Holds all open sell orders for dry_run
self._dry_run_open_orders: Dict[str, Any] = {}
if config['dry_run']:
logger.info('Instance is running with dry_run enabled')
exchange_config = config['exchange']
self._api = self._init_ccxt(exchange_config)
self._api: ccxt.Exchange = self._init_ccxt(
exchange_config, ccxt_kwargs=exchange_config.get('ccxt_config'))
self._api_async: ccxt_async.Exchange = self._init_ccxt(
exchange_config, ccxt_async, ccxt_kwargs=exchange_config.get('ccxt_async_config'))
logger.info('Using Exchange "%s"', self.name)
self.markets = self._load_markets()
# Check if all pairs are available
self.validate_pairs(config['exchange']['pair_whitelist'])
self.validate_ordertypes(config.get('order_types', {}))
self.validate_order_time_in_force(config.get('order_time_in_force', {}))
if config.get('ticker_interval'):
# Check if timeframe is available
self.validate_timeframes(config['ticker_interval'])
def _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_kwargs: dict = None) -> ccxt.Exchange:
"""
Initialize ccxt with given config and return valid
ccxt instance.
@@ -83,16 +125,22 @@ 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')
ex_config = {
'apiKey': exchange_config.get('key'),
'secret': exchange_config.get('secret'),
'password': exchange_config.get('password'),
'uid': exchange_config.get('uid', ''),
'enableRateLimit': exchange_config.get('ccxt_rate_limit', True)
}
if ccxt_kwargs:
logger.info('Applying additional ccxt config: %s', ccxt_kwargs)
ex_config.update(ccxt_kwargs)
try:
api = getattr(ccxt, 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),
})
api = getattr(ccxt_module, name.lower())(ex_config)
except (KeyError, AttributeError):
raise OperationalException(f'Exchange {name} is not supported')
@@ -110,16 +158,42 @@ class Exchange(object):
"""exchange ccxt id"""
return self._api.id
def klines(self, pair_interval: Tuple[str, str], copy=True) -> DataFrame:
# create key tuple
if pair_interval in self._klines:
return self._klines[pair_interval].copy() if copy else self._klines[pair_interval]
else:
return DataFrame()
def set_sandbox(self, api, exchange_config: dict, name: str):
if exchange_config.get('sandbox'):
if api.urls.get('test'):
api.urls['api'] = api.urls['test']
logger.info("Enabled Sandbox API on %s", name)
else:
logger.warning(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 +202,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,9 +213,10 @@ 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}')
f'Pair {pair} is not available at {self.name}'
f'Please remove {pair} from your whitelist.')
def validate_timeframes(self, timeframe: List[str]) -> None:
"""
@@ -154,6 +227,30 @@ class Exchange(object):
raise OperationalException(
f'Invalid ticker {timeframe}, this Exchange supports {timeframes}')
def validate_ordertypes(self, order_types: Dict) -> None:
"""
Checks if order-types configured in strategy/config are supported
"""
if any(v == 'market' for k, v in order_types.items()):
if not self.exchange_has('createMarketOrder'):
raise OperationalException(
f'Exchange {self.name} does not support market orders.')
if order_types.get('stoploss_on_exchange'):
if self.name != 'Binance':
raise OperationalException(
'On exchange stoploss is not supported for %s.' % self.name
)
def validate_order_time_in_force(self, order_time_in_force: Dict) -> None:
"""
Checks if order time in force configured in strategy/config are supported
"""
if any(v != 'gtc' for k, v in order_time_in_force.items()):
if self.name != 'Binance':
raise OperationalException(
f'Time in force policies are not supporetd for {self.name} yet.')
def exchange_has(self, endpoint: str) -> bool:
"""
Checks if exchange implements a specific API endpoint.
@@ -185,14 +282,15 @@ class Exchange(object):
price = ceil(big_price) / pow(10, symbol_prec)
return price
def buy(self, pair: str, rate: float, amount: float) -> Dict:
def buy(self, pair: str, ordertype: str, amount: float,
rate: float, time_in_force) -> Dict:
if self._conf['dry_run']:
order_id = f'dry_run_buy_{randint(0, 10**6)}'
self._dry_run_open_orders[order_id] = {
'pair': pair,
'price': rate,
'amount': amount,
'type': 'limit',
'type': ordertype,
'side': 'buy',
'remaining': 0.0,
'datetime': arrow.utcnow().isoformat(),
@@ -204,9 +302,14 @@ class Exchange(object):
try:
# Set the precision for amount and price(rate) as accepted by the exchange
amount = self.symbol_amount_prec(pair, amount)
rate = self.symbol_price_prec(pair, rate)
rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None
if time_in_force == 'gtc':
return self._api.create_order(pair, ordertype, 'buy', amount, rate)
else:
return self._api.create_order(pair, ordertype, 'buy',
amount, rate, {'timeInForce': time_in_force})
return self._api.create_limit_buy_order(pair, amount, rate)
except ccxt.InsufficientFunds as e:
raise DependencyException(
f'Insufficient funds to create limit buy order on market {pair}.'
@@ -223,14 +326,15 @@ class Exchange(object):
except ccxt.BaseError as e:
raise OperationalException(e)
def sell(self, pair: str, rate: float, amount: float) -> Dict:
def sell(self, pair: str, ordertype: str, amount: float,
rate: float, time_in_force='gtc') -> Dict:
if self._conf['dry_run']:
order_id = f'dry_run_sell_{randint(0, 10**6)}'
self._dry_run_open_orders[order_id] = {
'pair': pair,
'price': rate,
'amount': amount,
'type': 'limit',
'type': ordertype,
'side': 'sell',
'remaining': 0.0,
'datetime': arrow.utcnow().isoformat(),
@@ -241,9 +345,14 @@ class Exchange(object):
try:
# Set the precision for amount and price(rate) as accepted by the exchange
amount = self.symbol_amount_prec(pair, amount)
rate = self.symbol_price_prec(pair, rate)
rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None
if time_in_force == 'gtc':
return self._api.create_order(pair, ordertype, 'sell', amount, rate)
else:
return self._api.create_order(pair, ordertype, 'sell',
amount, rate, {'timeInForce': time_in_force})
return self._api.create_limit_sell_order(pair, amount, rate)
except ccxt.InsufficientFunds as e:
raise DependencyException(
f'Insufficient funds to create limit sell order on market {pair}.'
@@ -260,6 +369,64 @@ class Exchange(object):
except ccxt.BaseError as e:
raise OperationalException(e)
def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict:
"""
creates a stoploss limit order.
NOTICE: it is not supported by all exchanges. only binance is tested for now.
"""
# Set the precision for amount and price(rate) as accepted by the exchange
amount = self.symbol_amount_prec(pair, amount)
rate = self.symbol_price_prec(pair, rate)
stop_price = self.symbol_price_prec(pair, stop_price)
# Ensure rate is less than stop price
if stop_price <= rate:
raise OperationalException(
'In stoploss limit order, stop price should be more than limit price')
if self._conf['dry_run']:
order_id = f'dry_run_buy_{randint(0, 10**6)}'
self._dry_run_open_orders[order_id] = {
'info': {},
'id': order_id,
'pair': pair,
'price': stop_price,
'amount': amount,
'type': 'stop_loss_limit',
'side': 'sell',
'remaining': amount,
'datetime': arrow.utcnow().isoformat(),
'status': 'open',
'fee': None
}
return self._dry_run_open_orders[order_id]
try:
order = self._api.create_order(pair, 'stop_loss_limit', 'sell',
amount, rate, {'stopPrice': stop_price})
logger.info('stoploss limit order added for %s. '
'stop price: %s. limit: %s' % (pair, stop_price, rate))
return order
except ccxt.InsufficientFunds as e:
raise DependencyException(
f'Insufficient funds to place stoploss limit order on market {pair}. '
f'Tried to put a stoploss amount {amount} with '
f'stop {stop_price} and limit {rate} (total {rate*amount}).'
f'Message: {e}')
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not place stoploss limit order on market {pair}.'
f'Tried to place stoploss amount {amount} with '
f'stop {stop_price} and limit {rate} (total {rate*amount}).'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place stoploss limit order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_balance(self, currency: str) -> float:
if self._conf['dry_run']:
@@ -311,6 +478,8 @@ class Exchange(object):
def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict:
if refresh or pair not in self._cached_ticker.keys():
try:
if pair not in self._api.markets:
raise DependencyException(f"Pair {pair} not available")
data = self._api.fetch_ticker(pair)
try:
self._cached_ticker[pair] = {
@@ -322,48 +491,113 @@ 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]
@retrier
def get_candle_history(self, pair: str, tick_interval: str,
since_ms: Optional[int] = None) -> List[Dict]:
def get_history(self, pair: str, tick_interval: str,
since_ms: int) -> List:
"""
Gets candle history using asyncio and returns the list of candles.
Handles all async doing.
"""
return asyncio.get_event_loop().run_until_complete(
self._async_get_history(pair=pair, tick_interval=tick_interval,
since_ms=since_ms))
async def _async_get_history(self, pair: str,
tick_interval: str,
since_ms: int) -> List:
# Assume exchange returns 500 candles
_LIMIT = 500
one_call = constants.TICKER_INTERVAL_MINUTES[tick_interval] * 60 * _LIMIT * 1000
logger.debug("one_call: %s", one_call)
input_coroutines = [self._async_get_candle_history(
pair, tick_interval, since) for since in
range(since_ms, arrow.utcnow().timestamp * 1000, one_call)]
tickers = await asyncio.gather(*input_coroutines, return_exceptions=True)
# Combine tickers
data: List = []
for p, ticker_interval, ticker in tickers:
if p == pair:
data.extend(ticker)
# Sort data again after extending the result - above calls return in "async order"
data = sorted(data, key=lambda x: x[0])
logger.info("downloaded %s with length %s.", pair, len(data))
return data
def refresh_latest_ohlcv(self, pair_list: List[Tuple[str, str]]) -> List[Tuple[str, List]]:
"""
Refresh in-memory ohlcv asyncronously and set `_klines` with the result
"""
logger.debug("Refreshing ohlcv data for %d pairs", len(pair_list))
input_coroutines = []
# Gather corotines to run
for pair, ticker_interval in set(pair_list):
# Calculating ticker interval in second
interval_in_sec = constants.TICKER_INTERVAL_MINUTES[ticker_interval] * 60
if not ((self._pairs_last_refresh_time.get((pair, ticker_interval), 0)
+ interval_in_sec) >= arrow.utcnow().timestamp
and (pair, ticker_interval) in self._klines):
input_coroutines.append(self._async_get_candle_history(pair, ticker_interval))
else:
logger.debug("Using cached ohlcv data for %s, %s ...", pair, ticker_interval)
tickers = asyncio.get_event_loop().run_until_complete(
asyncio.gather(*input_coroutines, return_exceptions=True))
# handle caching
for res in tickers:
if isinstance(res, Exception):
logger.warning("Async code raised an exception: %s", res.__class__.__name__)
continue
pair = res[0]
tick_interval = res[1]
ticks = res[2]
# keeping last candle time as last refreshed time of the pair
if ticks:
self._pairs_last_refresh_time[(pair, tick_interval)] = ticks[-1][0] // 1000
# keeping parsed dataframe in cache
self._klines[(pair, tick_interval)] = parse_ticker_dataframe(
ticks, tick_interval, fill_missing=True)
return tickers
@retrier_async
async def _async_get_candle_history(self, pair: str, tick_interval: str,
since_ms: Optional[int] = None) -> Tuple[str, str, List]:
"""
Asyncronously gets candle histories using fetch_ohlcv
returns tuple: (pair, tick_interval, ohlcv_list)
"""
try:
# last item should be in the time interval [now - tick_interval, now]
till_time_ms = arrow.utcnow().shift(
minutes=-constants.TICKER_INTERVAL_MINUTES[tick_interval]
).timestamp * 1000
# it looks as if some exchanges return cached data
# and they update it one in several minute, so 10 mins interval
# is necessary to skeep downloading of an empty array when all
# chached data was already downloaded
till_time_ms = min(till_time_ms, arrow.utcnow().shift(minutes=-10).timestamp * 1000)
# fetch ohlcv asynchronously
logger.debug("fetching %s, %s since %s ...", pair, tick_interval, since_ms)
data: List[Dict[Any, Any]] = []
while not since_ms or since_ms < till_time_ms:
data_part = self._api.fetch_ohlcv(pair, timeframe=tick_interval, since=since_ms)
data = await self._api_async.fetch_ohlcv(pair, timeframe=tick_interval,
since=since_ms)
# Because some exchange sort Tickers ASC and other DESC.
# Ex: Bittrex returns a list of tickers ASC (oldest first, newest last)
# when GDAX returns a list of tickers DESC (newest first, oldest last)
data_part = sorted(data_part, key=lambda x: x[0])
# Because some exchange sort Tickers ASC and other DESC.
# Ex: Bittrex returns a list of tickers ASC (oldest first, newest last)
# when GDAX returns a list of tickers DESC (newest first, oldest last)
# Only sort if necessary to save computing time
try:
if data and data[0][0] > data[-1][0]:
data = sorted(data, key=lambda x: x[0])
except IndexError:
logger.exception("Error loading %s. Result was %s.", pair, data)
return pair, tick_interval, []
logger.debug("done fetching %s, %s ...", pair, tick_interval)
return pair, tick_interval, data
if not data_part:
break
logger.debug('Downloaded data for %s time range [%s, %s]',
pair,
arrow.get(data_part[0][0] / 1000).format(),
arrow.get(data_part[-1][0] / 1000).format())
data.extend(data_part)
since_ms = data[-1][0] + 1
return data
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {self._api.name} does not support fetching historical candlestick data.'
@@ -409,6 +643,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 +681,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 +728,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

@@ -1,33 +0,0 @@
"""
Functions to analyze ticker data with indicators and produce buy and sell signals
"""
import logging
from pandas import DataFrame, to_datetime
logger = logging.getLogger(__name__)
def parse_ticker_dataframe(ticker: list) -> DataFrame:
"""
Analyses the trend for the given ticker history
:param ticker: See exchange.get_candle_history
:return: DataFrame
"""
cols = ['date', 'open', 'high', 'low', 'close', 'volume']
frame = DataFrame(ticker, columns=cols)
frame['date'] = to_datetime(frame['date'],
unit='ms',
utc=True,
infer_datetime_format=True)
# group by index and aggregate results to eliminate duplicate ticks
frame = frame.groupby(by='date', as_index=False, sort=True).agg({
'open': 'first',
'high': 'max',
'low': 'min',
'close': 'last',
'volume': 'max',
})
frame.drop(frame.tail(1).index, inplace=True) # eliminate partial candle
return frame

View File

@@ -10,17 +10,21 @@ from datetime import datetime
from typing import Any, Callable, Dict, List, Optional
import arrow
import requests
from cachetools import TTLCache, cached
from requests.exceptions import RequestException
from freqtrade import (DependencyException, OperationalException,
TemporaryError, __version__, constants, persistence)
from freqtrade.data.converter import order_book_to_dataframe
from freqtrade.data.dataprovider import DataProvider
from freqtrade.edge import Edge
from freqtrade.exchange import Exchange
from freqtrade.persistence import Trade
from freqtrade.rpc import RPCManager, RPCMessageType
from freqtrade.resolvers import StrategyResolver, PairListResolver
from freqtrade.state import State
from freqtrade.strategy.interface import SellType
from freqtrade.strategy.resolver import IStrategy, StrategyResolver
from freqtrade.strategy.interface import SellType, IStrategy
from freqtrade.wallets import Wallets
logger = logging.getLogger(__name__)
@@ -31,11 +35,11 @@ class FreqtradeBot(object):
This is from here the bot start its logic.
"""
def __init__(self, config: Dict[str, Any])-> None:
def __init__(self, config: Dict[str, Any]) -> None:
"""
Init all variables and object the bot need to work
:param config: configuration dict, you can use the Configuration.get_config()
method to get the config dict.
Init all variables and objects the bot needs to work
:param config: configuration dict, you can use Configuration.get_config()
to get the config dict.
"""
logger.info(
@@ -49,9 +53,25 @@ class FreqtradeBot(object):
# Init objects
self.config = config
self.strategy: IStrategy = StrategyResolver(self.config).strategy
self.rpc: RPCManager = RPCManager(self)
self.persistence = None
self.exchange = Exchange(self.config)
self.wallets = Wallets(self.exchange)
self.dataprovider = DataProvider(self.config, self.exchange)
# Attach Dataprovider to Strategy baseclass
IStrategy.dp = self.dataprovider
# Attach Wallets to Strategy baseclass
IStrategy.wallets = self.wallets
pairlistname = self.config.get('pairlist', {}).get('method', 'StaticPairList')
self.pairlists = PairListResolver(pairlistname, self, self.config).pairlist
# Initializing Edge only if enabled
self.edge = Edge(self.config, self.exchange, self.strategy) if \
self.config.get('edge', {}).get('enabled', False) else None
self.active_pair_whitelist: List[str] = self.config['exchange']['pair_whitelist']
self._init_modules()
def _init_modules(self) -> None:
@@ -95,7 +115,7 @@ class FreqtradeBot(object):
})
logger.info('Changing state to: %s', state.name)
if state == State.RUNNING:
self._startup_messages()
self.rpc.startup_messages(self.config, self.pairlists)
if state == State.STOPPED:
time.sleep(1)
@@ -105,45 +125,10 @@ class FreqtradeBot(object):
constants.PROCESS_THROTTLE_SECS
)
nb_assets = self.config.get('dynamic_whitelist', None)
self._throttle(func=self._process,
min_secs=min_secs,
nb_assets=nb_assets)
min_secs=min_secs)
return state
def _startup_messages(self) -> None:
if self.config.get('dry_run', False):
self.rpc.send_msg({
'type': RPCMessageType.WARNING_NOTIFICATION,
'status': 'Dry run is enabled. All trades are simulated.'
})
stake_currency = self.config['stake_currency']
stake_amount = self.config['stake_amount']
minimal_roi = self.config['minimal_roi']
ticker_interval = self.config['ticker_interval']
exchange_name = self.config['exchange']['name']
strategy_name = self.config.get('strategy', '')
self.rpc.send_msg({
'type': RPCMessageType.CUSTOM_NOTIFICATION,
'status': f'*Exchange:* `{exchange_name}`\n'
f'*Stake per trade:* `{stake_amount} {stake_currency}`\n'
f'*Minimum ROI:* `{minimal_roi}`\n'
f'*Ticker Interval:* `{ticker_interval}`\n'
f'*Strategy:* `{strategy_name}`'
})
if self.config.get('dynamic_whitelist', False):
top_pairs = 'top ' + str(self.config.get('dynamic_whitelist', 20))
specific_pairs = ''
else:
top_pairs = 'whitelisted'
specific_pairs = '\n' + ', '.join(self.config['exchange'].get('pair_whitelist', ''))
self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Searching for {top_pairs} {stake_currency} pairs to buy and sell...'
f'{specific_pairs}'
})
def _throttle(self, func: Callable[..., Any], min_secs: float, *args, **kwargs) -> Any:
"""
Throttles the given callable that it
@@ -160,29 +145,38 @@ class FreqtradeBot(object):
time.sleep(duration)
return result
def _process(self, nb_assets: Optional[int] = 0) -> bool:
def _process(self) -> bool:
"""
Queries the persistence layer for open trades and handles them,
otherwise a new trade is created.
:param: nb_assets: the maximum number of pairs to be traded at the same time
:return: True if one or more trades has been created or closed, False otherwise
"""
state_changed = False
try:
# Refresh whitelist based on wallet maintenance
sanitized_list = self._refresh_whitelist(
self._gen_pair_whitelist(
self.config['stake_currency']
) if nb_assets else self.config['exchange']['pair_whitelist']
)
# Refresh whitelist
self.pairlists.refresh_pairlist()
self.active_pair_whitelist = self.pairlists.whitelist
# Keep only the subsets of pairs wanted (up to nb_assets)
final_list = sanitized_list[:nb_assets] if nb_assets else sanitized_list
self.config['exchange']['pair_whitelist'] = final_list
# Calculating Edge positiong
if self.edge:
self.edge.calculate()
self.active_pair_whitelist = self.edge.adjust(self.active_pair_whitelist)
# Query trades from persistence layer
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
# Extend active-pair whitelist with pairs from open trades
# ensures that tickers are downloaded for open trades
self.active_pair_whitelist.extend([trade.pair for trade in trades
if trade.pair not in self.active_pair_whitelist])
# Create pair-whitelist tuple with (pair, ticker_interval)
pair_whitelist_tuple = [(pair, self.config['ticker_interval'])
for pair in self.active_pair_whitelist]
# Refreshing candles
self.dataprovider.refresh(pair_whitelist_tuple,
self.strategy.informative_pairs())
# First process current opened trades
for trade in trades:
state_changed |= self.process_maybe_execute_sell(trade)
@@ -197,7 +191,7 @@ class FreqtradeBot(object):
Trade.session.flush()
except TemporaryError as error:
logger.warning('%s, retrying in 30 seconds...', error)
logger.warning(f"Error: {error}, retrying in {constants.RETRY_TIMEOUT} seconds...")
time.sleep(constants.RETRY_TIMEOUT)
except OperationalException:
tb = traceback.format_exc()
@@ -210,82 +204,51 @@ class FreqtradeBot(object):
self.state = State.STOPPED
return state_changed
@cached(TTLCache(maxsize=1, ttl=1800))
def _gen_pair_whitelist(self, base_currency: str, key: str = 'quoteVolume') -> List[str]:
"""
Updates the whitelist with with a dynamically generated list
:param base_currency: base currency as str
:param key: sort key (defaults to 'quoteVolume')
:return: List of pairs
"""
if not self.exchange.exchange_has('fetchTickers'):
raise OperationalException(
'Exchange does not support dynamic whitelist.'
'Please edit your config and restart the bot'
)
tickers = self.exchange.get_tickers()
# check length so that we make sure that '/' is actually in the string
tickers = [v for k, v in tickers.items()
if len(k.split('/')) == 2 and k.split('/')[1] == base_currency]
sorted_tickers = sorted(tickers, reverse=True, key=lambda t: t[key])
pairs = [s['symbol'] for s in sorted_tickers]
return pairs
def _refresh_whitelist(self, whitelist: List[str]) -> List[str]:
"""
Check available markets and remove pair from whitelist if necessary
:param whitelist: the sorted list (based on BaseVolume) of pairs the user might want to
trade
:return: the list of pairs the user wants to trade without the one unavailable or
black_listed
"""
sanitized_whitelist = whitelist
markets = self.exchange.get_markets()
markets = [m for m in markets if m['quote'] == self.config['stake_currency']]
known_pairs = set()
for market in markets:
pair = market['symbol']
# pair is not int the generated dynamic market, or in the blacklist ... ignore it
if pair not in whitelist or pair in self.config['exchange'].get('pair_blacklist', []):
continue
# else the pair is valid
known_pairs.add(pair)
# Market is not active
if not market['active']:
sanitized_whitelist.remove(pair)
logger.info(
'Ignoring %s from whitelist. Market is not active.',
pair
)
# We need to remove pairs that are unknown
final_list = [x for x in sanitized_whitelist if x in known_pairs]
return final_list
def get_target_bid(self, ticker: Dict[str, float]) -> float:
def get_target_bid(self, pair: str) -> 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'])
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]
logger.info('...top %s order book buy rate %0.8f', order_book_top, order_book_rate)
used_rate = order_book_rate
else:
logger.info('Using Last Ask / Last Price')
ticker = self.exchange.get_ticker(pair)
if ticker['ask'] < ticker['last']:
ticker_rate = ticker['ask']
else:
balance = self.config['bid_strategy']['ask_last_balance']
ticker_rate = ticker['ask'] + balance * (ticker['last'] - ticker['ask'])
used_rate = ticker_rate
def _get_trade_stake_amount(self) -> Optional[float]:
return used_rate
def _get_trade_stake_amount(self, pair) -> Optional[float]:
"""
Check if stake amount can be fulfilled with the available balance
for the stake currency
:return: float: Stake Amount
"""
stake_amount = self.config['stake_amount']
avaliable_amount = self.exchange.get_balance(self.config['stake_currency'])
if self.edge:
return self.edge.stake_amount(
pair,
self.wallets.get_free(self.config['stake_currency']),
self.wallets.get_total(self.config['stake_currency']),
Trade.total_open_trades_stakes()
)
else:
stake_amount = self.config['stake_amount']
avaliable_amount = self.wallets.get_free(self.config['stake_currency'])
if stake_amount == constants.UNLIMITED_STAKE_AMOUNT:
open_trades = len(Trade.query.filter(Trade.is_open.is_(True)).all())
@@ -297,9 +260,8 @@ class FreqtradeBot(object):
# Check if stake_amount is fulfilled
if avaliable_amount < stake_amount:
raise DependencyException(
'Available balance(%f %s) is lower than stake amount(%f %s)' % (
avaliable_amount, self.config['stake_currency'],
stake_amount, self.config['stake_currency'])
f"Available balance({avaliable_amount} {self.config['stake_currency']}) is "
f"lower than stake amount({stake_amount} {self.config['stake_currency']})"
)
return stake_amount
@@ -328,12 +290,14 @@ class FreqtradeBot(object):
if not min_stake_amounts:
return None
amount_reserve_percent = 1 - 0.05 # reserve 5% + stoploss
# reserve some percent defined in config (5% default) + stoploss
amount_reserve_percent = 1.0 - self.config.get('amount_reserve_percent',
constants.DEFAULT_AMOUNT_RESERVE_PERCENT)
if self.strategy.stoploss is not None:
amount_reserve_percent += self.strategy.stoploss
# it should not be more than 50%
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:
"""
@@ -342,16 +306,7 @@ class FreqtradeBot(object):
:return: True if a trade object has been created and persisted, False otherwise
"""
interval = self.strategy.ticker_interval
stake_amount = self._get_trade_stake_amount()
if not stake_amount:
return False
logger.info(
'Checking buy signals to create a new trade with stake_amount: %f ...',
stake_amount
)
whitelist = copy.deepcopy(self.config['exchange']['pair_whitelist'])
whitelist = copy.deepcopy(self.active_pair_whitelist)
# Remove currently opened and latest pairs from whitelist
for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
@@ -362,16 +317,49 @@ 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
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.dataprovider.ohlcv(_pair, self.strategy.ticker_interval))
if buy and not sell:
stake_amount = self._get_trade_stake_amount(_pair)
if not stake_amount:
return False
logger.info(f"Buy signal found: about create a new trade with stake_amount: "
f"{stake_amount} ...")
bidstrat_check_depth_of_market = self.config.get('bid_strategy', {}).\
get('check_depth_of_market', {})
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 execute_buy(self, pair: str, stake_amount: float) -> bool:
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, price: Optional[float] = None) -> bool:
"""
Executes a limit buy for the given pair
:param pair: pair for which we want to create a LIMIT_BUY
@@ -381,32 +369,75 @@ class FreqtradeBot(object):
pair_url = self.exchange.get_pair_detail_url(pair)
stake_currency = self.config['stake_currency']
fiat_currency = self.config.get('fiat_display_currency', None)
time_in_force = self.strategy.order_time_in_force['buy']
# Calculate amount
buy_limit = self.get_target_bid(self.exchange.get_ticker(pair))
if price:
buy_limit_requested = price
else:
# Calculate amount
buy_limit_requested = self.get_target_bid(pair)
min_stake_amount = self._get_min_pair_stake_amount(pair_s, buy_limit)
min_stake_amount = self._get_min_pair_stake_amount(pair_s, buy_limit_requested)
if min_stake_amount is not None and min_stake_amount > stake_amount:
logger.warning(
f'Can\'t open a new trade for {pair_s}: stake amount'
f' is too small ({stake_amount} < {min_stake_amount})'
f'Can\'t open a new trade for {pair_s}: stake amount '
f'is too small ({stake_amount} < {min_stake_amount})'
)
return False
amount = stake_amount / buy_limit
amount = stake_amount / buy_limit_requested
order_id = self.exchange.buy(pair, buy_limit, amount)['id']
order = self.exchange.buy(pair=pair, ordertype=self.strategy.order_types['buy'],
amount=amount, rate=buy_limit_requested,
time_in_force=time_in_force)
order_id = order['id']
order_status = order.get('status', None)
# we assume the order is executed at the price requested
buy_limit_filled_price = buy_limit_requested
if order_status == 'expired' or order_status == 'rejected':
order_type = self.strategy.order_types['buy']
order_tif = self.strategy.order_time_in_force['buy']
# return false if the order is not filled
if float(order['filled']) == 0:
logger.warning('Buy %s order with time in force %s for %s is %s by %s.'
' zero amount is fulfilled.',
order_tif, order_type, pair_s, order_status, self.exchange.name)
return False
else:
# the order is partially fulfilled
# in case of IOC orders we can check immediately
# if the order is fulfilled fully or partially
logger.warning('Buy %s order with time in force %s for %s is %s by %s.'
' %s amount fulfilled out of %s (%s remaining which is canceled).',
order_tif, order_type, pair_s, order_status, self.exchange.name,
order['filled'], order['amount'], order['remaining']
)
stake_amount = order['cost']
amount = order['amount']
buy_limit_filled_price = order['price']
order_id = None
# in case of FOK the order may be filled immediately and fully
elif order_status == 'closed':
stake_amount = order['cost']
amount = order['amount']
buy_limit_filled_price = order['price']
order_id = None
self.rpc.send_msg({
'type': RPCMessageType.BUY_NOTIFICATION,
'exchange': self.exchange.name.capitalize(),
'pair': pair_s,
'market_url': pair_url,
'limit': buy_limit,
'limit': buy_limit_filled_price,
'stake_amount': stake_amount,
'stake_currency': stake_currency,
'fiat_currency': fiat_currency
})
# Fee is applied twice because we make a LIMIT_BUY and LIMIT_SELL
fee = self.exchange.get_fee(symbol=pair, taker_or_maker='maker')
trade = Trade(
@@ -415,16 +446,21 @@ class FreqtradeBot(object):
amount=amount,
fee_open=fee,
fee_close=fee,
open_rate=buy_limit,
open_rate_requested=buy_limit,
open_rate=buy_limit_filled_price,
open_rate_requested=buy_limit_requested,
open_date=datetime.utcnow(),
exchange=self.exchange.id,
open_order_id=order_id,
strategy=self.strategy.get_strategy_name(),
ticker_interval=constants.TICKER_INTERVAL_MINUTES[self.config['ticker_interval']]
)
Trade.session.add(trade)
Trade.session.flush()
# Updating wallets
self.wallets.update()
return True
def process_maybe_execute_buy(self) -> bool:
@@ -463,13 +499,26 @@ class FreqtradeBot(object):
trade.fee_open = 0
except OperationalException as exception:
logger.warning("could not update trade amount: %s", exception)
logger.warning("Could not update trade amount: %s", exception)
trade.update(order)
if self.strategy.order_types.get('stoploss_on_exchange') and trade.is_open:
result = self.handle_stoploss_on_exchange(trade)
if result:
self.wallets.update()
return result
if trade.is_open and trade.open_order_id is None:
# Check if we can sell our current pair
return self.handle_trade(trade)
result = self.handle_trade(trade)
# Updating wallets if any trade occured
if result:
self.wallets.update()
return result
except DependencyException as exception:
logger.warning('Unable to sell trade: %s', exception)
return False
@@ -509,12 +558,12 @@ class FreqtradeBot(object):
fee_abs += exectrade['fee']['cost']
if amount != order_amount:
logger.warning(f"amount {amount} does not match amount {trade.amount}")
logger.warning(f"Amount {amount} does not match amount {trade.amount}")
raise OperationalException("Half bought? Amounts don't match")
real_amount = amount - fee_abs
if fee_abs != 0:
logger.info(f"""Applying fee on amount for {trade} \
(from {order_amount} to {real_amount}) from Trades""")
logger.info(f"Applying fee on amount for {trade} "
f"(from {order_amount} to {real_amount}) from Trades")
return real_amount
def handle_trade(self, trade: Trade) -> bool:
@@ -523,23 +572,125 @@ class FreqtradeBot(object):
:return: True if trade has been sold, False otherwise
"""
if not trade.is_open:
raise ValueError(f'attempt to handle closed trade: {trade}')
raise ValueError(f'Attempt to handle closed trade: {trade}')
logger.debug('Handling %s ...', trade)
current_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)
(buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.ticker_interval,
ticker)
(buy, sell) = self.strategy.get_signal(
trade.pair, self.strategy.ticker_interval,
self.dataprovider.ohlcv(trade.pair, self.strategy.ticker_interval))
config_ask_strategy = self.config.get('ask_strategy', {})
if config_ask_strategy.get('use_order_book', False):
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]
logger.info(' order book asks top %s: %0.8f', i, order_book_rate)
sell_rate = order_book_rate
if self.check_sell(trade, sell_rate, buy, sell):
return True
else:
logger.debug('checking sell')
sell_rate = self.exchange.get_ticker(trade.pair)['bid']
if self.check_sell(trade, sell_rate, buy, sell):
return True
logger.debug('Found no sell signal for %s.', trade)
return False
def handle_stoploss_on_exchange(self, trade: Trade) -> bool:
"""
Check if trade is fulfilled in which case the stoploss
on exchange should be added immediately if stoploss on exchange
is enabled.
"""
result = False
# If trade is open and the buy order is fulfilled but there is no stoploss,
# then we add a stoploss on exchange
if not trade.open_order_id and not trade.stoploss_order_id:
if self.edge:
stoploss = self.edge.stoploss(pair=trade.pair)
else:
stoploss = self.strategy.stoploss
stop_price = trade.open_rate * (1 + stoploss)
# limit price should be less than stop price.
# 0.99 is arbitrary here.
limit_price = stop_price * 0.99
stoploss_order_id = self.exchange.stoploss_limit(
pair=trade.pair, amount=trade.amount, stop_price=stop_price, rate=limit_price
)['id']
trade.stoploss_order_id = str(stoploss_order_id)
trade.stoploss_last_update = datetime.now()
# Or the trade open and there is already a stoploss on exchange.
# so we check if it is hit ...
elif trade.stoploss_order_id:
logger.debug('Handling stoploss on exchange %s ...', trade)
order = self.exchange.get_order(trade.stoploss_order_id, trade.pair)
if order['status'] == 'closed':
trade.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
trade.update(order)
result = True
elif self.config.get('trailing_stop', False):
# if trailing stoploss is enabled we check if stoploss value has changed
# in which case we cancel stoploss order and put another one with new
# value immediately
self.handle_trailing_stoploss_on_exchange(trade, order)
return result
def handle_trailing_stoploss_on_exchange(self, trade: Trade, order):
"""
Check to see if stoploss on exchange should be updated
in case of trailing stoploss on exchange
:param Trade: Corresponding Trade
:param order: Current on exchange stoploss order
:return: None
"""
if trade.stop_loss > float(order['info']['stopPrice']):
# we check if the update is neccesary
update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60)
if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() > update_beat:
# cancelling the current stoploss on exchange first
logger.info('Trailing stoploss: cancelling current stoploss on exchange '
'in order to add another one ...')
if self.exchange.cancel_order(order['id'], trade.pair):
# creating the new one
stoploss_order_id = self.exchange.stoploss_limit(
pair=trade.pair, amount=trade.amount,
stop_price=trade.stop_loss, rate=trade.stop_loss * 0.99
)['id']
trade.stoploss_order_id = str(stoploss_order_id)
def check_sell(self, trade: Trade, sell_rate: float, buy: bool, sell: bool) -> bool:
if self.edge:
stoploss = self.edge.stoploss(trade.pair)
should_sell = self.strategy.should_sell(
trade, sell_rate, datetime.utcnow(), buy, sell, force_stoploss=stoploss)
else:
should_sell = self.strategy.should_sell(trade, sell_rate, datetime.utcnow(), buy, sell)
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)
self.execute_sell(trade, sell_rate, should_sell.sell_type)
logger.info('executed sell, reason: %s', should_sell.sell_type)
return True
logger.info('Found no sell signals for whitelisted currencies. Trying again..')
return False
def check_handle_timedout(self) -> None:
@@ -562,7 +713,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,
@@ -571,34 +722,44 @@ class FreqtradeBot(object):
ordertime = arrow.get(order['datetime']).datetime
# Check if trade is still actually open
if int(order['remaining']) == 0:
if float(order['remaining']) == 0.0:
self.wallets.update()
continue
# Check if trade is still actually open
if order['status'] == 'open':
# Handle cancelled on exchange
if order['status'] == 'canceled':
if order['side'] == 'buy':
self.handle_buy_order_full_cancel(trade, "canceled on Exchange")
elif order['side'] == 'sell':
self.handle_timedout_limit_sell(trade, order)
self.wallets.update()
# Check if order is still actually open
elif order['status'] == 'open':
if order['side'] == 'buy' and ordertime < buy_timeoutthreashold:
self.handle_timedout_limit_buy(trade, order)
self.wallets.update()
elif order['side'] == 'sell' and ordertime < sell_timeoutthreashold:
self.handle_timedout_limit_sell(trade, order)
self.wallets.update()
def handle_buy_order_full_cancel(self, trade: Trade, reason: str) -> None:
"""Close trade in database and send message"""
Trade.session.delete(trade)
Trade.session.flush()
logger.info('Buy order %s for %s.', reason, trade)
self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Unfilled buy order for {trade.pair} {reason}'
})
# FIX: 20180110, why is cancel.order unconditionally here, whereas
# it is conditionally called in the
# handle_timedout_limit_sell()?
def handle_timedout_limit_buy(self, trade: Trade, order: Dict) -> bool:
"""Buy timeout - cancel order
:return: True if order was fully cancelled
"""
pair_s = trade.pair.replace('_', '/')
self.exchange.cancel_order(trade.open_order_id, trade.pair)
if order['remaining'] == order['amount']:
# if trade is not partially completed, just delete the trade
Trade.session.delete(trade)
Trade.session.flush()
logger.info('Buy order timeout for %s.', trade)
self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Unfilled buy order for {pair_s} cancelled due to timeout'
})
self.handle_buy_order_full_cancel(trade, "cancelled due to timeout")
return True
# if trade is partially complete, edit the stake details for the trade
@@ -609,20 +770,24 @@ class FreqtradeBot(object):
logger.info('Partial buy order timeout for %s.', trade)
self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Remaining buy order for {pair_s} cancelled due to timeout'
'status': f'Remaining buy order for {trade.pair} cancelled due to timeout'
})
return False
# FIX: 20180110, should cancel_order() be cond. or unconditionally called?
def handle_timedout_limit_sell(self, trade: Trade, order: Dict) -> bool:
"""
Sell timeout - cancel order and update trade
:return: True if order was fully cancelled
"""
pair_s = trade.pair.replace('_', '/')
if order['remaining'] == order['amount']:
# if trade is not partially completed, just cancel the trade
self.exchange.cancel_order(trade.open_order_id, trade.pair)
if order["status"] != "canceled":
reason = "due to timeout"
self.exchange.cancel_order(trade.open_order_id, trade.pair)
logger.info('Sell order timeout for %s.', trade)
else:
reason = "on exchange"
logger.info('Sell order canceled on exchange for %s.', trade)
trade.close_rate = None
trade.close_profit = None
trade.close_date = None
@@ -630,9 +795,9 @@ class FreqtradeBot(object):
trade.open_order_id = None
self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Unfilled sell order for {pair_s} cancelled due to timeout'
'status': f'Unfilled sell order for {trade.pair} cancelled {reason}'
})
logger.info('Sell order timeout for %s.', trade)
return True
# TODO: figure out how to handle partially complete sell orders
@@ -646,8 +811,27 @@ class FreqtradeBot(object):
:param sellreason: Reason the sell was triggered
:return: None
"""
sell_type = 'sell'
if sell_reason in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
sell_type = 'stoploss'
# if stoploss is on exchange and we are on dry_run mode,
# we consider the sell price stop price
if self.config.get('dry_run', False) and sell_type == 'stoploss' \
and self.strategy.order_types['stoploss_on_exchange']:
limit = trade.stop_loss
# First cancelling stoploss on exchange ...
if self.strategy.order_types.get('stoploss_on_exchange') and trade.stoploss_order_id:
self.exchange.cancel_order(trade.stoploss_order_id, trade.pair)
# Execute sell and update trade record
order_id = self.exchange.sell(str(trade.pair), limit, trade.amount)['id']
order_id = self.exchange.sell(pair=str(trade.pair),
ordertype=self.strategy.order_types[sell_type],
amount=trade.amount, rate=limit,
time_in_force=self.strategy.order_time_in_force['sell']
)['id']
trade.open_order_id = order_id
trade.close_rate_requested = limit
trade.sell_reason = sell_reason.value
@@ -670,6 +854,7 @@ class FreqtradeBot(object):
'current_rate': current_rate,
'profit_amount': profit_trade,
'profit_percent': profit_percent,
'sell_reason': sell_reason.value
}
# For regular case, when the configuration exists

View File

@@ -25,7 +25,7 @@ def main(sysargv: List[str]) -> None:
"""
arguments = Arguments(
sysargv,
'Simple High Frequency Trading Bot for crypto currencies'
'Free, open source crypto trading bot'
)
args = arguments.get_parsed_arg()
@@ -39,13 +39,13 @@ def main(sysargv: List[str]) -> None:
return_code = 1
try:
# Load and validate configuration
config = Configuration(args).get_config()
config = Configuration(args, None).get_config()
# Init the bot
freqtrade = FreqtradeBot(config)
state = None
while 1:
while True:
state = freqtrade.worker(old_state=state)
if state == State.RELOAD_CONF:
freqtrade = reconfigure(freqtrade, args)
@@ -76,7 +76,7 @@ def reconfigure(freqtrade: FreqtradeBot, args: Namespace) -> FreqtradeBot:
freqtrade.cleanup()
# Create new instance
freqtrade = FreqtradeBot(Configuration(args).get_config())
freqtrade = FreqtradeBot(Configuration(args, None).get_config())
freqtrade.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': 'config reloaded'

View File

@@ -3,7 +3,6 @@ Various tool function for Freqtrade and scripts
"""
import gzip
import json
import logging
import re
from datetime import datetime
@@ -11,6 +10,7 @@ from typing import Dict
import numpy as np
from pandas import DataFrame
import rapidjson
logger = logging.getLogger(__name__)
@@ -38,12 +38,7 @@ def datesarray_to_datetimearray(dates: np.ndarray) -> np.ndarray:
An numpy-array of datetimes
:return: numpy-array of datetime
"""
times = []
dates = dates.astype(datetime)
for index in range(0, dates.size):
date = dates[index].to_pydatetime()
times.append(date)
return np.array(times)
return dates.dt.to_pydatetime()
def common_datearray(dfs: Dict[str, DataFrame]) -> np.ndarray:
@@ -71,16 +66,45 @@ def file_dump_json(filename, data, is_zip=False) -> None:
:param data: JSON Data to save
:return:
"""
print(f'dumping json to "{filename}"')
logger.info(f'dumping json to "{filename}"')
if is_zip:
if not filename.endswith('.gz'):
filename = filename + '.gz'
with gzip.open(filename, 'w') as fp:
json.dump(data, fp, default=str)
rapidjson.dump(data, fp, default=str, number_mode=rapidjson.NM_NATIVE)
else:
with open(filename, 'w') as fp:
json.dump(data, fp, default=str)
rapidjson.dump(data, fp, default=str, number_mode=rapidjson.NM_NATIVE)
logger.debug(f'done json to "{filename}"')
def json_load(datafile):
"""
load data with rapidjson
Use this to have a consistent experience,
sete number_mode to "NM_NATIVE" for greatest speed
"""
return rapidjson.load(datafile, number_mode=rapidjson.NM_NATIVE)
def file_load_json(file):
gzipfile = file.with_suffix(file.suffix + '.gz')
# Try gzip file first, otherwise regular json file.
if gzipfile.is_file():
logger.debug('Loading ticker data from file %s', gzipfile)
with gzip.open(gzipfile) as tickerdata:
pairdata = json_load(tickerdata)
elif file.is_file():
logger.debug('Loading ticker data from file %s', file)
with open(file) as tickerdata:
pairdata = json_load(tickerdata)
else:
return None
return pairdata
def format_ms_time(date: int) -> str:

View File

@@ -1,229 +1,49 @@
# pragma pylint: disable=missing-docstring
import gzip
import json
import logging
import os
from typing import Optional, List, Dict, Tuple, Any
import arrow
from datetime import datetime
from typing import Dict, Tuple
import operator
from freqtrade import misc, constants, OperationalException
from freqtrade.exchange import Exchange
from freqtrade.arguments import TimeRange
import arrow
from pandas import DataFrame
from freqtrade.optimize.default_hyperopt import DefaultHyperOpts # noqa: F401
logger = logging.getLogger(__name__)
def trim_tickerlist(tickerlist: List[Dict], timerange: TimeRange) -> List[Dict]:
if not tickerlist:
return tickerlist
start_index = 0
stop_index = len(tickerlist)
if timerange.starttype == 'line':
stop_index = timerange.startts
if timerange.starttype == 'index':
start_index = timerange.startts
elif timerange.starttype == 'date':
while (start_index < len(tickerlist) and
tickerlist[start_index][0] < timerange.startts * 1000):
start_index += 1
if timerange.stoptype == 'line':
start_index = len(tickerlist) + timerange.stopts
if timerange.stoptype == 'index':
stop_index = timerange.stopts
elif timerange.stoptype == 'date':
while (stop_index > 0 and
tickerlist[stop_index-1][0] > timerange.stopts * 1000):
stop_index -= 1
if start_index > stop_index:
raise ValueError(f'The timerange [{timerange.startts},{timerange.stopts}] is incorrect')
return tickerlist[start_index:stop_index]
def load_tickerdata_file(
datadir: str, pair: str,
ticker_interval: str,
timerange: Optional[TimeRange] = None) -> Optional[List[Dict]]:
def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]:
"""
Load a pair from file,
:return dict OR empty if unsuccesful
Get the maximum timeframe for the given backtest data
:param data: dictionary with preprocessed backtesting data
:return: tuple containing min_date, max_date
"""
path = make_testdata_path(datadir)
pair_s = pair.replace('/', '_')
file = os.path.join(path, f'{pair_s}-{ticker_interval}.json')
gzipfile = file + '.gz'
# If the file does not exist we download it when None is returned.
# If file exists, read the file, load the json
if os.path.isfile(gzipfile):
logger.debug('Loading ticker data from file %s', gzipfile)
with gzip.open(gzipfile) as tickerdata:
pairdata = json.load(tickerdata)
elif os.path.isfile(file):
logger.debug('Loading ticker data from file %s', file)
with open(file) as tickerdata:
pairdata = json.load(tickerdata)
else:
return None
if timerange:
pairdata = trim_tickerlist(pairdata, timerange)
return pairdata
timeframe = [
(arrow.get(frame['date'].min()), arrow.get(frame['date'].max()))
for frame in data.values()
]
return min(timeframe, key=operator.itemgetter(0))[0], \
max(timeframe, key=operator.itemgetter(1))[1]
def load_data(datadir: str,
ticker_interval: str,
pairs: List[str],
refresh_pairs: Optional[bool] = False,
exchange: Optional[Exchange] = None,
timerange: TimeRange = TimeRange(None, None, 0, 0)) -> Dict[str, List]:
def validate_backtest_data(data: Dict[str, DataFrame], min_date: datetime,
max_date: datetime, ticker_interval_mins: int) -> bool:
"""
Loads ticker history data for the given parameters
:return: dict
Validates preprocessed backtesting data for missing values and shows warnings about it that.
:param data: dictionary with preprocessed backtesting data
:param min_date: start-date of the data
:param max_date: end-date of the data
:param ticker_interval_mins: ticker interval in minutes
"""
result = {}
# If the user force the refresh of pairs
if refresh_pairs:
logger.info('Download data for all pairs and store them in %s', datadir)
if not exchange:
raise OperationalException("Exchange needs to be initialized when "
"calling load_data with refresh_pairs=True")
download_pairs(datadir, exchange, pairs, ticker_interval, timerange=timerange)
for pair in pairs:
pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
if pairdata:
result[pair] = pairdata
else:
logger.warning(
'No data for pair: "%s", Interval: %s. '
'Use --refresh-pairs-cached to download the data',
pair,
ticker_interval
)
return result
def make_testdata_path(datadir: str) -> str:
"""Return the path where testdata files are stored"""
return datadir or os.path.abspath(
os.path.join(
os.path.dirname(__file__), '..', 'tests', 'testdata'
)
)
def download_pairs(datadir, exchange: Exchange, pairs: List[str],
ticker_interval: str,
timerange: TimeRange = TimeRange(None, None, 0, 0)) -> bool:
"""For each pairs passed in parameters, download the ticker intervals"""
for pair in pairs:
try:
download_backtesting_testdata(datadir,
exchange=exchange,
pair=pair,
tick_interval=ticker_interval,
timerange=timerange)
except BaseException:
logger.info(
'Failed to download the pair: "%s", Interval: %s',
pair,
ticker_interval
)
return False
return True
def load_cached_data_for_updating(filename: str,
tick_interval: str,
timerange: Optional[TimeRange]) -> Tuple[
List[Any],
Optional[int]]:
"""
Load cached data and choose what part of the data should be updated
"""
since_ms = None
# user sets timerange, so find the start time
if timerange:
if timerange.starttype == 'date':
since_ms = timerange.startts * 1000
elif timerange.stoptype == 'line':
num_minutes = timerange.stopts * constants.TICKER_INTERVAL_MINUTES[tick_interval]
since_ms = arrow.utcnow().shift(minutes=num_minutes).timestamp * 1000
# read the cached file
if os.path.isfile(filename):
with open(filename, "rt") as 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:
data.pop()
else:
data = []
if data:
if since_ms and since_ms < data[0][0]:
# the data is requested for earlier period than the cache has
# so fully redownload all the data
data = []
else:
# a part of the data was already downloaded, so
# download unexist data only
since_ms = data[-1][0] + 1
return (data, since_ms)
def download_backtesting_testdata(datadir: str,
exchange: Exchange,
pair: str,
tick_interval: str = '5m',
timerange: Optional[TimeRange] = None) -> None:
"""
Download the latest ticker intervals from the exchange for the pairs 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,
the full data will be redownloaded
Based on @Rybolov work: https://github.com/rybolov/freqtrade-data
:param pairs: list of pairs 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')
logger.info(
'Download the pair: "%s", Interval: %s',
pair,
tick_interval
)
data, since_ms = load_cached_data_for_updating(filename, tick_interval, timerange)
logger.debug("Current Start: %s", misc.format_ms_time(data[1][0]) if data else 'None')
logger.debug("Current End: %s", misc.format_ms_time(data[-1][0]) if data else 'None')
new_data = exchange.get_candle_history(pair=pair, tick_interval=tick_interval,
since_ms=since_ms)
data.extend(new_data)
logger.debug("New Start: %s", misc.format_ms_time(data[0][0]))
logger.debug("New End: %s", misc.format_ms_time(data[-1][0]))
misc.file_dump_json(filename, data)
# total difference in minutes / interval-minutes
expected_frames = int((max_date - min_date).total_seconds() // 60 // ticker_interval_mins)
found_missing = False
for pair, df in data.items():
dflen = len(df)
if dflen < expected_frames:
found_missing = True
logger.warning("%s has missing frames: expected %s, got %s, that's %s missing values",
pair, expected_frames, dflen, expected_frames - dflen)
return found_missing

View File

@@ -4,26 +4,26 @@
This module contains the backtesting logic
"""
import logging
import operator
from argparse import Namespace
from copy import deepcopy
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, Dict, List, NamedTuple, Optional, Tuple
from typing import Any, Dict, List, NamedTuple, Optional
import arrow
from pandas import DataFrame
from tabulate import tabulate
import freqtrade.optimize as optimize
from freqtrade import optimize
from freqtrade import DependencyException, constants
from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration
from freqtrade.exchange import Exchange
from freqtrade.data import history
from freqtrade.misc import file_dump_json
from freqtrade.persistence import Trade
from freqtrade.strategy.interface import SellType
from freqtrade.strategy.resolver import IStrategy, StrategyResolver
from freqtrade.resolvers import StrategyResolver
from freqtrade.state import RunMode
from freqtrade.strategy.interface import SellType, IStrategy
logger = logging.getLogger(__name__)
@@ -68,6 +68,7 @@ class Backtesting(object):
if self.config.get('strategy_list', None):
# Force one interval
self.ticker_interval = str(self.config.get('ticker_interval'))
self.ticker_interval_mins = constants.TICKER_INTERVAL_MINUTES[self.ticker_interval]
for strat in list(self.config['strategy_list']):
stratconf = deepcopy(self.config)
stratconf['strategy'] = strat
@@ -75,8 +76,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])
@@ -90,43 +89,37 @@ class Backtesting(object):
"""
self.strategy = strategy
self.ticker_interval = self.config.get('ticker_interval')
self.ticker_interval_mins = constants.TICKER_INTERVAL_MINUTES[self.ticker_interval]
self.tickerdata_to_dataframe = strategy.tickerdata_to_dataframe
self.advise_buy = strategy.advise_buy
self.advise_sell = strategy.advise_sell
@staticmethod
def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]:
"""
Get the maximum timeframe for the given backtest data
:param data: dictionary with preprocessed backtesting data
:return: tuple containing min_date, max_date
"""
timeframe = [
(arrow.get(frame['date'].min()), arrow.get(frame['date'].max()))
for frame in data.values()
]
return min(timeframe, key=operator.itemgetter(0))[0], \
max(timeframe, key=operator.itemgetter(1))[1]
def _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
"""
stake_currency = str(self.config.get('stake_currency'))
max_open_trades = self.config.get('max_open_trades')
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', 'd', '.1f', '.1f')
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', '.2f', 'd', '.1f', '.1f')
tabular_data = []
headers = ['pair', 'buy count', 'avg profit %', 'cum profit %',
'total profit ' + stake_currency, 'avg duration', 'profit', 'loss']
'tot profit ' + stake_currency, 'tot profit %', 'avg duration',
'profit', 'loss']
for pair in data:
result = results[results.pair == pair]
if skip_nan and result.profit_abs.isnull().all():
continue
tabular_data.append([
pair,
len(result.index),
result.profit_percent.mean() * 100.0,
result.profit_percent.sum() * 100.0,
result.profit_abs.sum(),
result.profit_percent.sum() * 100.0 / max_open_trades,
str(timedelta(
minutes=round(result.trade_duration.mean()))) if not result.empty else '0:00',
len(result[result.profit_abs > 0]),
@@ -140,12 +133,15 @@ class Backtesting(object):
results.profit_percent.mean() * 100.0,
results.profit_percent.sum() * 100.0,
results.profit_abs.sum(),
results.profit_percent.sum() * 100.0 / max_open_trades,
str(timedelta(
minutes=round(results.trade_duration.mean()))) if not results.empty else '0:00',
len(results[results.profit_abs > 0]),
len(results[results.profit_abs < 0])
])
return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe")
# Ignore type as floatfmt does allow tuples but mypy does not know that
return tabulate(tabular_data, headers=headers, # type: ignore
floatfmt=floatfmt, tablefmt="pipe")
def _generate_text_table_sell_reason(self, data: Dict[str, Dict], results: DataFrame) -> str:
"""
@@ -162,11 +158,13 @@ class Backtesting(object):
Generate summary table per strategy
"""
stake_currency = str(self.config.get('stake_currency'))
max_open_trades = self.config.get('max_open_trades')
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', 'd', '.1f', '.1f')
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', '.2f', 'd', '.1f', '.1f')
tabular_data = []
headers = ['Strategy', 'buy count', 'avg profit %', 'cum profit %',
'total profit ' + stake_currency, 'avg duration', 'profit', 'loss']
'tot profit ' + stake_currency, 'tot profit %', 'avg duration',
'profit', 'loss']
for strategy, results in all_results.items():
tabular_data.append([
strategy,
@@ -174,12 +172,15 @@ class Backtesting(object):
results.profit_percent.mean() * 100.0,
results.profit_percent.sum() * 100.0,
results.profit_abs.sum(),
results.profit_percent.sum() * 100.0 / max_open_trades,
str(timedelta(
minutes=round(results.trade_duration.mean()))) if not results.empty else '0:00',
len(results[results.profit_abs > 0]),
len(results[results.profit_abs < 0])
])
return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe")
# Ignore type as floatfmt does allow tuples but mypy does not know that
return tabulate(tabular_data, headers=headers, # type: ignore
floatfmt=floatfmt, tablefmt="pipe")
def _store_backtest_result(self, recordfilename: str, results: DataFrame,
strategyname: Optional[str] = None) -> None:
@@ -221,21 +222,38 @@ class Backtesting(object):
buy_signal = sell_row.buy
sell = self.strategy.should_sell(trade, sell_row.open, sell_row.date, buy_signal,
sell_row.sell)
sell_row.sell, low=sell_row.low, high=sell_row.high)
if sell.sell_flag:
trade_dur = int((sell_row.date - buy_row.date).total_seconds() // 60)
# Special handling if high or low hit STOP_LOSS or ROI
if sell.sell_type in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
# Set close_rate to stoploss
closerate = trade.stop_loss
elif sell.sell_type == (SellType.ROI):
# get next entry in min_roi > to trade duration
# Interface.py skips on trade_duration <= duration
roi_entry = max(list(filter(lambda x: trade_dur >= x,
self.strategy.minimal_roi.keys())))
roi = self.strategy.minimal_roi[roi_entry]
# - (Expected abs profit + open_rate + open_fee) / (fee_close -1)
closerate = - (trade.open_rate * roi + trade.open_rate *
(1 + trade.fee_open)) / (trade.fee_close - 1)
else:
closerate = sell_row.open
return BacktestResult(pair=pair,
profit_percent=trade.calc_profit_percent(rate=sell_row.open),
profit_abs=trade.calc_profit(rate=sell_row.open),
profit_percent=trade.calc_profit_percent(rate=closerate),
profit_abs=trade.calc_profit(rate=closerate),
open_time=buy_row.date,
close_time=sell_row.date,
trade_duration=int((
sell_row.date - buy_row.date).total_seconds() // 60),
trade_duration=trade_dur,
open_index=buy_row.Index,
close_index=sell_row.Index,
open_at_end=False,
open_rate=buy_row.open,
close_rate=sell_row.open,
close_rate=closerate,
sell_reason=sell.sell_type
)
if partial_ticker:
@@ -275,12 +293,17 @@ class Backtesting(object):
position_stacking: do we allow position stacking? (default: False)
:return: DataFrame
"""
headers = ['date', 'buy', 'open', 'close', 'sell']
headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high']
processed = args['processed']
max_open_trades = args.get('max_open_trades', 0)
position_stacking = args.get('position_stacking', False)
start_date = args['start_date']
end_date = args['end_date']
trades = []
trade_count_lock: Dict = {}
ticker: Dict = {}
pairs = []
# Create ticker dict
for pair, pair_data in processed.items():
pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run
@@ -295,15 +318,28 @@ class Backtesting(object):
# Convert from Pandas to list for performance reasons
# (Looping Pandas is slow.)
ticker = [x for x in ticker_data.itertuples()]
ticker[pair] = [x for x in ticker_data.itertuples()]
pairs.append(pair)
lock_pair_until: Dict = {}
tmp = start_date + timedelta(minutes=self.ticker_interval_mins)
index = 0
# Loop timerange and test per pair
while tmp < end_date:
# print(f"time: {tmp}")
for i, pair in enumerate(ticker):
try:
row = ticker[pair][index]
except IndexError:
# missing Data for one pair ...
# Warnings for this are shown by `validate_backtest_data`
continue
lock_pair_until = None
for index, row in enumerate(ticker):
if row.buy == 0 or row.sell == 1:
continue # skip rows where no buy signal or that would immediately sell off
if not position_stacking:
if lock_pair_until is not None and row.date <= lock_pair_until:
if pair in lock_pair_until and row.date <= lock_pair_until[pair]:
continue
if max_open_trades > 0:
# Check if max_open_trades has already been reached for the given date
@@ -312,17 +348,19 @@ class Backtesting(object):
trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1
trade_entry = self._get_sell_trade_entry(pair, row, ticker[index + 1:],
trade_entry = self._get_sell_trade_entry(pair, row, ticker[pair][index + 1:],
trade_count_lock, args)
if trade_entry:
lock_pair_until = trade_entry.close_time
lock_pair_until[pair] = trade_entry.close_time
trades.append(trade_entry)
else:
# Set lock_pair_until to end of testing period if trade could not be closed
# This happens only if the buy-signal was with the last candle
lock_pair_until = ticker_data.iloc[-1].date
lock_pair_until[pair] = end_date
tmp += timedelta(minutes=self.ticker_interval_mins)
index += 1
return DataFrame.from_records(trades, columns=BacktestResult._fields)
def start(self) -> None:
@@ -330,22 +368,23 @@ 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_latest_ohlcv([(pair, self.ticker_interval) for pair in pairs])
data = {key[0]: value for key, value in self.exchange._klines.items()}
else:
logger.info('Using local backtesting data (using whitelist in given config) ...')
timerange = Arguments.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange')))
data = optimize.load_data(
self.config['datadir'],
data = history.load_data(
datadir=Path(self.config['datadir']) if self.config.get('datadir') else None,
pairs=pairs,
ticker_interval=self.ticker_interval,
refresh_pairs=self.config.get('refresh_pairs', False),
@@ -368,17 +407,18 @@ class Backtesting(object):
logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())
self._set_strategy(strat)
# need to reprocess data every time to populate signals
preprocessed = self.tickerdata_to_dataframe(data)
# Print timeframe
min_date, max_date = self.get_timeframe(preprocessed)
min_date, max_date = optimize.get_timeframe(data)
# Validate dataframe for missing values (mainly at start and end, as fillup is called)
optimize.validate_backtest_data(data, min_date, max_date,
constants.TICKER_INTERVAL_MINUTES[self.ticker_interval])
logger.info(
'Measuring data from %s up to %s (%s days)..',
min_date.isoformat(),
max_date.isoformat(),
(max_date - min_date).days
)
# need to reprocess data every time to populate signals
preprocessed = self.strategy.tickerdata_to_dataframe(data)
# Execute backtest and print results
all_results[self.strategy.get_strategy_name()] = self.backtest(
@@ -387,6 +427,8 @@ class Backtesting(object):
'processed': preprocessed,
'max_open_trades': max_open_trades,
'position_stacking': self.config.get('position_stacking', False),
'start_date': min_date,
'end_date': max_date,
}
)
@@ -397,18 +439,18 @@ class Backtesting(object):
strategy if len(self.strategylist) > 1 else None)
print(f"Result for strategy {strategy}")
print(' BACKTESTING REPORT '.center(119, '='))
print(' BACKTESTING REPORT '.center(133, '='))
print(self._generate_text_table(data, results))
print(' SELL REASON STATS '.center(119, '='))
print(' SELL REASON STATS '.center(133, '='))
print(self._generate_text_table_sell_reason(data, results))
print(' LEFT OPEN TRADES REPORT '.center(119, '='))
print(self._generate_text_table(data, results.loc[results.open_at_end]))
print(' LEFT OPEN TRADES REPORT '.center(133, '='))
print(self._generate_text_table(data, results.loc[results.open_at_end], True))
print()
if len(all_results) > 1:
# Print Strategy summary table
print(' Strategy Summary '.center(119, '='))
print(' Strategy Summary '.center(133, '='))
print(self._generate_text_table_strategy(all_results))
print('\nFor more details, please look at the detail tables above')
@@ -419,7 +461,7 @@ def setup_configuration(args: Namespace) -> Dict[str, Any]:
:param args: Cli args from Arguments()
:return: Configuration
"""
configuration = Configuration(args)
configuration = Configuration(args, RunMode.BACKTEST)
config = configuration.get_config()
# Ensure we do not use Exchange credentials

View File

@@ -0,0 +1,226 @@
# pragma pylint: disable=missing-docstring, invalid-name, pointless-string-statement
import talib.abstract as ta
from pandas import DataFrame
from typing import Dict, Any, Callable, List
from functools import reduce
from skopt.space import Categorical, Dimension, Integer, Real
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.optimize.hyperopt_interface import IHyperOpt
class_name = 'DefaultHyperOpts'
class DefaultHyperOpts(IHyperOpt):
"""
Default hyperopt provided by freqtrade bot.
You can override it with your own hyperopt
"""
@staticmethod
def populate_indicators(dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['adx'] = ta.ADX(dataframe)
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['mfi'] = ta.MFI(dataframe)
dataframe['rsi'] = ta.RSI(dataframe)
stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd']
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# Bollinger bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['bb_upperband'] = bollinger['upper']
dataframe['sar'] = ta.SAR(dataframe)
return dataframe
@staticmethod
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Define the buy strategy parameters to be used by hyperopt
"""
def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Buy strategy Hyperopt will build and use
"""
conditions = []
# GUARDS AND TRENDS
if 'mfi-enabled' in params and params['mfi-enabled']:
conditions.append(dataframe['mfi'] < params['mfi-value'])
if 'fastd-enabled' in params and params['fastd-enabled']:
conditions.append(dataframe['fastd'] < params['fastd-value'])
if 'adx-enabled' in params and params['adx-enabled']:
conditions.append(dataframe['adx'] > params['adx-value'])
if 'rsi-enabled' in params and params['rsi-enabled']:
conditions.append(dataframe['rsi'] < params['rsi-value'])
# TRIGGERS
if 'trigger' in params:
if params['trigger'] == 'bb_lower':
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
if params['trigger'] == 'macd_cross_signal':
conditions.append(qtpylib.crossed_above(
dataframe['macd'], dataframe['macdsignal']
))
if params['trigger'] == 'sar_reversal':
conditions.append(qtpylib.crossed_above(
dataframe['close'], dataframe['sar']
))
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
return dataframe
return populate_buy_trend
@staticmethod
def indicator_space() -> List[Dimension]:
"""
Define your Hyperopt space for searching strategy parameters
"""
return [
Integer(10, 25, name='mfi-value'),
Integer(15, 45, name='fastd-value'),
Integer(20, 50, name='adx-value'),
Integer(20, 40, name='rsi-value'),
Categorical([True, False], name='mfi-enabled'),
Categorical([True, False], name='fastd-enabled'),
Categorical([True, False], name='adx-enabled'),
Categorical([True, False], name='rsi-enabled'),
Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger')
]
@staticmethod
def sell_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Define the sell strategy parameters to be used by hyperopt
"""
def populate_sell_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Sell strategy Hyperopt will build and use
"""
# print(params)
conditions = []
# GUARDS AND TRENDS
if 'sell-mfi-enabled' in params and params['sell-mfi-enabled']:
conditions.append(dataframe['mfi'] > params['sell-mfi-value'])
if 'sell-fastd-enabled' in params and params['sell-fastd-enabled']:
conditions.append(dataframe['fastd'] > params['sell-fastd-value'])
if 'sell-adx-enabled' in params and params['sell-adx-enabled']:
conditions.append(dataframe['adx'] < params['sell-adx-value'])
if 'sell-rsi-enabled' in params and params['sell-rsi-enabled']:
conditions.append(dataframe['rsi'] > params['sell-rsi-value'])
# TRIGGERS
if 'sell-trigger' in params:
if params['sell-trigger'] == 'sell-bb_upper':
conditions.append(dataframe['close'] > dataframe['bb_upperband'])
if params['sell-trigger'] == 'sell-macd_cross_signal':
conditions.append(qtpylib.crossed_above(
dataframe['macdsignal'], dataframe['macd']
))
if params['sell-trigger'] == 'sell-sar_reversal':
conditions.append(qtpylib.crossed_above(
dataframe['sar'], dataframe['close']
))
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'sell'] = 1
return dataframe
return populate_sell_trend
@staticmethod
def sell_indicator_space() -> List[Dimension]:
"""
Define your Hyperopt space for searching sell strategy parameters
"""
return [
Integer(75, 100, name='sell-mfi-value'),
Integer(50, 100, name='sell-fastd-value'),
Integer(50, 100, name='sell-adx-value'),
Integer(60, 100, name='sell-rsi-value'),
Categorical([True, False], name='sell-mfi-enabled'),
Categorical([True, False], name='sell-fastd-enabled'),
Categorical([True, False], name='sell-adx-enabled'),
Categorical([True, False], name='sell-rsi-enabled'),
Categorical(['sell-bb_upper',
'sell-macd_cross_signal',
'sell-sar_reversal'], name='sell-trigger')
]
@staticmethod
def generate_roi_table(params: Dict) -> Dict[int, float]:
"""
Generate the ROI table that will be used by Hyperopt
"""
roi_table = {}
roi_table[0] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
roi_table[params['roi_t3']] = params['roi_p1'] + params['roi_p2']
roi_table[params['roi_t3'] + params['roi_t2']] = params['roi_p1']
roi_table[params['roi_t3'] + params['roi_t2'] + params['roi_t1']] = 0
return roi_table
@staticmethod
def stoploss_space() -> List[Dimension]:
"""
Stoploss Value to search
"""
return [
Real(-0.5, -0.02, name='stoploss'),
]
@staticmethod
def roi_space() -> List[Dimension]:
"""
Values to search for each ROI steps
"""
return [
Integer(10, 120, name='roi_t1'),
Integer(10, 60, name='roi_t2'),
Integer(10, 40, name='roi_t3'),
Real(0.01, 0.04, name='roi_p1'),
Real(0.01, 0.07, name='roi_p2'),
Real(0.01, 0.20, name='roi_p3'),
]
def populate_buy_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators. Should be a copy of from strategy
must align to populate_indicators in this file
Only used when --spaces does not include buy
"""
dataframe.loc[
(
(dataframe['close'] < dataframe['bb_lowerband']) &
(dataframe['mfi'] < 16) &
(dataframe['adx'] > 25) &
(dataframe['rsi'] < 21)
),
'buy'] = 1
return dataframe
def populate_sell_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Based on TA indicators. Should be a copy of from strategy
must align to populate_indicators in this file
Only used when --spaces does not include sell
"""
dataframe.loc[
(
(qtpylib.crossed_above(
dataframe['macdsignal'], dataframe['macd']
)) &
(dataframe['fastd'] > 54)
),
'sell'] = 1
return dataframe

View File

@@ -0,0 +1,109 @@
# pragma pylint: disable=missing-docstring, W0212, too-many-arguments
"""
This module contains the edge backtesting interface
"""
import logging
from argparse import Namespace
from typing import Dict, Any
from tabulate import tabulate
from freqtrade.edge import Edge
from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration
from freqtrade.exchange import Exchange
from freqtrade.resolvers import StrategyResolver
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
class EdgeCli(object):
"""
EdgeCli class, this class contains all the logic to run edge backtesting
To run a edge backtest:
edge = EdgeCli(config)
edge.start()
"""
def __init__(self, config: Dict[str, Any]) -> None:
self.config = config
# Reset keys for edge
self.config['exchange']['key'] = ''
self.config['exchange']['secret'] = ''
self.config['exchange']['password'] = ''
self.config['exchange']['uid'] = ''
self.config['dry_run'] = True
self.exchange = Exchange(self.config)
self.strategy = StrategyResolver(self.config).strategy
self.edge = Edge(config, self.exchange, self.strategy)
self.edge._refresh_pairs = self.config.get('refresh_pairs', False)
self.timerange = Arguments.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange')))
self.edge._timerange = self.timerange
def _generate_edge_table(self, results: dict) -> str:
floatfmt = ('s', '.10g', '.2f', '.2f', '.2f', '.2f', 'd', '.d')
tabular_data = []
headers = ['pair', 'stoploss', 'win rate', 'risk reward ratio',
'required risk reward', 'expectancy', 'total number of trades',
'average duration (min)']
for result in results.items():
if result[1].nb_trades > 0:
tabular_data.append([
result[0],
result[1].stoploss,
result[1].winrate,
result[1].risk_reward_ratio,
result[1].required_risk_reward,
result[1].expectancy,
result[1].nb_trades,
round(result[1].avg_trade_duration)
])
# Ignore type as floatfmt does allow tuples but mypy does not know that
return tabulate(tabular_data, headers=headers, # type: ignore
floatfmt=floatfmt, tablefmt="pipe")
def start(self) -> None:
self.edge.calculate()
print('') # blank like for readability
print(self._generate_edge_table(self.edge._cached_pairs))
def setup_configuration(args: Namespace) -> Dict[str, Any]:
"""
Prepare the configuration for edge backtesting
:param args: Cli args from Arguments()
:return: Configuration
"""
configuration = Configuration(args, RunMode.EDGECLI)
config = configuration.get_config()
# Ensure we do not use Exchange credentials
config['exchange']['key'] = ''
config['exchange']['secret'] = ''
return config
def start(args: Namespace) -> None:
"""
Start Edge script
:param args: Cli args from Arguments()
:return: None
"""
# Initialize configuration
config = setup_configuration(args)
logger.info('Starting freqtrade in Edge mode')
# Initialize Edge object
edge_cli = EdgeCli(config)
edge_cli.start()

View File

@@ -9,22 +9,24 @@ import multiprocessing
import os
import sys
from argparse import Namespace
from functools import reduce
from math import exp
from operator import itemgetter
from typing import Any, Callable, Dict, List
from pathlib import Path
from pprint import pprint
from typing import Any, Dict, List
import talib.abstract as ta
from joblib import Parallel, delayed, dump, load, wrap_non_picklable_objects
from pandas import DataFrame
from sklearn.externals.joblib import Parallel, delayed, dump, load
from skopt import Optimizer
from skopt.space import Categorical, Dimension, Integer, Real
from skopt.space import Dimension
import freqtrade.vendor.qtpylib.indicators as qtpylib
from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration
from freqtrade.optimize import load_data
from freqtrade.data.history import load_data
from freqtrade.optimize import get_timeframe
from freqtrade.optimize.backtesting import Backtesting
from freqtrade.state import RunMode
from freqtrade.resolvers import HyperOptResolver
logger = logging.getLogger(__name__)
@@ -42,6 +44,9 @@ class Hyperopt(Backtesting):
"""
def __init__(self, config: Dict[str, Any]) -> None:
super().__init__(config)
self.config = config
self.custom_hyperopt = HyperOptResolver(self.config).hyperopt
# set TARGET_TRADES to suit your number concurrent trades so its realistic
# to the number of days
self.target_trades = 600
@@ -74,24 +79,6 @@ class Hyperopt(Backtesting):
arg_dict = {dim.name: value for dim, value in zip(dimensions, params)}
return arg_dict
@staticmethod
def populate_indicators(dataframe: DataFrame, metadata: dict) -> DataFrame:
dataframe['adx'] = ta.ADX(dataframe)
macd = ta.MACD(dataframe)
dataframe['macd'] = macd['macd']
dataframe['macdsignal'] = macd['macdsignal']
dataframe['mfi'] = ta.MFI(dataframe)
dataframe['rsi'] = ta.RSI(dataframe)
stoch_fast = ta.STOCHF(dataframe)
dataframe['fastd'] = stoch_fast['fastd']
dataframe['minus_di'] = ta.MINUS_DI(dataframe)
# Bollinger bands
bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2)
dataframe['bb_lowerband'] = bollinger['lower']
dataframe['sar'] = ta.SAR(dataframe)
return dataframe
def save_trials(self) -> None:
"""
Save hyperopt trials to file
@@ -116,12 +103,13 @@ class Hyperopt(Backtesting):
results = sorted(self.trials, key=itemgetter('loss'))
best_result = results[0]
logger.info(
'Best result:\n%s\nwith values:\n%s',
best_result['result'],
best_result['params']
'Best result:\n%s\nwith values:\n',
best_result['result']
)
pprint(best_result['params'], indent=4)
if 'roi_t1' in best_result['params']:
logger.info('ROI table:\n%s', self.generate_roi_table(best_result['params']))
logger.info('ROI table:')
pprint(self.custom_hyperopt.generate_roi_table(best_result['params']), indent=4)
def log_results(self, results) -> None:
"""
@@ -149,59 +137,6 @@ class Hyperopt(Backtesting):
result = trade_loss + profit_loss + duration_loss
return result
@staticmethod
def generate_roi_table(params: Dict) -> Dict[int, float]:
"""
Generate the ROI table thqt will be used by Hyperopt
"""
roi_table = {}
roi_table[0] = params['roi_p1'] + params['roi_p2'] + params['roi_p3']
roi_table[params['roi_t3']] = params['roi_p1'] + params['roi_p2']
roi_table[params['roi_t3'] + params['roi_t2']] = params['roi_p1']
roi_table[params['roi_t3'] + params['roi_t2'] + params['roi_t1']] = 0
return roi_table
@staticmethod
def roi_space() -> List[Dimension]:
"""
Values to search for each ROI steps
"""
return [
Integer(10, 120, name='roi_t1'),
Integer(10, 60, name='roi_t2'),
Integer(10, 40, name='roi_t3'),
Real(0.01, 0.04, name='roi_p1'),
Real(0.01, 0.07, name='roi_p2'),
Real(0.01, 0.20, name='roi_p3'),
]
@staticmethod
def stoploss_space() -> List[Dimension]:
"""
Stoploss search space
"""
return [
Real(-0.5, -0.02, name='stoploss'),
]
@staticmethod
def indicator_space() -> List[Dimension]:
"""
Define your Hyperopt space for searching strategy parameters
"""
return [
Integer(10, 25, name='mfi-value'),
Integer(15, 45, name='fastd-value'),
Integer(20, 50, name='adx-value'),
Integer(20, 40, name='rsi-value'),
Categorical([True, False], name='mfi-enabled'),
Categorical([True, False], name='fastd-enabled'),
Categorical([True, False], name='adx-enabled'),
Categorical([True, False], name='rsi-enabled'),
Categorical(['bb_lower', 'macd_cross_signal', 'sar_reversal'], name='trigger')
]
def has_space(self, space: str) -> bool:
"""
Tell if a space value is contained in the configuration
@@ -216,71 +151,46 @@ class Hyperopt(Backtesting):
"""
spaces: List[Dimension] = []
if self.has_space('buy'):
spaces += Hyperopt.indicator_space()
spaces += self.custom_hyperopt.indicator_space()
if self.has_space('sell'):
spaces += self.custom_hyperopt.sell_indicator_space()
# Make sure experimental is enabled
if 'experimental' not in self.config:
self.config['experimental'] = {}
self.config['experimental']['use_sell_signal'] = True
if self.has_space('roi'):
spaces += Hyperopt.roi_space()
spaces += self.custom_hyperopt.roi_space()
if self.has_space('stoploss'):
spaces += Hyperopt.stoploss_space()
spaces += self.custom_hyperopt.stoploss_space()
return spaces
@staticmethod
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Define the buy strategy parameters to be used by hyperopt
"""
def populate_buy_trend(dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Buy strategy Hyperopt will build and use
"""
conditions = []
# GUARDS AND TRENDS
if 'mfi-enabled' in params and params['mfi-enabled']:
conditions.append(dataframe['mfi'] < params['mfi-value'])
if 'fastd-enabled' in params and params['fastd-enabled']:
conditions.append(dataframe['fastd'] < params['fastd-value'])
if 'adx-enabled' in params and params['adx-enabled']:
conditions.append(dataframe['adx'] > params['adx-value'])
if 'rsi-enabled' in params and params['rsi-enabled']:
conditions.append(dataframe['rsi'] < params['rsi-value'])
# TRIGGERS
if params['trigger'] == 'bb_lower':
conditions.append(dataframe['close'] < dataframe['bb_lowerband'])
if params['trigger'] == 'macd_cross_signal':
conditions.append(qtpylib.crossed_above(
dataframe['macd'], dataframe['macdsignal']
))
if params['trigger'] == 'sar_reversal':
conditions.append(qtpylib.crossed_above(
dataframe['close'], dataframe['sar']
))
dataframe.loc[
reduce(lambda x, y: x & y, conditions),
'buy'] = 1
return dataframe
return populate_buy_trend
def generate_optimizer(self, _params) -> Dict:
def generate_optimizer(self, _params: Dict) -> Dict:
params = self.get_args(_params)
if self.has_space('roi'):
self.strategy.minimal_roi = self.generate_roi_table(params)
self.strategy.minimal_roi = self.custom_hyperopt.generate_roi_table(params)
if self.has_space('buy'):
self.advise_buy = self.buy_strategy_generator(params)
self.advise_buy = self.custom_hyperopt.buy_strategy_generator(params)
elif hasattr(self.custom_hyperopt, 'populate_buy_trend'):
self.advise_buy = self.custom_hyperopt.populate_buy_trend # type: ignore
if self.has_space('sell'):
self.advise_sell = self.custom_hyperopt.sell_strategy_generator(params)
elif hasattr(self.custom_hyperopt, 'populate_sell_trend'):
self.advise_sell = self.custom_hyperopt.populate_sell_trend # type: ignore
if self.has_space('stoploss'):
self.strategy.stoploss = params['stoploss']
processed = load(TICKERDATA_PICKLE)
min_date, max_date = get_timeframe(processed)
results = self.backtest(
{
'stake_amount': self.config['stake_amount'],
'processed': processed,
'position_stacking': self.config.get('position_stacking', True),
'start_date': min_date,
'end_date': max_date,
}
)
result_explanation = self.format_results(results)
@@ -329,7 +239,8 @@ class Hyperopt(Backtesting):
)
def run_optimizer_parallel(self, parallel, asked) -> List:
return parallel(delayed(self.generate_optimizer)(v) for v in asked)
return parallel(delayed(
wrap_non_picklable_objects(self.generate_optimizer))(v) for v in asked)
def load_previous_results(self):
""" read trials file if we have one """
@@ -344,15 +255,16 @@ class Hyperopt(Backtesting):
timerange = Arguments.parse_timerange(None if self.config.get(
'timerange') is None else str(self.config.get('timerange')))
data = load_data(
datadir=str(self.config.get('datadir')),
datadir=Path(self.config['datadir']) if self.config.get('datadir') else None,
pairs=self.config['exchange']['pair_whitelist'],
ticker_interval=self.ticker_interval,
timerange=timerange
)
if self.has_space('buy'):
self.strategy.advise_indicators = Hyperopt.populate_indicators # type: ignore
dump(self.tickerdata_to_dataframe(data), TICKERDATA_PICKLE)
if self.has_space('buy') or self.has_space('sell'):
self.strategy.advise_indicators = \
self.custom_hyperopt.populate_indicators # type: ignore
dump(self.strategy.tickerdata_to_dataframe(data), TICKERDATA_PICKLE)
self.exchange = None # type: ignore
self.load_previous_results()
@@ -395,13 +307,20 @@ def start(args: Namespace) -> None:
# Initialize configuration
# Monkey patch the configuration with hyperopt_conf.py
configuration = Configuration(args)
configuration = Configuration(args, RunMode.HYPEROPT)
logger.info('Starting freqtrade in Hyperopt mode')
config = configuration.load_config()
config['exchange']['key'] = ''
config['exchange']['secret'] = ''
if config.get('strategy') and config.get('strategy') != 'DefaultStrategy':
logger.error("Please don't use --strategy for hyperopt.")
logger.error(
"Read the documentation at "
"https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md "
"to understand how to configure hyperopt.")
raise ValueError("--strategy configured but not supported for hyperopt")
# Initialize backtesting object
hyperopt = Hyperopt(config)
hyperopt.start()

View File

@@ -0,0 +1,80 @@
"""
IHyperOpt interface
This module defines the interface to apply for hyperopts
"""
from abc import ABC, abstractmethod
from typing import Dict, Any, Callable, List
from pandas import DataFrame
from skopt.space import Dimension
class IHyperOpt(ABC):
"""
Interface for freqtrade hyperopts
Defines the mandatory structure must follow any custom strategies
Attributes you can use:
minimal_roi -> Dict: Minimal ROI designed for the strategy
stoploss -> float: optimal stoploss designed for the strategy
ticker_interval -> int: value of the ticker interval to use for the strategy
"""
@staticmethod
@abstractmethod
def populate_indicators(dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Populate indicators that will be used in the Buy and Sell strategy
:param dataframe: Raw data from the exchange and parsed by parse_ticker_dataframe()
:return: a Dataframe with all mandatory indicators for the strategies
"""
@staticmethod
@abstractmethod
def buy_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Create a buy strategy generator
"""
@staticmethod
@abstractmethod
def sell_strategy_generator(params: Dict[str, Any]) -> Callable:
"""
Create a sell strategy generator
"""
@staticmethod
@abstractmethod
def indicator_space() -> List[Dimension]:
"""
Create an indicator space
"""
@staticmethod
@abstractmethod
def sell_indicator_space() -> List[Dimension]:
"""
Create a sell indicator space
"""
@staticmethod
@abstractmethod
def generate_roi_table(params: Dict) -> Dict[int, float]:
"""
Create an roi table
"""
@staticmethod
@abstractmethod
def stoploss_space() -> List[Dimension]:
"""
Create a stoploss space
"""
@staticmethod
@abstractmethod
def roi_space() -> List[Dimension]:
"""
Create a roi space
"""

View File

@@ -0,0 +1,91 @@
"""
Static List provider
Provides lists as configured in config.json
"""
import logging
from abc import ABC, abstractmethod
from typing import List
logger = logging.getLogger(__name__)
class IPairList(ABC):
def __init__(self, freqtrade, config: dict) -> None:
self._freqtrade = freqtrade
self._config = config
self._whitelist = self._config['exchange']['pair_whitelist']
self._blacklist = self._config['exchange'].get('pair_blacklist', [])
@property
def name(self) -> str:
"""
Gets name of the class
-> no need to overwrite in subclasses
"""
return self.__class__.__name__
@property
def whitelist(self) -> List[str]:
"""
Has the current whitelist
-> no need to overwrite in subclasses
"""
return self._whitelist
@property
def blacklist(self) -> List[str]:
"""
Has the current blacklist
-> no need to overwrite in subclasses
"""
return self._blacklist
@abstractmethod
def short_desc(self) -> str:
"""
Short whitelist method description - used for startup-messages
-> Please overwrite in subclasses
"""
@abstractmethod
def refresh_pairlist(self) -> None:
"""
Refreshes pairlists and assigns them to self._whitelist and self._blacklist respectively
-> Please overwrite in subclasses
"""
def _validate_whitelist(self, whitelist: List[str]) -> List[str]:
"""
Check available markets and remove pair from whitelist if necessary
:param whitelist: the sorted list (based on BaseVolume) of pairs the user might want to
trade
:return: the list of pairs the user wants to trade without the one unavailable or
black_listed
"""
sanitized_whitelist = whitelist
markets = self._freqtrade.exchange.get_markets()
# Filter to markets in stake currency
markets = [m for m in markets if m['quote'] == self._config['stake_currency']]
known_pairs = set()
for market in markets:
pair = market['symbol']
# pair is not int the generated dynamic market, or in the blacklist ... ignore it
if pair not in whitelist or pair in self.blacklist:
continue
# else the pair is valid
known_pairs.add(pair)
# Market is not active
if not market['active']:
sanitized_whitelist.remove(pair)
logger.info(
'Ignoring %s from whitelist. Market is not active.',
pair
)
# We need to remove pairs that are unknown
return [x for x in sanitized_whitelist if x in known_pairs]

View File

@@ -0,0 +1,30 @@
"""
Static List provider
Provides lists as configured in config.json
"""
import logging
from freqtrade.pairlist.IPairList import IPairList
logger = logging.getLogger(__name__)
class StaticPairList(IPairList):
def __init__(self, freqtrade, config: dict) -> None:
super().__init__(freqtrade, config)
def short_desc(self) -> str:
"""
Short whitelist method description - used for startup-messages
-> Please overwrite in subclasses
"""
return f"{self.name}: {self.whitelist}"
def refresh_pairlist(self) -> None:
"""
Refreshes pairlists and assigns them to self._whitelist and self._blacklist respectively
"""
self._whitelist = self._validate_whitelist(self._config['exchange']['pair_whitelist'])

View File

@@ -0,0 +1,75 @@
"""
Static List provider
Provides lists as configured in config.json
"""
import logging
from typing import List
from cachetools import TTLCache, cached
from freqtrade.pairlist.IPairList import IPairList
from freqtrade import OperationalException
logger = logging.getLogger(__name__)
SORT_VALUES = ['askVolume', 'bidVolume', 'quoteVolume']
class VolumePairList(IPairList):
def __init__(self, freqtrade, config: dict) -> None:
super().__init__(freqtrade, config)
self._whitelistconf = self._config.get('pairlist', {}).get('config')
if 'number_assets' not in self._whitelistconf:
raise OperationalException(
f'`number_assets` not specified. Please check your configuration '
'for "pairlist.config.number_assets"')
self._number_pairs = self._whitelistconf['number_assets']
self._sort_key = self._whitelistconf.get('sort_key', 'quoteVolume')
if not self._freqtrade.exchange.exchange_has('fetchTickers'):
raise OperationalException(
'Exchange does not support dynamic whitelist.'
'Please edit your config and restart the bot'
)
if not self._validate_keys(self._sort_key):
raise OperationalException(
f'key {self._sort_key} not in {SORT_VALUES}')
def _validate_keys(self, key):
return key in SORT_VALUES
def short_desc(self) -> str:
"""
Short whitelist method description - used for startup-messages
-> Please overwrite in subclasses
"""
return f"{self.name} - top {self._whitelistconf['number_assets']} volume pairs."
def refresh_pairlist(self) -> None:
"""
Refreshes pairlists and assigns them to self._whitelist and self._blacklist respectively
-> Please overwrite in subclasses
"""
# Generate dynamic whitelist
pairs = self._gen_pair_whitelist(self._config['stake_currency'], self._sort_key)
# Validate whitelist to only have active market pairs
self._whitelist = self._validate_whitelist(pairs)[:self._number_pairs]
@cached(TTLCache(maxsize=1, ttl=1800))
def _gen_pair_whitelist(self, base_currency: str, key: str) -> List[str]:
"""
Updates the whitelist with with a dynamically generated list
:param base_currency: base currency as str
:param key: sort key (defaults to 'quoteVolume')
:return: List of pairs
"""
tickers = self._freqtrade.exchange.get_tickers()
# check length so that we make sure that '/' is actually in the string
tickers = [v for k, v in tickers.items()
if len(k.split('/')) == 2 and k.split('/')[1] == base_currency]
sorted_tickers = sorted(tickers, reverse=True, key=lambda t: t[key])
pairs = [s['symbol'] for s in sorted_tickers]
return pairs

View File

View File

@@ -4,7 +4,7 @@ This module contains the class to persist trades into SQLite
import logging
from datetime import datetime
from decimal import Decimal, getcontext
from decimal import Decimal
from typing import Any, Dict, Optional
import arrow
@@ -14,6 +14,7 @@ from sqlalchemy.exc import NoSuchModuleError
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm.scoping import scoped_session
from sqlalchemy.orm.session import sessionmaker
from sqlalchemy import func
from sqlalchemy.pool import StaticPool
from freqtrade import OperationalException
@@ -79,16 +80,20 @@ 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'):
if not has_column(cols, 'stoploss_last_update'):
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')
close_rate_requested = get_column_def(cols, 'close_rate_requested', 'null')
stop_loss = get_column_def(cols, 'stop_loss', '0.0')
initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0')
stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null')
stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null')
max_rate = get_column_def(cols, 'max_rate', '0.0')
sell_reason = get_column_def(cols, 'sell_reason', 'null')
strategy = get_column_def(cols, 'strategy', 'null')
@@ -96,6 +101,9 @@ def check_migrate(engine) -> None:
# Schema migration necessary
engine.execute(f"alter table trades rename to {table_back_name}")
# drop indexes on backup table
for index in inspector.get_indexes(table_back_name):
engine.execute(f"drop index {index['name']}")
# let SQLAlchemy create the schema as required
_DECL_BASE.metadata.create_all(engine)
@@ -104,7 +112,8 @@ def check_migrate(engine) -> None:
(id, exchange, pair, is_open, fee_open, fee_close, open_rate,
open_rate_requested, close_rate, close_rate_requested, close_profit,
stake_amount, amount, open_date, close_date, open_order_id,
stop_loss, initial_stop_loss, max_rate, sell_reason, strategy,
stop_loss, initial_stop_loss, stoploss_order_id, stoploss_last_update,
max_rate, sell_reason, strategy,
ticker_interval
)
select id, lower(exchange),
@@ -120,8 +129,9 @@ def check_migrate(engine) -> None:
{close_rate_requested} close_rate_requested, close_profit,
stake_amount, amount, open_date, close_date, open_order_id,
{stop_loss} stop_loss, {initial_stop_loss} initial_stop_loss,
{max_rate} max_rate, {sell_reason} sell_reason, {strategy} strategy,
{ticker_interval} ticker_interval
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
{max_rate} max_rate, {sell_reason} sell_reason,
{strategy} strategy, {ticker_interval} ticker_interval
from {table_back_name}
""")
@@ -175,6 +185,10 @@ class Trade(_DECL_BASE):
stop_loss = Column(Float, nullable=True, default=0.0)
# absolute value of the initial stop loss
initial_stop_loss = Column(Float, nullable=True, default=0.0)
# stoploss order id which is on exchange
stoploss_order_id = Column(String, nullable=True, index=True)
# last update time of the stoploss order on exchange
stoploss_last_update = Column(DateTime, nullable=True)
# absolute value of the highest reached price
max_rate = Column(Float, nullable=True, default=0.0)
sell_reason = Column(String, nullable=True)
@@ -208,11 +222,13 @@ class Trade(_DECL_BASE):
logger.debug("assigning new stop loss")
self.stop_loss = new_loss
self.initial_stop_loss = new_loss
self.stoploss_last_update = datetime.utcnow()
# evaluate if the stop loss needs to be updated
else:
if new_loss > self.stop_loss: # stop losses only walk up, never down!
self.stop_loss = new_loss
self.stoploss_last_update = datetime.utcnow()
logger.debug("adjusted stop loss")
else:
logger.debug("keeping current stop loss")
@@ -237,17 +253,21 @@ class Trade(_DECL_BASE):
if order['status'] == 'open' or order['price'] is None:
return
logger.info('Updating trade (id=%d) ...', self.id)
logger.info('Updating trade (id=%s) ...', self.id)
getcontext().prec = 8 # Bittrex do not go above 8 decimal
if order_type == 'limit' and order['side'] == 'buy':
if order_type in ('market', 'limit') and order['side'] == 'buy':
# Update open rate and actual amount
self.open_rate = Decimal(order['price'])
self.amount = Decimal(order['amount'])
logger.info('LIMIT_BUY has been fulfilled for %s.', self)
logger.info('%s_BUY has been fulfilled for %s.', order_type.upper(), self)
self.open_order_id = None
elif order_type == 'limit' and order['side'] == 'sell':
elif order_type in ('market', 'limit') and order['side'] == 'sell':
self.close(order['price'])
logger.info('%s_SELL has been fulfilled for %s.', order_type.upper(), self)
elif order_type == 'stop_loss_limit':
self.stoploss_order_id = None
logger.info('STOP_LOSS_LIMIT is hit for %s.', self)
self.close(order['average'])
else:
raise ValueError(f'Unknown order type: {order_type}')
cleanup()
@@ -271,12 +291,11 @@ class Trade(_DECL_BASE):
self,
fee: Optional[float] = None) -> float:
"""
Calculate the open_rate in BTC
Calculate the open_rate including fee.
:param fee: fee to use on the open rate (optional).
If rate is not set self.fee will be used
:return: Price in BTC of the open trade
:return: Price in of the open trade incl. Fees
"""
getcontext().prec = 8
buy_trade = (Decimal(self.amount) * Decimal(self.open_rate))
fees = buy_trade * Decimal(fee or self.fee_open)
@@ -287,14 +306,13 @@ class Trade(_DECL_BASE):
rate: Optional[float] = None,
fee: Optional[float] = None) -> float:
"""
Calculate the close_rate in BTC
Calculate the close_rate including fee
:param fee: fee to use on the close rate (optional).
If rate is not set self.fee will be used
:param rate: rate to compare with (optional).
If rate is not set self.close_rate will be used
:return: Price in BTC of the open trade
"""
getcontext().prec = 8
if rate is None and not self.close_rate:
return 0.0
@@ -308,12 +326,12 @@ class Trade(_DECL_BASE):
rate: Optional[float] = None,
fee: Optional[float] = None) -> float:
"""
Calculate the profit in BTC between Close and Open trade
Calculate the absolute profit in stake currency between Close and Open trade
:param fee: fee to use on the close rate (optional).
If rate is not set self.fee will be used
:param rate: close rate to compare with (optional).
If rate is not set self.close_rate will be used
:return: profit in BTC as float
:return: profit in stake currency as float
"""
open_trade_price = self.calc_open_trade_price()
close_trade_price = self.calc_close_trade_price(
@@ -334,7 +352,6 @@ class Trade(_DECL_BASE):
:param fee: fee to use on the close rate (optional).
:return: profit in percentage as float
"""
getcontext().prec = 8
open_trade_price = self.calc_open_trade_price()
close_trade_price = self.calc_close_trade_price(
@@ -343,3 +360,14 @@ class Trade(_DECL_BASE):
)
profit_percent = (close_trade_price / open_trade_price) - 1
return float(f"{profit_percent:.8f}")
@staticmethod
def total_open_trades_stakes() -> float:
"""
Calculates total invested amount in open trades
in stake currency
"""
total_open_stake_amount = Trade.session.query(func.sum(Trade.stake_amount))\
.filter(Trade.is_open.is_(True))\
.scalar()
return total_open_stake_amount or 0

View File

@@ -0,0 +1,4 @@
from freqtrade.resolvers.iresolver import IResolver # noqa: F401
from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver # noqa: F401
from freqtrade.resolvers.pairlist_resolver import PairListResolver # noqa: F401
from freqtrade.resolvers.strategy_resolver import StrategyResolver # noqa: F401

View File

@@ -0,0 +1,74 @@
# pragma pylint: disable=attribute-defined-outside-init
"""
This module load custom hyperopts
"""
import logging
from pathlib import Path
from typing import Optional, Dict
from freqtrade.constants import DEFAULT_HYPEROPT
from freqtrade.optimize.hyperopt_interface import IHyperOpt
from freqtrade.resolvers import IResolver
logger = logging.getLogger(__name__)
class HyperOptResolver(IResolver):
"""
This class contains all the logic to load custom hyperopt class
"""
__slots__ = ['hyperopt']
def __init__(self, config: Optional[Dict] = None) -> None:
"""
Load the custom class from config parameter
:param config: configuration dictionary or None
"""
config = config or {}
# Verify the hyperopt is in the configuration, otherwise fallback to the default hyperopt
hyperopt_name = config.get('hyperopt') or DEFAULT_HYPEROPT
self.hyperopt = self._load_hyperopt(hyperopt_name, extra_dir=config.get('hyperopt_path'))
if not hasattr(self.hyperopt, 'populate_buy_trend'):
logger.warning("Custom Hyperopt does not provide populate_buy_trend. "
"Using populate_buy_trend from DefaultStrategy.")
if not hasattr(self.hyperopt, 'populate_sell_trend'):
logger.warning("Custom Hyperopt does not provide populate_sell_trend. "
"Using populate_sell_trend from DefaultStrategy.")
def _load_hyperopt(
self, hyperopt_name: str, extra_dir: Optional[str] = None) -> IHyperOpt:
"""
Search and loads the specified hyperopt.
:param hyperopt_name: name of the module to import
:param extra_dir: additional directory to search for the given hyperopt
:return: HyperOpt instance or None
"""
current_path = Path(__file__).parent.parent.joinpath('optimize').resolve()
abs_paths = [
current_path.parent.parent.joinpath('user_data/hyperopts'),
current_path,
]
if extra_dir:
# Add extra hyperopt directory on top of search paths
abs_paths.insert(0, Path(extra_dir))
for _path in abs_paths:
try:
hyperopt = self._search_object(directory=_path, object_type=IHyperOpt,
object_name=hyperopt_name)
if hyperopt:
logger.info('Using resolved hyperopt %s from \'%s\'', hyperopt_name, _path)
return hyperopt
except FileNotFoundError:
logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd()))
raise ImportError(
"Impossible to load Hyperopt '{}'. This class does not exist"
" or contains Python code errors".format(hyperopt_name)
)

View File

@@ -0,0 +1,61 @@
# pragma pylint: disable=attribute-defined-outside-init
"""
This module load custom objects
"""
import importlib.util
import inspect
import logging
from pathlib import Path
from typing import Optional, Type, Any
logger = logging.getLogger(__name__)
class IResolver(object):
"""
This class contains all the logic to load custom classes
"""
@staticmethod
def _get_valid_object(object_type, module_path: Path,
object_name: str) -> Optional[Type[Any]]:
"""
Returns the first object with matching object_type and object_name in the path given.
:param object_type: object_type (class)
:param module_path: absolute path to the module
:param object_name: Class name of the object
:return: class or None
"""
# Generate spec based on absolute path
spec = importlib.util.spec_from_file_location('unknown', str(module_path))
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) # type: ignore # importlib does not use typehints
valid_objects_gen = (
obj for name, obj in inspect.getmembers(module, inspect.isclass)
if object_name == name and object_type in obj.__bases__
)
return next(valid_objects_gen, None)
@staticmethod
def _search_object(directory: Path, object_type, object_name: str,
kwargs: dict = {}) -> Optional[Any]:
"""
Search for the objectname in the given directory
:param directory: relative or absolute directory path
:return: object instance
"""
logger.debug('Searching for %s %s in \'%s\'', object_type.__name__, object_name, directory)
for entry in directory.iterdir():
# Only consider python files
if not str(entry).endswith('.py'):
logger.debug('Ignoring %s', entry)
continue
obj = IResolver._get_valid_object(
object_type, Path.resolve(directory.joinpath(entry)), object_name
)
if obj:
return obj(**kwargs)
return None

View File

@@ -0,0 +1,59 @@
# pragma pylint: disable=attribute-defined-outside-init
"""
This module load custom hyperopts
"""
import logging
from pathlib import Path
from freqtrade.pairlist.IPairList import IPairList
from freqtrade.resolvers import IResolver
logger = logging.getLogger(__name__)
class PairListResolver(IResolver):
"""
This class contains all the logic to load custom hyperopt class
"""
__slots__ = ['pairlist']
def __init__(self, pairlist_name: str, freqtrade, config: dict) -> None:
"""
Load the custom class from config parameter
:param config: configuration dictionary or None
"""
self.pairlist = self._load_pairlist(pairlist_name, kwargs={'freqtrade': freqtrade,
'config': config})
def _load_pairlist(
self, pairlist_name: str, kwargs: dict) -> IPairList:
"""
Search and loads the specified pairlist.
:param pairlist_name: name of the module to import
:param extra_dir: additional directory to search for the given pairlist
:return: PairList instance or None
"""
current_path = Path(__file__).parent.parent.joinpath('pairlist').resolve()
abs_paths = [
current_path.parent.parent.joinpath('user_data/pairlist'),
current_path,
]
for _path in abs_paths:
try:
pairlist = self._search_object(directory=_path, object_type=IPairList,
object_name=pairlist_name,
kwargs=kwargs)
if pairlist:
logger.info('Using resolved pairlist %s from \'%s\'', pairlist_name, _path)
return pairlist
except FileNotFoundError:
logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd()))
raise ImportError(
"Impossible to load Pairlist '{}'. This class does not exist"
" or contains Python code errors".format(pairlist_name)
)

View File

@@ -0,0 +1,165 @@
# pragma pylint: disable=attribute-defined-outside-init
"""
This module load custom strategies
"""
import logging
import tempfile
from base64 import urlsafe_b64decode
from collections import OrderedDict
from inspect import getfullargspec
from pathlib import Path
from typing import Dict, Optional
from freqtrade import constants
from freqtrade.resolvers import IResolver
from freqtrade.strategy import import_strategy
from freqtrade.strategy.interface import IStrategy
logger = logging.getLogger(__name__)
class StrategyResolver(IResolver):
"""
This class contains all the logic to load custom strategy class
"""
__slots__ = ['strategy']
def __init__(self, config: Optional[Dict] = None) -> None:
"""
Load the custom class from config parameter
:param config: configuration dictionary or None
"""
config = config or {}
# Verify the strategy is in the configuration, otherwise fallback to the default strategy
strategy_name = config.get('strategy') or constants.DEFAULT_STRATEGY
self.strategy: IStrategy = self._load_strategy(strategy_name,
config=config,
extra_dir=config.get('strategy_path'))
# make sure experimental dict is available
if 'experimental' not in config:
config['experimental'] = {}
# Set attributes
# Check if we need to override configuration
# (Attribute name, default, experimental)
attributes = [("minimal_roi", None, False),
("ticker_interval", None, False),
("stoploss", None, False),
("trailing_stop", None, False),
("trailing_stop_positive", None, False),
("trailing_stop_positive_offset", 0.0, False),
("process_only_new_candles", None, False),
("order_types", None, False),
("order_time_in_force", None, False),
("use_sell_signal", False, True),
("sell_profit_only", False, True),
("ignore_roi_if_buy_signal", False, True),
]
for attribute, default, experimental in attributes:
if experimental:
self._override_attribute_helper(config['experimental'], attribute, default)
else:
self._override_attribute_helper(config, attribute, default)
# Loop this list again to have output combined
for attribute, _, exp in attributes:
if exp and attribute in config['experimental']:
logger.info("Strategy using %s: %s", attribute, config['experimental'][attribute])
elif attribute in config:
logger.info("Strategy using %s: %s", attribute, config[attribute])
# Sort and apply type conversions
self.strategy.minimal_roi = OrderedDict(sorted(
{int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(),
key=lambda t: t[0]))
self.strategy.stoploss = float(self.strategy.stoploss)
self._strategy_sanity_validations()
def _override_attribute_helper(self, config, attribute: str, default):
"""
Override attributes in the strategy.
Prevalence:
- Configuration
- Strategy
- default (if not None)
"""
if attribute in config:
setattr(self.strategy, attribute, config[attribute])
logger.info("Override strategy '%s' with value in config file: %s.",
attribute, config[attribute])
elif hasattr(self.strategy, attribute):
config[attribute] = getattr(self.strategy, attribute)
# Explicitly check for None here as other "falsy" values are possible
elif default is not None:
setattr(self.strategy, attribute, default)
config[attribute] = default
def _strategy_sanity_validations(self):
if not all(k in self.strategy.order_types for k in constants.REQUIRED_ORDERTYPES):
raise ImportError(f"Impossible to load Strategy '{self.strategy.__class__.__name__}'. "
f"Order-types mapping is incomplete.")
if not all(k in self.strategy.order_time_in_force for k in constants.REQUIRED_ORDERTIF):
raise ImportError(f"Impossible to load Strategy '{self.strategy.__class__.__name__}'. "
f"Order-time-in-force mapping is incomplete.")
def _load_strategy(
self, strategy_name: str, config: dict, extra_dir: Optional[str] = None) -> IStrategy:
"""
Search and loads the specified strategy.
:param strategy_name: name of the module to import
:param config: configuration for the strategy
:param extra_dir: additional directory to search for the given strategy
:return: Strategy instance or None
"""
current_path = Path(__file__).parent.parent.joinpath('strategy').resolve()
abs_paths = [
Path.cwd().joinpath('user_data/strategies'),
current_path,
]
if extra_dir:
# Add extra strategy directory on top of search paths
abs_paths.insert(0, Path(extra_dir).resolve())
if ":" in strategy_name:
logger.info("loading base64 endocded strategy")
strat = strategy_name.split(":")
if len(strat) == 2:
temp = Path(tempfile.mkdtemp("freq", "strategy"))
name = strat[0] + ".py"
temp.joinpath(name).write_text(urlsafe_b64decode(strat[1]).decode('utf-8'))
temp.joinpath("__init__.py").touch()
strategy_name = strat[0]
# register temp path with the bot
abs_paths.insert(0, temp.resolve())
for _path in abs_paths:
try:
strategy = self._search_object(directory=_path, object_type=IStrategy,
object_name=strategy_name, kwargs={'config': config})
if strategy:
logger.info('Using resolved strategy %s from \'%s\'', strategy_name, _path)
strategy._populate_fun_len = len(
getfullargspec(strategy.populate_indicators).args)
strategy._buy_fun_len = len(getfullargspec(strategy.populate_buy_trend).args)
strategy._sell_fun_len = len(getfullargspec(strategy.populate_sell_trend).args)
return import_strategy(strategy, config=config)
except FileNotFoundError:
logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd()))
raise ImportError(
"Impossible to load Strategy '{}'. This class does not exist"
" or contains Python code errors".format(strategy_name)
)

View File

@@ -10,12 +10,13 @@ from typing import Dict, Any, List, Optional
import arrow
import sqlalchemy as sql
from numpy import mean, nan_to_num
from numpy import mean, nan_to_num, NAN
from pandas import DataFrame
from freqtrade.fiat_convert import CryptoToFiatConverter
from freqtrade import TemporaryError, DependencyException
from freqtrade.misc import shorten_date
from freqtrade.persistence import Trade
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
from freqtrade.state import State
from freqtrade.strategy.interface import SellType
@@ -83,9 +84,7 @@ class RPC(object):
"""
# Fetch open trade
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
if self._freqtrade.state != State.RUNNING:
raise RPCException('trader is not running')
elif not trades:
if not trades:
raise RPCException('no active trade')
else:
results = []
@@ -94,7 +93,10 @@ class RPC(object):
if trade.open_order_id:
order = self._freqtrade.exchange.get_order(trade.open_order_id, trade.pair)
# calculate profit and send message to user
current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid']
try:
current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid']
except DependencyException:
current_rate = NAN
current_profit = trade.calc_profit_percent(current_rate)
fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%'
if trade.close_profit else None)
@@ -117,15 +119,16 @@ class RPC(object):
def _rpc_status_table(self) -> DataFrame:
trades = Trade.query.filter(Trade.is_open.is_(True)).all()
if self._freqtrade.state != State.RUNNING:
raise RPCException('trader is not running')
elif not trades:
if not trades:
raise RPCException('no active order')
else:
trades_list = []
for trade in trades:
# calculate profit and send message to user
current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid']
try:
current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid']
except DependencyException:
current_rate = NAN
trade_perc = (100 * trade.calc_profit_percent(current_rate))
trades_list.append([
trade.id,
@@ -210,7 +213,10 @@ class RPC(object):
profit_closed_percent.append(profit_percent)
else:
# Get current rate
current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid']
try:
current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid']
except DependencyException:
current_rate = NAN
profit_percent = trade.calc_profit_percent(rate=current_rate)
profit_all_coin.append(
@@ -273,10 +279,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, DependencyException):
continue
est_btc: float = rate * balance['total']
total = total + est_btc
output.append({
@@ -359,6 +368,7 @@ class RPC(object):
# Execute sell for all open orders
for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
_exec_forcesell(trade)
Trade.session.flush()
return
# Query for trade
@@ -375,13 +385,45 @@ class RPC(object):
_exec_forcesell(trade)
Trade.session.flush()
def _rpc_forcebuy(self, pair: str, price: Optional[float]) -> Optional[Trade]:
"""
Handler for forcebuy <asset> <price>
Buys a pair trade at the given or current price
"""
if not self._freqtrade.config.get('forcebuy_enable', False):
raise RPCException('Forcebuy not enabled.')
if self._freqtrade.state != State.RUNNING:
raise RPCException('trader is not running')
# Check pair is in stake currency
stake_currency = self._freqtrade.config.get('stake_currency')
if not pair.endswith(stake_currency):
raise RPCException(
f'Wrong pair selected. Please pairs with stake {stake_currency} pairs only')
# check if valid pair
# check if pair already has an open pair
trade = Trade.query.filter(Trade.is_open.is_(True)).filter(Trade.pair.is_(pair)).first()
if trade:
raise RPCException(f'position for {pair} already open - id: {trade.id}')
# gen stake amount
stakeamount = self._freqtrade._get_trade_stake_amount(pair)
# execute buy
if self._freqtrade.execute_buy(pair, stakeamount, price):
trade = Trade.query.filter(Trade.is_open.is_(True)).filter(Trade.pair.is_(pair)).first()
return trade
else:
return None
def _rpc_performance(self) -> List[Dict]:
"""
Handler for performance.
Shows a performance statistic from finished trades
"""
if self._freqtrade.state != State.RUNNING:
raise RPCException('trader is not running')
pair_rates = Trade.session.query(Trade.pair,
sql.func.sum(Trade.close_profit).label('profit_sum'),
@@ -401,3 +443,11 @@ class RPC(object):
raise RPCException('trader is not running')
return Trade.query.filter(Trade.is_open.is_(True)).all()
def _rpc_whitelist(self) -> Dict:
""" Returns the currently active whitelist"""
res = {'method': self._freqtrade.pairlists.name,
'length': len(self._freqtrade.pairlists.whitelist),
'whitelist': self._freqtrade.active_pair_whitelist
}
return res

View File

@@ -4,7 +4,7 @@ This module contains class to manage RPC communications (Telegram, Slack, ...)
import logging
from typing import List, Dict, Any
from freqtrade.rpc import RPC
from freqtrade.rpc import RPC, RPCMessageType
logger = logging.getLogger(__name__)
@@ -51,3 +51,29 @@ class RPCManager(object):
for mod in self.registered_modules:
logger.debug('Forwarding message to rpc.%s', mod.name)
mod.send_msg(msg)
def startup_messages(self, config, pairlist) -> None:
if config.get('dry_run', False):
self.send_msg({
'type': RPCMessageType.WARNING_NOTIFICATION,
'status': 'Dry run is enabled. All trades are simulated.'
})
stake_currency = config['stake_currency']
stake_amount = config['stake_amount']
minimal_roi = config['minimal_roi']
ticker_interval = config['ticker_interval']
exchange_name = config['exchange']['name']
strategy_name = config.get('strategy', '')
self.send_msg({
'type': RPCMessageType.CUSTOM_NOTIFICATION,
'status': f'*Exchange:* `{exchange_name}`\n'
f'*Stake per trade:* `{stake_amount} {stake_currency}`\n'
f'*Minimum ROI:* `{minimal_roi}`\n'
f'*Ticker Interval:* `{ticker_interval}`\n'
f'*Strategy:* `{strategy_name}`'
})
self.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Searching for {stake_currency} pairs to buy and sell '
f'based on {pairlist.short_desc()}'
})

View File

@@ -12,8 +12,8 @@ from telegram.error import NetworkError, TelegramError
from telegram.ext import CommandHandler, Updater
from freqtrade.__init__ import __version__
from freqtrade.fiat_convert import CryptoToFiatConverter
from freqtrade.rpc import RPC, RPCException, RPCMessageType
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
logger = logging.getLogger(__name__)
@@ -86,10 +86,12 @@ class Telegram(RPC):
CommandHandler('start', self._start),
CommandHandler('stop', self._stop),
CommandHandler('forcesell', self._forcesell),
CommandHandler('forcebuy', self._forcebuy),
CommandHandler('performance', self._performance),
CommandHandler('daily', self._daily),
CommandHandler('count', self._count),
CommandHandler('reload_conf', self._reload_conf),
CommandHandler('whitelist', self._whitelist),
CommandHandler('help', self._help),
CommandHandler('version', self._version),
]
@@ -123,9 +125,9 @@ class Telegram(RPC):
else:
msg['stake_amount_fiat'] = 0
message = "*{exchange}:* Buying [{pair}]({market_url})\n" \
"with limit `{limit:.8f}\n" \
"({stake_amount:.6f} {stake_currency}".format(**msg)
message = ("*{exchange}:* Buying [{pair}]({market_url})\n"
"with limit `{limit:.8f}\n"
"({stake_amount:.6f} {stake_currency}").format(**msg)
if msg.get('fiat_currency', None):
message += ",{stake_amount_fiat:.3f} {fiat_currency}".format(**msg)
@@ -135,12 +137,13 @@ class Telegram(RPC):
msg['amount'] = round(msg['amount'], 8)
msg['profit_percent'] = round(msg['profit_percent'] * 100, 2)
message = "*{exchange}:* Selling [{pair}]({market_url})\n" \
"*Limit:* `{limit:.8f}`\n" \
"*Amount:* `{amount:.8f}`\n" \
"*Open Rate:* `{open_rate:.8f}`\n" \
"*Current Rate:* `{current_rate:.8f}`\n" \
"*Profit:* `{profit_percent:.2f}%`".format(**msg)
message = ("*{exchange}:* Selling [{pair}]({market_url})\n"
"*Limit:* `{limit:.8f}`\n"
"*Amount:* `{amount:.8f}`\n"
"*Open Rate:* `{open_rate:.8f}`\n"
"*Current Rate:* `{current_rate:.8f}`\n"
"*Sell Reason:* `{sell_reason}`\n"
"*Profit:* `{profit_percent:.2f}%`").format(**msg)
# Check if all sell properties are available.
# This might not be the case if the message origin is triggered by /forcesell
@@ -148,8 +151,8 @@ class Telegram(RPC):
and self._fiat_converter):
msg['profit_fiat'] = self._fiat_converter.convert_amount(
msg['profit_amount'], msg['stake_currency'], msg['fiat_currency'])
message += '` ({gain}: {profit_amount:.8f} {stake_currency}`' \
'` / {profit_fiat:.3f} {fiat_currency})`'.format(**msg)
message += ('` ({gain}: {profit_amount:.8f} {stake_currency}`'
'` / {profit_fiat:.3f} {fiat_currency})`').format(**msg)
elif msg['type'] == RPCMessageType.STATUS_NOTIFICATION:
message = '*Status:* `{status}`'.format(**msg)
@@ -243,14 +246,14 @@ class Telegram(RPC):
stake_cur,
fiat_disp_cur
)
stats = tabulate(stats,
headers=[
'Day',
f'Profit {stake_cur}',
f'Profit {fiat_disp_cur}'
],
tablefmt='simple')
message = f'<b>Daily Profit over the last {timescale} days</b>:\n<pre>{stats}</pre>'
stats_tab = tabulate(stats,
headers=[
'Day',
f'Profit {stake_cur}',
f'Profit {fiat_disp_cur}'
],
tablefmt='simple')
message = f'<b>Daily Profit over the last {timescale} days</b>:\n<pre>{stats_tab}</pre>'
self._send_msg(message, bot=bot, parse_mode=ParseMode.HTML)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@@ -307,11 +310,14 @@ class Telegram(RPC):
result = self._rpc_balance(self._config.get('fiat_display_currency', ''))
output = ''
for currency in result['currencies']:
output += "*{currency}:*\n" \
"\t`Available: {available: .8f}`\n" \
"\t`Balance: {balance: .8f}`\n" \
"\t`Pending: {pending: .8f}`\n" \
"\t`Est. BTC: {est_btc: .8f}`\n".format(**currency)
if currency['est_btc'] > 0.0001:
output += "*{currency}:*\n" \
"\t`Available: {available: .8f}`\n" \
"\t`Balance: {balance: .8f}`\n" \
"\t`Pending: {pending: .8f}`\n" \
"\t`Est. BTC: {est_btc: .8f}`\n".format(**currency)
else:
output += "*{currency}:* not showing <1$ amount \n".format(**currency)
output += "\n*Estimated Value*:\n" \
"\t`BTC: {total: .8f}`\n" \
@@ -372,6 +378,24 @@ class Telegram(RPC):
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only
def _forcebuy(self, bot: Bot, update: Update) -> None:
"""
Handler for /forcebuy <asset> <price>.
Buys a pair trade at the given or current price
:param bot: telegram bot
:param update: message update
:return: None
"""
message = update.message.text.replace('/forcebuy', '').strip().split()
pair = message[0]
price = float(message[1]) if len(message) > 1 else None
try:
self._rpc_forcebuy(pair, price)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only
def _performance(self, bot: Bot, update: Update) -> None:
"""
@@ -416,6 +440,23 @@ class Telegram(RPC):
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only
def _whitelist(self, bot: Bot, update: Update) -> None:
"""
Handler for /whitelist
Shows the currently active whitelist
"""
try:
whitelist = self._rpc_whitelist()
message = f"Using whitelist `{whitelist['method']}` with {whitelist['length']} pairs\n"
message += f"`{', '.join(whitelist['whitelist'])}`"
logger.debug(message)
self._send_msg(message)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only
def _help(self, bot: Bot, update: Update) -> None:
"""
@@ -437,6 +478,8 @@ class Telegram(RPC):
"*/count:* `Show number of trades running compared to allowed number of trades`" \
"\n" \
"*/balance:* `Show account balance per currency`\n" \
"*/reload_conf:* `Reload configuration file` \n" \
"*/whitelist:* `Show current whitelist` \n" \
"*/help:* `This help message`\n" \
"*/version:* `Show version`"

View File

@@ -3,13 +3,26 @@
"""
Bot state constant
"""
import enum
from enum import Enum
class State(enum.Enum):
class State(Enum):
"""
Bot application states
"""
RUNNING = 0
STOPPED = 1
RELOAD_CONF = 2
RUNNING = 1
STOPPED = 2
RELOAD_CONF = 3
class RunMode(Enum):
"""
Bot running mode (backtest, hyperopt, ...)
can be "live", "dry-run", "backtest", "edgecli", "hyperopt".
"""
LIVE = "live"
DRY_RUN = "dry_run"
BACKTEST = "backtest"
EDGECLI = "edgecli"
HYPEROPT = "hyperopt"
OTHER = "other" # Used for plotting scripts and test

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

@@ -16,10 +16,10 @@ class DefaultStrategy(IStrategy):
# Minimal ROI designed for the strategy
minimal_roi = {
"40": 0.0,
"30": 0.01,
"20": 0.02,
"0": 0.04
"40": 0.0,
"30": 0.01,
"20": 0.02,
"0": 0.04
}
# Optimal stoploss designed for the strategy
@@ -28,6 +28,33 @@ class DefaultStrategy(IStrategy):
# Optimal ticker interval for the strategy
ticker_interval = '5m'
# Optional order type mapping
order_types = {
'buy': 'limit',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False
}
# Optional time in force for orders
order_time_in_force = {
'buy': 'gtc',
'sell': 'gtc',
}
def informative_pairs(self):
"""
Define additional, informative pair/interval combinations to be cached from the exchange.
These pair/interval combinations are non-tradeable, unless they are part
of the whitelist as well.
For more information, please consult the documentation
:return: List of tuples in the format (pair, interval)
Sample: return [("ETH/USDT", "5m"),
("BTC/USDT", "15m"),
]
"""
return []
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Adds several different TA indicators to the given DataFrame

View File

@@ -13,8 +13,9 @@ import arrow
from pandas import DataFrame
from freqtrade import constants
from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe
from freqtrade.data.dataprovider import DataProvider
from freqtrade.persistence import Trade
from freqtrade.wallets import Wallets
logger = logging.getLogger(__name__)
@@ -33,6 +34,7 @@ class SellType(Enum):
"""
ROI = "roi"
STOP_LOSS = "stop_loss"
STOPLOSS_ON_EXCHANGE = "stoploss_on_exchange"
TRAILING_STOP_LOSS = "trailing_stop_loss"
SELL_SIGNAL = "sell_signal"
FORCE_SELL = "force_sell"
@@ -67,11 +69,42 @@ class IStrategy(ABC):
# associated stoploss
stoploss: float
# trailing stoploss
trailing_stop: bool = False
trailing_stop_positive: float
trailing_stop_positive_offset: float
# associated ticker interval
ticker_interval: str
# Optional order types
order_types: Dict = {
'buy': 'limit',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False,
'stoploss_on_exchange_interval': 60,
}
# Optional time in force
order_time_in_force: Dict = {
'buy': 'gtc',
'sell': 'gtc',
}
# run "populate_indicators" only for new candle
process_only_new_candles: bool = False
# Class level variables (intentional) containing
# the dataprovider (dp) (access to other candles, historic data, ...)
# and wallets - access to the current balance.
dp: DataProvider
wallets: Wallets
def __init__(self, config: dict) -> None:
self.config = config
# Dict to determine if analysis is necessary
self._last_candle_seen_per_pair: Dict[str, datetime] = {}
@abstractmethod
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
@@ -100,37 +133,70 @@ class IStrategy(ABC):
:return: DataFrame with sell column
"""
def informative_pairs(self) -> List[Tuple[str, str]]:
"""
Define additional, informative pair/interval combinations to be cached from the exchange.
These pair/interval combinations are non-tradeable, unless they are part
of the whitelist as well.
For more information, please consult the documentation
:return: List of tuples in the format (pair, interval)
Sample: return [("ETH/USDT", "5m"),
("BTC/USDT", "15m"),
]
"""
return []
def get_strategy_name(self) -> str:
"""
Returns strategy class name
"""
return self.__class__.__name__
def analyze_ticker(self, ticker_history: List[Dict], metadata: dict) -> DataFrame:
def analyze_ticker(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
"""
Parses the given ticker history and returns a populated DataFrame
add several TA indicators and buy signal to it
:return DataFrame with ticker data and indicator data
"""
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 false
if (not self.process_only_new_candles or
self._last_candle_seen_per_pair.get(pair, None) != dataframe.iloc[-1]['date']):
# Defs that only make change on new candle data.
logger.debug("TA Analysis Launched")
dataframe = self.advise_indicators(dataframe, metadata)
dataframe = self.advise_buy(dataframe, metadata)
dataframe = self.advise_sell(dataframe, metadata)
self._last_candle_seen_per_pair[pair] = dataframe.iloc[-1]['date']
else:
logger.debug("Skipping TA Analysis for already analyzed candle")
dataframe['buy'] = 0
dataframe['sell'] = 0
# Other Defs in strategy that want to be called every loop here
# twitter_sell = self.watch_twitter_feed(dataframe, metadata)
logger.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,
dataframe: DataFrame) -> Tuple[bool, bool]:
"""
Calculates current signal based several technical analysis indicators
:param pair: pair in format ANT/BTC
:param interval: Interval to use (in min)
:param dataframe: Dataframe to analyze
:return: (Buy, Sell) A bool-tuple indicating buy/sell signal
"""
if not ticker_hist:
if not isinstance(dataframe, DataFrame) or dataframe.empty:
logger.warning('Empty ticker history for pair %s', pair)
return False, False
try:
dataframe = self.analyze_ticker(ticker_hist, {'pair': pair})
dataframe = self.analyze_ticker(dataframe, {'pair': pair})
except ValueError as error:
logger.warning(
'Unable to analyze ticker for pair %s: %s',
@@ -155,7 +221,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,
@@ -174,18 +241,28 @@ class IStrategy(ABC):
return buy, sell
def should_sell(self, trade: Trade, rate: float, date: datetime, buy: bool,
sell: bool) -> SellCheckTuple:
sell: bool, low: float = None, high: float = None,
force_stoploss: float = 0) -> SellCheckTuple:
"""
This function evaluate if on the condition required to trigger a sell has been reached
if the threshold is reached and updates the trade record.
:return: True if trade should be sold, False otherwise
"""
current_profit = trade.calc_profit_percent(rate)
stoplossflag = self.stop_loss_reached(current_rate=rate, trade=trade, current_time=date,
current_profit=current_profit)
# Set current rate to low for backtesting sell
current_rate = low or rate
current_profit = trade.calc_profit_percent(current_rate)
stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade,
current_time=date, current_profit=current_profit,
force_stoploss=force_stoploss)
if stoplossflag.sell_flag:
return stoplossflag
# Set current rate to low for backtesting sell
current_rate = high or rate
current_profit = trade.calc_profit_percent(current_rate)
experimental = self.config.get('experimental', {})
if buy and experimental.get('ignore_roi_if_buy_signal', False):
@@ -208,7 +285,7 @@ class IStrategy(ABC):
return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime,
current_profit: float) -> SellCheckTuple:
current_profit: float, force_stoploss: float) -> SellCheckTuple:
"""
Based on current profit of the trade and configured (trailing) stoploss,
decides to sell or not
@@ -216,13 +293,16 @@ class IStrategy(ABC):
"""
trailing_stop = self.config.get('trailing_stop', False)
trade.adjust_stop_loss(trade.open_rate, force_stoploss if force_stoploss
else self.stoploss, initial=True)
trade.adjust_stop_loss(trade.open_rate, self.stoploss, initial=True)
# evaluate if the stoploss was hit
if self.stoploss is not None and trade.stop_loss >= current_rate:
# evaluate if the stoploss was hit if stoploss is not on exchange
if ((self.stoploss is not None) and
(trade.stop_loss >= current_rate) and
(not self.order_types.get('stoploss_on_exchange'))):
selltype = SellType.STOP_LOSS
if trailing_stop:
# If Trailing stop (and max-rate did move above open rate)
if trailing_stop and trade.open_rate != trade.max_rate:
selltype = SellType.TRAILING_STOP_LOSS
logger.debug(
f"HIT STOP: current price at {current_rate:.6f}, "
@@ -239,8 +319,9 @@ class IStrategy(ABC):
# check if we have a special stop loss for positive condition
# and if profit is positive
stop_loss_value = self.stoploss
sl_offset = self.config.get('trailing_stop_positive_offset', 0.0)
stop_loss_value = force_stoploss if force_stoploss else self.stoploss
sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0
if 'trailing_stop_positive' in self.config and current_profit > sl_offset:
@@ -257,17 +338,18 @@ class IStrategy(ABC):
def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool:
"""
Based an earlier trade and current price and ROI configuration, decides whether bot should
sell
sell. Requires current_profit to be in percent!!
:return True if bot should sell at current rate
"""
# Check if time matches and current rate is above threshold
time_diff = (current_time.timestamp() - trade.open_date.timestamp()) / 60
for duration, threshold in self.minimal_roi.items():
if time_diff <= duration:
return False
if current_profit > threshold:
return True
trade_dur = (current_time.timestamp() - trade.open_date.timestamp()) / 60
# Get highest entry in ROI dict where key >= trade-duration
roi_entry = max(list(filter(lambda x: trade_dur >= x, self.minimal_roi.keys())))
threshold = self.minimal_roi[roi_entry]
if current_profit > threshold:
return True
return False
@@ -275,7 +357,7 @@ class IStrategy(ABC):
"""
Creates a dataframe and populates indicators for given ticker data
"""
return {pair: self.advise_indicators(parse_ticker_dataframe(pair_data), {'pair': pair})
return {pair: self.advise_indicators(pair_data, {'pair': pair})
for pair, pair_data in tickerdata.items()}
def advise_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:

View File

@@ -1,168 +0,0 @@
# pragma pylint: disable=attribute-defined-outside-init
"""
This module load custom strategies
"""
import importlib.util
import inspect
import logging
import os
import tempfile
from base64 import urlsafe_b64decode
from collections import OrderedDict
from pathlib import Path
from typing import Dict, Optional, Type
from freqtrade import constants
from freqtrade.strategy import import_strategy
from freqtrade.strategy.interface import IStrategy
logger = logging.getLogger(__name__)
class StrategyResolver(object):
"""
This class contains all the logic to load custom strategy class
"""
__slots__ = ['strategy']
def __init__(self, config: Optional[Dict] = None) -> None:
"""
Load the custom class from config parameter
:param config: configuration dictionary or None
"""
config = config or {}
# Verify the strategy is in the configuration, otherwise fallback to the default strategy
strategy_name = config.get('strategy') or constants.DEFAULT_STRATEGY
self.strategy: IStrategy = self._load_strategy(strategy_name,
config=config,
extra_dir=config.get('strategy_path'))
# Set attributes
# Check if we need to override configuration
if 'minimal_roi' in config:
self.strategy.minimal_roi = config['minimal_roi']
logger.info("Override strategy \'minimal_roi\' with value in config file.")
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']
)
else:
config['stoploss'] = self.strategy.stoploss
if 'ticker_interval' in config:
self.strategy.ticker_interval = config['ticker_interval']
logger.info(
"Override strategy \'ticker_interval\' with value in config file: %s.",
config['ticker_interval']
)
else:
config['ticker_interval'] = self.strategy.ticker_interval
# Sort and apply type conversions
self.strategy.minimal_roi = OrderedDict(sorted(
{int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(),
key=lambda t: t[0]))
self.strategy.stoploss = float(self.strategy.stoploss)
def _load_strategy(
self, strategy_name: str, config: dict, extra_dir: Optional[str] = None) -> IStrategy:
"""
Search and loads the specified strategy.
:param strategy_name: name of the module to import
:param config: configuration for the strategy
:param extra_dir: additional directory to search for the given strategy
:return: Strategy instance or None
"""
current_path = os.path.dirname(os.path.realpath(__file__))
abs_paths = [
os.path.join(os.getcwd(), 'user_data', 'strategies'),
current_path,
]
if extra_dir:
# Add extra strategy directory on top of search paths
abs_paths.insert(0, extra_dir)
if ":" in strategy_name:
logger.info("loading base64 endocded strategy")
strat = strategy_name.split(":")
if len(strat) == 2:
temp = Path(tempfile.mkdtemp("freq", "strategy"))
name = strat[0] + ".py"
temp.joinpath(name).write_text(urlsafe_b64decode(strat[1]).decode('utf-8'))
temp.joinpath("__init__.py").touch()
strategy_name = os.path.splitext(name)[0]
# register temp path with the bot
abs_paths.insert(0, str(temp.resolve()))
for path in abs_paths:
try:
strategy = self._search_strategy(path, strategy_name=strategy_name, config=config)
if strategy:
logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path)
strategy._populate_fun_len = len(
inspect.getfullargspec(strategy.populate_indicators).args)
strategy._buy_fun_len = len(
inspect.getfullargspec(strategy.populate_buy_trend).args)
strategy._sell_fun_len = len(
inspect.getfullargspec(strategy.populate_sell_trend).args)
return import_strategy(strategy, config=config)
except FileNotFoundError:
logger.warning('Path "%s" does not exist', path)
raise ImportError(
"Impossible to load Strategy '{}'. This class does not exist"
" or contains Python code errors".format(strategy_name)
)
@staticmethod
def _get_valid_strategies(module_path: str, strategy_name: str) -> Optional[Type[IStrategy]]:
"""
Returns a list of all possible strategies for the given module_path
:param module_path: absolute path to the module
:param strategy_name: Class name of the strategy
:return: Tuple with (name, class) or None
"""
# Generate spec based on absolute path
spec = importlib.util.spec_from_file_location('unknown', module_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) # type: ignore # importlib does not use typehints
valid_strategies_gen = (
obj for name, obj in inspect.getmembers(module, inspect.isclass)
if strategy_name == name and IStrategy in obj.__bases__
)
return next(valid_strategies_gen, None)
@staticmethod
def _search_strategy(directory: str, strategy_name: str, config: dict) -> Optional[IStrategy]:
"""
Search for the strategy_name in the given directory
:param directory: relative or absolute directory path
:return: name of the strategy class
"""
logger.debug('Searching for strategy %s in \'%s\'', strategy_name, directory)
for entry in os.listdir(directory):
# Only consider python files
if not entry.endswith('.py'):
logger.debug('Ignoring %s', entry)
continue
strategy = StrategyResolver._get_valid_strategies(
os.path.abspath(os.path.join(directory, entry)), strategy_name
)
if strategy:
return strategy(config)
return None

View File

@@ -1,17 +1,20 @@
# pragma pylint: disable=missing-docstring
import json
import logging
import re
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
from telegram import Chat, Message, Update
from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe
from freqtrade import constants
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.exchange import Exchange
from freqtrade.edge import Edge, PairInfo
from freqtrade.freqtradebot import FreqtradeBot
logging.getLogger('').setLevel(logging.INFO)
@@ -25,22 +28,60 @@ def log_has(line, logs):
False)
def patch_exchange(mocker, api_mock=None) -> None:
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
def log_has_re(line, logs):
return reduce(lambda a, b: a or b,
filter(lambda x: re.match(line, x[2]), logs),
False)
def patch_exchange(mocker, api_mock=None, id='bittrex') -> None:
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_ordertypes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value=id))
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value=id.title()))
if api_mock:
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
else:
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock())
def get_patched_exchange(mocker, config, api_mock=None) -> Exchange:
patch_exchange(mocker, api_mock)
def get_patched_exchange(mocker, config, api_mock=None, id='bittrex') -> Exchange:
patch_exchange(mocker, api_mock, id)
exchange = Exchange(config)
return exchange
def patch_wallet(mocker, free=999.9) -> None:
mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock(
return_value=free
))
def patch_edge(mocker) -> None:
# "ETH/BTC",
# "LTC/BTC",
# "XRP/BTC",
# "NEO/BTC"
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
return_value={
'NEO/BTC': PairInfo(-0.20, 0.66, 3.71, 0.50, 1.71, 10, 25),
'LTC/BTC': PairInfo(-0.21, 0.66, 3.71, 0.50, 1.71, 11, 20),
}
))
mocker.patch('freqtrade.edge.Edge.calculate', MagicMock(return_value=True))
def get_patched_edge(mocker, config) -> Edge:
patch_edge(mocker)
edge = Edge(config)
return edge
# Functions for recurrent object patching
def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
"""
This function patch _init_modules() to not call dependencies
@@ -48,7 +89,6 @@ def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
:param config: Config to pass to the bot
:return: None
"""
# mocker.patch('freqtrade.fiat_convert.Market', {'price_usd': 12345.0})
patch_coinmarketcap(mocker, {'price_usd': 12345.0})
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
mocker.patch('freqtrade.freqtradebot.persistence.init', MagicMock())
@@ -73,7 +113,7 @@ def patch_coinmarketcap(mocker, value: Optional[Dict[str, float]] = None) -> Non
'website_slug': 'ethereum'}
]})
mocker.patch.multiple(
'freqtrade.fiat_convert.Market',
'freqtrade.rpc.fiat_convert.Market',
ticker=tickermock,
listings=listmock,
@@ -102,7 +142,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",
@@ -343,6 +394,36 @@ def limit_buy_order():
}
@pytest.fixture(scope='function')
def market_buy_order():
return {
'id': 'mocked_market_buy',
'type': 'market',
'side': 'buy',
'pair': 'mocked',
'datetime': arrow.utcnow().isoformat(),
'price': 0.00004099,
'amount': 91.99181073,
'remaining': 0.0,
'status': 'closed'
}
@pytest.fixture
def market_sell_order():
return {
'id': 'mocked_limit_sell',
'type': 'market',
'side': 'sell',
'pair': 'mocked',
'datetime': arrow.utcnow().isoformat(),
'price': 0.00004173,
'amount': 91.99181073,
'remaining': 0.0,
'status': 'closed'
}
@pytest.fixture
def limit_buy_order_old():
return {
@@ -404,7 +485,40 @@ def limit_sell_order():
@pytest.fixture
def ticker_history():
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_list():
return [
[
1511686200000, # unix timestamp ms
@@ -433,6 +547,11 @@ def ticker_history():
]
@pytest.fixture
def ticker_history(ticker_history_list):
return parse_ticker_dataframe(ticker_history_list, "5m", True)
@pytest.fixture
def tickers():
return MagicMock(return_value={
@@ -612,7 +731,7 @@ def tickers():
@pytest.fixture
def result():
with open('freqtrade/tests/testdata/UNITTEST_BTC-1m.json') as data_file:
return parse_ticker_dataframe(json.load(data_file))
return parse_ticker_dataframe(json.load(data_file), '1m', True)
# FIX:
# Create an fixture/function
@@ -706,3 +825,26 @@ def buy_order_fee():
'status': 'closed',
'fee': None
}
@pytest.fixture(scope="function")
def edge_conf(default_conf):
default_conf['max_open_trades'] = -1
default_conf['stake_amount'] = constants.UNLIMITED_STAKE_AMOUNT
default_conf['edge'] = {
"enabled": True,
"process_throttle_secs": 1800,
"calculate_since_number_of_days": 14,
"capital_available_percentage": 0.5,
"allowed_risk": 0.01,
"stoploss_range_min": -0.01,
"stoploss_range_max": -0.1,
"stoploss_range_step": -0.01,
"maximum_winrate": 0.80,
"minimum_expectancy": 0.20,
"min_trade_number": 15,
"max_trade_duration_minute": 1440,
"remove_pumps": False
}
return default_conf

View File

View File

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

View File

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

View File

@@ -0,0 +1,475 @@
# pragma pylint: disable=missing-docstring, protected-access, C0103
import json
import os
from pathlib import Path
import uuid
from shutil import copyfile
import arrow
from pandas import DataFrame
import pytest
from freqtrade import OperationalException
from freqtrade.arguments import TimeRange
from freqtrade.data import history
from freqtrade.data.history import (download_pair_history,
load_cached_data_for_updating,
load_tickerdata_file,
make_testdata_path,
trim_tickerlist)
from freqtrade.misc import file_dump_json
from freqtrade.tests.conftest import get_patched_exchange, log_has
# Change this if modifying UNITTEST/BTC testdatafile
_BTC_UNITTEST_LENGTH = 13681
def _backup_file(file: str, copy_file: bool = False) -> None:
"""
Backup existing file to avoid deleting the user file
:param file: complete path to the file
:param touch_file: create an empty file in replacement
:return: None
"""
file_swp = file + '.swp'
if os.path.isfile(file):
os.rename(file, file_swp)
if copy_file:
copyfile(file_swp, file)
def _clean_test_file(file: str) -> None:
"""
Backup existing file to avoid deleting the user file
:param file: complete path to the file
:return: None
"""
file_swp = file + '.swp'
# 1. Delete file from the test
if os.path.isfile(file):
os.remove(file)
# 2. Rollback to the initial file
if os.path.isfile(file_swp):
os.rename(file_swp, file)
def test_load_data_30min_ticker(mocker, caplog, default_conf) -> None:
ld = history.load_pair_history(pair='UNITTEST/BTC', ticker_interval='30m', datadir=None)
assert isinstance(ld, DataFrame)
assert not log_has('Download the pair: "UNITTEST/BTC", Interval: 30m', caplog.record_tuples)
def test_load_data_7min_ticker(mocker, caplog, default_conf) -> None:
ld = history.load_pair_history(pair='UNITTEST/BTC', ticker_interval='7m', datadir=None)
assert not isinstance(ld, DataFrame)
assert ld is None
assert log_has(
'No data for pair: "UNITTEST/BTC", Interval: 7m. '
'Use --refresh-pairs-cached to download the data', caplog.record_tuples)
def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None:
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)
history.load_data(datadir=None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
assert os.path.isfile(file) is True
assert not log_has('Download the pair: "UNITTEST/BTC", Interval: 1m', caplog.record_tuples)
_clean_test_file(file)
def test_load_data_with_new_pair_1min(ticker_history_list, mocker, caplog, default_conf) -> None:
"""
Test load_pair_history() with 1 min ticker
"""
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=ticker_history_list)
exchange = get_patched_exchange(mocker, default_conf)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
_backup_file(file)
# do not download a new pair if refresh_pairs isn't set
history.load_pair_history(datadir=None,
ticker_interval='1m',
refresh_pairs=False,
pair='MEME/BTC')
assert os.path.isfile(file) is False
assert log_has('No data for pair: "MEME/BTC", Interval: 1m. '
'Use --refresh-pairs-cached to download the data',
caplog.record_tuples)
# download a new pair if refresh_pairs is set
history.load_pair_history(datadir=None,
ticker_interval='1m',
refresh_pairs=True,
exchange=exchange,
pair='MEME/BTC')
assert os.path.isfile(file) is True
assert log_has('Download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples)
with pytest.raises(OperationalException, match=r'Exchange needs to be initialized when.*'):
history.load_pair_history(datadir=None,
ticker_interval='1m',
refresh_pairs=True,
exchange=None,
pair='MEME/BTC')
_clean_test_file(file)
def test_testdata_path() -> None:
assert str(Path('freqtrade') / 'tests' / 'testdata') in str(make_testdata_path(None))
def test_load_cached_data_for_updating(mocker) -> None:
datadir = Path(__file__).parent.parent.joinpath('testdata')
test_data = None
test_filename = datadir.joinpath('UNITTEST_BTC-1m.json')
with open(test_filename, "rt") as file:
test_data = json.load(file)
# change now time to test 'line' cases
# now = last cached item + 1 hour
now_ts = test_data[-1][0] / 1000 + 60 * 60
mocker.patch('arrow.utcnow', return_value=arrow.get(now_ts))
# timeframe starts earlier than the cached data
# should fully update data
timerange = TimeRange('date', None, test_data[0][0] / 1000 - 1, 0)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
assert data == []
assert start_ts == test_data[0][0] - 1000
# same with 'line' timeframe
num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 120
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
TimeRange(None, 'line', 0, -num_lines))
assert data == []
assert start_ts < test_data[0][0] - 1
# timeframe starts in the center of the cached data
# should return the chached data w/o the last item
timerange = TimeRange('date', None, test_data[0][0] / 1000 + 1, 0)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
assert data == test_data[:-1]
assert test_data[-2][0] < start_ts < test_data[-1][0]
# same with 'line' timeframe
num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 30
timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
assert data == test_data[:-1]
assert test_data[-2][0] < start_ts < test_data[-1][0]
# timeframe starts after the chached data
# should return the chached data w/o the last item
timerange = TimeRange('date', None, test_data[-1][0] / 1000 + 1, 0)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
assert data == test_data[:-1]
assert test_data[-2][0] < start_ts < test_data[-1][0]
# same with 'line' timeframe
num_lines = 30
timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
assert data == test_data[:-1]
assert test_data[-2][0] < start_ts < test_data[-1][0]
# no timeframe is set
# should return the chached data w/o the last item
num_lines = 30
timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
assert data == test_data[:-1]
assert test_data[-2][0] < start_ts < test_data[-1][0]
# no datafile exist
# should return timestamp start time
timerange = TimeRange('date', None, now_ts - 10000, 0)
data, start_ts = load_cached_data_for_updating(test_filename.with_name('unexist'),
'1m',
timerange)
assert data == []
assert start_ts == (now_ts - 10000) * 1000
# same with 'line' timeframe
num_lines = 30
timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename.with_name('unexist'),
'1m',
timerange)
assert data == []
assert start_ts == (now_ts - num_lines * 60) * 1000
# no datafile exist, no timeframe is set
# should return an empty array and None
data, start_ts = load_cached_data_for_updating(test_filename.with_name('unexist'),
'1m',
None)
assert data == []
assert start_ts is None
def test_download_pair_history(ticker_history_list, mocker, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=ticker_history_list)
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')
file2_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'CFI_BTC-1m.json')
file2_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'CFI_BTC-5m.json')
_backup_file(file1_1)
_backup_file(file1_5)
_backup_file(file2_1)
_backup_file(file2_5)
assert os.path.isfile(file1_1) is False
assert os.path.isfile(file2_1) is False
assert download_pair_history(datadir=None, exchange=exchange,
pair='MEME/BTC',
tick_interval='1m')
assert download_pair_history(datadir=None, exchange=exchange,
pair='CFI/BTC',
tick_interval='1m')
assert not exchange._pairs_last_refresh_time
assert os.path.isfile(file1_1) is True
assert os.path.isfile(file2_1) is True
# clean files freshly downloaded
_clean_test_file(file1_1)
_clean_test_file(file2_1)
assert os.path.isfile(file1_5) is False
assert os.path.isfile(file2_5) is False
assert download_pair_history(datadir=None, exchange=exchange,
pair='MEME/BTC',
tick_interval='5m')
assert download_pair_history(datadir=None, exchange=exchange,
pair='CFI/BTC',
tick_interval='5m')
assert not exchange._pairs_last_refresh_time
assert os.path.isfile(file1_5) is True
assert os.path.isfile(file2_5) is True
# clean files freshly downloaded
_clean_test_file(file1_5)
_clean_test_file(file2_5)
def test_download_pair_history2(mocker, default_conf) -> None:
tick = [
[1509836520000, 0.00162008, 0.00162008, 0.00162008, 0.00162008, 108.14853839],
[1509836580000, 0.00161, 0.00161, 0.00161, 0.00161, 82.390199]
]
json_dump_mock = mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=tick)
exchange = get_patched_exchange(mocker, default_conf)
download_pair_history(None, exchange, pair="UNITTEST/BTC", tick_interval='1m')
download_pair_history(None, exchange, pair="UNITTEST/BTC", tick_interval='3m')
assert json_dump_mock.call_count == 2
def test_download_backtesting_data_exception(ticker_history, mocker, caplog, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_history',
side_effect=BaseException('File Error'))
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')
_backup_file(file1_1)
_backup_file(file1_5)
assert not download_pair_history(datadir=None, exchange=exchange,
pair='MEME/BTC',
tick_interval='1m')
# clean files freshly downloaded
_clean_test_file(file1_1)
_clean_test_file(file1_5)
assert log_has('Failed to download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples)
def test_load_tickerdata_file() -> None:
# 7 does not exist in either format.
assert not load_tickerdata_file(None, 'UNITTEST/BTC', '7m')
# 1 exists only as a .json
tickerdata = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
assert _BTC_UNITTEST_LENGTH == len(tickerdata)
# 8 .json is empty and will fail if it's loaded. .json.gz is a copy of 1.json
tickerdata = load_tickerdata_file(None, 'UNITTEST/BTC', '8m')
assert _BTC_UNITTEST_LENGTH == len(tickerdata)
def test_load_partial_missing(caplog) -> None:
# Make sure we start fresh - test missing data at start
start = arrow.get('2018-01-01T00:00:00')
end = arrow.get('2018-01-11T00:00:00')
tickerdata = history.load_data(None, '5m', ['UNITTEST/BTC'],
refresh_pairs=False,
timerange=TimeRange('date', 'date',
start.timestamp, end.timestamp))
# timedifference in 5 minutes
td = ((end - start).total_seconds() // 60 // 5) + 1
assert td != len(tickerdata['UNITTEST/BTC'])
start_real = tickerdata['UNITTEST/BTC'].iloc[0, 0]
assert log_has(f'Missing data at start for pair '
f'UNITTEST/BTC, data starts at {start_real.strftime("%Y-%m-%d %H:%M:%S")}',
caplog.record_tuples)
# Make sure we start fresh - test missing data at end
caplog.clear()
start = arrow.get('2018-01-10T00:00:00')
end = arrow.get('2018-02-20T00:00:00')
tickerdata = history.load_data(datadir=None, ticker_interval='5m',
pairs=['UNITTEST/BTC'], refresh_pairs=False,
timerange=TimeRange('date', 'date',
start.timestamp, end.timestamp))
# timedifference in 5 minutes
td = ((end - start).total_seconds() // 60 // 5) + 1
assert td != len(tickerdata['UNITTEST/BTC'])
# Shift endtime with +5 - as last candle is dropped (partial candle)
end_real = arrow.get(tickerdata['UNITTEST/BTC'].iloc[-1, 0]).shift(minutes=5)
assert log_has(f'Missing data at end for pair '
f'UNITTEST/BTC, data ends at {end_real.strftime("%Y-%m-%d %H:%M:%S")}',
caplog.record_tuples)
def test_init(default_conf, mocker) -> None:
exchange = get_patched_exchange(mocker, default_conf)
assert {} == history.load_data(
datadir='',
exchange=exchange,
pairs=[],
refresh_pairs=True,
ticker_interval=default_conf['ticker_interval']
)
def test_trim_tickerlist() -> None:
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-1m.json')
with open(file) as data_file:
ticker_list = json.load(data_file)
ticker_list_len = len(ticker_list)
# Test the pattern ^(-\d+)$
# This pattern uses the latest N elements
timerange = TimeRange(None, 'line', 0, -5)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_len == 5
assert ticker_list[0] is not ticker[0] # The first element should be different
assert ticker_list[-1] is ticker[-1] # The last element must be the same
# Test the pattern ^(\d+)-$
# This pattern keep X element from the end
timerange = TimeRange('line', None, 5, 0)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_len == 5
assert ticker_list[0] is ticker[0] # The first element must be the same
assert ticker_list[-1] is not ticker[-1] # The last element should be different
# Test the pattern ^(\d+)-(\d+)$
# This pattern extract a window
timerange = TimeRange('index', 'index', 5, 10)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_len == 5
assert ticker_list[0] is not ticker[0] # The first element should be different
assert ticker_list[5] is ticker[0] # The list starts at the index 5
assert ticker_list[9] is ticker[-1] # The list ends at the index 9 (5 elements)
# Test the pattern ^(\d{8})-(\d{8})$
# This pattern extract a window between the dates
timerange = TimeRange('date', 'date', ticker_list[5][0] / 1000, ticker_list[10][0] / 1000 - 1)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_len == 5
assert ticker_list[0] is not ticker[0] # The first element should be different
assert ticker_list[5] is ticker[0] # The list starts at the index 5
assert ticker_list[9] is ticker[-1] # The list ends at the index 9 (5 elements)
# Test the pattern ^-(\d{8})$
# This pattern extracts elements from the start to the date
timerange = TimeRange(None, 'date', 0, ticker_list[10][0] / 1000 - 1)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_len == 10
assert ticker_list[0] is ticker[0] # The start of the list is included
assert ticker_list[9] is ticker[-1] # The element 10 is not included
# Test the pattern ^(\d{8})-$
# This pattern extracts elements from the date to now
timerange = TimeRange('date', None, ticker_list[10][0] / 1000 - 1, None)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_len == ticker_list_len - 10
assert ticker_list[10] is ticker[0] # The first element is element #10
assert ticker_list[-1] is ticker[-1] # The last element is the same
# Test a wrong pattern
# This pattern must return the list unchanged
timerange = TimeRange(None, None, None, 5)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_list_len == ticker_len
# Test invalid timerange (start after stop)
timerange = TimeRange('index', 'index', 10, 5)
with pytest.raises(ValueError, match=r'The timerange .* is incorrect'):
trim_tickerlist(ticker_list, timerange)
assert ticker_list_len == ticker_len
# passing empty list
timerange = TimeRange(None, None, None, 5)
ticker = trim_tickerlist([], timerange)
assert 0 == len(ticker)
assert not ticker
def test_file_dump_json_tofile() -> None:
file = os.path.join(os.path.dirname(__file__), '..', 'testdata',
'test_{id}.json'.format(id=str(uuid.uuid4())))
data = {'bar': 'foo'}
# check the file we will create does not exist
assert os.path.isfile(file) is False
# Create the Json file
file_dump_json(file, data)
# Check the file was create
assert os.path.isfile(file) is True
# Open the Json file created and test the data is in it
with open(file) as data_file:
json_from_file = json.load(data_file)
assert 'bar' in json_from_file
assert json_from_file['bar'] == 'foo'
# Remove the file
_clean_test_file(file)

View File

View File

@@ -0,0 +1,362 @@
# pragma pylint: disable=missing-docstring, C0103, C0330
# pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments
import logging
import math
from unittest.mock import MagicMock
import arrow
import numpy as np
import pytest
from pandas import DataFrame, to_datetime
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.edge import Edge, PairInfo
from freqtrade.strategy.interface import SellType
from freqtrade.tests.conftest import get_patched_freqtradebot
from freqtrade.tests.optimize import (BTContainer, BTrade,
_build_backtest_dataframe,
_get_frame_time_from_offset)
# Cases to be tested:
# 1) Open trade should be removed from the end
# 2) Two complete trades within dataframe (with sell hit for all)
# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
# 4) Entered, sl 3%, candle drops 4%, recovers to 1% => Trade closed, 3% loss
# 5) Stoploss and sell are hit. should sell on stoploss
####################################################################
ticker_start_time = arrow.get(2018, 10, 3)
ticker_interval_in_minute = 60
_ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7}
# Open trade should be removed from the end
tc0 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 1]], # enter trade (signal on last candle)
stop_loss=-0.99, roi=float('inf'), profit_perc=0.00,
trades=[]
)
# Two complete trades within dataframe(with sell hit for all)
tc1 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 1], # enter trade (signal on last candle)
[2, 5000, 5025, 4975, 4987, 6172, 0, 0], # exit at open
[3, 5000, 5025, 4975, 4987, 6172, 1, 0], # no action
[4, 5000, 5025, 4975, 4987, 6172, 0, 0], # should enter the trade
[5, 5000, 5025, 4975, 4987, 6172, 0, 1], # no action
[6, 5000, 5025, 4975, 4987, 6172, 0, 0], # should sell
],
stop_loss=-0.99, roi=float('inf'), profit_perc=0.00,
trades=[BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=1, close_tick=2),
BTrade(sell_reason=SellType.SELL_SIGNAL, open_tick=4, close_tick=6)]
)
# 3) Entered, sl 1%, candle drops 8% => Trade closed, 1% loss
tc2 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4600, 4987, 6172, 0, 0], # enter trade, stoploss hit
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
],
stop_loss=-0.01, roi=float('inf'), profit_perc=-0.01,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
)
# 4) Entered, sl 3 %, candle drops 4%, recovers to 1 % = > Trade closed, 3 % loss
tc3 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4800, 4987, 6172, 0, 0], # enter trade, stoploss hit
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
],
stop_loss=-0.03, roi=float('inf'), profit_perc=-0.03,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
)
# 5) Stoploss and sell are hit. should sell on stoploss
tc4 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4800, 4987, 6172, 0, 1], # enter trade, stoploss hit, sell signal
[2, 5000, 5025, 4975, 4987, 6172, 0, 0],
],
stop_loss=-0.03, roi=float('inf'), profit_perc=-0.03,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=1)]
)
TESTS = [
tc0,
tc1,
tc2,
tc3,
tc4
]
@pytest.mark.parametrize("data", TESTS)
def test_edge_results(edge_conf, mocker, caplog, data) -> None:
"""
run functional tests
"""
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
frame = _build_backtest_dataframe(data.data)
caplog.set_level(logging.DEBUG)
edge.fee = 0
trades = edge._find_trades_for_stoploss_range(frame, 'TEST/BTC', [data.stop_loss])
results = edge._fill_calculable_fields(DataFrame(trades)) if trades else DataFrame()
print(results)
assert len(trades) == len(data.trades)
if not results.empty:
assert round(results["profit_percent"].sum(), 3) == round(data.profit_perc, 3)
for c, trade in enumerate(data.trades):
res = results.iloc[c]
assert res.exit_type == trade.sell_reason
assert res.open_time == _get_frame_time_from_offset(trade.open_tick)
assert res.close_time == _get_frame_time_from_offset(trade.close_tick)
def test_adjust(mocker, edge_conf):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
return_value={
'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
'C/D': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
'N/O': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60)
}
))
pairs = ['A/B', 'C/D', 'E/F', 'G/H']
assert(edge.adjust(pairs) == ['E/F', 'C/D'])
def test_stoploss(mocker, edge_conf):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
return_value={
'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
'C/D': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
'N/O': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60)
}
))
assert edge.stoploss('E/F') == -0.01
def test_nonexisting_stoploss(mocker, edge_conf):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
return_value={
'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
}
))
assert edge.stoploss('N/O') == -0.1
def test_stake_amount(mocker, edge_conf):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
return_value={
'E/F': PairInfo(-0.02, 0.66, 3.71, 0.50, 1.71, 10, 60),
}
))
free = 100
total = 100
in_trade = 25
assert edge.stake_amount('E/F', free, total, in_trade) == 31.25
free = 20
total = 100
in_trade = 25
assert edge.stake_amount('E/F', free, total, in_trade) == 20
free = 0
total = 100
in_trade = 25
assert edge.stake_amount('E/F', free, total, in_trade) == 0
def test_nonexisting_stake_amount(mocker, edge_conf):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
return_value={
'E/F': PairInfo(-0.11, 0.66, 3.71, 0.50, 1.71, 10, 60),
}
))
# should use strategy stoploss
assert edge.stake_amount('N/O', 1, 2, 1) == 0.15
def _validate_ohlc(buy_ohlc_sell_matrice):
for index, ohlc in enumerate(buy_ohlc_sell_matrice):
# if not high < open < low or not high < close < low
if not ohlc[3] >= ohlc[2] >= ohlc[4] or not ohlc[3] >= ohlc[5] >= ohlc[4]:
raise Exception('Line ' + str(index + 1) + ' of ohlc has invalid values!')
return True
def _build_dataframe(buy_ohlc_sell_matrice):
_validate_ohlc(buy_ohlc_sell_matrice)
tickers = []
for ohlc in buy_ohlc_sell_matrice:
ticker = {
'date': ticker_start_time.shift(
minutes=(
ohlc[0] *
ticker_interval_in_minute)).timestamp *
1000,
'buy': ohlc[1],
'open': ohlc[2],
'high': ohlc[3],
'low': ohlc[4],
'close': ohlc[5],
'sell': ohlc[6]}
tickers.append(ticker)
frame = DataFrame(tickers)
frame['date'] = to_datetime(frame['date'],
unit='ms',
utc=True,
infer_datetime_format=True)
return frame
def _time_on_candle(number):
return np.datetime64(ticker_start_time.shift(
minutes=(number * ticker_interval_in_minute)).timestamp * 1000, 'ms')
def test_edge_heartbeat_calculate(mocker, edge_conf):
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
heartbeat = edge_conf['edge']['process_throttle_secs']
# should not recalculate if heartbeat not reached
edge._last_updated = arrow.utcnow().timestamp - heartbeat + 1
assert edge.calculate() is False
def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=False,
timerange=None, exchange=None):
hz = 0.1
base = 0.001
ETHBTC = [
[
ticker_start_time.shift(minutes=(x * ticker_interval_in_minute)).timestamp * 1000,
math.sin(x * hz) / 1000 + base,
math.sin(x * hz) / 1000 + base + 0.0001,
math.sin(x * hz) / 1000 + base - 0.0001,
math.sin(x * hz) / 1000 + base,
123.45
] for x in range(0, 500)]
hz = 0.2
base = 0.002
LTCBTC = [
[
ticker_start_time.shift(minutes=(x * ticker_interval_in_minute)).timestamp * 1000,
math.sin(x * hz) / 1000 + base,
math.sin(x * hz) / 1000 + base + 0.0001,
math.sin(x * hz) / 1000 + base - 0.0001,
math.sin(x * hz) / 1000 + base,
123.45
] for x in range(0, 500)]
pairdata = {'NEO/BTC': parse_ticker_dataframe(ETHBTC, '1h', fill_missing=True),
'LTC/BTC': parse_ticker_dataframe(LTCBTC, '1h', fill_missing=True)}
return pairdata
def test_edge_process_downloaded_data(mocker, edge_conf):
edge_conf['datadir'] = None
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001))
mocker.patch('freqtrade.data.history.load_data', mocked_load_data)
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
assert edge.calculate()
assert len(edge._cached_pairs) == 2
assert edge._last_updated <= arrow.utcnow().timestamp + 2
def test_process_expectancy(mocker, edge_conf):
edge_conf['edge']['min_trade_number'] = 2
freqtrade = get_patched_freqtradebot(mocker, edge_conf)
def get_fee():
return 0.001
freqtrade.exchange.get_fee = get_fee
edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy)
trades = [
{'pair': 'TEST/BTC',
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:05:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:10:00.000000000'),
'open_index': 1,
'close_index': 1,
'trade_duration': '',
'open_rate': 17,
'close_rate': 17,
'exit_type': 'sell_signal'},
{'pair': 'TEST/BTC',
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:20:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:25:00.000000000'),
'open_index': 4,
'close_index': 4,
'trade_duration': '',
'open_rate': 20,
'close_rate': 20,
'exit_type': 'sell_signal'},
{'pair': 'TEST/BTC',
'stoploss': -0.9,
'profit_percent': '',
'profit_abs': '',
'open_time': np.datetime64('2018-10-03T00:30:00.000000000'),
'close_time': np.datetime64('2018-10-03T00:40:00.000000000'),
'open_index': 6,
'close_index': 7,
'trade_duration': '',
'open_rate': 26,
'close_rate': 34,
'exit_type': 'sell_signal'}
]
trades_df = DataFrame(trades)
trades_df = edge._fill_calculable_fields(trades_df)
final = edge._process_expectancy(trades_df)
assert len(final) == 1
assert 'TEST/BTC' in final
assert final['TEST/BTC'].stoploss == -0.9
assert round(final['TEST/BTC'].winrate, 10) == 0.3333333333
assert round(final['TEST/BTC'].risk_reward_ratio, 10) == 306.5384615384
assert round(final['TEST/BTC'].required_risk_reward, 10) == 2.0
assert round(final['TEST/BTC'].expectancy, 10) == 101.5128205128

View File

View File

@@ -1,18 +1,29 @@
# pragma pylint: disable=missing-docstring, C0103, bad-continuation, global-statement
# pragma pylint: disable=protected-access
import copy
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
from pandas import DataFrame
from freqtrade import DependencyException, OperationalException, TemporaryError
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 +38,58 @@ 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_init_ccxt_kwargs(default_conf, mocker, caplog):
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
caplog.set_level(logging.INFO)
conf = copy.deepcopy(default_conf)
conf['exchange']['ccxt_async_config'] = {'aiohttp_trust_env': True}
ex = Exchange(conf)
assert log_has("Applying additional ccxt config: {'aiohttp_trust_env': True}",
caplog.record_tuples)
assert ex._api_async.aiohttp_trust_env
assert not ex._api.aiohttp_trust_env
# Reset logging and config
caplog.clear()
conf = copy.deepcopy(default_conf)
conf['exchange']['ccxt_config'] = {'TestKWARG': 11}
ex = Exchange(conf)
assert not log_has("Applying additional ccxt config: {'aiohttp_trust_env': True}",
caplog.record_tuples)
assert not ex._api_async.aiohttp_trust_env
assert hasattr(ex._api, 'TestKWARG')
assert ex._api.TestKWARG == 11
assert not hasattr(ex._api_async, 'TestKWARG')
assert log_has("Applying additional ccxt config: {'TestKWARG': 11}",
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 +121,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 +145,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 +167,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 +189,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 +197,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 +244,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 +267,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 +280,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 +298,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 +319,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 +335,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,10 +352,63 @@ 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)
def test_validate_order_types(default_conf, mocker):
api_mock = MagicMock()
type(api_mock).has = PropertyMock(return_value={'createMarketOrder': True})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.name', 'Bittrex')
default_conf['order_types'] = {
'buy': 'limit',
'sell': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': False
}
Exchange(default_conf)
type(api_mock).has = PropertyMock(return_value={'createMarketOrder': False})
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
default_conf['order_types'] = {
'buy': 'limit',
'sell': 'limit',
'stoploss': 'market',
'stoploss_on_exchange': 'false'
}
with pytest.raises(OperationalException,
match=r'Exchange .* does not support market orders.'):
Exchange(default_conf)
default_conf['order_types'] = {
'buy': 'limit',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': True
}
with pytest.raises(OperationalException,
match=r'On exchange stoploss is not supported for .*'):
Exchange(default_conf)
def test_validate_order_types_not_in_config(default_conf, mocker):
api_mock = MagicMock()
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange._load_markets', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
conf = copy.deepcopy(default_conf)
Exchange(conf)
def test_exchange_has(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf)
assert not exchange.exchange_has('ASDFASDF')
@@ -273,7 +427,8 @@ def test_buy_dry_run(default_conf, mocker):
default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf)
order = exchange.buy(pair='ETH/BTC', rate=200, amount=1)
order = exchange.buy(pair='ETH/BTC', ordertype='limit',
amount=1, rate=200, time_in_force='gtc')
assert 'id' in order
assert 'dry_run_buy_' in order['id']
@@ -281,47 +436,106 @@ def test_buy_dry_run(default_conf, mocker):
def test_buy_prod(default_conf, mocker):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
api_mock.create_limit_buy_order = MagicMock(return_value={
order_type = 'market'
time_in_force = 'gtc'
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
order = exchange.buy(pair='ETH/BTC', rate=200, amount=1)
order = exchange.buy(pair='ETH/BTC', ordertype=order_type,
amount=1, rate=200, time_in_force=time_in_force)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'buy'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is None
api_mock.create_order.reset_mock()
order_type = 'limit'
order = exchange.buy(
pair='ETH/BTC',
ordertype=order_type,
amount=1,
rate=200,
time_in_force=time_in_force)
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'buy'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] == 200
# test exception handling
with pytest.raises(DependencyException):
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InsufficientFunds)
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.buy(pair='ETH/BTC', rate=200, amount=1)
exchange.buy(pair='ETH/BTC', ordertype=order_type,
amount=1, rate=200, time_in_force=time_in_force)
with pytest.raises(DependencyException):
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.InvalidOrder)
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.buy(pair='ETH/BTC', rate=200, amount=1)
exchange.buy(pair='ETH/BTC', ordertype=order_type,
amount=1, rate=200, time_in_force=time_in_force)
with pytest.raises(TemporaryError):
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.NetworkError)
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.buy(pair='ETH/BTC', rate=200, amount=1)
exchange.buy(pair='ETH/BTC', ordertype=order_type,
amount=1, rate=200, time_in_force=time_in_force)
with pytest.raises(OperationalException):
api_mock.create_limit_buy_order = MagicMock(side_effect=ccxt.BaseError)
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.buy(pair='ETH/BTC', rate=200, amount=1)
exchange.buy(pair='ETH/BTC', ordertype=order_type,
amount=1, rate=200, time_in_force=time_in_force)
def test_buy_considers_time_in_force(default_conf, mocker):
api_mock = MagicMock()
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_type = 'market'
time_in_force = 'ioc'
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
order = exchange.buy(pair='ETH/BTC', ordertype=order_type,
amount=1, rate=200, time_in_force=time_in_force)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'buy'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is None
assert api_mock.create_order.call_args[0][5] == {'timeInForce': 'ioc'}
def test_sell_dry_run(default_conf, mocker):
default_conf['dry_run'] = True
exchange = get_patched_exchange(mocker, default_conf)
order = exchange.sell(pair='ETH/BTC', rate=200, amount=1)
order = exchange.sell(pair='ETH/BTC', ordertype='limit', amount=1, rate=200)
assert 'id' in order
assert 'dry_run_sell_' in order['id']
@@ -329,7 +543,8 @@ def test_sell_dry_run(default_conf, mocker):
def test_sell_prod(default_conf, mocker):
api_mock = MagicMock()
order_id = 'test_prod_sell_{}'.format(randint(0, 10 ** 6))
api_mock.create_limit_sell_order = MagicMock(return_value={
order_type = 'market'
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
@@ -338,32 +553,48 @@ def test_sell_prod(default_conf, mocker):
default_conf['dry_run'] = False
exchange = get_patched_exchange(mocker, default_conf, api_mock)
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
order = exchange.sell(pair='ETH/BTC', rate=200, amount=1)
order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'sell'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] is None
api_mock.create_order.reset_mock()
order_type = 'limit'
order = exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'sell'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] == 200
# test exception handling
with pytest.raises(DependencyException):
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InsufficientFunds)
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.sell(pair='ETH/BTC', rate=200, amount=1)
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
with pytest.raises(DependencyException):
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.InvalidOrder)
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.sell(pair='ETH/BTC', rate=200, amount=1)
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
with pytest.raises(TemporaryError):
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.NetworkError)
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.sell(pair='ETH/BTC', rate=200, amount=1)
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
with pytest.raises(OperationalException):
api_mock.create_limit_sell_order = MagicMock(side_effect=ccxt.BaseError)
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.sell(pair='ETH/BTC', rate=200, amount=1)
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
def test_get_balance_dry_run(default_conf, mocker):
@@ -472,6 +703,7 @@ def test_get_ticker(default_conf, mocker):
'last': 0.0001,
}
api_mock.fetch_ticker = MagicMock(return_value=tick)
api_mock.markets = {'ETH/BTC': {}}
exchange = get_patched_exchange(mocker, default_conf, api_mock)
# retrieve original ticker
ticker = exchange.get_ticker(pair='ETH/BTC')
@@ -514,6 +746,207 @@ def test_get_ticker(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_ticker(pair='ETH/BTC', refresh=True)
with pytest.raises(DependencyException, match=r'Pair XRP/ETH not available'):
exchange.get_ticker(pair='XRP/ETH', 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_interval, 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_latest_ohlcv(mocker, default_conf, caplog) -> None:
tick = [
[
(arrow.utcnow().timestamp - 1) * 1000, # unix timestamp ms
1, # open
2, # high
3, # low
4, # close
5, # volume (in quote currency)
],
[
arrow.utcnow().timestamp * 1000, # unix timestamp ms
3, # open
1, # high
4, # low
6, # 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', '5m'), ('XRP/ETH', '5m')]
# empty dicts
assert not exchange._klines
exchange.refresh_latest_ohlcv(pairs)
assert log_has(f'Refreshing ohlcv data for {len(pairs)} pairs', caplog.record_tuples)
assert exchange._klines
assert exchange._api_async.fetch_ohlcv.call_count == 2
for pair in pairs:
assert isinstance(exchange.klines(pair), DataFrame)
assert len(exchange.klines(pair)) > 0
# klines function should return a different object on each call
# if copy is "True"
assert exchange.klines(pair) is not exchange.klines(pair)
assert exchange.klines(pair) is not exchange.klines(pair, copy=True)
assert exchange.klines(pair, copy=True) is not exchange.klines(pair, copy=True)
assert exchange.klines(pair, copy=False) is exchange.klines(pair, copy=False)
# test caching
exchange.refresh_latest_ohlcv([('IOTA/ETH', '5m'), ('XRP/ETH', '5m')])
assert exchange._api_async.fetch_ohlcv.call_count == 2
assert log_has(f"Using cached ohlcv data for {pairs[0][0]}, {pairs[0][1]} ...",
caplog.record_tuples)
@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) == 3
assert res[0] == pair
assert res[1] == "5m"
assert res[2] == tick
assert exchange._api_async.fetch_ohlcv.call_count == 1
assert not log_has(f"Using cached ohlcv 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) == 3
assert res[0] == pair
assert res[1] == "5m"
assert res[2] == tick
assert exchange._api_async.fetch_ohlcv.call_count == 1
def test_refresh_latest_ohlcv_inv_result(default_conf, mocker, caplog):
async def mock_get_candle_hist(pair, *args, **kwargs):
if pair == 'ETH/BTC':
return [[]]
else:
raise TypeError()
exchange = get_patched_exchange(mocker, default_conf)
# Monkey-patch async function with empty result
exchange._api_async.fetch_ohlcv = MagicMock(side_effect=mock_get_candle_hist)
pairs = [("ETH/BTC", "5m"), ("XRP/BTC", "5m")]
res = exchange.refresh_latest_ohlcv(pairs)
assert exchange._klines
assert exchange._api_async.fetch_ohlcv.call_count == 2
assert type(res) is list
assert len(res) == 2
# Test that each is in list at least once as order is not guaranteed
assert type(res[0]) is tuple or type(res[1]) is tuple
assert type(res[0]) is TypeError or type(res[1]) is TypeError
assert log_has("Error loading ETH/BTC. Result was [[]].", caplog.record_tuples)
assert log_has("Async code raised an exception: TypeError", caplog.record_tuples)
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):
@@ -524,65 +957,10 @@ def make_fetch_ohlcv_mock(data):
return fetch_ohlcv_mock
def test_get_candle_history(default_conf, mocker):
api_mock = MagicMock()
tick = [
[
1511686200000, # unix timestamp ms
1, # open
2, # high
3, # low
4, # close
5, # volume (in quote currency)
]
]
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick))
exchange = get_patched_exchange(mocker, default_conf, api_mock)
# retrieve original ticker
ticks = exchange.get_candle_history('ETH/BTC', default_conf['ticker_interval'])
assert ticks[0][0] == 1511686200000
assert ticks[0][1] == 1
assert ticks[0][2] == 2
assert ticks[0][3] == 3
assert ticks[0][4] == 4
assert ticks[0][5] == 5
# change ticker and ensure tick changes
new_tick = [
[
1511686210000, # unix timestamp ms
6, # open
7, # high
8, # low
9, # close
10, # volume (in quote currency)
]
]
api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(new_tick))
exchange = get_patched_exchange(mocker, default_conf, api_mock)
ticks = exchange.get_candle_history('ETH/BTC', default_conf['ticker_interval'])
assert ticks[0][0] == 1511686210000
assert ticks[0][1] == 6
assert ticks[0][2] == 7
assert ticks[0][3] == 8
assert ticks[0][4] == 9
assert ticks[0][5] == 10
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
"get_candle_history", "fetch_ohlcv",
pair='ABCD/BTC', tick_interval=default_conf['ticker_interval'])
with pytest.raises(OperationalException, match=r'Exchange .* does not support.*'):
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.get_candle_history(pair='ABCD/BTC', tick_interval=default_conf['ticker_interval'])
def test_get_candle_history_sort(default_conf, mocker):
api_mock = MagicMock()
@pytest.mark.asyncio
async def test___async_get_candle_history_sort(default_conf, mocker):
def sort_data(data, key):
return sorted(data, key=key)
# GDAX use-case (real data from GDAX)
# This ticker history is ordered DESC (newest first, oldest last)
@@ -598,13 +976,15 @@ def test_get_candle_history_sort(default_conf, mocker):
[1527830700000, 0.07652, 0.07652, 0.07651, 0.07652, 10.04822687],
[1527830400000, 0.07649, 0.07651, 0.07649, 0.07651, 2.5734867]
]
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick))
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange = get_patched_exchange(mocker, default_conf)
exchange._api_async.fetch_ohlcv = get_mock_coro(tick)
sort_mock = mocker.patch('freqtrade.exchange.sorted', MagicMock(side_effect=sort_data))
# Test the ticker history sort
ticks = exchange.get_candle_history('ETH/BTC', default_conf['ticker_interval'])
res = await exchange._async_get_candle_history('ETH/BTC', default_conf['ticker_interval'])
assert res[0] == 'ETH/BTC'
ticks = res[2]
assert sort_mock.call_count == 1
assert ticks[0][0] == 1527830400000
assert ticks[0][1] == 0.07649
assert ticks[0][2] == 0.07651
@@ -633,11 +1013,16 @@ def test_get_candle_history_sort(default_conf, mocker):
[1527830100000, 0.076695, 0.07671, 0.07624171, 0.07671, 1.80689244],
[1527830400000, 0.07671, 0.07674399, 0.07629216, 0.07655213, 2.31452783]
]
type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True})
api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick))
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange._api_async.fetch_ohlcv = get_mock_coro(tick)
# Reset sort mock
sort_mock = mocker.patch('freqtrade.exchange.sorted', MagicMock(side_effect=sort_data))
# Test the ticker history sort
ticks = exchange.get_candle_history('ETH/BTC', default_conf['ticker_interval'])
res = await exchange._async_get_candle_history('ETH/BTC', default_conf['ticker_interval'])
assert res[0] == 'ETH/BTC'
assert res[1] == default_conf['ticker_interval']
ticks = res[2]
# Sorted not called again - data is already in order
assert sort_mock.call_count == 0
assert ticks[0][0] == 1527827700000
assert ticks[0][1] == 0.07659999
assert ticks[0][2] == 0.0766
@@ -705,8 +1090,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 +1098,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)
@@ -825,13 +1207,83 @@ def test_get_fee(default_conf, mocker):
'get_fee', 'calculate_fee')
def test_get_amount_lots(default_conf, mocker):
def test_stoploss_limit_order(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)
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
order_type = 'stop_loss_limit'
assert exchange.get_amount_lots('LTC/BTC', 1.54) == 1
assert marketmock.call_count == 1
api_mock.create_order = MagicMock(return_value={
'id': order_id,
'info': {
'foo': 'bar'
}
})
default_conf['dry_run'] = False
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
with pytest.raises(OperationalException):
order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=190, rate=200)
api_mock.create_order.reset_mock()
order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
assert 'id' in order
assert 'info' in order
assert order['id'] == order_id
assert api_mock.create_order.call_args[0][0] == 'ETH/BTC'
assert api_mock.create_order.call_args[0][1] == order_type
assert api_mock.create_order.call_args[0][2] == 'sell'
assert api_mock.create_order.call_args[0][3] == 1
assert api_mock.create_order.call_args[0][4] == 200
assert api_mock.create_order.call_args[0][5] == {'stopPrice': 220}
# test exception handling
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
with pytest.raises(TemporaryError):
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
with pytest.raises(OperationalException):
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError)
exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
def test_stoploss_limit_order_dry_run(default_conf, mocker):
api_mock = MagicMock()
order_type = 'stop_loss_limit'
default_conf['dry_run'] = True
mocker.patch('freqtrade.exchange.Exchange.symbol_amount_prec', lambda s, x, y: y)
mocker.patch('freqtrade.exchange.Exchange.symbol_price_prec', lambda s, x, y: y)
exchange = get_patched_exchange(mocker, default_conf, api_mock, 'binance')
with pytest.raises(OperationalException):
order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=190, rate=200)
api_mock.create_order.reset_mock()
order = exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
assert 'id' in order
assert 'info' in order
assert 'type' in order
assert order['type'] == order_type
assert order['price'] == 220
assert order['amount'] == 1

View File

@@ -1,21 +0,0 @@
# pragma pylint: disable=missing-docstring, C0103
from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe
def test_dataframe_correct_length(result):
dataframe = parse_ticker_dataframe(result)
assert len(result.index) - 1 == len(dataframe.index) # last partial candle removed
def test_dataframe_correct_columns(result):
assert result.columns.tolist() == \
['date', 'open', 'high', 'low', 'close', 'volume']
def test_parse_ticker_dataframe(ticker_history):
columns = ['date', 'open', 'high', 'low', 'close', 'volume']
# Test file with BV data
dataframe = parse_ticker_dataframe(ticker_history)
assert dataframe.columns.tolist() == columns

View File

@@ -0,0 +1,46 @@
from typing import NamedTuple, List
import arrow
from pandas import DataFrame
from freqtrade.strategy.interface import SellType
from freqtrade.constants import TICKER_INTERVAL_MINUTES
ticker_start_time = arrow.get(2018, 10, 3)
tests_ticker_interval = "1h"
class BTrade(NamedTuple):
"""
Minimalistic Trade result used for functional backtesting
"""
sell_reason: SellType
open_tick: int
close_tick: int
class BTContainer(NamedTuple):
"""
Minimal BacktestContainer defining Backtest inputs and results.
"""
data: List[float]
stop_loss: float
roi: float
trades: List[BTrade]
profit_perc: float
def _get_frame_time_from_offset(offset):
return ticker_start_time.shift(minutes=(offset * TICKER_INTERVAL_MINUTES[tests_ticker_interval])
).datetime.replace(tzinfo=None)
def _build_backtest_dataframe(ticker_with_signals):
columns = ['date', 'open', 'high', 'low', 'close', 'volume', 'buy', 'sell']
frame = DataFrame.from_records(ticker_with_signals, columns=columns)
frame['date'] = frame['date'].apply(_get_frame_time_from_offset)
# Ensure floats are in place
for column in ['open', 'high', 'low', 'close', 'volume']:
frame[column] = frame[column].astype('float64')
return frame

View File

@@ -0,0 +1,182 @@
# pragma pylint: disable=missing-docstring, W0212, line-too-long, C0103, C0330, unused-argument
import logging
from unittest.mock import MagicMock
from pandas import DataFrame
import pytest
from freqtrade.optimize import get_timeframe
from freqtrade.optimize.backtesting import Backtesting
from freqtrade.strategy.interface import SellType
from freqtrade.tests.optimize import (BTrade, BTContainer, _build_backtest_dataframe,
_get_frame_time_from_offset, tests_ticker_interval)
from freqtrade.tests.conftest import patch_exchange
# Test 0 Minus 8% Close
# Test with Stop-loss at 1%
# TC1: Stop-Loss Triggered 1% loss
tc0 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4600, 4600, 6172, 0, 0], # exit with stoploss hit
[3, 4975, 5000, 4980, 4977, 6172, 0, 0],
[4, 4977, 4987, 4977, 4995, 6172, 0, 0],
[5, 4995, 4995, 4995, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi=1, profit_perc=-0.01,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 1 Minus 4% Low, minus 1% close
# Test with Stop-Loss at 3%
# TC2: Stop-Loss Triggered 3% Loss
tc1 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4962, 4975, 6172, 0, 0],
[3, 4975, 5000, 4800, 4962, 6172, 0, 0], # exit with stoploss hit
[4, 4962, 4987, 4937, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.03, roi=1, profit_perc=-0.03,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 3 Candle drops 4%, Recovers 1%.
# Entry Criteria Met
# Candle drops 20%
# Candle Data for test 3
# Test with Stop-Loss at 2%
# TC3: Trade-A: Stop-Loss Triggered 2% Loss
# Trade-B: Stop-Loss Triggered 2% Loss
tc2 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5012, 4800, 4975, 6172, 0, 0], # exit with stoploss hit
[3, 4975, 5000, 4950, 4962, 6172, 1, 0],
[4, 4975, 5000, 4950, 4962, 6172, 0, 0], # enter trade 2 (signal on last candle)
[5, 4962, 4987, 4000, 4000, 6172, 0, 0], # exit with stoploss hit
[6, 4950, 4975, 4975, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi=1, profit_perc=-0.04,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2),
BTrade(sell_reason=SellType.STOP_LOSS, open_tick=4, close_tick=5)]
)
# Test 4 Minus 3% / recovery +15%
# Candle Data for test 3 Candle drops 3% Closed 15% up
# Test with Stop-loss at 2% ROI 6%
# TC4: Stop-Loss Triggered 2% Loss
tc3 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5750, 4850, 5750, 6172, 0, 0], # Exit with stoploss hit
[3, 4975, 5000, 4950, 4962, 6172, 0, 0],
[4, 4962, 4987, 4937, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi=0.06, profit_perc=-0.02,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 4 / Drops 0.5% Closes +20%
# Set stop-loss at 1% ROI 3%
# TC5: ROI triggers 3% Gain
tc4 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4980, 4987, 6172, 1, 0],
[1, 5000, 5025, 4980, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5025, 4975, 4987, 6172, 0, 0],
[3, 4975, 6000, 4975, 6000, 6172, 0, 0], # ROI
[4, 4962, 4987, 4972, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.01, roi=0.03, profit_perc=0.03,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=3)]
)
# Test 6 / Drops 3% / Recovers 6% Positive / Closes 1% positve
# Candle Data for test 6
# Set stop-loss at 2% ROI at 5%
# TC6: Stop-Loss triggers 2% Loss
tc5 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[2, 4987, 5300, 4850, 5050, 6172, 0, 0], # Exit with stoploss
[3, 4975, 5000, 4950, 4962, 6172, 0, 0],
[4, 4962, 4987, 4972, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi=0.05, profit_perc=-0.02,
trades=[BTrade(sell_reason=SellType.STOP_LOSS, open_tick=1, close_tick=2)]
)
# Test 7 - 6% Positive / 1% Negative / Close 1% Positve
# Candle Data for test 7
# Set stop-loss at 2% ROI at 3%
# TC7: ROI Triggers 3% Gain
tc6 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
[2, 4987, 5300, 4950, 5050, 6172, 0, 0],
[3, 4975, 5000, 4950, 4962, 6172, 0, 0],
[4, 4962, 4987, 4972, 4950, 6172, 0, 0],
[5, 4950, 4975, 4925, 4950, 6172, 0, 0]],
stop_loss=-0.02, roi=0.03, profit_perc=0.03,
trades=[BTrade(sell_reason=SellType.ROI, open_tick=1, close_tick=2)]
)
TESTS = [
tc0,
tc1,
tc2,
tc3,
tc4,
tc5,
tc6,
]
@pytest.mark.parametrize("data", TESTS)
def test_backtest_results(default_conf, fee, mocker, caplog, data) -> None:
"""
run functional tests
"""
default_conf["stoploss"] = data.stop_loss
default_conf["minimal_roi"] = {"0": data.roi}
default_conf['ticker_interval'] = tests_ticker_interval
mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.0))
patch_exchange(mocker)
frame = _build_backtest_dataframe(data.data)
backtesting = Backtesting(default_conf)
backtesting.advise_buy = lambda a, m: frame
backtesting.advise_sell = lambda a, m: frame
caplog.set_level(logging.DEBUG)
pair = 'UNITTEST/BTC'
# Dummy data as we mock the analyze functions
data_processed = {pair: DataFrame()}
min_date, max_date = get_timeframe({pair: frame})
results = backtesting.backtest(
{
'stake_amount': default_conf['stake_amount'],
'processed': data_processed,
'max_open_trades': 10,
'start_date': min_date,
'end_date': max_date,
}
)
print(results.T)
assert len(results) == len(data.trades)
assert round(results["profit_percent"].sum(), 3) == round(data.profit_perc, 3)
for c, trade in enumerate(data.trades):
res = results.iloc[c]
assert res.sell_reason == trade.sell_reason
assert res.open_time == _get_frame_time_from_offset(trade.open_tick)
assert res.close_time == _get_frame_time_from_offset(trade.close_tick)

View File

@@ -11,13 +11,17 @@ import pandas as pd
import pytest
from arrow import Arrow
from freqtrade import DependencyException, constants, optimize
from freqtrade import DependencyException, constants
from freqtrade.arguments import Arguments, TimeRange
from freqtrade.data import history
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.optimize import get_timeframe
from freqtrade.optimize.backtesting import (Backtesting, setup_configuration,
start)
from freqtrade.tests.conftest import log_has, patch_exchange
from freqtrade.strategy.interface import SellType
from freqtrade.state import RunMode
from freqtrade.strategy.default_strategy import DefaultStrategy
from freqtrade.strategy.interface import SellType
from freqtrade.tests.conftest import log_has, patch_exchange
def get_args(args) -> List[str]:
@@ -33,22 +37,13 @@ def trim_dictlist(dict_list, num):
def load_data_test(what):
timerange = TimeRange(None, 'line', 0, -101)
data = optimize.load_data(None, ticker_interval='1m',
pairs=['UNITTEST/BTC'], timerange=timerange)
pair = data['UNITTEST/BTC']
pair = history.load_tickerdata_file(None, ticker_interval='1m',
pair='UNITTEST/BTC', timerange=timerange)
datalen = len(pair)
# Depending on the what parameter we now adjust the
# loaded data looks:
# pair :: [[ 1509836520000, unix timestamp in ms
# 0.00162008, open
# 0.00162008, high
# 0.00162008, low
# 0.00162008, close
# 108.14853839 base volume
# ]]
base = 0.001
if what == 'raise':
return {'UNITTEST/BTC': [
data = [
[
pair[x][0], # Keep old dates
x * base, # But replace O,H,L,C
@@ -57,9 +52,9 @@ def load_data_test(what):
x * base,
pair[x][5], # Keep old volume
] for x in range(0, datalen)
]}
]
if what == 'lower':
return {'UNITTEST/BTC': [
data = [
[
pair[x][0], # Keep old dates
1 - x * base, # But replace O,H,L,C
@@ -68,10 +63,10 @@ def load_data_test(what):
1 - x * base,
pair[x][5] # Keep old volume
] for x in range(0, datalen)
]}
]
if what == 'sine':
hz = 0.1 # frequency
return {'UNITTEST/BTC': [
data = [
[
pair[x][0], # Keep old dates
math.sin(x * hz) / 1000 + base, # But replace O,H,L,C
@@ -80,23 +75,27 @@ def load_data_test(what):
math.sin(x * hz) / 1000 + base,
pair[x][5] # Keep old volume
] for x in range(0, datalen)
]}
return data
]
return {'UNITTEST/BTC': parse_ticker_dataframe(data, '1m', fill_missing=True)}
def simple_backtest(config, contour, num_results, mocker) -> None:
patch_exchange(mocker)
config['ticker_interval'] = '1m'
backtesting = Backtesting(config)
data = load_data_test(contour)
processed = backtesting.tickerdata_to_dataframe(data)
processed = backtesting.strategy.tickerdata_to_dataframe(data)
min_date, max_date = get_timeframe(processed)
assert isinstance(processed, dict)
results = backtesting.backtest(
{
'stake_amount': config['stake_amount'],
'processed': processed,
'max_open_trades': 1,
'position_stacking': False
'position_stacking': False,
'start_date': min_date,
'end_date': max_date,
}
)
# results :: <class 'pandas.core.frame.DataFrame'>
@@ -105,30 +104,34 @@ def simple_backtest(config, contour, num_results, mocker) -> None:
def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=False,
timerange=None, exchange=None):
tickerdata = optimize.load_tickerdata_file(datadir, 'UNITTEST/BTC', '1m', timerange=timerange)
pairdata = {'UNITTEST/BTC': tickerdata}
tickerdata = history.load_tickerdata_file(datadir, 'UNITTEST/BTC', '1m', timerange=timerange)
pairdata = {'UNITTEST/BTC': parse_ticker_dataframe(tickerdata, '1m', fill_missing=True)}
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)
return ticks[pair]
ticks = history.load_tickerdata_file(None, ticker_interval=tickfreq, pair=pair)
ticks = ticks[-201:]
return ticks
# FIX: fixturize this?
def _make_backtest_conf(mocker, conf=None, pair='UNITTEST/BTC', record=None):
data = optimize.load_data(None, ticker_interval='8m', pairs=[pair])
data = history.load_data(datadir=None, ticker_interval='1m', pairs=[pair])
data = trim_dictlist(data, -201)
patch_exchange(mocker)
backtesting = Backtesting(conf)
processed = backtesting.strategy.tickerdata_to_dataframe(data)
min_date, max_date = get_timeframe(processed)
return {
'stake_amount': conf['stake_amount'],
'processed': backtesting.tickerdata_to_dataframe(data),
'processed': processed,
'max_open_trades': 10,
'position_stacking': False,
'record': record
'record': record,
'start_date': min_date,
'end_date': max_date,
}
@@ -198,12 +201,15 @@ def test_setup_configuration_without_arguments(mocker, default_conf, caplog) ->
assert 'timerange' not in config
assert 'export' not in config
assert 'runmode' in config
assert config['runmode'] == RunMode.BACKTEST
def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> None:
def test_setup_bt_configuration_with_arguments(mocker, default_conf, caplog) -> None:
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf)
))
mocker.patch('freqtrade.configuration.Configuration._create_datadir', lambda s, c, x: x)
args = [
'--config', 'config.json',
@@ -227,6 +233,8 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
assert 'exchange' in config
assert 'pair_whitelist' in config['exchange']
assert 'datadir' in config
assert config['runmode'] == RunMode.BACKTEST
assert log_has(
'Using data folder: {} ...'.format(config['datadir']),
caplog.record_tuples
@@ -313,22 +321,22 @@ def test_backtesting_init(mocker, default_conf) -> None:
backtesting = Backtesting(default_conf)
assert backtesting.config == default_conf
assert backtesting.ticker_interval == '5m'
assert callable(backtesting.tickerdata_to_dataframe)
assert callable(backtesting.strategy.tickerdata_to_dataframe)
assert callable(backtesting.advise_buy)
assert callable(backtesting.advise_sell)
get_fee.assert_called()
assert backtesting.fee == 0.5
def test_tickerdata_to_dataframe(default_conf, mocker) -> None:
def test_tickerdata_to_dataframe_bt(default_conf, mocker) -> None:
patch_exchange(mocker)
timerange = TimeRange(None, 'line', 0, -100)
tick = optimize.load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
tickerlist = {'UNITTEST/BTC': tick}
tick = history.load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick, '1m', fill_missing=True)}
backtesting = Backtesting(default_conf)
data = backtesting.tickerdata_to_dataframe(tickerlist)
assert len(data['UNITTEST/BTC']) == 99
data = backtesting.strategy.tickerdata_to_dataframe(tickerlist)
assert len(data['UNITTEST/BTC']) == 102
# Load strategy to compare the result between Backtesting function and strategy are the same
strategy = DefaultStrategy(default_conf)
@@ -336,24 +344,9 @@ def test_tickerdata_to_dataframe(default_conf, mocker) -> None:
assert data['UNITTEST/BTC'].equals(data2['UNITTEST/BTC'])
def test_get_timeframe(default_conf, mocker) -> None:
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
data = backtesting.tickerdata_to_dataframe(
optimize.load_data(
None,
ticker_interval='1m',
pairs=['UNITTEST/BTC']
)
)
min_date, max_date = backtesting.get_timeframe(data)
assert min_date.isoformat() == '2017-11-04T23:02:00+00:00'
assert max_date.isoformat() == '2017-11-14T22:58:00+00:00'
def test_generate_text_table(default_conf, mocker):
patch_exchange(mocker)
default_conf['max_open_trades'] = 2
backtesting = Backtesting(default_conf)
results = pd.DataFrame(
@@ -369,13 +362,13 @@ def test_generate_text_table(default_conf, mocker):
result_str = (
'| pair | buy count | avg profit % | cum profit % | '
'total profit BTC | avg duration | profit | loss |\n'
'tot profit BTC | tot profit % | avg duration | profit | loss |\n'
'|:--------|------------:|---------------:|---------------:|'
'-------------------:|:---------------|---------:|-------:|\n'
'| ETH/BTC | 2 | 15.00 | 30.00 | '
'0.60000000 | 0:20:00 | 2 | 0 |\n'
'| TOTAL | 2 | 15.00 | 30.00 | '
'0.60000000 | 0:20:00 | 2 | 0 |'
'-----------------:|---------------:|:---------------|---------:|-------:|\n'
'| ETH/BTC | 2 | 15.00 | 30.00 | '
'0.60000000 | 15.00 | 0:20:00 | 2 | 0 |\n'
'| TOTAL | 2 | 15.00 | 30.00 | '
'0.60000000 | 15.00 | 0:20:00 | 2 | 0 |'
)
assert backtesting._generate_text_table(data={'ETH/BTC': {}}, results=results) == result_str
@@ -411,6 +404,7 @@ def test_generate_text_table_strategyn(default_conf, mocker):
Test Backtesting.generate_text_table_sell_reason() method
"""
patch_exchange(mocker)
default_conf['max_open_trades'] = 2
backtesting = Backtesting(default_conf)
results = {}
results['ETH/BTC'] = pd.DataFrame(
@@ -438,34 +432,34 @@ def test_generate_text_table_strategyn(default_conf, mocker):
result_str = (
'| Strategy | buy count | avg profit % | cum profit % '
'| total profit BTC | avg duration | profit | loss |\n'
'| tot profit BTC | tot profit % | avg duration | profit | loss |\n'
'|:-----------|------------:|---------------:|---------------:'
'|-------------------:|:---------------|---------:|-------:|\n'
'|-----------------:|---------------:|:---------------|---------:|-------:|\n'
'| ETH/BTC | 3 | 20.00 | 60.00 '
'| 1.10000000 | 0:17:00 | 3 | 0 |\n'
'| 1.10000000 | 30.00 | 0:17:00 | 3 | 0 |\n'
'| LTC/BTC | 3 | 30.00 | 90.00 '
'| 1.30000000 | 0:20:00 | 3 | 0 |'
'| 1.30000000 | 45.00 | 0:20:00 | 3 | 0 |'
)
print(backtesting._generate_text_table_strategy(all_results=results))
assert backtesting._generate_text_table_strategy(all_results=results) == result_str
def test_backtesting_start(default_conf, mocker, caplog) -> None:
def get_timeframe(input1, input2):
def get_timeframe(input1):
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.data.history.load_data', mocked_load_data)
mocker.patch('freqtrade.optimize.get_timeframe', get_timeframe)
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', MagicMock())
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.optimize.backtesting.Backtesting',
backtest=MagicMock(),
_generate_text_table=MagicMock(return_value='1'),
get_timeframe=get_timeframe,
)
default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
default_conf['ticker_interval'] = 1
default_conf['ticker_interval'] = '1m'
default_conf['live'] = False
default_conf['datadir'] = None
default_conf['export'] = None
@@ -486,17 +480,17 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
def test_backtesting_start_no_data(default_conf, mocker, caplog) -> None:
def get_timeframe(input1, input2):
def get_timeframe(input1):
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.data.history.load_data', MagicMock(return_value={}))
mocker.patch('freqtrade.optimize.get_timeframe', get_timeframe)
mocker.patch('freqtrade.exchange.Exchange.refresh_latest_ohlcv', MagicMock())
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.optimize.backtesting.Backtesting',
backtest=MagicMock(),
_generate_text_table=MagicMock(return_value='1'),
get_timeframe=get_timeframe,
)
default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
@@ -518,15 +512,19 @@ def test_backtest(default_conf, fee, mocker) -> None:
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
pair = 'UNITTEST/BTC'
data = optimize.load_data(None, ticker_interval='5m', pairs=['UNITTEST/BTC'])
data = trim_dictlist(data, -200)
data_processed = backtesting.tickerdata_to_dataframe(data)
timerange = TimeRange(None, 'line', 0, -201)
data = history.load_data(datadir=None, ticker_interval='5m', pairs=['UNITTEST/BTC'],
timerange=timerange)
data_processed = backtesting.strategy.tickerdata_to_dataframe(data)
min_date, max_date = get_timeframe(data_processed)
results = backtesting.backtest(
{
'stake_amount': default_conf['stake_amount'],
'processed': data_processed,
'max_open_trades': 10,
'position_stacking': False
'position_stacking': False,
'start_date': min_date,
'end_date': max_date,
}
)
assert not results.empty
@@ -534,18 +532,19 @@ def test_backtest(default_conf, fee, mocker) -> None:
expected = pd.DataFrame(
{'pair': [pair, pair],
'profit_percent': [0.00029975, 0.00056708],
'profit_abs': [1.49e-06, 7.6e-07],
'open_time': [Arrow(2018, 1, 29, 18, 40, 0).datetime,
Arrow(2018, 1, 30, 3, 30, 0).datetime],
'close_time': [Arrow(2018, 1, 29, 22, 40, 0).datetime,
Arrow(2018, 1, 30, 4, 20, 0).datetime],
'open_index': [77, 183],
'close_index': [125, 193],
'trade_duration': [240, 50],
'profit_percent': [0.0, 0.0],
'profit_abs': [0.0, 0.0],
'open_time': pd.to_datetime([Arrow(2018, 1, 29, 18, 40, 0).datetime,
Arrow(2018, 1, 30, 3, 30, 0).datetime], utc=True
),
'close_time': pd.to_datetime([Arrow(2018, 1, 29, 22, 35, 0).datetime,
Arrow(2018, 1, 30, 4, 10, 0).datetime], utc=True),
'open_index': [78, 184],
'close_index': [125, 192],
'trade_duration': [235, 40],
'open_at_end': [False, False],
'open_rate': [0.104445, 0.10302485],
'close_rate': [0.105, 0.10359999],
'close_rate': [0.104969, 0.103541],
'sell_reason': [SellType.ROI, SellType.ROI]
})
pd.testing.assert_frame_equal(results, expected)
@@ -555,9 +554,11 @@ def test_backtest(default_conf, fee, mocker) -> None:
# Check open trade rate alignes to open rate
assert ln is not None
assert round(ln.iloc[0]["open"], 6) == round(t["open_rate"], 6)
# check close trade rate alignes to close rate
# check close trade rate alignes to close rate or is between high and low
ln = data_pair.loc[data_pair["date"] == t["close_time"]]
assert round(ln.iloc[0]["open"], 6) == round(t["close_rate"], 6)
assert (round(ln.iloc[0]["open"], 6) == round(t["close_rate"], 6) or
round(ln.iloc[0]["low"], 6) < round(
t["close_rate"], 6) < round(ln.iloc[0]["high"], 6))
def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None:
@@ -565,15 +566,20 @@ def test_backtest_1min_ticker_interval(default_conf, fee, mocker) -> None:
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
# Run a backtesting for an exiting 5min ticker_interval
data = optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
data = trim_dictlist(data, -200)
# Run a backtesting for an exiting 1min ticker_interval
timerange = TimeRange(None, 'line', 0, -200)
data = history.load_data(datadir=None, ticker_interval='1m', pairs=['UNITTEST/BTC'],
timerange=timerange)
processed = backtesting.strategy.tickerdata_to_dataframe(data)
min_date, max_date = get_timeframe(processed)
results = backtesting.backtest(
{
'stake_amount': default_conf['stake_amount'],
'processed': backtesting.tickerdata_to_dataframe(data),
'processed': processed,
'max_open_trades': 1,
'position_stacking': False
'position_stacking': False,
'start_date': min_date,
'end_date': max_date,
}
)
assert not results.empty
@@ -585,7 +591,7 @@ def test_processed(default_conf, mocker) -> None:
backtesting = Backtesting(default_conf)
dict_of_tickerrows = load_data_test('raise')
dataframes = backtesting.tickerdata_to_dataframe(dict_of_tickerrows)
dataframes = backtesting.strategy.tickerdata_to_dataframe(dict_of_tickerrows)
dataframe = dataframes['UNITTEST/BTC']
cols = dataframe.columns
# assert the dataframe got some of the indicator columns
@@ -596,26 +602,14 @@ def test_processed(default_conf, mocker) -> None:
def test_backtest_pricecontours(default_conf, fee, mocker) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
tests = [['raise', 18], ['lower', 0], ['sine', 16]]
tests = [['raise', 19], ['lower', 0], ['sine', 18]]
# We need to enable sell-signal - otherwise it sells on ROI!!
default_conf['experimental'] = {"use_sell_signal": True}
for [contour, numres] in tests:
simple_backtest(default_conf, contour, numres, mocker)
# Test backtest using offline data (testdata directory)
def test_backtest_ticks(default_conf, fee, mocker):
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
patch_exchange(mocker)
ticks = [1, 5]
fun = Backtesting(default_conf).advise_buy
for _ in ticks:
backtest_conf = _make_backtest_conf(mocker, conf=default_conf)
backtesting = Backtesting(default_conf)
backtesting.advise_buy = fun # Override
backtesting.advise_sell = fun # Override
results = backtesting.backtest(backtest_conf)
assert not results.empty
def test_backtest_clash_buy_sell(mocker, default_conf):
# Override the default buy trend function in our default_strategy
def fun(dataframe=None, pair=None):
@@ -648,15 +642,94 @@ def test_backtest_only_sell(mocker, default_conf):
def test_backtest_alternate_buy_sell(default_conf, fee, mocker):
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
mocker.patch('freqtrade.optimize.backtesting.file_dump_json', MagicMock())
backtest_conf = _make_backtest_conf(mocker, conf=default_conf, pair='UNITTEST/BTC')
# We need to enable sell-signal - otherwise it sells on ROI!!
default_conf['experimental'] = {"use_sell_signal": True}
default_conf['ticker_interval'] = '1m'
backtesting = Backtesting(default_conf)
backtesting.advise_buy = _trend_alternate # Override
backtesting.advise_sell = _trend_alternate # Override
results = backtesting.backtest(backtest_conf)
backtesting._store_backtest_result("test_.json", results)
assert len(results) == 4
# 200 candles in backtest data
# won't buy on first (shifted by 1)
# 100 buys signals
assert len(results) == 100
# One trade was force-closed at the end
assert len(results.loc[results.open_at_end]) == 1
assert len(results.loc[results.open_at_end]) == 0
def test_backtest_multi_pair(default_conf, fee, mocker):
def evaluate_result_multi(results, freq, max_open_trades):
# Find overlapping trades by expanding each trade once per period
# and then counting overlaps
dates = [pd.Series(pd.date_range(row[1].open_time, row[1].close_time, freq=freq))
for row in results[['open_time', 'close_time']].iterrows()]
deltas = [len(x) for x in dates]
dates = pd.Series(pd.concat(dates).values, name='date')
df2 = pd.DataFrame(np.repeat(results.values, deltas, axis=0), columns=results.columns)
df2 = df2.astype(dtype={"open_time": "datetime64", "close_time": "datetime64"})
df2 = pd.concat([dates, df2], axis=1)
df2 = df2.set_index('date')
df_final = df2.resample(freq)[['pair']].count()
return df_final[df_final['pair'] > max_open_trades]
def _trend_alternate_hold(dataframe=None, metadata=None):
"""
Buy every 8th candle - sell every other 8th -2 (hold on to pairs a bit)
"""
multi = 8
dataframe['buy'] = np.where(dataframe.index % multi == 0, 1, 0)
dataframe['sell'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0)
if metadata['pair'] in('ETH/BTC', 'LTC/BTC'):
dataframe['buy'] = dataframe['buy'].shift(-4)
dataframe['sell'] = dataframe['sell'].shift(-4)
return dataframe
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
patch_exchange(mocker)
pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC']
data = history.load_data(datadir=None, ticker_interval='5m', pairs=pairs)
data = trim_dictlist(data, -500)
# We need to enable sell-signal - otherwise it sells on ROI!!
default_conf['experimental'] = {"use_sell_signal": True}
default_conf['ticker_interval'] = '5m'
backtesting = Backtesting(default_conf)
backtesting.advise_buy = _trend_alternate_hold # Override
backtesting.advise_sell = _trend_alternate_hold # Override
data_processed = backtesting.strategy.tickerdata_to_dataframe(data)
min_date, max_date = get_timeframe(data_processed)
backtest_conf = {
'stake_amount': default_conf['stake_amount'],
'processed': data_processed,
'max_open_trades': 3,
'position_stacking': False,
'start_date': min_date,
'end_date': max_date,
}
results = backtesting.backtest(backtest_conf)
# Make sure we have parallel trades
assert len(evaluate_result_multi(results, '5min', 2)) > 0
# make sure we don't have trades with more than configured max_open_trades
assert len(evaluate_result_multi(results, '5min', 3)) == 0
backtest_conf = {
'stake_amount': default_conf['stake_amount'],
'processed': data_processed,
'max_open_trades': 1,
'position_stacking': False,
'start_date': min_date,
'end_date': max_date,
}
results = backtesting.backtest(backtest_conf)
assert len(evaluate_result_multi(results, '5min', 1)) == 0
def test_backtest_record(default_conf, fee, mocker):
@@ -733,9 +806,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 +854,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

@@ -0,0 +1,136 @@
# pragma pylint: disable=missing-docstring, C0103, C0330
# pragma pylint: disable=protected-access, too-many-lines, invalid-name, too-many-arguments
from unittest.mock import MagicMock
import json
from typing import List
from freqtrade.edge import PairInfo
from freqtrade.arguments import Arguments
from freqtrade.optimize.edge_cli import (EdgeCli, setup_configuration, start)
from freqtrade.state import RunMode
from freqtrade.tests.conftest import log_has, patch_exchange
def get_args(args) -> List[str]:
return Arguments(args, '').get_parsed_arg()
def test_setup_configuration_without_arguments(mocker, default_conf, caplog) -> None:
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf)
))
args = [
'--config', 'config.json',
'--strategy', 'DefaultStrategy',
'edge'
]
config = setup_configuration(get_args(args))
assert config['runmode'] == RunMode.EDGECLI
assert 'max_open_trades' in config
assert 'stake_currency' in config
assert 'stake_amount' in config
assert 'exchange' in config
assert 'pair_whitelist' in config['exchange']
assert 'datadir' in config
assert log_has(
'Using data folder: {} ...'.format(config['datadir']),
caplog.record_tuples
)
assert 'ticker_interval' in config
assert not log_has('Parameter -i/--ticker-interval detected ...', caplog.record_tuples)
assert 'refresh_pairs' not in config
assert not log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples)
assert 'timerange' not in config
assert 'stoploss_range' not in config
def test_setup_edge_configuration_with_arguments(mocker, edge_conf, caplog) -> None:
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(edge_conf)
))
mocker.patch('freqtrade.configuration.Configuration._create_datadir', lambda s, c, x: x)
args = [
'--config', 'config.json',
'--strategy', 'DefaultStrategy',
'--datadir', '/foo/bar',
'edge',
'--ticker-interval', '1m',
'--refresh-pairs-cached',
'--timerange', ':100',
'--stoplosses=-0.01,-0.10,-0.001'
]
config = setup_configuration(get_args(args))
assert 'max_open_trades' in config
assert 'stake_currency' in config
assert 'stake_amount' in config
assert 'exchange' in config
assert 'pair_whitelist' in config['exchange']
assert 'datadir' in config
assert config['runmode'] == RunMode.EDGECLI
assert log_has(
'Using data folder: {} ...'.format(config['datadir']),
caplog.record_tuples
)
assert 'ticker_interval' in config
assert log_has('Parameter -i/--ticker-interval detected ...', caplog.record_tuples)
assert log_has(
'Using ticker_interval: 1m ...',
caplog.record_tuples
)
assert 'refresh_pairs' in config
assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples)
assert 'timerange' in config
assert log_has(
'Parameter --timerange detected: {} ...'.format(config['timerange']),
caplog.record_tuples
)
def test_start(mocker, fee, edge_conf, caplog) -> None:
start_mock = MagicMock()
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
patch_exchange(mocker)
mocker.patch('freqtrade.optimize.edge_cli.EdgeCli.start', start_mock)
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(edge_conf)
))
args = [
'--config', 'config.json',
'--strategy', 'DefaultStrategy',
'edge'
]
args = get_args(args)
start(args)
assert log_has(
'Starting freqtrade in Edge mode',
caplog.record_tuples
)
assert start_mock.call_count == 1
def test_edge_init(mocker, edge_conf) -> None:
patch_exchange(mocker)
edge_cli = EdgeCli(edge_conf)
assert edge_cli.config == edge_conf
assert callable(edge_cli.edge.calculate)
def test_generate_edge_table(edge_conf, mocker):
patch_exchange(mocker)
edge_cli = EdgeCli(edge_conf)
results = {}
results['ETH/BTC'] = PairInfo(-0.01, 0.60, 2, 1, 3, 10, 60)
assert edge_cli._generate_edge_table(results).count(':|') == 7
assert edge_cli._generate_edge_table(results).count('| ETH/BTC |') == 1
assert edge_cli._generate_edge_table(results).count(
'| risk reward ratio | required risk reward | expectancy |') == 1

View File

@@ -1,13 +1,16 @@
# pragma pylint: disable=missing-docstring,W0212,C0103
from datetime import datetime
import os
from unittest.mock import MagicMock
import pandas as pd
import pytest
from freqtrade.optimize.__init__ import load_tickerdata_file
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.data.history import load_tickerdata_file
from freqtrade.optimize.hyperopt import Hyperopt, start
from freqtrade.strategy.resolver import StrategyResolver
from freqtrade.optimize.default_hyperopt import DefaultHyperOpts
from freqtrade.resolvers import StrategyResolver, HyperOptResolver
from freqtrade.tests.conftest import log_has, patch_exchange
from freqtrade.tests.optimize.test_backtesting import get_args
@@ -36,6 +39,28 @@ def create_trials(mocker, hyperopt) -> None:
return [{'loss': 1, 'result': 'foo', 'params': {}}]
def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
mocker.patch(
'freqtrade.configuration.Configuration._load_config_file',
lambda *args, **kwargs: default_conf
)
hyperopts = DefaultHyperOpts
delattr(hyperopts, 'populate_buy_trend')
delattr(hyperopts, 'populate_sell_trend')
mocker.patch(
'freqtrade.resolvers.hyperopt_resolver.HyperOptResolver._load_hyperopt',
MagicMock(return_value=hyperopts)
)
x = HyperOptResolver(default_conf, ).hyperopt
assert not hasattr(x, 'populate_buy_trend')
assert not hasattr(x, 'populate_sell_trend')
assert log_has("Custom Hyperopt does not provide populate_sell_trend. "
"Using populate_sell_trend from DefaultStrategy.", caplog.record_tuples)
assert log_has("Custom Hyperopt does not provide populate_buy_trend. "
"Using populate_buy_trend from DefaultStrategy.", caplog.record_tuples)
def test_start(mocker, default_conf, caplog) -> None:
start_mock = MagicMock()
mocker.patch(
@@ -65,6 +90,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'})
@@ -150,7 +200,7 @@ def test_roi_table_generation(hyperopt) -> None:
'roi_p3': 3,
}
assert hyperopt.generate_roi_table(params) == {0: 6, 15: 3, 25: 1, 30: 0}
assert hyperopt.custom_hyperopt.generate_roi_table(params) == {0: 6, 15: 3, 25: 1, 30: 0}
def test_start_calls_optimizer(mocker, default_conf, caplog) -> None:
@@ -169,12 +219,12 @@ def test_start_calls_optimizer(mocker, default_conf, caplog) -> None:
default_conf.update({'spaces': 'all'})
hyperopt = Hyperopt(default_conf)
hyperopt.tickerdata_to_dataframe = MagicMock()
hyperopt.strategy.tickerdata_to_dataframe = MagicMock()
hyperopt.start()
parallel.assert_called_once()
assert 'Best result:\nfoo result\nwith values:\n{}' in caplog.text
assert 'Best result:\nfoo result\nwith values:\n\n' in caplog.text
assert dumper.called
@@ -216,9 +266,10 @@ def test_has_space(hyperopt):
def test_populate_indicators(hyperopt) -> None:
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
tickerlist = {'UNITTEST/BTC': tick}
dataframes = hyperopt.tickerdata_to_dataframe(tickerlist)
dataframe = hyperopt.populate_indicators(dataframes['UNITTEST/BTC'], {'pair': 'UNITTEST/BTC'})
tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick, '1m', fill_missing=True)}
dataframes = hyperopt.strategy.tickerdata_to_dataframe(tickerlist)
dataframe = hyperopt.custom_hyperopt.populate_indicators(dataframes['UNITTEST/BTC'],
{'pair': 'UNITTEST/BTC'})
# Check if some indicators are generated. We will not test all of them
assert 'adx' in dataframe
@@ -228,11 +279,12 @@ def test_populate_indicators(hyperopt) -> None:
def test_buy_strategy_generator(hyperopt) -> None:
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
tickerlist = {'UNITTEST/BTC': tick}
dataframes = hyperopt.tickerdata_to_dataframe(tickerlist)
dataframe = hyperopt.populate_indicators(dataframes['UNITTEST/BTC'], {'pair': 'UNITTEST/BTC'})
tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick, '1m', fill_missing=True)}
dataframes = hyperopt.strategy.tickerdata_to_dataframe(tickerlist)
dataframe = hyperopt.custom_hyperopt.populate_indicators(dataframes['UNITTEST/BTC'],
{'pair': 'UNITTEST/BTC'})
populate_buy_trend = hyperopt.buy_strategy_generator(
populate_buy_trend = hyperopt.custom_hyperopt.buy_strategy_generator(
{
'adx-value': 20,
'fastd-value': 20,
@@ -266,6 +318,10 @@ def test_generate_optimizer(mocker, default_conf) -> None:
'freqtrade.optimize.hyperopt.Hyperopt.backtest',
MagicMock(return_value=backtest_result)
)
mocker.patch(
'freqtrade.optimize.hyperopt.get_timeframe',
MagicMock(return_value=(datetime(2017, 12, 10), datetime(2017, 12, 13)))
)
patch_exchange(mocker)
mocker.patch('freqtrade.optimize.hyperopt.load', MagicMock())
@@ -279,6 +335,15 @@ def test_generate_optimizer(mocker, default_conf) -> None:
'mfi-enabled': False,
'rsi-enabled': False,
'trigger': 'macd_cross_signal',
'sell-adx-value': 0,
'sell-fastd-value': 75,
'sell-mfi-value': 0,
'sell-rsi-value': 0,
'sell-adx-enabled': False,
'sell-fastd-enabled': True,
'sell-mfi-enabled': False,
'sell-rsi-enabled': False,
'sell-trigger': 'macd_cross_signal',
'roi_t1': 60.0,
'roi_t2': 30.0,
'roi_t3': 20.0,

View File

@@ -1,435 +1,65 @@
# pragma pylint: disable=missing-docstring, protected-access, C0103
import json
import os
import uuid
from shutil import copyfile
import arrow
from freqtrade import optimize
from freqtrade import optimize, constants
from freqtrade.arguments import TimeRange
from freqtrade.misc import file_dump_json
from freqtrade.optimize.__init__ import (download_backtesting_testdata,
download_pairs,
load_cached_data_for_updating,
load_tickerdata_file,
make_testdata_path, trim_tickerlist)
from freqtrade.tests.conftest import get_patched_exchange, log_has
# Change this if modifying UNITTEST/BTC testdatafile
_BTC_UNITTEST_LENGTH = 13681
from freqtrade.data import history
from freqtrade.strategy.default_strategy import DefaultStrategy
from freqtrade.tests.conftest import log_has, patch_exchange
def _backup_file(file: str, copy_file: bool = False) -> None:
"""
Backup existing file to avoid deleting the user file
:param file: complete path to the file
:param touch_file: create an empty file in replacement
:return: None
"""
file_swp = file + '.swp'
if os.path.isfile(file):
os.rename(file, file_swp)
def test_get_timeframe(default_conf, mocker) -> None:
patch_exchange(mocker)
strategy = DefaultStrategy(default_conf)
if copy_file:
copyfile(file_swp, file)
data = strategy.tickerdata_to_dataframe(
history.load_data(
datadir=None,
ticker_interval='1m',
pairs=['UNITTEST/BTC']
)
)
min_date, max_date = optimize.get_timeframe(data)
assert min_date.isoformat() == '2017-11-04T23:02:00+00:00'
assert max_date.isoformat() == '2017-11-14T22:58:00+00:00'
def _clean_test_file(file: str) -> None:
"""
Backup existing file to avoid deleting the user file
:param file: complete path to the file
:return: None
"""
file_swp = file + '.swp'
# 1. Delete file from the test
if os.path.isfile(file):
os.remove(file)
def test_validate_backtest_data_warn(default_conf, mocker, caplog) -> None:
patch_exchange(mocker)
strategy = DefaultStrategy(default_conf)
# 2. Rollback to the initial file
if os.path.isfile(file_swp):
os.rename(file_swp, file)
data = strategy.tickerdata_to_dataframe(
history.load_data(
datadir=None,
ticker_interval='1m',
pairs=['UNITTEST/BTC'],
fill_up_missing=False
)
)
min_date, max_date = optimize.get_timeframe(data)
caplog.clear()
assert optimize.validate_backtest_data(data, min_date, max_date,
constants.TICKER_INTERVAL_MINUTES["1m"])
assert len(caplog.record_tuples) == 1
assert log_has(
"UNITTEST/BTC has missing frames: expected 14396, got 13680, that's 716 missing values",
caplog.record_tuples)
def test_load_data_30min_ticker(ticker_history, mocker, caplog, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_candle_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')
assert os.path.isfile(file) is True
assert not log_has('Download the pair: "UNITTEST/BTC", Interval: 30m', caplog.record_tuples)
_clean_test_file(file)
def test_validate_backtest_data(default_conf, mocker, caplog) -> None:
patch_exchange(mocker)
strategy = DefaultStrategy(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)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-5m.json')
_backup_file(file, copy_file=True)
optimize.load_data(None, pairs=['UNITTEST/BTC'], ticker_interval='5m')
assert os.path.isfile(file) is True
assert not log_has('Download the pair: "UNITTEST/BTC", Interval: 5m', caplog.record_tuples)
_clean_test_file(file)
def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_candle_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'])
assert os.path.isfile(file) is True
assert not log_has('Download the pair: "UNITTEST/BTC", Interval: 1m', caplog.record_tuples)
_clean_test_file(file)
def test_load_data_with_new_pair_1min(ticker_history, mocker, caplog, default_conf) -> None:
"""
Test load_data() with 1 min ticker
"""
mocker.patch('freqtrade.exchange.Exchange.get_candle_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')
_backup_file(file)
# do not download a new pair if refresh_pairs isn't set
optimize.load_data(None,
ticker_interval='1m',
refresh_pairs=False,
pairs=['MEME/BTC'])
assert os.path.isfile(file) is False
assert log_has('No data for pair: "MEME/BTC", Interval: 1m. '
'Use --refresh-pairs-cached to download the data',
caplog.record_tuples)
# download a new pair if refresh_pairs is set
optimize.load_data(None,
ticker_interval='1m',
refresh_pairs=True,
exchange=exchange,
pairs=['MEME/BTC'])
assert os.path.isfile(file) is True
assert log_has('Download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples)
_clean_test_file(file)
def test_testdata_path() -> None:
assert os.path.join('freqtrade', 'tests', 'testdata') in make_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)
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')
file2_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'CFI_BTC-1m.json')
file2_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'CFI_BTC-5m.json')
_backup_file(file1_1)
_backup_file(file1_5)
_backup_file(file2_1)
_backup_file(file2_5)
assert os.path.isfile(file1_1) is False
assert os.path.isfile(file2_1) is False
assert download_pairs(None, exchange,
pairs=['MEME/BTC', 'CFI/BTC'], ticker_interval='1m') is True
assert os.path.isfile(file1_1) is True
assert os.path.isfile(file2_1) is True
# clean files freshly downloaded
_clean_test_file(file1_1)
_clean_test_file(file2_1)
assert os.path.isfile(file1_5) is False
assert os.path.isfile(file2_5) is False
assert download_pairs(None, exchange,
pairs=['MEME/BTC', 'CFI/BTC'], ticker_interval='5m') is True
assert os.path.isfile(file1_5) is True
assert os.path.isfile(file2_5) is True
# clean files freshly downloaded
_clean_test_file(file1_5)
_clean_test_file(file2_5)
def test_load_cached_data_for_updating(mocker) -> None:
datadir = os.path.join(os.path.dirname(__file__), '..', 'testdata')
test_data = None
test_filename = os.path.join(datadir, 'UNITTEST_BTC-1m.json')
with open(test_filename, "rt") as file:
test_data = json.load(file)
# change now time to test 'line' cases
# now = last cached item + 1 hour
now_ts = test_data[-1][0] / 1000 + 60 * 60
mocker.patch('arrow.utcnow', return_value=arrow.get(now_ts))
# timeframe starts earlier than the cached data
# should fully update data
timerange = TimeRange('date', None, test_data[0][0] / 1000 - 1, 0)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
assert data == []
assert start_ts == test_data[0][0] - 1000
# same with 'line' timeframe
num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 120
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
TimeRange(None, 'line', 0, -num_lines))
assert data == []
assert start_ts < test_data[0][0] - 1
# timeframe starts in the center of the cached data
# should return the chached data w/o the last item
timerange = TimeRange('date', None, test_data[0][0] / 1000 + 1, 0)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
assert data == test_data[:-1]
assert test_data[-2][0] < start_ts < test_data[-1][0]
# same with 'line' timeframe
num_lines = (test_data[-1][0] - test_data[1][0]) / 1000 / 60 + 30
timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
assert data == test_data[:-1]
assert test_data[-2][0] < start_ts < test_data[-1][0]
# timeframe starts after the chached data
# should return the chached data w/o the last item
timerange = TimeRange('date', None, test_data[-1][0] / 1000 + 1, 0)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
assert data == test_data[:-1]
assert test_data[-2][0] < start_ts < test_data[-1][0]
# same with 'line' timeframe
num_lines = 30
timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
assert data == test_data[:-1]
assert test_data[-2][0] < start_ts < test_data[-1][0]
# no timeframe is set
# should return the chached data w/o the last item
num_lines = 30
timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename,
'1m',
timerange)
assert data == test_data[:-1]
assert test_data[-2][0] < start_ts < test_data[-1][0]
# no datafile exist
# should return timestamp start time
timerange = TimeRange('date', None, now_ts - 10000, 0)
data, start_ts = load_cached_data_for_updating(test_filename + 'unexist',
'1m',
timerange)
assert data == []
assert start_ts == (now_ts - 10000) * 1000
# same with 'line' timeframe
num_lines = 30
timerange = TimeRange(None, 'line', 0, -num_lines)
data, start_ts = load_cached_data_for_updating(test_filename + 'unexist',
'1m',
timerange)
assert data == []
assert start_ts == (now_ts - num_lines * 60) * 1000
# no datafile exist, no timeframe is set
# should return an empty array and None
data, start_ts = load_cached_data_for_updating(test_filename + 'unexist',
'1m',
None)
assert data == []
assert start_ts is 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.optimize.__init__.download_backtesting_testdata',
side_effect=BaseException('File Error'))
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')
_backup_file(file1_1)
_backup_file(file1_5)
download_pairs(None, exchange, pairs=['MEME/BTC'], ticker_interval='1m')
# clean files freshly downloaded
_clean_test_file(file1_1)
_clean_test_file(file1_5)
assert log_has('Failed to download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples)
def test_download_backtesting_testdata(ticker_history, mocker, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=ticker_history)
exchange = get_patched_exchange(mocker, default_conf)
# Download a 1 min ticker file
file1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'XEL_BTC-1m.json')
_backup_file(file1)
download_backtesting_testdata(None, exchange, pair="XEL/BTC", tick_interval='1m')
assert os.path.isfile(file1) is True
_clean_test_file(file1)
# Download a 5 min ticker file
file2 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'STORJ_BTC-5m.json')
_backup_file(file2)
download_backtesting_testdata(None, exchange, pair="STORJ/BTC", tick_interval='5m')
assert os.path.isfile(file2) is True
_clean_test_file(file2)
def test_download_backtesting_testdata2(mocker, default_conf) -> None:
tick = [
[1509836520000, 0.00162008, 0.00162008, 0.00162008, 0.00162008, 108.14853839],
[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)
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')
assert json_dump_mock.call_count == 2
def test_load_tickerdata_file() -> None:
# 7 does not exist in either format.
assert not load_tickerdata_file(None, 'UNITTEST/BTC', '7m')
# 1 exists only as a .json
tickerdata = load_tickerdata_file(None, 'UNITTEST/BTC', '1m')
assert _BTC_UNITTEST_LENGTH == len(tickerdata)
# 8 .json is empty and will fail if it's loaded. .json.gz is a copy of 1.json
tickerdata = load_tickerdata_file(None, 'UNITTEST/BTC', '8m')
assert _BTC_UNITTEST_LENGTH == len(tickerdata)
def test_init(default_conf, mocker) -> None:
exchange = get_patched_exchange(mocker, default_conf)
assert {} == optimize.load_data(
'',
exchange=exchange,
pairs=[],
refresh_pairs=True,
ticker_interval=default_conf['ticker_interval']
timerange = TimeRange('index', 'index', 200, 250)
data = strategy.tickerdata_to_dataframe(
history.load_data(
datadir=None,
ticker_interval='5m',
pairs=['UNITTEST/BTC'],
timerange=timerange
)
)
def test_trim_tickerlist() -> None:
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-1m.json')
with open(file) as data_file:
ticker_list = json.load(data_file)
ticker_list_len = len(ticker_list)
# Test the pattern ^(-\d+)$
# This pattern uses the latest N elements
timerange = TimeRange(None, 'line', 0, -5)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_len == 5
assert ticker_list[0] is not ticker[0] # The first element should be different
assert ticker_list[-1] is ticker[-1] # The last element must be the same
# Test the pattern ^(\d+)-$
# This pattern keep X element from the end
timerange = TimeRange('line', None, 5, 0)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_len == 5
assert ticker_list[0] is ticker[0] # The first element must be the same
assert ticker_list[-1] is not ticker[-1] # The last element should be different
# Test the pattern ^(\d+)-(\d+)$
# This pattern extract a window
timerange = TimeRange('index', 'index', 5, 10)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_len == 5
assert ticker_list[0] is not ticker[0] # The first element should be different
assert ticker_list[5] is ticker[0] # The list starts at the index 5
assert ticker_list[9] is ticker[-1] # The list ends at the index 9 (5 elements)
# Test the pattern ^(\d{8})-(\d{8})$
# This pattern extract a window between the dates
timerange = TimeRange('date', 'date', ticker_list[5][0] / 1000, ticker_list[10][0] / 1000 - 1)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_len == 5
assert ticker_list[0] is not ticker[0] # The first element should be different
assert ticker_list[5] is ticker[0] # The list starts at the index 5
assert ticker_list[9] is ticker[-1] # The list ends at the index 9 (5 elements)
# Test the pattern ^-(\d{8})$
# This pattern extracts elements from the start to the date
timerange = TimeRange(None, 'date', 0, ticker_list[10][0] / 1000 - 1)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_len == 10
assert ticker_list[0] is ticker[0] # The start of the list is included
assert ticker_list[9] is ticker[-1] # The element 10 is not included
# Test the pattern ^(\d{8})-$
# This pattern extracts elements from the date to now
timerange = TimeRange('date', None, ticker_list[10][0] / 1000 - 1, None)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_len == ticker_list_len - 10
assert ticker_list[10] is ticker[0] # The first element is element #10
assert ticker_list[-1] is ticker[-1] # The last element is the same
# Test a wrong pattern
# This pattern must return the list unchanged
timerange = TimeRange(None, None, None, 5)
ticker = trim_tickerlist(ticker_list, timerange)
ticker_len = len(ticker)
assert ticker_list_len == ticker_len
def test_file_dump_json() -> None:
file = os.path.join(os.path.dirname(__file__), '..', 'testdata',
'test_{id}.json'.format(id=str(uuid.uuid4())))
data = {'bar': 'foo'}
# check the file we will create does not exist
assert os.path.isfile(file) is False
# Create the Json file
file_dump_json(file, data)
# Check the file was create
assert os.path.isfile(file) is True
# Open the Json file created and test the data is in it
with open(file) as data_file:
json_from_file = json.load(data_file)
assert 'bar' in json_from_file
assert json_from_file['bar'] == 'foo'
# Remove the file
_clean_test_file(file)
min_date, max_date = optimize.get_timeframe(data)
caplog.clear()
assert not optimize.validate_backtest_data(data, min_date, max_date,
constants.TICKER_INTERVAL_MINUTES["5m"])
assert len(caplog.record_tuples) == 0

View File

View File

@@ -0,0 +1,170 @@
# pragma pylint: disable=missing-docstring,C0103,protected-access
from unittest.mock import MagicMock
from freqtrade import OperationalException
from freqtrade.constants import AVAILABLE_PAIRLISTS
from freqtrade.resolvers import PairListResolver
from freqtrade.tests.conftest import get_patched_freqtradebot
import pytest
# whitelist, blacklist
@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'
]
default_conf['exchange']['pair_blacklist'] = [
'BLK/BTC'
]
default_conf['pairlist'] = {'method': 'StaticPairList',
'config': {'number_assets': 3}
}
return default_conf
def test_load_pairlist_noexist(mocker, markets, default_conf):
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
mocker.patch('freqtrade.exchange.Exchange.get_markets', markets)
with pytest.raises(ImportError,
match=r"Impossible to load Pairlist 'NonexistingPairList'."
r" This class does not exist or contains Python code errors"):
PairListResolver('NonexistingPairList', freqtradebot, default_conf).pairlist
def test_refresh_market_pair_not_in_whitelist(mocker, markets, whitelist_conf):
freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf)
mocker.patch('freqtrade.exchange.Exchange.get_markets', markets)
freqtradebot.pairlists.refresh_pairlist()
# List ordered by BaseVolume
whitelist = ['ETH/BTC', 'TKN/BTC']
# Ensure all except those in whitelist are removed
assert set(whitelist) == set(freqtradebot.pairlists.whitelist)
# Ensure config dict hasn't been changed
assert (whitelist_conf['exchange']['pair_whitelist'] ==
freqtradebot.config['exchange']['pair_whitelist'])
def test_refresh_pairlists(mocker, markets, whitelist_conf):
freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf)
mocker.patch('freqtrade.exchange.Exchange.get_markets', markets)
freqtradebot.pairlists.refresh_pairlist()
# List ordered by BaseVolume
whitelist = ['ETH/BTC', 'TKN/BTC']
# Ensure all except those in whitelist are removed
assert set(whitelist) == set(freqtradebot.pairlists.whitelist)
assert whitelist_conf['exchange']['pair_blacklist'] == freqtradebot.pairlists.blacklist
def test_refresh_pairlist_dynamic(mocker, markets, tickers, whitelist_conf):
whitelist_conf['pairlist'] = {'method': 'VolumePairList',
'config': {'number_assets': 5}
}
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_markets=markets,
get_tickers=tickers,
exchange_has=MagicMock(return_value=True)
)
freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf)
# argument: use the whitelist dynamically by exchange-volume
whitelist = ['ETH/BTC', 'TKN/BTC']
freqtradebot.pairlists.refresh_pairlist()
assert whitelist == freqtradebot.pairlists.whitelist
whitelist_conf['pairlist'] = {'method': 'VolumePairList',
'config': {}
}
with pytest.raises(OperationalException,
match=r'`number_assets` not specified. Please check your configuration '
r'for "pairlist.config.number_assets"'):
PairListResolver('VolumePairList', freqtradebot, whitelist_conf).pairlist
def test_VolumePairList_refresh_empty(mocker, markets_empty, whitelist_conf):
freqtradebot = get_patched_freqtradebot(mocker, whitelist_conf)
mocker.patch('freqtrade.exchange.Exchange.get_markets', markets_empty)
# argument: use the whitelist dynamically by exchange-volume
whitelist = []
whitelist_conf['exchange']['pair_whitelist'] = []
freqtradebot.pairlists.refresh_pairlist()
pairslist = whitelist_conf['exchange']['pair_whitelist']
assert set(whitelist) == set(pairslist)
def test_VolumePairList_whitelist_gen(mocker, whitelist_conf, markets, tickers) -> None:
whitelist_conf['pairlist']['method'] = 'VolumePairList'
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
mocker.patch('freqtrade.exchange.Exchange.get_markets', markets)
mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers)
# Test to retrieved BTC sorted on quoteVolume (default)
whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='BTC', key='quoteVolume')
assert whitelist == ['ETH/BTC', 'TKN/BTC', 'BLK/BTC', 'LTC/BTC']
# Test to retrieve BTC sorted on bidVolume
whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='BTC', key='bidVolume')
assert whitelist == ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'BLK/BTC']
# Test with USDT sorted on quoteVolume (default)
whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='USDT', key='quoteVolume')
assert whitelist == ['TKN/USDT', 'ETH/USDT', 'LTC/USDT', 'BLK/USDT']
# Test with ETH (our fixture does not have ETH, so result should be empty)
whitelist = freqtrade.pairlists._gen_pair_whitelist(base_currency='ETH', key='quoteVolume')
assert whitelist == []
def test_gen_pair_whitelist_not_supported(mocker, default_conf, tickers) -> None:
default_conf['pairlist'] = {'method': 'VolumePairList',
'config': {'number_assets': 10}
}
mocker.patch('freqtrade.exchange.Exchange.get_tickers', tickers)
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=False))
with pytest.raises(OperationalException):
get_patched_freqtradebot(mocker, default_conf)
@pytest.mark.parametrize("pairlist", AVAILABLE_PAIRLISTS)
def test_pairlist_class(mocker, whitelist_conf, markets, pairlist):
whitelist_conf['pairlist']['method'] = pairlist
mocker.patch('freqtrade.exchange.Exchange.get_markets', markets)
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
freqtrade = get_patched_freqtradebot(mocker, whitelist_conf)
assert freqtrade.pairlists.name == pairlist
assert pairlist in freqtrade.pairlists.short_desc()
assert isinstance(freqtrade.pairlists.whitelist, list)
assert isinstance(freqtrade.pairlists.blacklist, list)
whitelist = ['ETH/BTC', 'TKN/BTC']
new_whitelist = freqtrade.pairlists._validate_whitelist(whitelist)
assert set(whitelist) == set(new_whitelist)
whitelist = ['ETH/BTC', 'TKN/BTC', 'TRX/ETH']
new_whitelist = freqtrade.pairlists._validate_whitelist(whitelist)
# TRX/ETH was removed
assert set(['ETH/BTC', 'TKN/BTC']) == set(new_whitelist)
whitelist = ['ETH/BTC', 'TKN/BTC', 'BLK/BTC']
new_whitelist = freqtrade.pairlists._validate_whitelist(whitelist)
# BLK/BTC is in blacklist ...
assert set(['ETH/BTC', 'TKN/BTC']) == set(new_whitelist)

View File

View File

@@ -7,7 +7,7 @@ from unittest.mock import MagicMock
import pytest
from requests.exceptions import RequestException
from freqtrade.fiat_convert import CryptoFiat, CryptoToFiatConverter
from freqtrade.rpc.fiat_convert import CryptoFiat, CryptoToFiatConverter
from freqtrade.tests.conftest import log_has, patch_coinmarketcap
@@ -81,16 +81,18 @@ def test_fiat_convert_find_price(mocker):
assert fiat_convert.get_price(crypto_symbol='XRP', fiat_symbol='USD') == 0.0
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=12345.0)
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
return_value=12345.0)
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 12345.0
assert fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='usd') == 12345.0
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=13000.2)
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
return_value=13000.2)
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='EUR') == 13000.2
def test_fiat_convert_unsupported_crypto(mocker, caplog):
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[])
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[])
patch_coinmarketcap(mocker)
fiat_convert = CryptoToFiatConverter()
assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0
@@ -100,7 +102,8 @@ def test_fiat_convert_unsupported_crypto(mocker, caplog):
def test_fiat_convert_get_price(mocker):
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=28000.0)
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
return_value=28000.0)
fiat_convert = CryptoToFiatConverter()
@@ -114,7 +117,7 @@ def test_fiat_convert_get_price(mocker):
assert fiat_convert._pairs[0].crypto_symbol == 'BTC'
assert fiat_convert._pairs[0].fiat_symbol == 'USD'
assert fiat_convert._pairs[0].price == 28000.0
assert fiat_convert._pairs[0]._expiration is not 0
assert fiat_convert._pairs[0]._expiration != 0
assert len(fiat_convert._pairs) == 1
# Verify the cached is used
@@ -157,7 +160,7 @@ def test_fiat_init_network_exception(mocker):
# Because CryptoToFiatConverter is a Singleton we reset the listings
listmock = MagicMock(side_effect=RequestException)
mocker.patch.multiple(
'freqtrade.fiat_convert.Market',
'freqtrade.rpc.fiat_convert.Market',
listings=listmock,
)
# with pytest.raises(RequestEsxception):
@@ -187,7 +190,7 @@ def test_fiat_invalid_response(mocker, caplog):
# Because CryptoToFiatConverter is a Singleton we reset the listings
listmock = MagicMock(return_value="{'novalidjson':DEADBEEFf}")
mocker.patch.multiple(
'freqtrade.fiat_convert.Market',
'freqtrade.rpc.fiat_convert.Market',
listings=listmock,
)
# with pytest.raises(RequestEsxception):
@@ -203,7 +206,7 @@ def test_fiat_invalid_response(mocker, caplog):
def test_convert_amount(mocker):
patch_coinmarketcap(mocker)
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0)
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0)
fiat_convert = CryptoToFiatConverter()
result = fiat_convert.convert_amount(

View File

@@ -5,14 +5,16 @@ from datetime import datetime
from unittest.mock import MagicMock, ANY
import pytest
from numpy import isnan
from freqtrade.fiat_convert import CryptoToFiatConverter
from freqtrade import TemporaryError, DependencyException
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import Trade
from freqtrade.rpc import RPC, RPCException
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
from freqtrade.state import State
from freqtrade.tests.test_freqtradebot import patch_get_signal
from freqtrade.tests.conftest import patch_coinmarketcap
from freqtrade.tests.conftest import patch_coinmarketcap, patch_exchange
# Functions for recurrent object patching
@@ -29,7 +31,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
@@ -39,10 +41,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
patch_get_signal(freqtradebot, (True, False))
rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED
with pytest.raises(RPCException, match=r'.*trader is not running*'):
rpc._rpc_trade_status()
freqtradebot.state = State.RUNNING
with pytest.raises(RPCException, match=r'.*no active trade*'):
rpc._rpc_trade_status()
@@ -64,13 +62,34 @@ def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
'open_order': '(limit buy rem=0.00000000)'
} == results[0]
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available")))
# invalidate ticker cache
rpc._freqtrade.exchange._cached_ticker = {}
results = rpc._rpc_trade_status()
assert isnan(results[0]['current_profit'])
assert isnan(results[0]['current_rate'])
assert {
'trade_id': 1,
'pair': 'ETH/BTC',
'market_url': 'https://bittrex.com/Market/Index?MarketName=BTC-ETH',
'date': ANY,
'open_rate': 1.099e-05,
'close_rate': None,
'current_rate': ANY,
'amount': 90.99181074,
'close_profit': None,
'current_profit': ANY,
'open_order': '(limit buy rem=0.00000000)'
} == results[0]
def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -80,10 +99,6 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
patch_get_signal(freqtradebot, (True, False))
rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED
with pytest.raises(RPCException, match=r'.*trader is not running*'):
rpc._rpc_status_table()
freqtradebot.state = State.RUNNING
with pytest.raises(RPCException, match=r'.*no active order*'):
rpc._rpc_status_table()
@@ -94,14 +109,23 @@ def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
assert 'ETH/BTC' in result['Pair'].all()
assert '-0.59%' in result['Profit'].all()
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available")))
# invalidate ticker cache
rpc._freqtrade.exchange._cached_ticker = {}
result = rpc._rpc_status_table()
assert 'just now' in result['Since'].all()
assert 'ETH/BTC' in result['Pair'].all()
assert 'nan%' in result['Profit'].all()
def test_rpc_daily_profit(default_conf, update, ticker, fee,
limit_buy_order, limit_sell_order, markets, mocker) -> None:
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -147,15 +171,15 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
limit_buy_order, limit_sell_order, markets, mocker) -> None:
mocker.patch.multiple(
'freqtrade.fiat_convert.Market',
'freqtrade.rpc.fiat_convert.Market',
ticker=MagicMock(return_value={'price_usd': 15000.0}),
)
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -181,7 +205,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 +219,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)
@@ -217,20 +239,35 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
assert stats['best_pair'] == 'ETH/BTC'
assert prec_satoshi(stats['best_rate'], 6.2)
# Test non-available pair
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available")))
# invalidate ticker cache
rpc._freqtrade.exchange._cached_ticker = {}
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
assert stats['trade_count'] == 2
assert stats['first_trade_date'] == 'just now'
assert stats['latest_trade_date'] == 'just now'
assert stats['avg_duration'] == '0:00:00'
assert stats['best_pair'] == 'ETH/BTC'
assert prec_satoshi(stats['best_rate'], 6.2)
assert isnan(stats['profit_all_coin'])
# Test that rpc_trade_statistics can handle trades that lacks
# 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',
'freqtrade.rpc.fiat_convert.Market',
ticker=MagicMock(return_value={'price_usd': 15000.0}),
)
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.fiat_convert.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
@@ -251,7 +288,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 +321,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',
'freqtrade.rpc.fiat_convert.Market',
ticker=MagicMock(return_value={'price_usd': 15000.0}),
)
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
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 +358,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 +386,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 +410,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 +511,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 +547,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,
@@ -531,3 +570,108 @@ def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None:
trades = rpc._rpc_count()
nb_trades = len(trades)
assert nb_trades == 1
def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order) -> None:
default_conf['forcebuy_enable'] = True
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
buy_mm = MagicMock(return_value={'id': limit_buy_order['id']})
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
get_ticker=ticker,
get_fee=fee,
get_markets=markets,
buy=buy_mm
)
freqtradebot = FreqtradeBot(default_conf)
patch_get_signal(freqtradebot, (True, False))
rpc = RPC(freqtradebot)
pair = 'ETH/BTC'
trade = rpc._rpc_forcebuy(pair, None)
assert isinstance(trade, Trade)
assert trade.pair == pair
assert trade.open_rate == ticker()['ask']
# Test buy duplicate
with pytest.raises(RPCException, match=r'position for ETH/BTC already open - id: 1'):
rpc._rpc_forcebuy(pair, 0.0001)
pair = 'XRP/BTC'
trade = rpc._rpc_forcebuy(pair, 0.0001)
assert isinstance(trade, Trade)
assert trade.pair == pair
assert trade.open_rate == 0.0001
# Test buy pair not with stakes
with pytest.raises(RPCException, match=r'Wrong pair selected. Please pairs with stake.*'):
rpc._rpc_forcebuy('XRP/ETH', 0.0001)
pair = 'XRP/BTC'
# Test not buying
default_conf['stake_amount'] = 0.0000001
freqtradebot = FreqtradeBot(default_conf)
patch_get_signal(freqtradebot, (True, False))
rpc = RPC(freqtradebot)
pair = 'TKN/BTC'
trade = rpc._rpc_forcebuy(pair, None)
assert trade is None
def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
default_conf['forcebuy_enable'] = True
default_conf['initial_state'] = 'stopped'
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
freqtradebot = FreqtradeBot(default_conf)
patch_get_signal(freqtradebot, (True, False))
rpc = RPC(freqtradebot)
pair = 'ETH/BTC'
with pytest.raises(RPCException, match=r'trader is not running'):
rpc._rpc_forcebuy(pair, None)
def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
freqtradebot = FreqtradeBot(default_conf)
patch_get_signal(freqtradebot, (True, False))
rpc = RPC(freqtradebot)
pair = 'ETH/BTC'
with pytest.raises(RPCException, match=r'Forcebuy not enabled.'):
rpc._rpc_forcebuy(pair, None)
def test_rpc_whitelist(mocker, default_conf) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot)
ret = rpc._rpc_whitelist()
assert ret['method'] == 'StaticPairList'
assert ret['whitelist'] == default_conf['exchange']['pair_whitelist']
def test_rpc_whitelist_dynamic(mocker, default_conf) -> None:
patch_coinmarketcap(mocker)
patch_exchange(mocker)
default_conf['pairlist'] = {'method': 'VolumePairList',
'config': {'number_assets': 4}
}
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
freqtradebot = FreqtradeBot(default_conf)
rpc = RPC(freqtradebot)
ret = rpc._rpc_whitelist()
assert ret['method'] == 'VolumePairList'
assert ret['length'] == 4
assert ret['whitelist'] == default_conf['exchange']['pair_whitelist']

View File

@@ -113,3 +113,25 @@ def test_init_webhook_enabled(mocker, default_conf, caplog) -> None:
assert log_has('Enabling rpc.webhook ...', caplog.record_tuples)
assert len(rpc_manager.registered_modules) == 1
assert 'webhook' in [mod.name for mod in rpc_manager.registered_modules]
def test_startupmessages_telegram_enabled(mocker, default_conf, caplog) -> None:
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc_manager = RPCManager(freqtradebot)
rpc_manager.startup_messages(default_conf, freqtradebot.pairlists)
assert telegram_mock.call_count == 3
assert "*Exchange:* `bittrex`" in telegram_mock.call_args_list[1][0][0]['status']
telegram_mock.reset_mock()
default_conf['dry_run'] = True
default_conf['whitelist'] = {'method': 'VolumePairList',
'config': {'number_assets': 20}
}
rpc_manager.startup_messages(default_conf, freqtradebot.pairlists)
assert telegram_mock.call_count == 3
assert "Dry run is enabled." in telegram_mock.call_args_list[0][0][0]['status']

View File

@@ -17,6 +17,7 @@ from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import Trade
from freqtrade.rpc import RPCMessageType
from freqtrade.rpc.telegram import Telegram, authorized_only
from freqtrade.strategy.interface import SellType
from freqtrade.state import State
from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has,
patch_exchange)
@@ -71,8 +72,9 @@ def test_init(default_conf, mocker, caplog) -> None:
assert start_polling.start_polling.call_count == 1
message_str = "rpc.telegram is listening for following commands: [['status'], ['profit'], " \
"['balance'], ['start'], ['stop'], ['forcesell'], ['performance'], ['daily'], " \
"['count'], ['reload_conf'], ['help'], ['version']]"
"['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], " \
"['performance'], ['daily'], ['count'], ['reload_conf'], " \
"['whitelist'], ['help'], ['version']]"
assert log_has(message_str, caplog.record_tuples)
@@ -177,10 +179,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 +229,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
@@ -251,9 +252,10 @@ def test_status_handle(default_conf, update, ticker, fee, markets, mocker) -> No
telegram = Telegram(freqtradebot)
freqtradebot.state = State.STOPPED
# Status is also enabled when stopped
telegram._status(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
assert 'trader is not running' in msg_mock.call_args_list[0][0][0]
assert 'no active trade' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
freqtradebot.state = State.RUNNING
@@ -273,9 +275,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,
@@ -296,9 +298,10 @@ def test_status_table_handle(default_conf, update, ticker, fee, markets, mocker)
telegram = Telegram(freqtradebot)
freqtradebot.state = State.STOPPED
# Status table is also enabled when stopped
telegram._status_table(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
assert 'trader is not running' in msg_mock.call_args_list[0][0][0]
assert 'no active order' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
freqtradebot.state = State.RUNNING
@@ -324,13 +327,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 +398,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 +434,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
@@ -508,7 +511,12 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
'total': 10.0,
'free': 10.0,
'used': 0.0
}
},
'XRP': {
'total': 1.0,
'free': 1.0,
'used': 0.0
}
}
def mock_ticker(symbol, refresh):
@@ -518,7 +526,12 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
'ask': 10000.00,
'last': 10000.00,
}
elif symbol == 'XRP/BTC':
return {
'bid': 0.00001,
'ask': 0.00001,
'last': 0.00001,
}
return {
'bid': 0.1,
'ask': 0.1,
@@ -549,7 +562,8 @@ def test_telegram_balance_handle(default_conf, update, mocker) -> None:
assert '*USDT:*' in result
assert 'Balance:' in result
assert 'Est. BTC:' in result
assert 'BTC: 14.00000000' in result
assert 'BTC: 12.00000000' in result
assert '*XRP:* not showing <1$ amount' in result
def test_balance_handle_empty_response(default_conf, update, mocker) -> None:
@@ -678,7 +692,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
@@ -713,21 +727,23 @@ def test_forcesell_handle(default_conf, update, ticker, fee,
'open_rate': 1.099e-05,
'current_rate': 1.172e-05,
'profit_amount': 6.126e-05,
'profit_percent': 0.06110514,
'profit_percent': 0.0611052,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
'sell_reason': SellType.FORCE_SELL.value
} == last_msg
def test_forcesell_down_handle(default_conf, update, ticker, fee,
ticker_sell_down, markets, mocker) -> None:
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.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.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
_load_markets=MagicMock(return_value={}),
get_ticker=ticker,
get_fee=fee,
get_markets=markets
@@ -743,7 +759,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
)
@@ -767,21 +782,23 @@ def test_forcesell_down_handle(default_conf, update, ticker, fee,
'open_rate': 1.099e-05,
'current_rate': 1.044e-05,
'profit_amount': -5.492e-05,
'profit_percent': -0.05478343,
'profit_percent': -0.05478342,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
'sell_reason': SellType.FORCE_SELL.value
} == last_msg
def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker) -> None:
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.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
@@ -812,22 +829,24 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, markets, mocker
'open_rate': 1.099e-05,
'current_rate': 1.098e-05,
'profit_amount': -5.91e-06,
'profit_percent': -0.00589292,
'profit_percent': -0.00589291,
'stake_currency': 'BTC',
'fiat_currency': 'USD',
'sell_reason': SellType.FORCE_SELL.value
} == msg
def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
return_value=15000.0)
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_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))
@@ -857,9 +876,67 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
assert 'invalid argument' in msg_mock.call_args_list[0][0][0]
def test_forcebuy_handle(default_conf, update, markets, mocker) -> None:
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.telegram.Telegram._send_msg', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
_load_markets=MagicMock(return_value={}),
get_markets=markets
)
fbuy_mock = MagicMock(return_value=None)
mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock)
freqtradebot = FreqtradeBot(default_conf)
patch_get_signal(freqtradebot, (True, False))
telegram = Telegram(freqtradebot)
update.message.text = '/forcebuy ETH/BTC'
telegram._forcebuy(bot=MagicMock(), update=update)
assert fbuy_mock.call_count == 1
assert fbuy_mock.call_args_list[0][0][0] == 'ETH/BTC'
assert fbuy_mock.call_args_list[0][0][1] is None
# Reset and retry with specified price
fbuy_mock = MagicMock(return_value=None)
mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock)
update.message.text = '/forcebuy ETH/BTC 0.055'
telegram._forcebuy(bot=MagicMock(), update=update)
assert fbuy_mock.call_count == 1
assert fbuy_mock.call_args_list[0][0][0] == 'ETH/BTC'
assert isinstance(fbuy_mock.call_args_list[0][0][1], float)
assert fbuy_mock.call_args_list[0][0][1] == 0.055
def test_forcebuy_handle_exception(default_conf, update, markets, mocker) -> None:
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
mocker.patch('freqtrade.rpc.rpc.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.multiple(
'freqtrade.exchange.Exchange',
_load_markets=MagicMock(return_value={}),
get_markets=markets
)
freqtradebot = FreqtradeBot(default_conf)
patch_get_signal(freqtradebot, (True, False))
telegram = Telegram(freqtradebot)
update.message.text = '/forcebuy ETH/Nonepair'
telegram._forcebuy(bot=MagicMock(), update=update)
assert rpc_mock.call_count == 1
assert rpc_mock.call_args_list[0][0][0] == 'Forcebuy not enabled.'
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 +945,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
@@ -897,28 +973,9 @@ def test_performance_handle(default_conf, update, ticker, fee,
assert '<code>ETH/BTC\t6.20% (1)</code>' in msg_mock.call_args_list[0][0][0]
def test_performance_handle_invalid(default_conf, update, mocker) -> None:
patch_coinmarketcap(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)
# Trader is not running
freqtradebot.state = State.STOPPED
telegram._performance(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
assert 'not running' in msg_mock.call_args_list[0][0][0]
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 +984,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
@@ -958,6 +1014,46 @@ def test_count_handle(default_conf, update, ticker, fee, markets, mocker) -> Non
assert msg in msg_mock.call_args_list[0][0][0]
def test_whitelist_static(default_conf, update, mocker) -> None:
patch_coinmarketcap(mocker)
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
_send_msg=msg_mock
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot)
telegram._whitelist(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
assert ('Using whitelist `StaticPairList` with 4 pairs\n`ETH/BTC, LTC/BTC, XRP/BTC, NEO/BTC`'
in msg_mock.call_args_list[0][0][0])
def test_whitelist_dynamic(default_conf, update, mocker) -> None:
patch_coinmarketcap(mocker)
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
_send_msg=msg_mock
)
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
default_conf['pairlist'] = {'method': 'VolumePairList',
'config': {'number_assets': 4}
}
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot)
telegram._whitelist(bot=MagicMock(), update=update)
assert msg_mock.call_count == 1
assert ('Using whitelist `VolumePairList` with 4 pairs\n`ETH/BTC, LTC/BTC, XRP/BTC, NEO/BTC`'
in msg_mock.call_args_list[0][0][0])
def test_help_handle(default_conf, update, mocker) -> None:
patch_coinmarketcap(mocker)
msg_mock = MagicMock()
@@ -1041,16 +1137,18 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'profit_amount': -0.05746268,
'profit_percent': -0.57405275,
'stake_currency': 'ETH',
'fiat_currency': 'USD'
'fiat_currency': 'USD',
'sell_reason': SellType.STOP_LOSS.value
})
assert msg_mock.call_args[0][0] \
== '*Binance:* Selling [KEY/ETH]' \
'(https://www.binance.com/tradeDetail.html?symbol=KEY_ETH)\n' \
'*Limit:* `0.00003201`\n' \
'*Amount:* `1333.33333333`\n' \
'*Open Rate:* `0.00007500`\n' \
'*Current Rate:* `0.00003201`\n' \
'*Profit:* `-57.41%`` (loss: -0.05746268 ETH`` / -24.812 USD)`'
== ('*Binance:* Selling [KEY/ETH]'
'(https://www.binance.com/tradeDetail.html?symbol=KEY_ETH)\n'
'*Limit:* `0.00003201`\n'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Current Rate:* `0.00003201`\n'
'*Sell Reason:* `stop_loss`\n'
'*Profit:* `-57.41%`` (loss: -0.05746268 ETH`` / -24.812 USD)`')
msg_mock.reset_mock()
telegram.send_msg({
@@ -1066,15 +1164,17 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'profit_amount': -0.05746268,
'profit_percent': -0.57405275,
'stake_currency': 'ETH',
'sell_reason': SellType.STOP_LOSS.value
})
assert msg_mock.call_args[0][0] \
== '*Binance:* Selling [KEY/ETH]' \
'(https://www.binance.com/tradeDetail.html?symbol=KEY_ETH)\n' \
'*Limit:* `0.00003201`\n' \
'*Amount:* `1333.33333333`\n' \
'*Open Rate:* `0.00007500`\n' \
'*Current Rate:* `0.00003201`\n' \
'*Profit:* `-57.41%`'
== ('*Binance:* Selling [KEY/ETH]'
'(https://www.binance.com/tradeDetail.html?symbol=KEY_ETH)\n'
'*Limit:* `0.00003201`\n'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Current Rate:* `0.00003201`\n'
'*Sell Reason:* `stop_loss`\n'
'*Profit:* `-57.41%`')
# Reset singleton function to avoid random breaks
telegram._fiat_converter.convert_amount = old_convamount
@@ -1192,7 +1292,8 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
'profit_amount': -0.05746268,
'profit_percent': -0.57405275,
'stake_currency': 'ETH',
'fiat_currency': 'USD'
'fiat_currency': 'USD',
'sell_reason': SellType.STOP_LOSS.value
})
assert msg_mock.call_args[0][0] \
== '*Binance:* Selling [KEY/ETH]' \
@@ -1201,6 +1302,7 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
'*Amount:* `1333.33333333`\n' \
'*Open Rate:* `0.00007500`\n' \
'*Current Rate:* `0.00003201`\n' \
'*Sell Reason:* `stop_loss`\n' \
'*Profit:* `-57.41%`'

View File

@@ -7,6 +7,7 @@ from requests import RequestException
from freqtrade.rpc import RPCMessageType
from freqtrade.rpc.webhook import Webhook
from freqtrade.strategy.interface import SellType
from freqtrade.tests.conftest import get_patched_freqtradebot, log_has
@@ -80,6 +81,7 @@ def test_send_msg(default_conf, mocker):
'profit_amount': 0.001,
'profit_percent': 0.20,
'stake_currency': 'BTC',
'sell_reason': SellType.STOP_LOSS.value
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1

View File

View File

@@ -3,14 +3,14 @@ import json
import pytest
from pandas import DataFrame
from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.strategy.default_strategy import DefaultStrategy
@pytest.fixture
def result():
with open('freqtrade/tests/testdata/ETH_BTC-1m.json') as data_file:
return parse_ticker_dataframe(json.load(data_file))
return parse_ticker_dataframe(json.load(data_file), '1m', fill_missing=True)
def test_default_strategy_structure():

View File

@@ -7,7 +7,9 @@ import arrow
from pandas import DataFrame
from freqtrade.arguments import TimeRange
from freqtrade.optimize.__init__ import load_tickerdata_file
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.data.history 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
@@ -15,62 +17,69 @@ from freqtrade.strategy.default_strategy import DefaultStrategy
_STRATEGY = DefaultStrategy(config={})
def test_returns_latest_buy_signal(mocker, default_conf):
def test_returns_latest_buy_signal(mocker, default_conf, ticker_history):
mocker.patch.object(
_STRATEGY, 'analyze_ticker',
return_value=DataFrame([{'buy': 1, 'sell': 0, 'date': arrow.utcnow()}])
)
assert _STRATEGY.get_signal('ETH/BTC', '5m', MagicMock()) == (True, False)
assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (True, False)
mocker.patch.object(
_STRATEGY, 'analyze_ticker',
return_value=DataFrame([{'buy': 0, 'sell': 1, 'date': arrow.utcnow()}])
)
assert _STRATEGY.get_signal('ETH/BTC', '5m', MagicMock()) == (False, True)
assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (False, True)
def test_returns_latest_sell_signal(mocker, default_conf):
def test_returns_latest_sell_signal(mocker, default_conf, ticker_history):
mocker.patch.object(
_STRATEGY, 'analyze_ticker',
return_value=DataFrame([{'sell': 1, 'buy': 0, 'date': arrow.utcnow()}])
)
assert _STRATEGY.get_signal('ETH/BTC', '5m', MagicMock()) == (False, True)
assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (False, True)
mocker.patch.object(
_STRATEGY, 'analyze_ticker',
return_value=DataFrame([{'sell': 0, 'buy': 1, 'date': arrow.utcnow()}])
)
assert _STRATEGY.get_signal('ETH/BTC', '5m', MagicMock()) == (True, False)
assert _STRATEGY.get_signal('ETH/BTC', '5m', ticker_history) == (True, False)
def test_get_signal_empty(default_conf, mocker, caplog):
assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'],
None)
DataFrame())
assert log_has('Empty ticker history for pair foo', caplog.record_tuples)
caplog.clear()
assert (False, False) == _STRATEGY.get_signal('bar', default_conf['ticker_interval'],
[])
assert log_has('Empty ticker history for pair bar', caplog.record_tuples)
def test_get_signal_exception_valueerror(default_conf, mocker, caplog):
def test_get_signal_exception_valueerror(default_conf, mocker, caplog, ticker_history):
caplog.set_level(logging.INFO)
mocker.patch.object(
_STRATEGY, 'analyze_ticker',
side_effect=ValueError('xyz')
)
assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'], 1)
assert (False, False) == _STRATEGY.get_signal('foo', default_conf['ticker_interval'],
ticker_history)
assert log_has('Unable to analyze ticker for pair foo: xyz', caplog.record_tuples)
def test_get_signal_empty_dataframe(default_conf, mocker, caplog):
def test_get_signal_empty_dataframe(default_conf, mocker, caplog, ticker_history):
caplog.set_level(logging.INFO)
mocker.patch.object(
_STRATEGY, 'analyze_ticker',
return_value=DataFrame([])
)
assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], 1)
assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'],
ticker_history)
assert log_has('Empty dataframe for pair xyz', caplog.record_tuples)
def test_get_signal_old_dataframe(default_conf, mocker, caplog):
def test_get_signal_old_dataframe(default_conf, mocker, caplog, ticker_history):
caplog.set_level(logging.INFO)
# default_conf defines a 5m interval. we check interval * 2 + 5m
# this is necessary as the last candle is removed (partial candles) by default
@@ -80,7 +89,8 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog):
_STRATEGY, 'analyze_ticker',
return_value=DataFrame(ticks)
)
assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'], 1)
assert (False, False) == _STRATEGY.get_signal('xyz', default_conf['ticker_interval'],
ticker_history)
assert log_has(
'Outdated history for pair xyz. Last tick is 16 minutes old',
caplog.record_tuples
@@ -88,7 +98,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',
@@ -102,6 +111,151 @@ def test_tickerdata_to_dataframe(default_conf) -> None:
timerange = TimeRange(None, 'line', 0, -100)
tick = load_tickerdata_file(None, 'UNITTEST/BTC', '1m', timerange=timerange)
tickerlist = {'UNITTEST/BTC': tick}
tickerlist = {'UNITTEST/BTC': parse_ticker_dataframe(tick, '1m', True)}
data = strategy.tickerdata_to_dataframe(tickerlist)
assert len(data['UNITTEST/BTC']) == 99 # partial candle was removed
assert len(data['UNITTEST/BTC']) == 102 # partial candle was removed
def test_min_roi_reached(default_conf, fee) -> None:
# Use list to confirm sequence does not matter
min_roi_list = [{20: 0.05, 55: 0.01, 0: 0.1},
{0: 0.1, 20: 0.05, 55: 0.01}]
for roi in min_roi_list:
strategy = DefaultStrategy(default_conf)
strategy.minimal_roi = roi
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.02, arrow.utcnow().shift(minutes=-56).datetime)
assert strategy.min_roi_reached(trade, 0.12, arrow.utcnow().shift(minutes=-56).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_min_roi_reached2(default_conf, fee) -> None:
# test with ROI raising after last interval
min_roi_list = [{20: 0.07,
30: 0.05,
55: 0.30,
0: 0.1
},
{0: 0.1,
20: 0.07,
30: 0.05,
55: 0.30
},
]
for roi in min_roi_list:
strategy = DefaultStrategy(default_conf)
strategy.minimal_roi = roi
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.02, arrow.utcnow().shift(minutes=-56).datetime)
assert strategy.min_roi_reached(trade, 0.12, arrow.utcnow().shift(minutes=-56).datetime)
assert not strategy.min_roi_reached(trade, 0.04, arrow.utcnow().shift(minutes=-39).datetime)
assert strategy.min_roi_reached(trade, 0.071, arrow.utcnow().shift(minutes=-39).datetime)
assert not strategy.min_roi_reached(trade, 0.04, arrow.utcnow().shift(minutes=-26).datetime)
assert strategy.min_roi_reached(trade, 0.06, arrow.utcnow().shift(minutes=-26).datetime)
# Should not trigger with 20% profit since after 55 minutes only 30% is active.
assert not strategy.min_roi_reached(trade, 0.20, arrow.utcnow().shift(minutes=-2).datetime)
assert strategy.min_roi_reached(trade, 0.31, arrow.utcnow().shift(minutes=-2).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('Skipping 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('Skipping 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 'high' in ret.columns
assert 'low' in ret.columns
assert 'close' in ret.columns
assert isinstance(ret, DataFrame)
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('Skipping 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.columns
assert 'sell' in ret.columns
assert ret['buy'].sum() == 0
assert ret['sell'].sum() == 0
assert not log_has('TA Analysis Launched', caplog.record_tuples)
assert log_has('Skipping TA Analysis for already analyzed candle',
caplog.record_tuples)

View File

@@ -2,6 +2,7 @@
import logging
from base64 import urlsafe_b64encode
from os import path
from pathlib import Path
import warnings
import pytest
@@ -10,7 +11,7 @@ from pandas import DataFrame
from freqtrade.strategy import import_strategy
from freqtrade.strategy.default_strategy import DefaultStrategy
from freqtrade.strategy.interface import IStrategy
from freqtrade.strategy.resolver import StrategyResolver
from freqtrade.resolvers import StrategyResolver
def test_import_strategy(caplog):
@@ -40,21 +41,21 @@ def test_import_strategy(caplog):
def test_search_strategy():
default_config = {}
default_location = path.join(path.dirname(
path.realpath(__file__)), '..', '..', 'strategy'
)
default_location = Path(__file__).parent.parent.joinpath('strategy').resolve()
assert isinstance(
StrategyResolver._search_strategy(
default_location,
config=default_config,
strategy_name='DefaultStrategy'
StrategyResolver._search_object(
directory=default_location,
object_type=IStrategy,
kwargs={'config': default_config},
object_name='DefaultStrategy'
),
IStrategy
)
assert StrategyResolver._search_strategy(
default_location,
config=default_config,
strategy_name='NotFoundStrategy'
assert StrategyResolver._search_object(
directory=default_location,
object_type=IStrategy,
kwargs={'config': default_config},
object_name='NotFoundStrategy'
) is None
@@ -77,7 +78,7 @@ def test_load_strategy_invalid_directory(result, caplog):
resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir)
assert (
'freqtrade.strategy.resolver',
'freqtrade.resolvers.strategy_resolver',
logging.WARNING,
'Path "{}" does not exist'.format(extra_dir),
) in caplog.record_tuples
@@ -88,8 +89,8 @@ def test_load_strategy_invalid_directory(result, caplog):
def test_load_not_found_strategy():
strategy = StrategyResolver()
with pytest.raises(ImportError,
match=r'Impossible to load Strategy \'NotFoundStrategy\'.'
r' This class does not exist or contains Python code errors'):
match=r"Impossible to load Strategy 'NotFoundStrategy'."
r" This class does not exist or contains Python code errors"):
strategy._load_strategy(strategy_name='NotFoundStrategy', config={})
@@ -128,9 +129,9 @@ def test_strategy_override_minimal_roi(caplog):
resolver = StrategyResolver(config)
assert resolver.strategy.minimal_roi[0] == 0.5
assert ('freqtrade.strategy.resolver',
assert ('freqtrade.resolvers.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
@@ -143,9 +144,48 @@ def test_strategy_override_stoploss(caplog):
resolver = StrategyResolver(config)
assert resolver.strategy.stoploss == -0.5
assert ('freqtrade.strategy.resolver',
assert ('freqtrade.resolvers.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
def test_strategy_override_trailing_stop(caplog):
caplog.set_level(logging.INFO)
config = {
'strategy': 'DefaultStrategy',
'trailing_stop': True
}
resolver = StrategyResolver(config)
assert resolver.strategy.trailing_stop
assert isinstance(resolver.strategy.trailing_stop, bool)
assert ('freqtrade.resolvers.strategy_resolver',
logging.INFO,
"Override strategy 'trailing_stop' with value in config file: True."
) in caplog.record_tuples
def test_strategy_override_trailing_stop_positive(caplog):
caplog.set_level(logging.INFO)
config = {
'strategy': 'DefaultStrategy',
'trailing_stop_positive': -0.1,
'trailing_stop_positive_offset': -0.2
}
resolver = StrategyResolver(config)
assert resolver.strategy.trailing_stop_positive == -0.1
assert ('freqtrade.resolvers.strategy_resolver',
logging.INFO,
"Override strategy 'trailing_stop_positive' with value in config file: -0.1."
) in caplog.record_tuples
assert resolver.strategy.trailing_stop_positive_offset == -0.2
assert ('freqtrade.resolvers.strategy_resolver',
logging.INFO,
"Override strategy 'trailing_stop_positive' with value in config file: -0.1."
) in caplog.record_tuples
@@ -159,9 +199,154 @@ def test_strategy_override_ticker_interval(caplog):
resolver = StrategyResolver(config)
assert resolver.strategy.ticker_interval == 60
assert ('freqtrade.strategy.resolver',
assert ('freqtrade.resolvers.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.resolvers.strategy_resolver',
logging.INFO,
"Override strategy 'process_only_new_candles' with value in config file: True."
) in caplog.record_tuples
def test_strategy_override_order_types(caplog):
caplog.set_level(logging.INFO)
order_types = {
'buy': 'market',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': True,
}
config = {
'strategy': 'DefaultStrategy',
'order_types': order_types
}
resolver = StrategyResolver(config)
assert resolver.strategy.order_types
for method in ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']:
assert resolver.strategy.order_types[method] == order_types[method]
assert ('freqtrade.resolvers.strategy_resolver',
logging.INFO,
"Override strategy 'order_types' with value in config file:"
" {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit',"
" 'stoploss_on_exchange': True}."
) in caplog.record_tuples
config = {
'strategy': 'DefaultStrategy',
'order_types': {'buy': 'market'}
}
# Raise error for invalid configuration
with pytest.raises(ImportError,
match=r"Impossible to load Strategy 'DefaultStrategy'. "
r"Order-types mapping is incomplete."):
StrategyResolver(config)
def test_strategy_override_order_tif(caplog):
caplog.set_level(logging.INFO)
order_time_in_force = {
'buy': 'fok',
'sell': 'gtc',
}
config = {
'strategy': 'DefaultStrategy',
'order_time_in_force': order_time_in_force
}
resolver = StrategyResolver(config)
assert resolver.strategy.order_time_in_force
for method in ['buy', 'sell']:
assert resolver.strategy.order_time_in_force[method] == order_time_in_force[method]
assert ('freqtrade.resolvers.strategy_resolver',
logging.INFO,
"Override strategy 'order_time_in_force' with value in config file:"
" {'buy': 'fok', 'sell': 'gtc'}."
) in caplog.record_tuples
config = {
'strategy': 'DefaultStrategy',
'order_time_in_force': {'buy': 'fok'}
}
# Raise error for invalid configuration
with pytest.raises(ImportError,
match=r"Impossible to load Strategy 'DefaultStrategy'. "
r"Order-time-in-force mapping is incomplete."):
StrategyResolver(config)
def test_strategy_override_use_sell_signal(caplog):
caplog.set_level(logging.INFO)
config = {
'strategy': 'DefaultStrategy',
}
resolver = StrategyResolver(config)
assert not resolver.strategy.use_sell_signal
assert isinstance(resolver.strategy.use_sell_signal, bool)
# must be inserted to configuration
assert 'use_sell_signal' in config['experimental']
assert not config['experimental']['use_sell_signal']
config = {
'strategy': 'DefaultStrategy',
'experimental': {
'use_sell_signal': True,
},
}
resolver = StrategyResolver(config)
assert resolver.strategy.use_sell_signal
assert isinstance(resolver.strategy.use_sell_signal, bool)
assert ('freqtrade.resolvers.strategy_resolver',
logging.INFO,
"Override strategy 'use_sell_signal' with value in config file: True."
) in caplog.record_tuples
def test_strategy_override_use_sell_profit_only(caplog):
caplog.set_level(logging.INFO)
config = {
'strategy': 'DefaultStrategy',
}
resolver = StrategyResolver(config)
assert not resolver.strategy.sell_profit_only
assert isinstance(resolver.strategy.sell_profit_only, bool)
# must be inserted to configuration
assert 'sell_profit_only' in config['experimental']
assert not config['experimental']['sell_profit_only']
config = {
'strategy': 'DefaultStrategy',
'experimental': {
'sell_profit_only': True,
},
}
resolver = StrategyResolver(config)
assert resolver.strategy.sell_profit_only
assert isinstance(resolver.strategy.sell_profit_only, bool)
assert ('freqtrade.resolvers.strategy_resolver',
logging.INFO,
"Override strategy 'sell_profit_only' with value in config file: True."
) in caplog.record_tuples
@@ -179,7 +364,7 @@ def test_deprecate_populate_indicators(result):
in str(w[-1].message)
with warnings.catch_warnings(record=True) as w:
# Cause all warnings to always be triggered.
# Cause all warnings to always be triggered.
warnings.simplefilter("always")
resolver.strategy.advise_buy(indicators, 'ETH/BTC')
assert len(w) == 1
@@ -209,13 +394,13 @@ def test_call_deprecated_function(result, monkeypatch):
assert resolver.strategy._sell_fun_len == 2
indicator_df = resolver.strategy.advise_indicators(result, metadata=metadata)
assert type(indicator_df) is DataFrame
assert isinstance(indicator_df, DataFrame)
assert 'adx' in indicator_df.columns
buydf = resolver.strategy.advise_buy(result, metadata=metadata)
assert type(buydf) is DataFrame
assert isinstance(buydf, DataFrame)
assert 'buy' in buydf.columns
selldf = resolver.strategy.advise_sell(result, metadata=metadata)
assert type(selldf) is DataFrame
assert isinstance(selldf, DataFrame)
assert 'sell' in selldf

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