Compare commits

...

1979 Commits

Author SHA1 Message Date
Misagh
41e698c482 Merge pull request #1777 from freqtrade/new_release
Version to 0.18.5
2019-04-19 15:57:07 +02:00
Misagh
795c2e4aa2 version to 0.18.5 2019-04-18 08:07:43 +02:00
Misagh
031a63d5c2 Merge pull request #1771 from freqtrade/enable_ratelimit
Enable ratelimit
2019-04-17 17:31:21 +02:00
Misagh
f5ef8f5bc0 Merge pull request #1772 from freqtrade/fix/staticmethod_import
Gracefully handle pickle-errors when @staticmethod is used
2019-04-17 17:24:56 +02:00
Matthias
30f7536cbe Merge pull request #1773 from freqtrade/pyup/scheduled-update-2019-04-17
Scheduled daily dependency update on Wednesday
2019-04-17 15:03:20 +02:00
pyup-bot
8abdbc41e1 Update mypy from 0.700 to 0.701 2019-04-17 12:40:10 +00:00
pyup-bot
7f229bbf39 Update ccxt from 1.18.470 to 1.18.472 2019-04-17 12:40:09 +00:00
pyup-bot
d4947ba0ee Update ccxt from 1.18.470 to 1.18.472 2019-04-17 12:40:07 +00:00
Matthias
2cee716181 Gracefully handle pickle-errors when @staticmethod is used
pOinted out in https://github.com/freqtrade/freqtrade-strategies/issues/28
2019-04-16 20:25:48 +02:00
Matthias
a7383ad35d enable ratelimit in download-backtest-data too 2019-04-16 19:54:24 +02:00
Matthias
52cc2d224e improve documentation for exchange configuration 2019-04-16 19:51:56 +02:00
Matthias
5db10bdcc7 Add rateLimit parameters for different exchanges 2019-04-16 19:51:42 +02:00
Matthias
43119efaf0 Remove ccxt_rate_limit completely (was deprecated) 2019-04-16 19:41:02 +02:00
Matthias
16bf7aa3ab Merge pull request #1770 from freqtrade/pyup/scheduled-update-2019-04-16
Scheduled daily dependency update on Tuesday
2019-04-16 15:25:34 +02:00
pyup-bot
b2a623ee16 Update plotly from 3.7.1 to 3.8.0 2019-04-16 12:39:12 +00:00
pyup-bot
c40406d26e Update pytest from 4.4.0 to 4.4.1 2019-04-16 12:39:09 +00:00
pyup-bot
87ff5ad1e0 Update sqlalchemy from 1.3.2 to 1.3.3 2019-04-16 12:39:07 +00:00
pyup-bot
aa63f2be1f Update sqlalchemy from 1.3.2 to 1.3.3 2019-04-16 12:39:06 +00:00
pyup-bot
5cb90bdf77 Update ccxt from 1.18.468 to 1.18.470 2019-04-16 12:39:05 +00:00
pyup-bot
4f557af6cb Update ccxt from 1.18.468 to 1.18.470 2019-04-16 12:39:04 +00:00
Misagh
5f63797f17 Merge pull request #1762 from freqtrade/update_imageversion
Version bump to 3.7.3 in docker file
2019-04-15 19:45:21 +02:00
Misagh
bbb32ada4a Merge pull request #1763 from freqtrade/pi/docs
Update documentation for Raspberry
2019-04-15 19:44:53 +02:00
Misagh
fc33f19b06 Merge pull request #1767 from freqtrade/pyup/scheduled-update-2019-04-15
Scheduled daily dependency update on Monday
2019-04-15 15:20:33 +02:00
pyup-bot
7efab85b10 Update sqlalchemy from 1.3.1 to 1.3.2 2019-04-15 12:39:08 +00:00
pyup-bot
0ece168833 Update ccxt from 1.18.353 to 1.18.468 2019-04-15 12:39:06 +00:00
pyup-bot
6be4c6af0e Update ccxt from 1.18.466 to 1.18.468 2019-04-15 12:39:05 +00:00
Matthias
4f6df73156 Update documentation for Raspberry install since we now have a
rpi-requirements file
2019-04-14 15:57:44 +02:00
Matthias
cd20078bef Merge pull request #1742 from tl-nguyen/feature/add-dockerfile-for-pi
Add Dockerfile.pi for building docker image for raspberry pi
2019-04-14 15:53:37 +02:00
Matthias
5e0e8de4f6 Version bump to 3.7.3 in docker file 2019-04-14 13:13:28 +02:00
Matthias
ed5e76adac Merge pull request #1755 from hroff-1902/scripts-get_market_pairs
Minor: impoved argument and exception handling in scripts
2019-04-14 10:40:57 +02:00
Matthias
12265b245d Merge pull request #1738 from konqueror1/develop
Added command line options to override max_open_trades and stake_amount
2019-04-14 10:34:27 +02:00
Matthias
37b1389f12 Fix flake8 2019-04-14 10:17:06 +02:00
Matthias
b679eb1a95 Merge pull request #1761 from freqtrade/pyup/scheduled-update-2019-04-13
Scheduled daily dependency update on Saturday
2019-04-13 15:59:48 +02:00
pyup-bot
2f79cf1304 Update ccxt from 1.18.460 to 1.18.466 2019-04-13 12:39:05 +00:00
Misagh
3fe0cb9281 Merge pull request #1760 from freqtrade/pyup/scheduled-update-2019-04-12
Scheduled daily dependency update on Friday
2019-04-12 15:05:41 +02:00
pyup-bot
9f828224bc Update ccxt from 1.18.458 to 1.18.460 2019-04-12 12:39:05 +00:00
Misagh
2153e43969 Merge pull request #1759 from hroff-1902/patch-20
Docs: wrong rendering at freqtrade.io fixed
2019-04-12 10:48:02 +02:00
Misagh
c6d19a4afb Merge pull request #1758 from freqtrade/fix/rpcheader
Missing /daily header
2019-04-12 10:45:56 +02:00
hroff-1902
016e8fde89 wrong rendering at freqtrade.io fixed; other cosmetics in docs/
* Titles render wrong both in the docs dir and at freqtrade.io
* Last list of links renders wring at freqtrade.io
2019-04-12 10:54:28 +03:00
Matthias
d87db70ed0 Fix missing column header 2019-04-12 07:05:15 +02:00
Matthias
c3b9d69919 Add docstring explaining the source of the script 2019-04-12 07:05:00 +02:00
Matthias
f204af173d Merge pull request #1757 from freqtrade/pyup/scheduled-update-2019-04-11
Scheduled daily dependency update on Thursday
2019-04-11 15:52:05 +02:00
pyup-bot
12ca103f9f Update ccxt from 1.18.456 to 1.18.458 2019-04-11 12:38:06 +00:00
hroff-1902
c2ca899c7e fixed printed message; cosmetic changes in the code in scripts/download_backtest_data.py 2019-04-11 00:59:53 +03:00
hroff-1902
902ffa6853 impoved argument and exception handling in scripts/get_market_pairs.py 2019-04-11 00:15:17 +03:00
Misagh
93ebf163cb Merge pull request #1754 from freqtrade/pyup/scheduled-update-2019-04-10
Scheduled daily dependency update on Wednesday
2019-04-10 14:55:59 +02:00
pyup-bot
f736646ac6 Update ccxt from 1.18.445 to 1.18.456 2019-04-10 12:38:05 +00:00
Misagh
262113f9ee Merge pull request #1749 from freqtrade/telegram_long_msg
Telegram long /balance message
2019-04-10 10:30:49 +02:00
Matthias
e75cdd4c27 Rename variable, add more tests 2019-04-10 06:59:10 +02:00
Matthias
559257ed33 Merge pull request #1752 from freqtrade/pyup/scheduled-update-2019-04-09
Scheduled daily dependency update on Tuesday
2019-04-09 16:59:48 +02:00
pyup-bot
71e671f053 Update ccxt from 1.18.442 to 1.18.445 2019-04-09 12:38:06 +00:00
Matthias
6856848efc Merge pull request #1744 from hroff-1902/ccxt-parse_timeframe
cosmetic: rename interval, tick_interval, etc --> ticker_interval
2019-04-08 20:26:36 +02:00
Matthias
ff6967de9e Add test for too large balance 2019-04-08 19:59:54 +02:00
Matthias
5c4170951a Don't send too large messages 2019-04-08 19:59:30 +02:00
Misagh
500eb17449 Merge pull request #1747 from freqtrade/pyup/scheduled-update-2019-04-08
Scheduled daily dependency update on Monday
2019-04-08 16:30:12 +02:00
pyup-bot
ffdc33d964 Update ccxt from 1.18.437 to 1.18.442 2019-04-08 12:39:06 +00:00
Matthias
fb8555a6cc Merge pull request #1743 from freqtrade/pyup/scheduled-update-2019-04-07
Scheduled daily dependency update on Sunday
2019-04-07 19:17:31 +02:00
hroff-1902
ebf1126351 cosmetic: rename interval, tick_interval, etc --> ticker_interval 2019-04-07 16:28:53 +03:00
pyup-bot
3a81eb7d48 Update ccxt from 1.18.435 to 1.18.437 2019-04-07 12:38:05 +00:00
TL Nguyen
3ad4d937c5 Correct Dockerfile.pi file to use requirements-pi.txt 2019-04-07 14:07:26 +03:00
TL Nguyen
c35e5ca7dd Add back requirements-pi.txt file and put it into .pyup.yml 2019-04-07 14:05:41 +03:00
Matthias
4a6c8f3cb2 Merge pull request #1735 from hroff-1902/ccxt-parse_timeframe
Resolution for #1137
2019-04-07 12:52:13 +02:00
TL Nguyen
e7c8e62d75 Remove requirements-pi.txt, change Dockerfile.pi to utilize the requirements.txt instead 2019-04-07 10:31:03 +03:00
hroff-1902
d6d16b4696 docstrings improved 2019-04-07 00:22:02 +03:00
hroff-1902
dc1968b968 docstrings added 2019-04-06 23:36:55 +03:00
Misagh
4fef9448bf Merge pull request #1727 from mishaker/fix_cancel_order
Adding invalid order exception and fix #1726
2019-04-06 20:32:44 +02:00
Misagh
4bb5345e13 Merge pull request #1741 from freqtrade/abstract_count
rpc Count should be in rpc.py
2019-04-06 20:32:15 +02:00
Misagh
d294cab933 adding order id to invalidorder exception message 2019-04-06 20:27:03 +02:00
Matthias
f139178136 rpc_counts should be in .rpc 2019-04-06 20:11:41 +02:00
TL Nguyen
4eb0ed9f2f Add Dockerfile.pi for building docker image for raspberry pi 2019-04-06 21:11:14 +03:00
Matthias
7a598f32dc Move rpc-count calculation to _rpc class 2019-04-06 19:58:45 +02:00
Matthias
b776336ebf Merge pull request #1740 from freqtrade/pyup/scheduled-update-2019-04-06
Scheduled daily dependency update on Saturday
2019-04-06 14:57:54 +02:00
pyup-bot
481df98f58 Update ccxt from 1.18.432 to 1.18.435 2019-04-06 12:38:04 +00:00
hroff-1902
8cb1024ff6 Merge branch 'develop' into ccxt-parse_timeframe 2019-04-05 23:16:27 +03:00
Misagh
41ff2a9276 TemporaryError removed 2019-04-05 20:40:44 +02:00
Misagh
acb99a03e3 adding stoploss on exchange manual cancel note 2019-04-05 20:30:54 +02:00
Misagh
4b2eb22989 conflict with develop resolved 2019-04-05 20:23:15 +02:00
Misagh
a505826ec9 flake8 2019-04-05 20:20:41 +02:00
Misagh
54d068de44 missing test added 2019-04-05 20:20:16 +02:00
Misagh
25d8e93a90 remove unnecessary comment 2019-04-05 19:53:15 +02:00
Misagh
9712fb2d57 removing unnecessary comment 2019-04-05 19:49:02 +02:00
Misagh
2b49a11b2a returning InvalidOrder exception for get_order 2019-04-05 19:46:43 +02:00
Matthias
1bfc667515 Merge pull request #1737 from freqtrade/doc/simplify
Improve documentation formatting
2019-04-05 19:21:30 +02:00
Your Name
4c5432be6f Added command line options in backtesting to override max_open_trades and stake_amount 2019-04-05 16:48:14 +03:00
Misagh
9dc2a30793 Merge pull request #1683 from gianlup/fix_bt_partial_data
Fix backtest problem with partial data
2019-04-05 07:28:57 +02:00
Matthias
13e8f25ca9 Improve docs layout 2019-04-05 06:51:16 +02:00
Matthias
ac1964edb1 Remove unnecessary comment 2019-04-05 06:49:15 +02:00
Matthias
dbb1bbf101 Fix webhook documentation 2019-04-05 06:47:03 +02:00
Matthias
0ac80aacd1 Merge pull request #1736 from iuvbio/update/docs
Update/docs
2019-04-04 21:15:28 +02:00
iuvbio
7486cb7c64 fix admonitions 2019-04-04 21:05:26 +02:00
iuvbio
e3cdc0a05b typos and visual fixes 2019-04-04 20:53:28 +02:00
hroff-1902
6913bce6a1 flake8, import in script/plot_profit.py 2019-04-04 21:39:38 +03:00
Matthias
7010c835d2 Improve commentign 2019-04-04 20:23:10 +02:00
hroff-1902
2aa1b43f01 get rid of TICKER_INTERVAL_MINUTES dict, use ccxt's parse_timeframe() instead 2019-04-04 20:56:40 +03:00
Matthias
32cbb714f9 Improve commenting on backtsting and backtest_multi_tst 2019-04-04 19:44:03 +02:00
Misagh
7f4fd6168a test for canceled SL on exchange added 2019-04-04 17:23:21 +02:00
Misagh
647534a4f8 flake8 2019-04-04 17:17:21 +02:00
Misagh
31fa857319 typo 2019-04-04 17:15:51 +02:00
Misagh
a363d443bf stoploss on exchange canceled handled 2019-04-04 17:13:54 +02:00
Misagh
75c522e082 Merge pull request #1734 from freqtrade/pyup/scheduled-update-2019-04-04
Scheduled daily dependency update on Thursday
2019-04-04 14:56:55 +02:00
pyup-bot
ebeaf64fbb Update mypy from 0.670 to 0.700 2019-04-04 12:38:06 +00:00
pyup-bot
6afe232c4d Update ccxt from 1.18.430 to 1.18.432 2019-04-04 12:38:05 +00:00
Matthias
05df7f3394 Merge pull request #1733 from mishaker/stake_amount_bug
"stake amount" not "amount" should be shown for stake_amount :)
2019-04-04 14:13:00 +02:00
Misagh
0cdbe714d2 stake amount not amount 2019-04-04 12:06:45 +02:00
Misagh
9d6d60dcf0 Merge pull request #1689 from hroff-1902/main_refactoring
Main.py and freqtradebot refactoring
2019-04-04 11:19:15 +02:00
hroff-1902
65350ad552 final flake happy 2019-04-03 22:14:42 +03:00
Matthias
b437c3cf0c Merge pull request #1729 from mishaker/telegram_sl
Removing % sign from telegram message as it is already a pct.
2019-04-03 21:09:36 +02:00
Misagh
5488c66f53 flake8 2019-04-03 20:35:37 +02:00
Misagh
ef48193fad Merge pull request #1721 from hroff-1902/fix_1704
Fix #1704
2019-04-03 20:32:38 +02:00
Misagh
9ee1dd99eb tests fixed 2019-04-03 20:28:03 +02:00
Matthias
0307ba7883 Remove one branch - python does lazy evaluation 2019-04-03 20:04:04 +02:00
Matthias
1a5b0969b9 Fix tests (both tests where testing the same thing) 2019-04-03 19:53:10 +02:00
Matthias
3c399fbe3f Improve whitelist wordings 2019-04-03 19:51:46 +02:00
Matthias
a9a5c4a052 Merge pull request #1731 from mishaker/msg_stake
This adds stake amount in base currency to the RPC status message
2019-04-03 19:31:24 +02:00
Misagh
d5498c8712 adding % 2019-04-03 19:29:44 +02:00
Matthias
09321ccc9c Merge pull request #1728 from mishaker/edge_rpc_msg
Filtering edge pairs for RPC
2019-04-03 19:27:14 +02:00
Misagh
a3fe5f5757 adding stake amount to telegram message 2019-04-03 16:28:44 +02:00
Matthias
dfed713647 Merge pull request #1730 from freqtrade/pyup/scheduled-update-2019-04-03
Scheduled daily dependency update on Wednesday
2019-04-03 15:32:15 +02:00
pyup-bot
92dc3c89af Update sqlalchemy from 1.3.1 to 1.3.2 2019-04-03 12:38:07 +00:00
pyup-bot
eb610441b5 Update ccxt from 1.18.425 to 1.18.430 2019-04-03 12:38:06 +00:00
Misagh
67eeb145e1 flake8 2019-04-03 14:31:00 +02:00
Misagh
a3835b1279 flake8 2019-04-03 14:14:47 +02:00
Misagh
5f38d5ee63 removing % sign as it is already a pct 2019-04-03 14:07:33 +02:00
Misagh
53eaf85969 filtering edge pairs for RPC 2019-04-03 14:03:28 +02:00
hroff-1902
d54acca53a move tests back to original codebase to minimize changes 2019-04-03 00:55:59 +03:00
hroff-1902
2959156070 Merge branch 'develop' into main_refactoring 2019-04-03 00:50:33 +03:00
hroff-1902
b0ddb33acc tests cleanup: Worker --> FreqtradeBot where the Worker object is not really needed 2019-04-02 22:36:30 +03:00
hroff-1902
62141d3d27 test cloned, separate tests for worker and freqtrade states 2019-04-02 21:57:52 +03:00
Matthias
478c149bbb Merge pull request #1724 from mishaker/telegram_pct
Added percentage to telegram messages + documentation
2019-04-02 20:15:01 +02:00
Misagh
7b39a3084f formatting and readability 2019-04-02 20:08:10 +02:00
Misagh
a6daf0d991 formatting pct 2019-04-02 20:00:58 +02:00
Misagh
54f11ad603 enriching TSL log 2019-04-02 18:57:06 +02:00
Misagh
40df0dcf3d tests fixed 2019-04-02 18:45:18 +02:00
Misagh
99d256422e adding InvalidOrder to exchange 2019-04-02 18:31:03 +02:00
Misagh
389feda65f Invalid order exception added 2019-04-02 18:25:17 +02:00
Misagh
5a8f0f3557 Merge pull request #1725 from freqtrade/pyup/scheduled-update-2019-04-02
Scheduled daily dependency update on Tuesday
2019-04-02 16:37:58 +02:00
pyup-bot
b9b76977b6 Update ccxt from 1.18.420 to 1.18.425 2019-04-02 12:38:06 +00:00
Misagh
27917c2d89 Merge pull request #1720 from freqtrade/fix/fee_not_adjusted
Fix/fee not adjusted
2019-04-02 12:23:08 +02:00
Matthias
0cfdce0d5e Update function name from update_open_order to update_trade_state 2019-04-02 07:12:48 +02:00
hroff-1902
ab0e657d77 Check for empty whitelist moved to _process() 2019-04-01 21:36:53 +03:00
hroff-1902
34b40500c3 Check whitelist fetched from config for emptiness 2019-04-01 20:45:59 +03:00
Misagh
a3b0135557 documentation added for telegram 2019-04-01 19:25:13 +02:00
hroff-1902
8546db9dfd wording in the log message 2019-04-01 20:23:13 +03:00
Misagh
ab579587f2 adding percentage to telegram status messages 2019-04-01 19:13:45 +02:00
Matthias
ecd75e43b0 Merge pull request #1722 from freqtrade/pyup/scheduled-update-2019-04-01
Scheduled daily dependency update on Monday
2019-04-01 16:03:31 +02:00
pyup-bot
061f91ba41 Update pytest from 4.3.1 to 4.4.0 2019-04-01 12:38:07 +00:00
pyup-bot
97b31352c2 Update ccxt from 1.18.418 to 1.18.420 2019-04-01 12:38:06 +00:00
hroff-1902
77d2479c75 tests adjusted 2019-04-01 14:08:41 +03:00
hroff-1902
f0b2798c37 fix #1704 2019-04-01 14:08:03 +03:00
Misagh
8002936fe3 Merge pull request #1712 from freqtrade/log/tofile
Allow logging to file
2019-04-01 12:55:19 +02:00
Misagh
f440bb193d Merge pull request #1714 from freqtrade/cleanup_conftest
Cleanup tests a bit
2019-04-01 12:52:49 +02:00
Misagh
faa5883f09 Merge pull request #1716 from freqtrade/fix-jsonfull
fix typos in full_json_example
2019-04-01 12:50:47 +02:00
hroff-1902
7251e5bd62 bot state moved back to freqtradebot from worker 2019-03-31 23:39:55 +03:00
Matthias
7be90f71d3 Add test as called from execute_buy 2019-03-31 19:56:01 +02:00
Matthias
19d3a0cbac Update comment 2019-03-31 19:41:17 +02:00
Matthias
0ddafeeabf Split test for open_orders from maybe_sell 2019-03-31 16:05:40 +02:00
Matthias
b2ad402df4 Split tests for update-open_order 2019-03-31 15:51:45 +02:00
Matthias
e46dac3fbd Test stoploss does not raise dependencyexception 2019-03-31 15:45:22 +02:00
Matthias
5c8fbe2c6f Handle exception for stoploss independently of sell order 2019-03-31 15:41:10 +02:00
Matthias
f11a1b0122 Call update_open_order inline with buy
captures FOK / market orders
2019-03-31 15:40:43 +02:00
Matthias
8f4cca47e9 Refactor update_open_order into it's own function 2019-03-31 15:39:41 +02:00
Matthias
4fa736114c Don't set order_id to none here - it's used in "update_open_order".
should fix bugs observed in #1371 connected to stoploss
2019-03-31 15:38:25 +02:00
Misagh
13ac1e1957 Merge pull request #1719 from freqtrade/pyup/scheduled-update-2019-03-31
Scheduled daily dependency update on Sunday
2019-03-31 14:58:16 +02:00
pyup-bot
c28a0374f1 Update pytest-mock from 1.10.2 to 1.10.3 2019-03-31 12:38:04 +00:00
pyup-bot
93229fc54b Update ccxt from 1.18.415 to 1.18.418 2019-03-31 12:38:03 +00:00
Matthias
997190a050 Merge pull request #1709 from mishaker/sl_pct
Adding stoploss percentage to DB
2019-03-31 13:47:42 +02:00
Matthias
707a5fca91 ifix typos in full_json_example 2019-03-31 13:30:22 +02:00
Misagh
6d92b9b910 Merge branch 'develop' of https://github.com/freqtrade/freqtrade into sl_pct 2019-03-31 13:20:25 +02:00
Misagh
9b38c04579 negating SL pct and adding tests 2019-03-31 13:15:35 +02:00
hroff-1902
06144a1fc4 Wording in a comment 2019-03-30 23:33:52 +03:00
Matthias
0d152eb907 Merge pull request #1713 from freqtrade/pyup/scheduled-update-2019-03-30
Scheduled daily dependency update on Saturday
2019-03-30 13:53:43 +01:00
Matthias
1a61bf7bff sort imports 2019-03-30 13:48:30 +01:00
Matthias
87a296f728 No need to call patch_coinmarketcap each tim 2019-03-30 13:48:03 +01:00
Matthias
e98c0621d3 We don't need to call patch_coinmarketcap each time. 2019-03-30 13:47:30 +01:00
Matthias
40c0b4ef2e Autopatch coinmarketcap 2019-03-30 13:47:21 +01:00
pyup-bot
44142706c3 Update ccxt from 1.18.412 to 1.18.415 2019-03-30 12:38:03 +00:00
hroff-1902
208832e847 flake8, mypy resolved 2019-03-30 02:19:43 +03:00
Matthias
12066411db Update docs with logfile methods 2019-03-29 20:19:40 +01:00
Matthias
e5008fbf93 Add test for logfile attribute 2019-03-29 20:16:52 +01:00
Matthias
d4ffdaffc2 Correctly add types 2019-03-29 20:16:41 +01:00
Matthias
bb5a310aec Add --logfile argument 2019-03-29 20:13:15 +01:00
Matthias
ba558b2d75 Merge pull request #1711 from freqtrade/pyup/scheduled-update-2019-03-29
Scheduled daily dependency update on Friday
2019-03-29 15:16:53 +01:00
pyup-bot
82b344db1b Update ccxt from 1.18.407 to 1.18.412 2019-03-29 12:38:05 +00:00
Misagh
f2599ffe90 pct default to None 2019-03-29 08:08:29 +01:00
Misagh
50fc63251e added SL pct to DB 2019-03-28 21:18:26 +01:00
Misagh
b1ef39927c Merge pull request #1673 from freqtrade/refactor/persistance_stoplossupdate
trailing stop backtest problems
2019-03-28 20:44:24 +01:00
Matthias
b4472a165e Merge pull request #1707 from mishaker/telegram_msg
Telegram status message refactoring
2019-03-28 19:45:48 +01:00
Matthias
a87fc5f863 Fix tests - freqtrade should not be patched in this case 2019-03-28 19:37:50 +01:00
Misagh
2f3f5f19cd sl percentage removed form rpc test 2019-03-28 16:26:59 +01:00
Misagh
e11eb4775e stoploss precentage in telegram msg removed 2019-03-28 16:21:49 +01:00
Matthias
a15a3ae810 Merge pull request #1708 from freqtrade/pyup/scheduled-update-2019-03-28
Scheduled daily dependency update on Thursday
2019-03-28 14:45:56 +01:00
pyup-bot
daeb172ba1 Update ccxt from 1.18.406 to 1.18.407 2019-03-28 12:38:05 +00:00
Misagh
0e5b0ebda6 adding SL and SL percentage to telegram msg 2019-03-28 12:09:07 +01:00
hroff-1902
d5254dff7b Merge branch 'develop' into main_refactoring 2019-03-28 11:10:21 +03:00
Matthias
146d6bf7fb Merge pull request #1698 from mishaker/edge_rpc
Edge RPC
2019-03-28 06:22:38 +01:00
Matthias
0a8c1528cf Merge pull request #1686 from iuvbio/refactor/binance
Refactor/binance
2019-03-28 06:22:02 +01:00
Misagh
941921dd0f initial SL and SL added to RPC 2019-03-27 22:00:46 +01:00
Misagh
0ca3a38ba6 moved date to top and show open order only if it is not none 2019-03-27 21:39:17 +01:00
Misagh
1678a039ae removing close profit is trade is open 2019-03-27 21:32:56 +01:00
Misagh
e5406ed3cf typo in docs and comments 2019-03-27 21:22:25 +01:00
Misagh
4d9ca71c82 shifting edge help message a line lower 2019-03-27 21:20:09 +01:00
Misagh
6045f07a9c telegram message concatenation refactored 2019-03-27 21:12:57 +01:00
Matthias
9b22d5cab1 Fix typo, add test for validate_order_tif 2019-03-27 20:51:55 +01:00
Misagh
753b03d581 rolback on removing MD whitespaces 2019-03-27 18:19:42 +01:00
Misagh
1e37d8ccb3 flake8 2019-03-27 16:58:53 +01:00
Misagh
4038cdf70a "Edge" test for rpc telegram 2019-03-27 16:04:05 +01:00
Matthias
d09b33ae93 Merge pull request #1706 from freqtrade/pyup/scheduled-update-2019-03-27
Scheduled daily dependency update on Wednesday
2019-03-27 14:15:51 +01:00
Misagh
0687051ffb Update test_rpc.py
flake8
2019-03-27 14:04:33 +01:00
Misagh
8641da13b9 added RPC tests in case of edge enabled/disabled 2019-03-27 14:02:37 +01:00
pyup-bot
cc32566c92 Update ccxt from 1.18.400 to 1.18.406 2019-03-27 12:38:05 +00:00
Misagh
955e2d2826 Update test_rpc_telegram.py
telegram test_init fixed
2019-03-27 12:59:59 +01:00
Misagh
4e57969e4e documentation added 2019-03-27 12:54:00 +01:00
Misagh
52012003e9 Merge pull request #1700 from freqtrade/dataprovider/backtesting
Dataprovider during backtesting
2019-03-27 12:43:59 +01:00
Matthias
3bdc7b9a88 add missed "check" in docs 2019-03-27 10:51:13 +01:00
Misagh
a2a2489a97 Merge pull request #1701 from freqtrade/fix/blacklist_rpc_check
Check if added pair has correct stake-currency
2019-03-27 10:29:54 +01:00
Gianluca Puglia
b2c2b42408 Removed unwanted comment 2019-03-26 18:53:16 +01:00
hroff-1902
f5744cc9bf fix in the tests 2019-03-26 18:34:50 +03:00
Matthias
56264ea52a Merge pull request #1705 from freqtrade/pyup/scheduled-update-2019-03-26
Scheduled daily dependency update on Tuesday
2019-03-26 14:30:40 +01:00
pyup-bot
1f50bc79bc Update ccxt from 1.18.398 to 1.18.400 2019-03-26 13:37:03 +01:00
hroff-1902
c6d2c1e520 rest of telegram tests adjusted 2019-03-26 12:45:19 +03:00
hroff-1902
8aee009a0a test _reconfigure() adjusted 2019-03-26 12:42:19 +03:00
hroff-1902
5ccd618189 tests adjusted 2019-03-26 11:07:24 +03:00
hroff-1902
5161e1abb3 Allow to pass config into worker, as it's used in the tests 2019-03-26 11:07:02 +03:00
iuvbio
e15f2ef11a add order_time_in_force in _ft_has and revert binance 2019-03-26 00:49:39 +01:00
iuvbio
8dea640e9a remove exchange urls 2019-03-25 23:58:02 +01:00
iuvbio
4005b8d1d2 remove the if condition for binance 2019-03-25 23:57:14 +01:00
iuvbio
85ac99aee0 move exchange urls to constants 2019-03-25 23:57:14 +01:00
Matthias
e085fd9e95 Disable dataprovider from hyperopt.
Dataprovider uses weak links to initialize, which cannot be pickled, and
therefore cannot be used during hyperopt.
2019-03-25 19:49:58 +01:00
Matthias
f26ed1c8c1 Check if added pair has correct stake-currency 2019-03-25 19:40:21 +01:00
Matthias
4cf7282027 Update dataprovider docs 2019-03-25 19:31:10 +01:00
Matthias
0ae81d4115 Provide dataprovider access during backtesting 2019-03-25 19:26:51 +01:00
Matthias
226fc3d99b Check that dataprovider is part of strategy 2019-03-25 19:26:51 +01:00
Matthias
bd29b7d031 Test that dataprovider is loaded to strategy 2019-03-25 19:26:51 +01:00
hroff-1902
c8b0c9af0a Worker moved to new worker.py 2019-03-25 17:45:03 +03:00
Matthias
01c4f243d4 Merge pull request #1699 from freqtrade/pyup/scheduled-update-2019-03-25
Scheduled daily dependency update on Monday
2019-03-25 14:20:01 +01:00
pyup-bot
fe9322ecd5 Update pytest-mock from 1.10.1 to 1.10.2 2019-03-25 13:36:06 +01:00
pyup-bot
904b3008a9 Update ccxt from 1.18.395 to 1.18.398 2019-03-25 13:36:04 +01:00
Misagh
66f1e0f4cd help added 2019-03-25 10:25:07 +01:00
Misagh
e8bfeae048 conflict with develop resolved 2019-03-25 10:16:09 +01:00
Misagh
fd7278517d using items() 2019-03-25 09:48:41 +01:00
Misagh
b13735e4cc Merge pull request #1697 from freqtrade/feat/rpc_blacklist
add pairs to blacklist dynamically
2019-03-25 09:44:12 +01:00
Misagh
a8be277ca0 cached pairs iteration fixed + help added 2019-03-24 22:56:42 +01:00
Misagh
1dfbf6eed6 darfting edge rpc messages 2019-03-24 22:36:33 +01:00
Matthias
29b9bb96f3 Fix test to support adding things to pairlist 2019-03-24 19:49:49 +01:00
Matthias
14167f826b Fix typehints 2019-03-24 19:44:52 +01:00
Misagh
96ea27322d Merge pull request #1694 from freqtrade/doc/dataprovider
Add stake_currency to strategy, fix  documentation typo
2019-03-24 17:13:03 +01:00
Misagh
71d3a7de40 Merge pull request #1692 from freqtrade/feat/scripts_flake_mypy
run flake8 and mypy against scripts folder as well.
2019-03-24 17:08:52 +01:00
Misagh
fe3836b497 Merge pull request #1696 from freqtrade/docs/1521
Update documentation with correct way of calling
2019-03-24 17:06:46 +01:00
Matthias
49559f1a1a Improve documentation and help message 2019-03-24 16:33:21 +01:00
Matthias
042354d00f Test blacklist-adding 2019-03-24 16:30:11 +01:00
Matthias
f0d3901b6b Add blacklist-pair to documentation 2019-03-24 16:29:58 +01:00
Matthias
9d6f629f6a Support adding pairs to blacklist 2019-03-24 16:28:14 +01:00
Matthias
7b99d5ebcb Add blacklist and whitelist commands to telegram docs 2019-03-24 16:16:39 +01:00
Matthias
8b2174d249 Add tests for /blacklist handler 2019-03-24 16:09:20 +01:00
Matthias
ffdca7eea7 Add blacklist to default_config 2019-03-24 16:09:04 +01:00
Matthias
684727b32e Add black blacklist handler (ro) 2019-03-24 16:08:48 +01:00
Matthias
3a8b69d69b also support dry_run 2019-03-24 15:37:58 +01:00
Matthias
1bba9fcc53 Update documentation to use freqtrade, not freqtrade/main.py
fixes #1521
2019-03-24 15:13:17 +01:00
Matthias
f7fc9adc63 Run travis with freqtrade, not main.py 2019-03-24 15:13:03 +01:00
Matthias
e60d1788b2 Add new options to docu 2019-03-24 15:06:17 +01:00
Matthias
a7e13e96e4 Merge pull request #1695 from freqtrade/pyup/scheduled-update-2019-03-24
Scheduled daily dependency update on Sunday
2019-03-24 14:20:24 +01:00
pyup-bot
e644493e02 Update ccxt from 1.18.387 to 1.18.395 2019-03-24 13:35:03 +01:00
Matthias
06f4e627fc Add stake_currency to strategy, fix documentation typo 2019-03-23 20:40:07 +01:00
Misagh
e0775546f6 Merge pull request #1693 from freqtrade/fix/doc_formatting
Fix Documentation Boxes
2019-03-23 20:05:27 +01:00
Matthias
0dc96210b6 Fix formatting of boxes 2 2019-03-23 19:43:23 +01:00
Matthias
a95f30ce45 Fix custom boxes on documentation 2019-03-23 19:40:52 +01:00
Matthias
83a2427a61 Fix mypy in scripts 2019-03-23 19:37:17 +01:00
Matthias
184b13f2fb Flake8 for scripts 2019-03-23 19:18:10 +01:00
Matthias
9a632d9b7c Formatting 2019-03-23 16:51:36 +01:00
Matthias
c404e9ffd0 Simplify trailing_stop logic 2019-03-23 16:48:17 +01:00
Matthias
b1fe8c5325 Simplify stoploss_reached 2019-03-23 16:46:03 +01:00
Matthias
7307084dfd Move stoploss-adjustment to the top 2019-03-23 16:44:58 +01:00
Matthias
40899d08dd Fix failing test (all timezones are in UTC, so we should not convert to
None)
2019-03-23 15:24:11 +01:00
Matthias
00e6749d8b Refactor backtest() to be a bit more concise 2019-03-23 15:00:07 +01:00
Matthias
05466d318a Modify test to check for this condition 2019-03-23 14:50:18 +01:00
Matthias
6312d785d8 Merge pull request #1691 from freqtrade/pyup/scheduled-update-2019-03-23
Scheduled daily dependency update on Saturday
2019-03-23 13:52:00 +01:00
pyup-bot
34ff946f4d Update ccxt from 1.18.386 to 1.18.387 2019-03-23 13:35:03 +01:00
hroff-1902
158cb307f6 further refactoring of FreqtradeBot.process() 2019-03-23 00:20:20 +03:00
hroff-1902
e35daf95c0 minor cleanup 2019-03-22 23:41:48 +03:00
hroff-1902
b448890210 test_main.py adjusted (only beginning) 2019-03-22 22:03:15 +03:00
hroff-1902
be6836b0ef resolve python module circular dependency 2019-03-22 21:49:19 +03:00
hroff-1902
60afba5592 move worker stuff to main.py 2019-03-22 20:16:54 +03:00
Matthias
d043542094 Merge pull request #1688 from freqtrade/pyup/scheduled-update-2019-03-22
Scheduled daily dependency update on Friday
2019-03-22 15:30:33 +01:00
pyup-bot
89145a7711 Update ccxt from 1.18.385 to 1.18.386 2019-03-22 13:35:06 +01:00
Matthias
7744989583 Merge pull request #1661 from iuvbio/validate_whitelist
validate whitelist vs. validate pairs
2019-03-21 06:34:31 +01:00
Matthias
35d65bc7d7 Merge branch 'develop' into 'validate_whitelist' 2019-03-21 06:22:48 +01:00
Matthias
7fdb099097 Reformat log statement 2019-03-21 06:14:43 +01:00
Matthias
1f55356744 Merge pull request #1685 from hroff-1902/patch-20
docs for dry_run_wallet
2019-03-21 06:03:12 +01:00
hroff-1902
00821036bb docs for dry_run_wallet 2019-03-20 23:57:49 +03:00
Gianluca Puglia
6b89e86a97 Removed Timestamp cast 2019-03-20 19:44:59 +01:00
Matthias
65f5aa59e6 Merge pull request #1680 from hroff-1902/wallets_and_exchange_cleanup
Minor: Wallet and exchange cleanup
2019-03-20 19:31:02 +01:00
Gianluca Puglia
0eff324ce0 Use dedicated index for every pair 2019-03-20 18:38:10 +01:00
Matthias
676c6a784d Merge pull request #1681 from freqtrade/pyup/scheduled-update-2019-03-20
Scheduled daily dependency update on Wednesday
2019-03-20 14:18:38 +01:00
pyup-bot
cc369f41f5 Update coveralls from 1.6.0 to 1.7.0 2019-03-20 13:35:07 +01:00
pyup-bot
6c889895bd Update ccxt from 1.18.376 to 1.18.385 2019-03-20 13:35:05 +01:00
hroff-1902
580ada8c4f exchange cleanup 2019-03-19 20:52:35 +03:00
hroff-1902
aa15312670 wallets cleanup 2019-03-19 20:51:27 +03:00
Misagh
df6f3f6f32 Merge pull request #1679 from freqtrade/pyup/scheduled-update-2019-03-19
Scheduled daily dependency update on Tuesday
2019-03-19 13:49:46 +01:00
pyup-bot
2b09e3ca3d Update plotly from 3.7.0 to 3.7.1 2019-03-19 13:32:05 +01:00
pyup-bot
9a61067367 Update ccxt from 1.18.372 to 1.18.376 2019-03-19 13:32:04 +01:00
Matthias
c8617e70a8 Merge pull request #1668 from freqtrade/fix/1658_no_telegram_updates
No telegram rate updates when orderbook is enabled
2019-03-18 19:40:32 +01:00
Misagh
38b959f1a9 Merge pull request #1677 from freqtrade/pyup/scheduled-update-2019-03-18
Scheduled daily dependency update on Monday
2019-03-18 13:50:35 +01:00
pyup-bot
50ea4c39da Update ccxt from 1.18.368 to 1.18.372 2019-03-18 13:32:05 +01:00
Misagh
ff08416b12 Merge pull request #1674 from freqtrade/feat/stopbuy
Telegram `/stopbuy`
2019-03-18 09:00:47 +01:00
Matthias
8d173efe2d reword stopbuy message 2019-03-18 06:29:08 +01:00
Matthias
aa698a8412 rename /stopbuy message 2019-03-18 06:27:44 +01:00
Misagh
e6bfedb58b Merge pull request #1672 from freqtrade/doc/remove_double
Remove duplicate backtest-result-analysis documentation
2019-03-17 21:58:12 +01:00
Matthias
37e6b262eb Update docs to include /stopbuy 2019-03-17 19:36:25 +01:00
Matthias
9373d0c915 Add tests for /stopbuy 2019-03-17 19:36:02 +01:00
Matthias
a467d76832 Add /stopbuy command to telegram
fixes #1607
2019-03-17 19:35:25 +01:00
iuvbio
937399606e fix flake8 2019-03-17 18:24:29 +01:00
iuvbio
c2076af43b update tests 2019-03-17 18:18:44 +01:00
iuvbio
4de4a70be7 update log messages 2019-03-17 18:18:35 +01:00
Matthias
8afce7e651 Add testcase for Testcase 2 2019-03-17 16:26:38 +01:00
Matthias
2bf7f2feae Remove duplicate backtest-result-analysi documentation 2019-03-17 16:14:49 +01:00
iuvbio
8386496456 remove tests that are no longer applicable 2019-03-17 16:04:09 +01:00
iuvbio
7f9c76a6fc move stake check to the same condition as the other checks 2019-03-17 16:04:09 +01:00
iuvbio
d4d37667e1 use pairname for stake cur comparison 2019-03-17 16:04:09 +01:00
iuvbio
d4543be8eb edit comment 2019-03-17 16:04:09 +01:00
iuvbio
e38a3051a1 update docstring 2019-03-17 16:04:09 +01:00
iuvbio
c907e80c10 make sure no dups 2019-03-17 16:04:09 +01:00
iuvbio
a241e950f2 prune validate_pairs 2019-03-17 16:04:09 +01:00
iuvbio
39232cbcbb loop over whitelist only instead of all markets 2019-03-17 16:04:09 +01:00
Matthias
a7b60f6780 update trailing_stop with high in case of backtesting 2019-03-17 16:03:44 +01:00
Matthias
05ab1c2e0a Fix some comments 2019-03-17 16:02:13 +01:00
Matthias
8c7e8255bb Add detailed test for trailing stop 2019-03-17 16:01:34 +01:00
Matthias
f0e5113a7f Use Magicmock instead of lambda for mocking 2019-03-17 15:39:05 +01:00
Matthias
a830bee9c7 Enable trailing_stop for BTContainer tests 2019-03-17 15:28:04 +01:00
Matthias
bdc0134e88 Merge pull request #1671 from freqtrade/pyup/scheduled-update-2019-03-17
Scheduled daily dependency update on Sunday
2019-03-17 15:25:28 +01:00
pyup-bot
190ecb7ada Update ccxt from 1.18.367 to 1.18.368 2019-03-17 13:32:05 +01:00
Matthias
a77d513513 Fix backteest detail numbering ... 2019-03-17 13:27:32 +01:00
Matthias
7b99daebd7 Update docstring for adjust_stoploss 2019-03-17 13:19:24 +01:00
Matthias
2d4a2fd10b Use oppen_rate instead of artificial defaults 2019-03-17 13:12:04 +01:00
Matthias
a0e6cd93b6 Use bids, not asks for sell-rate detection 2019-03-17 11:27:01 +01:00
Misagh
b3f42dc51e Merge pull request #1635 from freqtrade/feat/btanlaysis
BTAnalysis - simplify backtest result analysis
2019-03-16 21:16:59 +01:00
Misagh
b0cad30796 Merge pull request #1670 from freqtrade/doc_update
Add 15min to documentation, fix link to "parameters in THE strategy"
2019-03-16 21:00:16 +01:00
Matthias
fc360608b7 Rename function to adjust_min_max 2019-03-16 20:14:45 +01:00
Matthias
01733c94fa Split up tests for adjust_stoploss and adjust_highlow 2019-03-16 20:04:55 +01:00
Matthias
68a9b14eca Min-rate should not default to 0 2019-03-16 20:04:39 +01:00
Matthias
738ed93221 call new function 2019-03-16 19:54:34 +01:00
Matthias
7166a474ae Add min_rate - always update min/max rates 2019-03-16 19:54:16 +01:00
Matthias
e632539b61 Add 15min to documentation, fix link to "parameters in THE strategy" 2019-03-16 19:51:39 +01:00
Matthias
e7f6df46e8 Add missing bt file 2019-03-16 19:15:20 +01:00
Matthias
a123246ac9 Add test for load_backtest_data 2019-03-16 17:50:57 +01:00
Matthias
ddb9933c91 Remove duplicate-check from test - it's in btanalysis 2019-03-16 17:28:28 +01:00
Matthias
9f7f089d8a adjust plot_dataframe to use btanalysis 2019-03-16 17:28:28 +01:00
Matthias
e1f48c2b46 Add btanalysis file 2019-03-16 17:28:28 +01:00
Matthias
d7017ce1e4 Document backtest-result loading 2019-03-16 17:28:28 +01:00
Matthias
6666d31ee9 Merge pull request #1648 from hroff-1902/sd-watchdog
Support for systemd watchdog
2019-03-16 13:46:04 +01:00
Matthias
29aa159827 Add test for get_sell_rate 2019-03-16 13:32:26 +01:00
Matthias
6bfc37309e refactor getting sell/current rate for telegram and selling
fix #1658
2019-03-16 13:24:10 +01:00
Matthias
71c530590e Merge pull request #1666 from freqtrade/telegram_help
Telegram help
2019-03-16 12:44:17 +01:00
Matthias
d596a877fa Update docs to link to ocnfiguration piece necessary 2019-03-16 11:07:16 +01:00
Matthias
b9b15e5f32 Align help message for forcebuy 2019-03-16 11:04:24 +01:00
Matthias
d66e6510e3 Merge pull request #1645 from mishaker/trailing_only_offset
Adding an option for trailing stoploss: "trailing_only_offset_is_reached"
2019-03-16 10:43:56 +01:00
Matthias
a233a8cc82 Be explicit in the documentation 2019-03-16 10:38:32 +01:00
Matthias
d42ebab575 Rename function and add test 2019-03-16 10:38:25 +01:00
Misagh
51af8c27f6 Merge pull request #1665 from freqtrade/catch/syntxerror
Catch syntaxerror on import
2019-03-16 08:15:09 +01:00
Matthias
44acf2f471 Catch syntaxerror on import 2019-03-15 19:50:38 +01:00
Matthias
ceb1e4c4f7 Merge pull request #1664 from freqtrade/pyup/scheduled-update-2019-03-15
Scheduled daily dependency update on Friday
2019-03-15 13:46:13 +01:00
pyup-bot
6db6c3b2cc Update ccxt from 1.18.362 to 1.18.367 2019-03-15 13:32:05 +01:00
Matthias
2e02e24e70 Merge pull request #1663 from iuvbio/fix/1662
Fix sort key not populated
2019-03-15 06:11:53 +01:00
iuvbio
95a3b5c41e check if ticker sort key is populated 2019-03-14 22:48:42 +01:00
Matthias
3fe06b3548 Merge pull request #1660 from freqtrade/pyup/scheduled-update-2019-03-14
Scheduled daily dependency update on Thursday
2019-03-14 19:36:41 +01:00
pyup-bot
1a83eed38f Update pandas from 0.24.1 to 0.24.2 2019-03-14 13:32:09 +01:00
pyup-bot
4fa1604230 Update ccxt from 1.18.361 to 1.18.362 2019-03-14 13:32:05 +01:00
misagh
edf2cd0b92 configuration test fixed 2019-03-14 09:26:31 +01:00
misagh
b5034cf535 TSL validator removed from exchange 2019-03-14 09:04:41 +01:00
misagh
29305dd070 config validation moved to configuration file 2019-03-14 09:01:03 +01:00
misagh
3c99e3b7c7 test adapted to new market refactoring 2019-03-14 09:00:28 +01:00
misagh
9a226ec7e6 conflict with develop resolved 2019-03-14 07:56:21 +01:00
Misagh
2959600f52 Merge pull request #1656 from freqtrade/fix/1633
Default value for minimal_roi
2019-03-14 07:51:07 +01:00
Matthias
ff9231eec4 Format attributes-table 2019-03-14 06:42:27 +01:00
Matthias
6b8f5963a8 Merge pull request #1623 from iuvbio/markets_refactor
Markets refactor
2019-03-14 06:22:18 +01:00
iuvbio
a1841c35ae reset _last_markets_refresh 2019-03-13 20:18:49 +01:00
iuvbio
aa2d747d8f update docs 2019-03-13 20:08:51 +01:00
Misagh
ee613b564c Merge pull request #1657 from freqtrade/fix/1653
send notification when stoploss_on_exchange is hit
2019-03-13 19:52:32 +01:00
Matthias
2bf5a3843d Use close_rate for notification if available 2019-03-13 19:41:58 +01:00
Matthias
29e84c9e88 Merge pull request #1659 from freqtrade/pyup/scheduled-update-2019-03-13
Scheduled daily dependency update on Wednesday
2019-03-13 14:33:10 +01:00
pyup-bot
23666858e2 Update pytest from 4.3.0 to 4.3.1 2019-03-13 13:32:06 +01:00
pyup-bot
5151a4521f Update ccxt from 1.18.358 to 1.18.361 2019-03-13 13:32:04 +01:00
Matthias
6b948cfc7e Don't move notify_sell to rpc_manager - it needs exchange stuff 2019-03-12 22:01:19 +01:00
Matthias
9054165e8a Adjust test, since rpc_message is now called on buy and sel 2019-03-12 21:55:18 +01:00
Matthias
11cc33a982 Refactor notify_sell to rpc_manager
* Call sell_notify also when stoploss_on_exchange is hit

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

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

Please review grammar, wording etc.
2019-03-04 11:05:12 +03:00
Matthias
b8eb3ecb1d Update hyperopts documentation to work and match the code 2019-03-04 07:24:49 +01:00
Matthias
2208a21a6c Update help strings 2019-03-04 07:24:41 +01:00
Matthias
2d0aca0d20 Move --customhyperopts to hyperopt section 2019-03-04 07:24:05 +01:00
Matthias
b7a558b951 Merge pull request #1596 from iuvbio/feature/volume-precision-pairlist
Feature/volume precision pairlist
2019-03-03 15:38:31 +01:00
Matthias
3c5deb9aaf Add test for precision_remove ...
BTT should not be in the list when that is enabled.
2019-03-03 15:31:48 +01:00
Matthias
4d64124eef Merge pull request #1613 from freqtrade/pyup/scheduled-update-2019-03-03
Scheduled daily dependency update on Sunday
2019-03-03 15:15:52 +01:00
iuvbio
e2cbb7e7da remove remnants markets and precisionlist 2019-03-03 13:41:51 +01:00
iuvbio
df79098adc update docs 2019-03-03 13:37:54 +01:00
pyup-bot
13ba5ba0db Update ccxt from 1.18.313 to 1.18.322 2019-03-03 13:32:03 +01:00
Misagh
2eb2ace539 Merge pull request #1605 from freqtrade/fix/1604
Add libssl-dev to fix #1604
2019-03-03 12:57:16 +01:00
iuvbio
064f6629ab delete separate pairlist 2019-03-03 00:35:25 +01:00
iuvbio
786244c0d3 Merge branch 'develop' into feature/volume-precision-pairlist 2019-03-02 18:55:40 +01:00
iuvbio
e1ae0d7e90 remove markets changes 2019-03-02 18:53:42 +01:00
iuvbio
c36fa0c7e2 add ticker argumet to get_target_bid 2019-03-02 17:24:48 +01:00
iuvbio
24c587518a add precision_filter 2019-03-02 17:24:28 +01:00
Matthias
4c1f2b2a5b Merge pull request #1612 from freqtrade/pyup/scheduled-update-2019-03-02
Scheduled daily dependency update on Saturday
2019-03-02 16:47:42 +01:00
pyup-bot
6bcfe65877 Update scikit-learn from 0.20.2 to 0.20.3 2019-03-02 13:32:04 +01:00
pyup-bot
28a70eba07 Update ccxt from 1.18.309 to 1.18.313 2019-03-02 13:32:03 +01:00
Matthias
285db2f40b Merge pull request #1611 from freqtrade/pyup/scheduled-update-2019-03-01
Scheduled daily dependency update on Friday
2019-03-01 14:10:20 +01:00
pyup-bot
0fc5445003 Update jsonschema from 3.0.0 to 3.0.1 2019-03-01 13:32:07 +01:00
pyup-bot
e8ea2e6f05 Update ccxt from 1.18.304 to 1.18.309 2019-03-01 13:32:06 +01:00
hroff-1902
b792f00553 exchange cleanup 2019-03-01 02:13:16 +03:00
hroff-1902
4df44d8b32 wallets cleanup 2019-03-01 01:26:29 +03:00
Matthias
58c296c1ff Merge pull request #1610 from freqtrade/pyup/scheduled-update-2019-02-28
Scheduled daily dependency update on Thursday
2019-02-28 16:34:22 +01:00
pyup-bot
13de66d559 Update ccxt from 1.18.297 to 1.18.304 2019-02-28 13:32:06 +01:00
Matthias
e5498ca20f Add libssl-dev to fix #1604 2019-02-27 17:51:00 +01:00
Misagh
0558b203fe Merge pull request #1603 from freqtrade/pyup/scheduled-update-2019-02-27
Scheduled daily dependency update on Wednesday
2019-02-27 14:33:34 +01:00
pyup-bot
38d09f9e78 Update numpy from 1.16.1 to 1.16.2 2019-02-27 13:32:05 +01:00
pyup-bot
768f62a24a Update ccxt from 1.18.296 to 1.18.297 2019-02-27 13:32:04 +01:00
Misagh
7e62a4a79c Merge pull request #1602 from hroff-1902/no-recursion-edge
[Minor] comments: removed mentioning recursion, typos, etc.
2019-02-27 11:50:29 +01:00
hroff-1902
761861f0b7 comments: removed mentioning recursion, typos, etc. 2019-02-27 13:35:06 +03:00
Misagh
4e291795a6 Merge pull request #1601 from hroff-1902/no-recursion-edge
eliminate recursion in Edge
2019-02-27 11:18:23 +01:00
Misagh
7fe9d9520a Merge pull request #1599 from freqtrade/remove_pairurl
Remove pairurl
2019-02-27 10:33:10 +01:00
hroff-1902
4c2961f0d9 eliminate recursion in _detect_next_stop_or_sell_point() 2019-02-27 06:31:27 +03:00
Matthias
ef26484153 Super() should not be called with parameters
source: https://realpython.com/python-super/
2019-02-26 21:01:50 +01:00
Matthias
79aac473b3 Remove market_url from tests 2019-02-26 19:27:28 +01:00
Matthias
5c3177cc79 Adapt documentation to remove market_url 2019-02-26 19:27:28 +01:00
Matthias
6c75b8a36a Remove pair market url 2019-02-26 19:27:28 +01:00
Matthias
ee0e381d65 Merge pull request #1595 from freqtrade/binance_subclass
Create binance Subclass and parametrize exchange-tests
2019-02-26 19:26:23 +01:00
Matthias
8cb7a7e7a5 Merge pull request #1598 from freqtrade/pyup/scheduled-update-2019-02-26
Scheduled daily dependency update on Tuesday
2019-02-26 15:46:02 +01:00
pyup-bot
bcf5b5fdcb Update flake8 from 3.7.6 to 3.7.7 2019-02-26 13:32:04 +01:00
pyup-bot
ef18ddd866 Update ccxt from 1.18.292 to 1.18.296 2019-02-26 13:32:03 +01:00
Misagh
cee4116b80 Merge pull request #1576 from hroff-1902/patch-10
Minor: code cleanup in _process()
2019-02-26 10:17:21 +01:00
Matthias
0c53bd6dd4 Complete refactor, moving query_trades to persistance as get_open_trades 2019-02-25 20:00:17 +01:00
Matthias
aff334fdd6 Merge pull request #1597 from freqtrade/pyup/scheduled-update-2019-02-25
Scheduled daily dependency update on Monday
2019-02-25 15:27:56 +01:00
pyup-bot
185bd1e53c Update ccxt from 1.18.290 to 1.18.292 2019-02-25 13:32:04 +01:00
Matthias
006635003e Fix small typos 2019-02-24 20:18:41 +01:00
Matthias
f2fd5205ef Fix typo 2019-02-24 20:13:38 +01:00
Matthias
31be4d2454 Add parametrized tests 2019-02-24 20:08:27 +01:00
Matthias
5c18346cd5 Add typehint to binance dict 2019-02-24 20:01:20 +01:00
Matthias
e0b634ba3b Parametrize exchanges and test multiple exchanges 2019-02-24 19:59:45 +01:00
Matthias
a05155cb75 Adapt failing test 2019-02-24 19:41:47 +01:00
Matthias
455b168366 add _ft_has to exchangeclass 2019-02-24 19:35:29 +01:00
Matthias
06f486a8eb Add binance exchange subclass 2019-02-24 19:30:05 +01:00
Misagh
42722b2873 Merge pull request #1593 from hroff-1902/patch-13
Edge doc file minor improvements, typos, formatting
2019-02-24 17:35:49 +01:00
Matthias
ecb5137dbe Merge pull request #1594 from freqtrade/pyup/scheduled-update-2019-02-24
Scheduled daily dependency update on Sunday
2019-02-24 13:50:54 +01:00
Matthias
2531961bf8 Merge pull request #1571 from hroff-1902/patch-9
multiple --config options
2019-02-24 13:50:39 +01:00
pyup-bot
417bf2c935 Update jsonschema from 2.6.0 to 3.0.0 2019-02-24 13:32:06 +01:00
pyup-bot
3673dba1e2 Update ccxt from 1.18.287 to 1.18.290 2019-02-24 13:32:05 +01:00
Matthias
9b288c6933 Add test to specifically test for merged dict 2019-02-24 13:29:22 +01:00
hroff-1902
5fac4f7b45 Edge doc file minor improvements, typos, formatting 2019-02-24 13:19:01 +03:00
Matthias
c7b6e19872 Merge pull request #1586 from iuvbio/order_creation
Order creation
2019-02-23 19:23:40 +01:00
Matthias
033e9e09fb Merge pull request #1592 from freqtrade/pyup/scheduled-update-2019-02-23
Scheduled daily dependency update on Saturday
2019-02-23 17:05:36 +01:00
iuvbio
3dcf3f8a82 Merge branch 'develop' into feature/volume-precision-pairlist 2019-02-23 16:28:37 +01:00
iuvbio
403ed48c3e rename _store_dry_order 2019-02-23 16:28:13 +01:00
iuvbio
4d797c9232 Merge branch 'develop' into order_creation 2019-02-23 16:21:52 +01:00
iuvbio
ec6794b9ba fix dry_orders 2019-02-23 16:03:15 +01:00
pyup-bot
634ce87bba Update ccxt from 1.18.281 to 1.18.287 2019-02-23 13:32:04 +01:00
iuvbio
98bca30dfb reorganize imports 2019-02-22 21:16:31 +01:00
iuvbio
cc0fae8e4e change < to <= 2019-02-22 21:13:08 +01:00
iuvbio
8d8da71f20 Merge branch 'develop' into feature/volume-precision-pairlist 2019-02-22 20:31:24 +01:00
iuvbio
9a097214a6 return complete dry_order in buy and sell 2019-02-22 19:22:48 +01:00
Matthias
619945b861 Merge pull request #1591 from hroff-1902/patch-12
minor: formatting math expression in FAQ
2019-02-22 19:08:33 +01:00
iuvbio
71774bce6f Merge branch 'develop' of https://github.com/freqtrade/freqtrade into order_creation 2019-02-22 19:02:31 +01:00
hroff-1902
9c54886f14 minor: formatting math expression in FAQ 2019-02-22 19:33:05 +03:00
Misagh
1252bacb7a Merge pull request #1590 from hroff-1902/patch-12
FAQ updated with question on Edge
2019-02-22 16:56:05 +01:00
hroff-1902
a1b00f9053 Edge question added; minor improvements (sections for Hyperopt and Edge) 2019-02-22 17:37:59 +03:00
Samuel Husso
e4369e06bc Merge pull request #1589 from freqtrade/pyup/scheduled-update-2019-02-22
Scheduled daily dependency update on Friday
2019-02-22 15:27:40 +02:00
pyup-bot
29b8b79732 Update ccxt from 1.18.280 to 1.18.281 2019-02-22 13:30:08 +01:00
Misagh
2c7c19dfb1 Merge pull request #1582 from freqtrade/move/exchange
Refactor exchange importing
2019-02-22 10:24:23 +01:00
iuvbio
b79d967371 add tests, further consolidate orders 2019-02-22 01:48:35 +01:00
iuvbio
69bb6ebaf6 fix comments 2019-02-21 22:43:15 +01:00
Matthias
57b2fb4645 Merge pull request #1584 from freqtrade/pyup/scheduled-update-2019-02-21
Scheduled daily dependency update on Thursday
2019-02-21 19:14:24 +01:00
iuvbio
bf5d2a68f5 Merge branch 'develop' into order_creation 2019-02-21 19:03:29 +01:00
pyup-bot
7738ebbc0f Update ccxt from 1.18.270 to 1.18.280 2019-02-21 13:31:05 +01:00
Matthias
905beef8a3 Merge pull request #1578 from freqtrade/release/0.18.1
Release/0.18.1
2019-02-21 12:33:09 +01:00
Matthias
6d7f788989 Merge pull request #1579 from freqtrade/version_bump_dev
Version bump develop to 0.18.2-dev
2019-02-21 12:32:45 +01:00
Matthias
be754244a3 Only resolve exchanges from correct location 2019-02-21 07:07:45 +01:00
Matthias
e0f426d863 Allow import freqtrade.exchange.* 2019-02-21 06:59:52 +01:00
Matthias
e987a915e8 Rename exchange file 2019-02-21 06:56:22 +01:00
Matthias
a79ff1c6c9 Merge pull request #1563 from iuvbio/kraken_support
Kraken support
2019-02-21 06:41:35 +01:00
Matthias
2dcb4134cc Merge branch 'develop' into pr/iuvbio/1563 2019-02-21 06:29:37 +01:00
Matthias
e309f75118 Merge pull request #1581 from hroff-1902/patch-11
Minor changes to exchange
2019-02-21 06:25:47 +01:00
Matthias
643402da1c Merge pull request #1580 from hroff-1902/patch-1
Minor: added amount_reserve_percent into config json-schema
2019-02-21 06:24:16 +01:00
iuvbio
a1d1abfffc Merge branch 'develop' into order_creation 2019-02-21 00:30:46 +01:00
iuvbio
b5758e67f9 order creation cleanup 2019-02-21 00:29:59 +01:00
hroff-1902
2851833726 added _now_is_time_to_refresh() 2019-02-21 01:20:24 +03:00
hroff-1902
c1ef6940b0 removed wrong comment: tuple is not created here 2019-02-21 00:47:18 +03:00
hroff-1902
2aba9c081c fixed typos in comments 2019-02-21 00:46:35 +03:00
hroff-1902
eb21170691 added amount_reserve_percent into config json-schema 2019-02-21 00:26:02 +03:00
Matthias
d9129cb9c5 Develop version bump to 0.18.2-dev 2019-02-20 21:07:54 +01:00
Matthias
af02e34b57 Version bump to 0.18.1 2019-02-20 21:04:52 +01:00
Matthias
4315c157c7 Move exception handling to resolver, add test 2019-02-20 20:13:23 +01:00
Matthias
9e082ca3a9 Merge pull request #1567 from gaugau3000/patch-1
prevent convert LF->CRLF line ending on window
2019-02-20 19:14:17 +01:00
gautier pialat
ba23f58ff3 change the way to clone git rep for Window with docker 2019-02-20 18:09:26 +01:00
hroff-1902
da5bef501e cleanup 2019-02-20 17:55:20 +03:00
hroff-1902
4fbba98168 tests adjusted for multiple --config options 2019-02-20 17:54:20 +03:00
hroff-1902
87c82dea3d support for multiple --config in the download_backtest_data.py utility 2019-02-20 17:00:35 +03:00
hroff-1902
c08a2b6638 help message fixed 2019-02-20 16:23:09 +03:00
hroff-1902
7bc874c7fd comments adjusted 2019-02-20 16:12:17 +03:00
Samuel Husso
d76dc3ca0e Merge pull request #1577 from freqtrade/pyup/scheduled-update-2019-02-20
Scheduled daily dependency update on Wednesday
2019-02-20 15:03:00 +02:00
hroff-1902
fac0e4e603 more code cleanup in _process() 2019-02-20 16:01:56 +03:00
pyup-bot
7dbb7a52ed Update coveralls from 1.5.1 to 1.6.0 2019-02-20 13:31:08 +01:00
pyup-bot
3ec3438acf Update pytest from 4.2.1 to 4.3.0 2019-02-20 13:31:07 +01:00
pyup-bot
1cd54829cc Update flake8 from 3.7.5 to 3.7.6 2019-02-20 13:31:06 +01:00
pyup-bot
bd6644a91a Update ccxt from 1.18.247 to 1.18.270 2019-02-20 13:31:05 +01:00
hroff-1902
199e3d2234 typo in a comment 2019-02-20 15:13:21 +03:00
hroff-1902
5906d37818 code cleanup in _process() 2019-02-20 15:12:04 +03:00
Misagh
ab62bbc0a4 Merge pull request #1572 from freqtrade/fix/coverage
Move coveralls to after_success
2019-02-20 09:06:04 +01:00
iuvbio
e495ffec78 align dry_run_orders 2019-02-20 02:38:16 +01:00
iuvbio
84ccb85184 Merge branch 'develop' into feature/volume-precision-pairlist 2019-02-20 01:03:03 +01:00
iuvbio
686949b258 Merge branch 'develop' into kraken_support 2019-02-20 00:52:10 +01:00
iuvbio
3e2f90a32a formatting 2019-02-19 22:27:20 +01:00
iuvbio
bb31e64752 add test_sell_kraken_trading_agreement 2019-02-19 21:56:20 +01:00
Matthias
58864adc4a Move coveralls to after_success 2019-02-19 19:54:53 +01:00
Matthias
788cbb6776 Merge pull request #1569 from hroff-1902/patch-8
Cosmetic: move default amount_reserve_percent value into constants
2019-02-19 19:19:54 +01:00
iuvbio
481cf02db9 add test and fix exchange_resolver 2019-02-19 19:15:22 +01:00
hroff-1902
2f225e2340 multiple --config options 2019-02-19 15:14:47 +03:00
hroff-1902
f9d68d919c move default amount_reserve_percent value into constants 2019-02-19 11:49:49 +03:00
gautier pialat
dffb27326e prevent convert LF->CRLF line ending on window
During docker built on windows if in  global git config  core.autocrlf = true then when have this message :

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

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

Has script files run on linux they need to use LF lines ending so we need to ensure they are not converted to CRLF lines ending if global git config is at  core.autocrlf = true.
2019-02-18 17:16:27 +01:00
Misagh
6d7834a389 Merge pull request #1566 from freqtrade/pyup/scheduled-update-2019-02-18
Scheduled daily dependency update on Monday
2019-02-18 13:58:48 +01:00
pyup-bot
f63fdf411d Update ccxt from 1.18.245 to 1.18.247 2019-02-18 13:30:10 +01:00
Misagh
cd48556c5a Merge pull request #1562 from freqtrade/test_strategy
Fix typo in test-strategy, add volume > 0 check to buy/sell check
2019-02-18 08:39:49 +01:00
iuvbio
eed1c2344d delete unnecessary arguments 2019-02-18 01:03:09 +01:00
iuvbio
4241caef95 changes to base and subclass 2019-02-17 23:34:15 +01:00
iuvbio
2103ae5fdf change rateLimit to 1000 2019-02-17 23:26:10 +01:00
iuvbio
6055906eb1 Merge branch 'develop' into feature/volume-precision-pairlist 2019-02-17 16:15:55 +01:00
iuvbio
e98edc1a1a Merge branch 'develop' into kraken_support 2019-02-17 16:15:24 +01:00
iuvbio
62382809b2 Merge branch 'develop' into feature/volume-precision-pairlist 2019-02-17 16:14:20 +01:00
iuvbio
0572336ff7 revert changes to history 2019-02-17 16:12:40 +01:00
Matthias
9fad83bd15 Fix 1 to 0 2019-02-17 15:55:47 +01:00
iuvbio
d8feceebb5 fix type-hints 2019-02-17 15:54:22 +01:00
Matthias
19625e9e1d Merge pull request #1561 from freqtrade/pyup/scheduled-update-2019-02-17
Scheduled daily dependency update on Sunday
2019-02-17 15:42:09 +01:00
Matthias
f1ededf0eb Fix typo in test-strategy, add volume > 0 check to buy/sell check 2019-02-17 15:40:08 +01:00
iuvbio
da4faacd6b flake8 2019-02-17 15:34:44 +01:00
iuvbio
39c28626aa remove error message to make pytest pass 2019-02-17 15:29:58 +01:00
iuvbio
5e8a7a03c3 correct time_in_force param 2019-02-17 15:26:33 +01:00
iuvbio
dd2522d8d0 Merge branch 'develop' into kraken_support 2019-02-17 15:21:14 +01:00
iuvbio
fe792882b5 load generic class if no subclass exists 2019-02-17 14:42:55 +01:00
pyup-bot
1bbb04da60 Update ccxt from 1.18.243 to 1.18.245 2019-02-17 13:30:07 +01:00
Matthias
e785a66768 Merge pull request #1560 from hroff-1902/patch-7
fetch amount_reserve_percent from the configuration file
2019-02-17 11:38:51 +01:00
hroff-1902
df8067d6c4 add description of the new configuration option amount_reserve_percent 2019-02-17 12:56:28 +03:00
iuvbio
d3ead2cd09 exchange import is not needed anymore 2019-02-17 04:25:39 +01:00
iuvbio
c879591f45 add exchange_resolver to resolver init 2019-02-17 04:22:24 +01:00
iuvbio
c315f63e4b use exchange_resolver in freqbot 2019-02-17 04:18:56 +01:00
iuvbio
2fb36b116d change variable names 2019-02-17 04:15:11 +01:00
iuvbio
ca388a9acf create exchange_resolver 2019-02-17 04:01:43 +01:00
iuvbio
32b02c9925 kraken subclass 2019-02-17 04:01:17 +01:00
iuvbio
54d5bce445 undo kraken specific changes 2019-02-17 03:59:40 +01:00
iuvbio
b7afcf3416 add VolumePrecisionPairList 2019-02-16 22:56:04 +01:00
hroff-1902
66cc600076 add amount_reserve_percent into the full config file 2019-02-16 19:53:35 +03:00
hroff-1902
ea6d4a9d36 fetch amount_reserve_percent from config 2019-02-16 19:50:55 +03:00
Matthias
e0c420b93f Merge pull request #1550 from hroff-1902/patch-2
execute_buy, handle_trade: do not use ticker if use_order_book:true is set in config
2019-02-16 13:55:33 +01:00
Matthias
67cea9dce6 Merge pull request #1558 from freqtrade/pyup/scheduled-update-2019-02-16
Scheduled daily dependency update on Saturday
2019-02-16 13:51:58 +01:00
pyup-bot
7c651632f1 Update joblib from 0.13.1 to 0.13.2 2019-02-16 13:32:06 +01:00
pyup-bot
13a16178d2 Update sqlalchemy from 1.2.17 to 1.2.18 2019-02-16 13:32:05 +01:00
pyup-bot
c2bc316e2f Update ccxt from 1.18.234 to 1.18.243 2019-02-16 13:32:04 +01:00
iuvbio
8ed3658447 Merge branch 'develop' into kraken_support 2019-02-15 23:27:41 +01:00
Crypto God
3aa614b983 bump version 2019-02-15 22:51:09 +01:00
Crypto God
3953092edd output error message 2019-02-15 22:50:31 +01:00
Crypto God
ef5a0b9afc add Kraken specifics 2019-02-15 22:50:11 +01:00
Matthias
98bd713624 iUpdate orderbook_bid_test 2019-02-14 19:15:16 +01:00
Matthias
f852be1a9b Fix tests for get_ticker fix 2019-02-14 07:27:13 +01:00
Matthias
dcc86bfa55 Merge pull request #1552 from freqtrade/pyup/scheduled-update-2019-02-13
Scheduled daily dependency update on wednesday
2019-02-13 14:35:43 +01:00
pyup-bot
aee7b2c29d Update pytest from 4.2.0 to 4.2.1 2019-02-13 13:32:10 +01:00
pyup-bot
c17eb89e84 Update arrow from 0.13.0 to 0.13.1 2019-02-13 13:32:08 +01:00
pyup-bot
aaa8567708 Update ccxt from 1.18.230 to 1.18.234 2019-02-13 13:32:06 +01:00
Matthias
7e1e09d45a Merge pull request #1551 from hroff-1902/patch-4
logs: cosmetic changes
2019-02-13 12:39:12 +01:00
hroff-1902
b87e15774b test adjusted 2019-02-13 13:02:57 +03:00
hroff-1902
8e7e670003 Orthography in the log message 2019-02-13 12:42:39 +03:00
hroff-1902
8fc8c985d8 logs: cosmetic changes
"30 seconds" was errorneously hardcoded in the text of the log message, while actually it's RETRY_TIMEOUT which may differ
2019-02-13 12:38:44 +03:00
hroff-1902
69a24c1272 no need for test_ticker parameter just for making current tests happy, tests should be reimplemented 2019-02-13 12:23:22 +03:00
hroff-1902
e8daadfb7e same approach for the sell side (at handle_trade) 2019-02-13 03:54:57 +03:00
hroff-1902
91629807f7 shame on me 2019-02-13 03:17:54 +03:00
hroff-1902
f551fb5ff7 adjusted for passing tests
Don't like this test_ticker parameter, but it's needed for tests to pass prepared ticker.
Any ideas?
2019-02-13 03:14:24 +03:00
hroff-1902
e8ef36fb6e execute_buy: do not use ticker if use_order_book:true is set in config
This PR corresponds to:
https://github.com/freqtrade/freqtrade/issues/1377#issue-386200394
in understanfing that pair Ticker is mostly statistics, but on the other side, create_trade/execute_buy.

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

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

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

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

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

1
.gitignore vendored
View File

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

View File

@@ -1,4 +1,34 @@
# autogenerated pyup.io config file
# 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
- requirements-pi.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 pytest-asyncio 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
# Allow failure for coveralls
- coveralls || true
name: pytest
- script:
- cp config.json.example config.json
- python freqtrade/main.py --datadir freqtrade/tests/testdata backtesting
- python freqtrade --datadir freqtrade/tests/testdata backtesting
name: backtest
- script:
- cp config.json.example config.json
- python freqtrade/main.py --datadir freqtrade/tests/testdata hyperopt -e 5
- script: flake8 freqtrade
- script: mypy freqtrade
- python freqtrade --datadir freqtrade/tests/testdata hyperopt -e 5
name: hyperopt
- script: flake8 freqtrade scripts
name: flake8
- script: mypy freqtrade scripts
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"
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,19 +1,20 @@
FROM python:3.7.0-slim-stretch
FROM python:3.7.3-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 libssl-dev \
&& 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 --no-cache-dir \

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 []

40
Dockerfile.pi Normal file
View File

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

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

141
README.md
View File

@@ -1,24 +1,25 @@
# freqtrade
# Freqtrade
[![Build Status](https://travis-ci.org/freqtrade/freqtrade.svg?branch=develop)](https://travis-ci.org/freqtrade/freqtrade)
[![Coverage Status](https://coveralls.io/repos/github/freqtrade/freqtrade/badge.svg?branch=develop&service=github)](https://coveralls.io/github/freqtrade/freqtrade?branch=develop)
[![Documentation](https://readthedocs.org/projects/freqtrade/badge/)](https://www.freqtrade.io)
[![Maintainability](https://api.codeclimate.com/v1/badges/5737e6d668200b7518ff/maintainability)](https://codeclimate.com/github/freqtrade/freqtrade/maintainability)
Simple High frequency trading bot for crypto currencies designed to support multi exchanges and be controlled via Telegram.
Freqtrade is a free and open source crypto trading bot written in Python. It is designed to support all major exchanges and be controlled via Telegram. It contains backtesting, plotting and money management tools as well as strategy optimization by machine learning.
![freqtrade](https://raw.githubusercontent.com/freqtrade/freqtrade/develop/docs/assets/freqtrade-screenshot.png)
## Disclaimer
This software is for educational purposes only. Do not risk money which
you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS
AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.
This software is for educational purposes only. Do not risk money which
you are afraid to lose. USE THE SOFTWARE AT YOUR OWN RISK. THE AUTHORS
AND ALL AFFILIATES ASSUME NO RESPONSIBILITY FOR YOUR TRADING RESULTS.
Always start by running a trading bot in Dry-run and do not engage money
before you understand how it works and what profit/loss you should
expect.
We strongly recommend you to have coding and Python knowledge. Do not
We strongly recommend you to have coding and Python knowledge. Do not
hesitate to read the source code and understand the mechanism of this bot.
## Exchange marketplaces supported
@@ -27,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,53 @@ git checkout develop
./setup.sh --install
```
_Windows installation is explained in [Installation doc](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md)_
For any other type of installation please refer to [Installation doc](https://www.freqtrade.io/en/latest/installation/).
## Documentation
We invite you to read the bot documentation to ensure you understand how the bot is working.
- [Index](https://github.com/freqtrade/freqtrade/blob/develop/docs/index.md)
- [Installation](https://github.com/freqtrade/freqtrade/blob/develop/docs/installation.md)
- [Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md)
- [Bot usage](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md)
- [How to run the bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#bot-commands)
- [How to use Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#backtesting-commands)
- [How to use Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands)
- [Strategy Optimization](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md)
- [Backtesting](https://github.com/freqtrade/freqtrade/blob/develop/docs/backtesting.md)
- [Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/hyperopt.md)
## Basic Usage
### Bot commands
```bash
usage: main.py [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
[--strategy-path PATH] [--dynamic-whitelist [INT]]
[--dry-run-db]
{backtesting,hyperopt} ...
```
usage: freqtrade [-h] [-v] [--logfile FILE] [--version] [-c PATH] [-d PATH]
[-s NAME] [--strategy-path PATH] [--dynamic-whitelist [INT]]
[--db-url PATH] [--sd-notify]
{backtesting,edge,hyperopt} ...
Simple High Frequency Trading Bot for crypto currencies
Free, open source crypto trading bot
positional arguments:
{backtesting,hyperopt}
backtesting backtesting module
hyperopt hyperopt module
{backtesting,edge,hyperopt}
backtesting Backtesting module.
edge Edge module.
hyperopt Hyperopt module.
optional arguments:
-h, --help show this help message and exit
-v, --verbose be verbose
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified
--version show program's version number and exit
-c PATH, --config PATH
specify configuration file (default: config.json)
Specify configuration file (default: None). Multiple
--config options may be used.
-d PATH, --datadir PATH
path to backtest data (default:
freqtrade/tests/testdata
Path to backtest data.
-s NAME, --strategy NAME
specify strategy class name (default: DefaultStrategy)
--strategy-path PATH specify additional strategy lookup path
Specify strategy class name (default:
DefaultStrategy).
--strategy-path PATH Specify additional strategy lookup path.
--dynamic-whitelist [INT]
dynamically generate and update whitelist based on 24h
BaseVolume (Default 20 currencies)
--dry-run-db Force dry run to use a local DB
"tradesv3.dry_run.sqlite" instead of memory DB. Work
only if dry_run is enabled.
Dynamically generate and update whitelist based on 24h
BaseVolume (default: 20). DEPRECATED.
--db-url PATH Override trades database URL, this is useful if
dry_run is enabled or in custom deployments (default:
None).
--sd-notify Notify systemd service manager.
```
### Telegram RPC commands
Telegram is not mandatory. However, this is a great way to control your bot. More details on our [documentation](https://github.com/freqtrade/freqtrade/blob/develop/docs/index.md)
Telegram is not mandatory. However, this is a great way to control your bot. More details on our [documentation](https://www.freqtrade.io/en/latest/telegram-usage/)
- `/start`: Starts the trader
- `/stop`: Stops the trader
@@ -171,28 +146,32 @@ information about the bot, we encourage you to join our slack channel.
### [Bugs / Issues](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
If you discover a bug in the bot, please
[search our issue tracker](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
first. If it hasn't been reported, please
[create a new issue](https://github.com/freqtrade/freqtrade/issues/new) and
ensure you follow the template guide so that our team can assist you as
If you discover a bug in the bot, please
[search our issue tracker](https://github.com/freqtrade/freqtrade/issues?q=is%3Aissue)
first. If it hasn't been reported, please
[create a new issue](https://github.com/freqtrade/freqtrade/issues/new) and
ensure you follow the template guide so that our team can assist you as
quickly as possible.
### [Feature Requests](https://github.com/freqtrade/freqtrade/labels/enhancement)
Have you a great idea to improve the bot you want to share? Please,
first search if this feature was not [already discussed](https://github.com/freqtrade/freqtrade/labels/enhancement).
If it hasn't been requested, please
[create a new request](https://github.com/freqtrade/freqtrade/issues/new)
and ensure you follow the template guide so that it does not get lost
If it hasn't been requested, please
[create a new request](https://github.com/freqtrade/freqtrade/issues/new)
and ensure you follow the template guide so that it does not get lost
in the bug reports.
### [Pull Requests](https://github.com/freqtrade/freqtrade/pulls)
Feel like our bot is missing a feature? We welcome your pull requests!
Please read our
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.
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.
@@ -212,7 +191,7 @@ To run this bot we recommend you a cloud instance with a minimum of:
### Software requirements
- [Python 3.6.x](http://docs.python-guide.org/en/latest/starting/installation/)
- [Python 3.6.x](http://docs.python-guide.org/en/latest/starting/installation/)
- [pip](https://pip.pypa.io/en/stable/installing/)
- [git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
- [TA-Lib](https://mrjbq7.github.io/ta-lib/install.html)

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

@@ -28,7 +28,11 @@
"name": "bittrex",
"key": "your_exchange_key",
"secret": "your_exchange_secret",
"ccxt_rate_limit": true,
"ccxt_config": {"enableRateLimit": true},
"ccxt_async_config": {
"enableRateLimit": true,
"rateLimit": 500
},
"pair_whitelist": [
"ETH/BTC",
"LTC/BTC",
@@ -50,12 +54,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,84 @@
{
"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": true,
"rateLimit": 200
},
"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,11 +3,13 @@
"stake_currency": "BTC",
"stake_amount": 0.05,
"fiat_display_currency": "USD",
"amount_reserve_percent" : 0.05,
"dry_run": false,
"ticker_interval": "5m",
"trailing_stop": false,
"trailing_stop_positive": 0.005,
"trailing_stop_positive_offset": 0.0051,
"trailing_only_offset_is_reached": false,
"minimal_roi": {
"40": 0.0,
"30": 0.01,
@@ -33,11 +35,35 @@
"order_book_min": 1,
"order_book_max": 9
},
"order_types": {
"buy": "limit",
"sell": "limit",
"stoploss": "market",
"stoploss_on_exchange": false,
"stoploss_on_exchange_interval": 60
},
"order_time_in_force": {
"buy": "gtc",
"sell": "gtc"
},
"pairlist": {
"method": "VolumePairList",
"config": {
"number_assets": 20,
"sort_key": "quoteVolume",
"precision_filter": false
}
},
"exchange": {
"name": "bittrex",
"key": "your_exchange_key",
"secret": "your_exchange_secret",
"ccxt_rate_limit": true,
"ccxt_config": {"enableRateLimit": true},
"ccxt_async_config": {
"enableRateLimit": false,
"rateLimit": 500,
"aiohttp_trust_env": false
},
"pair_whitelist": [
"ETH/BTC",
"LTC/BTC",
@@ -53,7 +79,23 @@
"pair_blacklist": [
"DOGE/BTC"
],
"outdated_offset": 5
"outdated_offset": 5,
"markets_refresh_interval": 60
},
"edge": {
"enabled": false,
"process_throttle_secs": 3600,
"calculate_since_number_of_days": 7,
"capital_available_percentage": 0.5,
"allowed_risk": 0.01,
"stoploss_range_min": -0.01,
"stoploss_range_max": -0.1,
"stoploss_range_step": -0.01,
"minimum_winrate": 0.60,
"minimum_expectancy": 0.20,
"min_trade_number": 10,
"max_trade_duration_minute": 1440,
"remove_pumps": false
},
"experimental": {
"use_sell_signal": false,
@@ -67,6 +109,7 @@
},
"db_url": "sqlite:///tradesv3.sqlite",
"initial_state": "running",
"forcebuy_enable": false,
"internals": {
"process_throttle_secs": 5
},

View File

@@ -0,0 +1,71 @@
{
"max_open_trades": 5,
"stake_currency": "EUR",
"stake_amount": 10,
"fiat_display_currency": "EUR",
"ticker_interval" : "5m",
"dry_run": true,
"db_url": "sqlite:///tradesv3.dryrun.sqlite",
"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": "kraken",
"key": "",
"secret": "",
"ccxt_config": {"enableRateLimit": true},
"ccxt_async_config": {
"enableRateLimit": true,
"rateLimit": 1000
},
"pair_whitelist": [
"ETH/EUR",
"BTC/EUR",
"BCH/EUR"
],
"pair_blacklist": [
]
},
"edge": {
"enabled": false,
"process_throttle_secs": 3600,
"calculate_since_number_of_days": 7,
"capital_available_percentage": 0.5,
"allowed_risk": 0.01,
"stoploss_range_min": -0.01,
"stoploss_range_max": -0.1,
"stoploss_range_step": -0.01,
"minimum_winrate": 0.60,
"minimum_expectancy": 0.20,
"min_trade_number": 10,
"max_trade_duration_minute": 1440,
"remove_pumps": false
},
"telegram": {
"enabled": false,
"token": "",
"chat_id": ""
},
"initial_state": "running",
"forcebuy_enable": false,
"internals": {
"process_throttle_secs": 5
}
}

View File

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

View File

@@ -1,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
@@ -23,7 +14,7 @@ Let assume you have a class called `AwesomeStrategy` in the file `awesome-strate
2. Start the bot with the param `--strategy AwesomeStrategy` (the parameter is the class name)
```bash
python3 ./freqtrade/main.py --strategy AwesomeStrategy
python3 freqtrade --strategy AwesomeStrategy
```
## Change your strategy
@@ -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 --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.
All methods return `None` in case of failure (do not raise an exception).
Please always check the mode of operation to select the correct method to get data (samples see below).
#### Possible options for DataProvider
- `available_pairs` - Property with tuples listing cached pairs with their intervals. (pair, interval)
- `ohlcv(pair, ticker_interval)` - Currently cached ticker data for all pairs in the whitelist, returns DataFrame or empty DataFrame
- `historic_ohlcv(pair, ticker_interval)` - Data stored on disk
- `runmode` - Property containing the current runmode.
#### ohlcv / historic_ohlcv
``` python
if self.dp:
if self.dp.runmode in ('live', 'dry_run'):
if (f'{self.stake_currency}/BTC', self.ticker_interval) in self.dp.available_pairs:
data_eth = self.dp.ohlcv(pair='{self.stake_currency}/BTC',
ticker_interval=self.ticker_interval)
else:
# Get historic ohlcv data (cached on disk).
history_eth = self.dp.historic_ohlcv(pair='{self.stake_currency}/BTC',
ticker_interval='1h')
```
!!! Warning Warning about backtesting
Be carefull when using dataprovider in backtesting. `historic_ohlcv()` provides the full time-range in one go,
so please be aware of it and make sure to not "look into the future" to avoid surprises when running in dry/live mode).
!!! Warning Warning in hyperopt
This option cannot currently be used during hyperopt.
#### 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 --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,57 +1,87 @@
# Bot usage
# Start the bot
This page explains the difference parameters of the bot and how to run it.
This page explains the different parameters of the bot and how to run it.
## Table of Contents
- [Bot commands](#bot-commands)
- [Backtesting commands](#backtesting-commands)
- [Hyperopt commands](#hyperopt-commands)
## Bot commands
```
usage: freqtrade [-h] [-v] [--version] [-c PATH] [-d PATH] [-s NAME]
[--strategy-path PATH] [--dynamic-whitelist [INT]]
[--db-url PATH]
{backtesting,hyperopt} ...
usage: freqtrade [-h] [-v] [--logfile FILE] [--version] [-c PATH] [-d PATH]
[-s NAME] [--strategy-path PATH] [--dynamic-whitelist [INT]]
[--db-url PATH] [--sd-notify]
{backtesting,edge,hyperopt} ...
Simple High Frequency Trading Bot for crypto currencies
Free, open source crypto trading bot
positional arguments:
{backtesting,hyperopt}
backtesting backtesting module
hyperopt hyperopt module
{backtesting,edge,hyperopt}
backtesting Backtesting module.
edge Edge module.
hyperopt Hyperopt module.
optional arguments:
-h, --help show this help message and exit
-v, --verbose be verbose
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
--logfile FILE Log to the file specified
--version show program's version number and exit
-c PATH, --config PATH
specify configuration file (default: config.json)
Specify configuration file (default: None). Multiple
--config options may be used.
-d PATH, --datadir PATH
path to backtest data
Path to backtest data.
-s NAME, --strategy NAME
specify strategy class name (default: DefaultStrategy)
--strategy-path PATH specify additional strategy lookup path
Specify strategy class name (default:
DefaultStrategy).
--strategy-path PATH Specify additional strategy lookup path.
--dynamic-whitelist [INT]
dynamically generate and update whitelist based on 24h
BaseVolume (default: 20)
Dynamically generate and update whitelist based on 24h
BaseVolume (default: 20). DEPRECATED.
--db-url PATH Override trades database URL, this is useful if
dry_run is enabled or in custom deployments (default:
sqlite:///tradesv3.sqlite)
None).
--sd-notify Notify systemd service manager.
```
### How to use a different config file?
### How to use a different configuration file?
The bot allows you to select which config file you want to use. Per
The bot allows you to select which configuration file you want to use. Per
default, the bot will load the file `./config.json`
```bash
python3 ./freqtrade/main.py -c path/far/far/away/config.json
python3 freqtrade -c path/far/far/away/config.json
```
### How to use --strategy?
### How to use multiple configuration files?
The bot allows you to use multiple configuration files by specifying multiple
`-c/--config` configuration options in the command line. Configuration parameters
defined in the last configuration file override parameters with the same name
defined in the previous configuration file specified in the command line.
For example, you can make a separate configuration file with your key and secrete
for the Exchange you use for trading, specify default configuration file with
empty key and secrete values while running in the Dry Mode (which does not actually
require them):
```bash
python3 freqtrade -c ./config.json
```
and specify both configuration files when running in the normal Live Trade Mode:
```bash
python3 freqtrade -c ./config.json -c path/to/secrets/keys.config.json
```
This could help you hide your private Exchange key and Exchange secrete on you local machine
by setting appropriate file permissions for the file which contains actual secrets and, additionally,
prevent unintended disclosure of sensitive private data when you publish examples
of your configuration in the project issues or in the Internet.
See more details on this technique with examples in the documentation page on
[configuration](configuration.md).
### How to use **--strategy**?
This parameter will allow you to load your custom strategy class.
Per default without `--strategy` or `-s` the bot will load the
@@ -61,65 +91,53 @@ The bot will search your strategy file within `user_data/strategies` and `freqtr
To load a strategy, simply pass the class name (e.g.: `CustomStrategy`) in this parameter.
**Example:**
**Example:**
In `user_data/strategies` you have a file `my_awesome_strategy.py` which has
a strategy class called `AwesomeStrategy` to load it:
```bash
python3 ./freqtrade/main.py --strategy AwesomeStrategy
python3 freqtrade --strategy AwesomeStrategy
```
If the bot does not find your strategy file, it will display in an error
If the bot does not find your strategy file, it will display in an error
message the reason (File not found, or errors in your code).
Learn more about strategy file in [optimize your bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-optimization.md).
Learn more about strategy file in
[optimize your bot](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!):
```bash
python3 ./freqtrade/main.py --strategy AwesomeStrategy --strategy-path /some/folder
python3 freqtrade --strategy AwesomeStrategy --strategy-path /some/folder
```
#### How to install a strategy?
This is very simple. Copy paste your strategy file into the folder
This is very simple. Copy paste your strategy file into the 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**?
Per default `--dynamic-whitelist` will retrieve the 20 currencies based
on BaseVolume. This value can be changed when you run the script.
!!! danger "DEPRECATED"
This command line option is deprecated. Please move your configurations using it
to the configurations that utilize the `StaticPairList` or `VolumePairList` methods set
in the configuration file
as outlined [here](configuration/#dynamic-pairlists)
**By Default**
Get the 20 currencies based on BaseVolume.
Description of this deprecated feature was moved to [here](deprecated.md).
Please no longer use it.
```bash
python3 ./freqtrade/main.py --dynamic-whitelist
```
### How to use **--db-url**?
**Customize the number of currencies to retrieve**
Get the 30 currencies based on BaseVolume.
```bash
python3 ./freqtrade/main.py --dynamic-whitelist 30
```
**Exception**
`--dynamic-whitelist` must be greater than 0. If you enter 0 or a
negative value (e.g -2), `--dynamic-whitelist` will use the default
value (20).
### How to use --db-url?
When you run the bot in Dry-run mode, per default no transactions are
stored in a database. If you want to store your bot actions in a DB
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
using `--db-url`. This can also be used to specify a custom database
in production mode. Example command:
```bash
python3 ./freqtrade/main.py -c config.json --db-url sqlite:///tradesv3.dry_run.sqlite
python3 freqtrade -c config.json --db-url sqlite:///tradesv3.dry_run.sqlite
```
## Backtesting commands
@@ -127,27 +145,27 @@ 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]
usage: freqtrade 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)
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)
stacking).
--dmmp, --disable-max-market-positions
Disable applying `max_open_trades` during backtest
(same as setting `max_open_trades` to a very high
number)
--timerange TIMERANGE
specify what timerange of data to use.
-l, --live using live data
number).
-l, --live Use live data.
-r, --refresh-pairs-cached
refresh the pairs files in tests/testdata with the
Refresh the pairs files in tests/testdata with the
latest data from the exchange. Use it if you want to
run your backtesting with up-to-date data.
--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]
@@ -157,7 +175,7 @@ optional arguments:
this together with --export trades, the strategy-name
is injected into the filename (so backtest-data.json
becomes backtest-data-DefaultStrategy.json
--export EXPORT export backtest results, argument are: trades Example
--export EXPORT Export backtest results, argument are: trades. Example
--export=trades
--export-filename PATH
Save backtest results to this filename requires
@@ -165,20 +183,20 @@ 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.
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.**
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.
To test your strategy with latest data, we recommend continuing using
!!! 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`.
## Hyperopt commands
@@ -187,30 +205,60 @@ To optimize your strategy, you can use hyperopt parameter hyperoptimization
to find optimal parameter values for your stategy.
```
usage: freqtrade hyperopt [-h] [-i TICKER_INTERVAL] [--eps] [--dmmp]
[--timerange TIMERANGE] [-e INT]
[-s {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...]]
usage: freqtrade hyperopt [-h] [-i TICKER_INTERVAL] [--timerange TIMERANGE]
[--customhyperopt NAME] [--eps] [--dmmp] [-e INT]
[-s {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]]
optional arguments:
-h, --help show this help message and exit
-i TICKER_INTERVAL, --ticker-interval TICKER_INTERVAL
specify ticker interval (1m, 5m, 30m, 1h, 1d)
Specify ticker interval (1m, 5m, 30m, 1h, 1d).
--timerange TIMERANGE
Specify what timerange of data to use.
--customhyperopt NAME
Specify hyperopt class name (default:
DefaultHyperOpts).
--eps, --enable-position-stacking
Allow buying the same pair multiple times (position
stacking)
stacking).
--dmmp, --disable-max-market-positions
Disable applying `max_open_trades` during backtest
(same as setting `max_open_trades` to a very high
number)
--timerange TIMERANGE
specify what timerange of data to use.
-e INT, --epochs INT specify number of epochs (default: 100)
-s {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...], --spaces {all,buy,roi,stoploss} [{all,buy,roi,stoploss} ...]
number).
-e INT, --epochs INT Specify number of epochs (default: 100).
-s {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...], --spaces {all,buy,sell,roi,stoploss} [{all,buy,sell,roi,stoploss} ...]
Specify which parameters to hyperopt. Space separate
list. Default: all
list. Default: all.
```
## Edge commands
To know your trade expectacny and winrate against historical data, you can use Edge.
```
usage: freqtrade 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 strategy the format is "min,max,step"
(without any space).example:
--stoplosses=-0.01,-0.1,-0.001
```
To understand edge and how to read the results, please read the [edge documentation](edge.md).
## A parameter missing in the configuration?
All parameters for `main.py`, `backtesting`, `hyperopt` are referenced
@@ -218,5 +266,5 @@ 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).
The optimal strategy of the bot will change with time depending of the market trends. The next step is to
[optimize your bot](bot-optimization.md).

View File

@@ -2,83 +2,120 @@
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
for your bot configuration.
The table below will list all configuration parameters.
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.
| `process_only_new_candles` | false | No | If set to true indicators are processed only once a new candle arrives. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. Can be set either in Configuration or in the strategy.
| `minimal_roi` | See below | No | Set the threshold in percent the bot will use to sell a trade. More information below. If set, this parameter will override `minimal_roi` from your strategy file.
| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. If set, this parameter will override `stoploss` from your strategy file.
| `trailing_stop` | false | No | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file).
| `trailing_stop_positve` | 0 | No | Changes stop-loss once profit has been reached.
| `trailing_stop_positve_offset` | 0 | No | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive.
| `unfilledtimeout.buy` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled.
| `unfilledtimeout.sell` | 10 | Yes | How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled.
| `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below.
| `bid_strategy.use_order_book` | false | No | Allows buying of pair using the rates in Order Book Bids.
| `bid_strategy.order_book_top` | 0 | No | Bot will use the top N rate in Order Book Bids. Ie. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids.
| `bid_strategy.check_depth_of_market.enabled` | false | No | Does not buy if the % difference of buy orders and sell orders is met in Order Book.
| `bid_strategy.check_depth_of_market.bids_to_ask_delta` | 0 | No | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher.
| `ask_strategy.use_order_book` | false | No | Allows selling of open traded pair using the rates in Order Book Asks.
| `ask_strategy.order_book_min` | 0 | No | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
| `ask_strategy.order_book_max` | 0 | No | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
| `exchange.name` | bittrex | Yes | Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
| `exchange.key` | key | No | API key to use for the exchange. Only required when you are in production mode.
| `exchange.secret` | secret | No | API secret to use for the exchange. Only required when you are in production mode.
| `exchange.pair_whitelist` | [] | No | List of currency to use by the bot. Can be overrided with `--dynamic-whitelist` param.
| `exchange.pair_blacklist` | [] | No | List of currency the bot must avoid. Useful when using `--dynamic-whitelist` param.
| `exchange.ccxt_rate_limit` | True | No | Have CCXT handle Exchange rate limits. Depending on the exchange, having this to false can lead to temporary bans from the exchange.
| `experimental.use_sell_signal` | false | No | Use your sell strategy in addition of the `minimal_roi`.
| `experimental.sell_profit_only` | false | No | waits until you have made a positive profit before taking a sell decision.
| `experimental.ignore_roi_if_buy_signal` | false | No | Does not sell if the buy-signal is still active. Takes preference over `minimal_roi` and `use_sell_signal`
| `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. [Strategy Override](#parameters-in-the-strategy).
| `stake_amount` | 0.05 | **Required.** Amount of crypto-currency your bot will use for each trade. Per default, the bot will use (0.05 BTC x 3) = 0.15 BTC in total will be always engaged. Set it to `"unlimited"` to allow the bot to use all available balance. [Strategy Override](#parameters-in-the-strategy).
| `amount_reserve_percent` | 0.05 | Reserve some amount in min pair stake amount. Default is 5%. The bot will reserve `amount_reserve_percent` + stop-loss value when calculating min pair stake amount in order to avoid possible trade refusals.
| `ticker_interval` | [1m, 5m, 15m, 30m, 1h, 1d, ...] | The ticker interval to use (1min, 5 min, 15 min, 30 min, 1 hour or 1 day). Default is 5 minutes. [Strategy Override](#parameters-in-the-strategy).
| `fiat_display_currency` | USD | **Required.** Fiat currency used to show your profits. More information below.
| `dry_run` | true | **Required.** Define if the bot must be in Dry-run or production mode.
| `dry_run_wallet` | 999.9 | Overrides the default amount of 999.9 stake currency units in the wallet used by the bot running in the Dry Run mode if you need it for any reason.
| `process_only_new_candles` | false | If set to true indicators are processed only once a new candle arrives. If false each loop populates the indicators, this will mean the same candle is processed many times creating system load but can be useful of your strategy depends on tick data not only candle. [Strategy Override](#parameters-in-the-strategy).
| `minimal_roi` | See below | Set the threshold in percent the bot will use to sell a trade. More information below. [Strategy Override](#parameters-in-the-strategy).
| `stoploss` | -0.10 | Value of the stoploss in percent used by the bot. More information below. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
| `trailing_stop` | false | Enables trailing stop-loss (based on `stoploss` in either configuration or strategy file). More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
| `trailing_stop_positive` | 0 | Changes stop-loss once profit has been reached. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
| `trailing_stop_positive_offset` | 0 | Offset on when to apply `trailing_stop_positive`. Percentage value which should be positive. More details in the [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
| `trailing_only_offset_is_reached` | false | Only apply trailing stoploss when the offset is reached. [stoploss documentation](stoploss.md). [Strategy Override](#parameters-in-the-strategy).
| `unfilledtimeout.buy` | 10 | **Required.** How long (in minutes) the bot will wait for an unfilled buy order to complete, after which the order will be cancelled.
| `unfilledtimeout.sell` | 10 | **Required.** How long (in minutes) the bot will wait for an unfilled sell order to complete, after which the order will be cancelled.
| `bid_strategy.ask_last_balance` | 0.0 | **Required.** Set the bidding price. More information [below](#understand-ask_last_balance).
| `bid_strategy.use_order_book` | false | Allows buying of pair using the rates in Order Book Bids.
| `bid_strategy.order_book_top` | 0 | Bot will use the top N rate in Order Book Bids. Ie. a value of 2 will allow the bot to pick the 2nd bid rate in Order Book Bids.
| `bid_strategy. check_depth_of_market.enabled` | false | Does not buy if the % difference of buy orders and sell orders is met in Order Book.
| `bid_strategy. check_depth_of_market.bids_to_ask_delta` | 0 | The % difference of buy orders and sell orders found in Order Book. A value lesser than 1 means sell orders is greater, while value greater than 1 means buy orders is higher.
| `ask_strategy.use_order_book` | false | Allows selling of open traded pair using the rates in Order Book Asks.
| `ask_strategy.order_book_min` | 0 | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
| `ask_strategy.order_book_max` | 0 | Bot will scan from the top min to max Order Book Asks searching for a profitable rate.
| `order_types` | None | Configure order-types depending on the action (`"buy"`, `"sell"`, `"stoploss"`, `"stoploss_on_exchange"`). [More information below](#understand-order_types). [Strategy Override](#parameters-in-the-strategy).
| `order_time_in_force` | None | Configure time in force for buy and sell orders. [More information below](#understand-order_time_in_force). [Strategy Override](#parameters-in-the-strategy).
| `exchange.name` | bittrex | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename).
| `exchange.sandbox` | false | Use the 'sandbox' version of the exchange, where the exchange provides a sandbox for risk-free integration. See [here](sandbox-testing.md) in more details.
| `exchange.key` | 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_config` | None | Additional CCXT parameters passed to the regular ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
| `exchange.ccxt_async_config` | None | Additional CCXT parameters passed to the async ccxt instance. Parameters may differ from exchange to exchange and are documented in the [ccxt documentation](https://ccxt.readthedocs.io/en/latest/manual.html#instantiation)
| `exchange.markets_refresh_interval` | 60 | The interval in minutes in which markets are reloaded.
| `edge` | false | Please refer to [edge configuration document](edge.md) for detailed explanation.
| `experimental.use_sell_signal` | false | Use your sell strategy in addition of the `minimal_roi`. [Strategy Override](#parameters-in-the-strategy).
| `experimental.sell_profit_only` | false | Waits until you have made a positive profit before taking a sell decision. [Strategy Override](#parameters-in-the-strategy).
| `experimental.ignore_roi_if_buy_signal` | false | Does not sell if the buy-signal is still active. Takes preference over `minimal_roi` and `use_sell_signal`. [Strategy Override](#parameters-in-the-strategy).
| `pairlist.method` | StaticPairList | Use Static whitelist. [More information below](#dynamic-pairlists).
| `pairlist.config` | None | Additional configuration for dynamic pairlists. [More information below](#dynamic-pairlists).
| `telegram.enabled` | true | **Required.** Enable or not the usage of Telegram.
| `telegram.token` | token | Your Telegram bot token. Only required if `telegram.enabled` is `true`.
| `telegram.chat_id` | chat_id | Your personal Telegram account id. Only required if `telegram.enabled` is `true`.
| `webhook.enabled` | false | Enable usage of Webhook notifications
| `webhook.url` | false | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
| `webhook.webhookbuy` | false | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `webhook.webhooksell` | false | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `webhook.webhookstatus` | false | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentationV](webhook-config.md) for more details.
| `db_url` | `sqlite:///tradesv3.sqlite`| Declares database URL to use. NOTE: This defaults to `sqlite://` if `dry_run` is `True`.
| `initial_state` | running | Defines the initial application state. More information below.
| `forcebuy_enable` | false | Enables the RPC Commands to force a buy. More information below.
| `strategy` | DefaultStrategy | Defines Strategy class to use.
| `strategy_path` | null | Adds an additional strategy lookup path (must be a folder).
| `internals.process_throttle_secs` | 5 | **Required.** Set the process throttle. Value in second.
| `internals.sd_notify` | false | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details.
| `logfile` | | Specify Logfile. Uses a rolling strategy of 10 files, with 1Mb per file.
### Parameters in the strategy
The following parameters can be set in either configuration file or strategy.
Values set in the configuration file always overwrite values set in the strategy.
* `stake_currency`
* `stake_amount`
* `ticker_interval`
* `minimal_roi`
* `stoploss`
* `trailing_stop`
* `trailing_stop_positive`
* `trailing_stop_positive_offset`
* `process_only_new_candles`
* `order_types`
* `order_time_in_force`
* `use_sell_signal` (experimental)
* `sell_profit_only` (experimental)
* `ignore_roi_if_buy_signal` (experimental)
### Understand stake_amount
`stake_amount` is an amount of crypto-currency your bot will use for each trade.
The minimal value is 0.0005. If there is not enough crypto-currency in
The `stake_amount` configuration parameter is an amount of crypto-currency your bot will use for each trade.
The minimal value is 0.0005. If there is not enough crypto-currency in
the account an exception is generated.
To allow the bot to trade all the avaliable `stake_currency` in your account set `stake_amount` = `unlimited`.
In this case a trade amount is calclulated as `currency_balanse / (max_open_trades - current_open_trades)`.
To allow the bot to trade all the available `stake_currency` in your account set
```json
"stake_amount" : "unlimited",
```
In this case a trade amount is calclulated as:
```python
currency_balanse / (max_open_trades - current_open_trades)
```
### Understand minimal_roi
`minimal_roi` is a JSON object where the key is a duration
The `minimal_roi` configuration parameter is a JSON object where the key is a duration
in minutes and the value is the minimum ROI in percent.
See the example below:
```
```json
"minimal_roi": {
"40": 0.0, # Sell after 40 minutes if the profit is not negative
"30": 0.01, # Sell after 30 minutes if there is at least 1% profit
@@ -87,18 +124,19 @@ See the example below:
},
```
Most of the strategy files already include the optimal `minimal_roi`
value. This parameter is optional. If you use it, it will take over the
Most of the strategy files already include the optimal `minimal_roi` value.
This parameter can be set in either Strategy or Configuration file. If you use it in the configuration file, it will override the
`minimal_roi` value from the strategy file.
If it is not set in either Strategy or Configuration, a default of 1000% `{"0": 10}` is used, and minimal roi is disabled unless your trade generates 1000% profit.
### Understand stoploss
`stoploss` is loss in percentage that should trigger a sale.
For example value `-0.10` will cause immediate sell if the
The `stoploss` configuration parameter is loss in percentage that should trigger a sale.
For example, value `-0.10` will cause immediate sell if the
profit dips below -10% for a given trade. This parameter is optional.
Most of the strategy files already include the optimal `stoploss`
value. This parameter is optional. If you use it, it will take over the
value. This parameter is optional. If you use it in the configuration file, it will take over the
`stoploss` value from the strategy file.
### Understand trailing stoploss
@@ -107,27 +145,116 @@ Go to the [trailing stoploss Documentation](stoploss.md) for details on trailing
### Understand initial_state
`initial_state` is an optional field that defines the initial application state.
The `initial_state` configuration parameter is an optional field that defines the initial application state.
Possible values are `running` or `stopped`. (default=`running`)
If the value is `stopped` the bot has to be started with `/start` first.
### Understand forcebuy_enable
The `forcebuy_enable` configuration parameter enables the usage of forcebuy commands via Telegram.
This is disabled for security reasons by default, and will show a warning message on startup if enabled.
For example, you can send `/forcebuy ETH/BTC` Telegram command when this feature if enabled to the bot,
who then buys the pair and holds it until a regular sell-signal (ROI, stoploss, /forcesell) appears.
This can be dangerous with some strategies, so use with care.
See [the telegram documentation](telegram-usage.md) for details on usage.
### Understand process_throttle_secs
`process_throttle_secs` is an optional field that defines in seconds how long the bot should wait
The `process_throttle_secs` configuration parameter is an optional field that defines in seconds how long the bot should wait
before asking the strategy if we should buy or a sell an asset. After each wait period, the strategy is asked again for
every opened trade wether or not we should sell, and for all the remaining pairs (either the dynamic list of pairs or
the static list of pairs) if we should buy.
### Understand ask_last_balance
`ask_last_balance` sets the bidding price. Value `0.0` will use `ask` price, `1.0` will
The `ask_last_balance` configuration parameter sets the bidding price. Value `0.0` will use `ask` price, `1.0` will
use the `last` price and values between those interpolate between ask and last
price. Using `ask` price will guarantee quick success in bid, but bot will also
end up paying more then would probably have been necessary.
### What values for exchange.name?
### Understand order_types
Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports 115 cryptocurrency
The `order_types` configuration parameter contains a dict mapping order-types to
market-types as well as stoploss on or off exchange type and stoploss on exchange
update interval in seconds. This allows to buy using limit orders, sell using
limit-orders, and create stoploss orders using market. It also allows to set the
stoploss "on exchange" which means stoploss order would be placed immediately once
the buy order is fulfilled. In case stoploss on exchange and `trailing_stop` are
both set, then the bot will use `stoploss_on_exchange_interval` to check it periodically
and update it if necessary (e.x. in case of trailing stoploss).
This can be set in the configuration file or in the strategy.
Values set in the configuration file overwrites values set in the strategy.
If this is configured, all 4 values (`buy`, `sell`, `stoploss` and
`stoploss_on_exchange`) need to be present, otherwise the bot will warn about it and fail to start.
The below is the default which is used if this is not configured in either strategy or configuration file.
```python
"order_types": {
"buy": "limit",
"sell": "limit",
"stoploss": "market",
"stoploss_on_exchange": False,
"stoploss_on_exchange_interval": 60
},
```
!!! Note
Not all exchanges support "market" orders.
The following message will be shown if your exchange does not support market orders:
`"Exchange <yourexchange> does not support market orders."`
!!! Note
Stoploss on exchange interval is not mandatory. Do not change its value if you are
unsure of what you are doing. For more information about how stoploss works please
read [the stoploss documentation](stoploss.md).
!!! Note
In case of stoploss on exchange if the stoploss is cancelled manually then
the bot would recreate one.
### Understand order_time_in_force
The `order_time_in_force` configuration parameter defines the policy by which the order
is executed on the exchange. Three commonly used time in force are:
**GTC (Good Till Canceled):**
This is most of the time the default time in force. It means the order will remain
on exchange till it is canceled by user. It can be fully or partially fulfilled.
If partially fulfilled, the remaining will stay on the exchange till cancelled.
**FOK (Full Or Kill):**
It means if the order is not executed immediately AND fully then it is canceled by the exchange.
**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.
The `order_time_in_force` parameter contains a dict with buy and sell time in force policy values.
This can be set in the configuration file or in the strategy.
Values set in the configuration file overwrites values set in the strategy.
The possible values are: `gtc` (default), `fok` or `ioc`.
``` python
"order_time_in_force": {
"buy": "gtc",
"sell": "gtc"
},
```
!!! 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.
### Exchange configuration
Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports over 100 cryptocurrency
exchange markets and trading APIs. The complete up-to-date list can be found in the
[CCXT repo homepage](https://github.com/ccxt/ccxt/tree/master/python). However, the bot was tested
with only Bittrex and Binance.
@@ -139,31 +266,65 @@ The bot was tested with the following exchanges:
Feel free to test other exchanges and submit your PR to improve the bot.
### What values for fiat_display_currency?
#### Sample exchange configuration
`fiat_display_currency` set the base currency to use for the conversion from coin to fiat in Telegram.
The valid values are: "AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD".
In addition to central bank currencies, a range of cryto currencies are supported.
The valid values are: "BTC", "ETH", "XRP", "LTC", "BCH", "USDT".
A exchange configuration for "binance" would look as follows:
## Switch to dry-run mode
```json
"exchange": {
"name": "binance",
"key": "your_exchange_key",
"secret": "your_exchange_secret",
"ccxt_config": {"enableRateLimit": true},
"ccxt_async_config": {
"enableRateLimit": true,
"rateLimit": 200
},
```
We recommend starting the bot in dry-run mode to see how your bot will
behave and how is the performance of your strategy. In Dry-run mode the
This configuration enables binance, as well as rate limiting to avoid bans from the exchange.
`"rateLimit": 200` defines a wait-event of 0.2s between each call. This can also be completely disabled by setting `"enableRateLimit"` to false.
!!! Note
Optimal settings for rate limiting depend on the exchange and the size of the whitelist, so an ideal parameter will vary on many other settings.
We try to provide sensible defaults per exchange where possible, if you encounter bans please make sure that `"enableRateLimit"` is enabled and increase the `"rateLimit"` parameter step by step.
### What values can be used for fiat_display_currency?
The `fiat_display_currency` configuration parameter sets the base currency to use for the
conversion from coin to fiat in the bot Telegram reports.
The valid values are:
```json
"AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", "EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY", "KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN", "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD"
```
In addition to fiat currencies, a range of cryto currencies are supported.
The valid values are:
```json
"BTC", "ETH", "XRP", "LTC", "BCH", "USDT"
```
## Switch to Dry-run mode
We recommend starting the bot in the Dry-run mode to see how your bot will
behave and what is the performance of your strategy. In the Dry-run mode the
bot does not engage your money. It only runs a live simulation without
creating trades.
creating trades on the exchange.
### To switch your bot in Dry-run mode:
1. Edit your `config.json` file
2. Switch dry-run to true and specify db_url for a persistent db
1. Edit your `config.json` configuration file.
2. Switch `dry-run` to `true` and specify `db_url` for a persistence database.
```json
"dry_run": true,
"db_url": "sqlite///tradesv3.dryrun.sqlite",
"db_url": "sqlite:///tradesv3.dryrun.sqlite",
```
3. Remove your Exchange API key (change them by fake api credentials)
3. Remove your Exchange API key and secrete (change them by empty values or fake credentials):
```json
"exchange": {
@@ -174,26 +335,61 @@ creating trades.
}
```
Once you will be happy with your bot performance, you can switch it to
production mode.
Once you will be happy with your bot performance running in the Dry-run mode,
you can switch it to production mode.
### Dynamic Pairlists
Dynamic pairlists select pairs for you based on the logic configured.
The bot runs against all pairs (with that stake) on the exchange, and a number of assets
(`number_assets`) is selected based on the selected criteria.
By default, the `StaticPairList` method is used.
The Pairlist method is configured as `pair_whitelist` parameter under the `exchange`
section of the configuration.
**Available Pairlist methods:**
* `StaticPairList`
* It uses configuration from `exchange.pair_whitelist` and `exchange.pair_blacklist`.
* `VolumePairList`
* Formerly available as `--dynamic-whitelist [<number_assets>]`. This command line
option is deprecated and should no longer be used.
* It selects `number_assets` top pairs based on `sort_key`, which can be one of
`askVolume`, `bidVolume` and `quoteVolume`, defaults to `quoteVolume`.
* There is a possibility to filter low-value coins that would not allow setting a stop loss
(set `precision_filter` parameter to `true` for this).
Example:
```json
"pairlist": {
"method": "VolumePairList",
"config": {
"number_assets": 20,
"sort_key": "quoteVolume",
"precision_filter": false
}
},
```
## 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
In production mode, the bot will engage your money. Be careful, since a wrong
strategy can lose all your money. Be aware of what you are doing when
you run it in production mode.
### To switch your bot in production mode:
### 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": {
@@ -204,16 +400,37 @@ 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
FreqTrade provides you with with an easy way to embed the strategy into your configuration file.
FreqTrade provides you with with an easy way to embed the strategy into your configuration file.
This is done by utilizing BASE64 encoding and providing this string at the strategy configuration field,
in your chosen config file.
##### Encoding a string as BASE64
#### Encoding a string as BASE64
This is a quick example, how to generate the BASE64 string in python
@@ -235,4 +452,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).

31
docs/deprecated.md Normal file
View File

@@ -0,0 +1,31 @@
# Deprecated features
This page contains description of the command line arguments, configuration parameters
and the bot features that were declared as DEPRECATED by the bot development team
and are no longer supported. Please avoid their usage in your configuration.
### The **--dynamic-whitelist** command line option
Per default `--dynamic-whitelist` will retrieve the 20 currencies based
on BaseVolume. This value can be changed when you run the script.
**By Default**
Get the 20 currencies based on BaseVolume.
```bash
python3 freqtrade --dynamic-whitelist
```
**Customize the number of currencies to retrieve**
Get the 30 currencies based on BaseVolume.
```bash
python3 freqtrade --dynamic-whitelist 30
```
**Exception**
`--dynamic-whitelist` must be greater than 0. If you enter 0 or a
negative value (e.g -2), `--dynamic-whitelist` will use the default
value (20).

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`)

263
docs/edge.md Normal file
View File

@@ -0,0 +1,263 @@
# 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. If enabled, it overrides the dynamic whitelist option.
!!! Note
Edge does not consider anything else than buy/sell/stoploss signals. So trailing stoploss, ROI, and everything else are ignored in its calculation.
## Introduction
Trading is all about probability. No one can claim that he has a strategy working all the time. You have to assume that sometimes you lose.
But it doesn't mean there is no rule, it only means rules should work "most of the time". Let's play a game: we toss a coin, heads: I give you 10$, tails: you give me 10$. Is it an interesting game? No, it's quite boring, isn't it?
But let's say the probability that we have heads is 80% (because our coin has the displaced distribution of mass or other defect), and the probability that we have tails is 20%. Now it is becoming interesting...
That means 10$ X 80% versus 10$ X 20%. 8$ versus 2$. That means over time you will win 8$ risking only 2$ on each toss of coin.
Let's complicate it more: you win 80% of the time but only 2$, I win 20% of the time but 8$. The calculation is: 80% X 2$ versus 20% X 8$. It is becoming boring again because overtime you win $1.6$ (80% X 2$) and me $1.6 (20% X 8$) too.
The question is: How do you calculate that? How do you know if you wanna play?
The answer comes to two factors:
- Win Rate
- Risk Reward Ratio
### Win Rate
Win Rate (*W*) is is the mean over some amount of trades (*N*) what is the percentage of winning trades to total number of trades (note that we don't consider how much you gained but only if you won or not).
W = (Number of winning trades) / (Total number of trades) = (Number of winning trades) / N
Complementary Loss Rate (*L*) is defined as
L = (Number of losing trades) / (Total number of trades) = (Number of losing trades) / N
or, which is the same, as
L = 1 W
### Risk Reward Ratio
Risk Reward Ratio (*R*) is a formula used to measure the expected gains of a given investment against the risk of loss. It is basically what you potentially win divided by what you potentially lose:
R = Profit / Loss
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 = (R X W) L
So lets say your Win rate is 28% and your Risk Reward Ratio is 5:
Expectancy = (5 X 0.28) 0.72 = 0.68
Superficially, this means that on average you expect this strategys trades to return .68 times the size of your loses. This is important for two reasons: First, it may seem obvious, but you know right away that you have a positive return. Second, you now have a number you can compare to other candidate systems to make decisions about which ones you employ.
It is important to remember that any system with an expectancy greater than 0 is profitable using past data. The key is finding one that will be profitable in the future.
You can also use this value to evaluate the effectiveness of modifications to this system.
**NOTICE:** It's important to keep in mind that Edge is testing your expectancy using historical data, there's no guarantee that you will have a similar edge in the future. It's still vital to do this testing in order to build confidence in your methodology, but be wary of "curve-fitting" your approach to the historical data as things are unlikely to play out the exact same way for future trades.
## 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 *N* trades for each stoploss. Here is an example:
| Pair | Stoploss | Win Rate | Risk Reward Ratio | Expectancy |
|----------|:-------------:|-------------:|------------------:|-----------:|
| XZC/ETH | -0.01 | 0.50 |1.176384 | 0.088 |
| XZC/ETH | -0.02 | 0.51 |1.115941 | 0.079 |
| XZC/ETH | -0.03 | 0.52 |1.359670 | 0.228 |
| XZC/ETH | -0.04 | 0.51 |1.234539 | 0.117 |
The goal here is to find the best stoploss for the strategy in order to have the maximum expectancy. In the above example stoploss at 3% leads to the maximum expectancy according to historical data.
Edge module then forces stoploss value it evaluated to your strategy dynamically.
### Position size
Edge also dictates the stake amount for each trade to the bot according to the following factors:
- Allowed capital at risk
- Stoploss
Allowed capital at risk is calculated as follows:
Allowed capital at risk = (Capital available_percentage) X (Allowed risk per trade)
Stoploss is calculated as described above against historical data.
Your position size then will be:
Position size = (Allowed capital at risk) / Stoploss
Example:
Let's say the stake currency is ETH and you have 10 ETH on the exchange, your capital available percentage is 50% and you would allow 1% of risk for each trade. thus your available capital for trading is **10 x 0.5 = 5 ETH** and allowed capital at risk would be **5 x 0.01 = 0.05 ETH**.
Let's assume Edge has calculated that for **XLM/ETH** market your stoploss should be at 2%. So your position size will be **0.05 / 0.02 = 2.5 ETH**.
Bot takes a position of 2.5 ETH on XLM/ETH (call it trade 1). Up next, you receive another buy signal while trade 1 is still open. This time on **BTC/ETH** market. Edge calculated stoploss for this market at 4%. So your position size would be 0.05 / 0.04 = 1.25 ETH (call it trade 2).
Note that available capital for trading didnt change for trade 2 even if you had already trade 1. The available capital doesnt mean the free amount on your wallet.
Now you have two trades open. The bot receives yet another buy signal for another market: **ADA/ETH**. This time the stoploss is calculated at 1%. So your position size is **0.05 / 0.01 = 5 ETH**. But there are already 3.75 ETH blocked in two previous trades. So the position size for this third trade would be **5 3.75 = 1.25 ETH**.
Available capital doesnt change before a position is sold. Lets assume that trade 1 receives a sell signal and it is sold with a profit of 1 ETH. Your total capital on exchange would be 11 ETH and the available capital for trading becomes 5.5 ETH.
So the Bot receives another buy signal for trade 4 with a stoploss at 2% then your position size would be **0.055 / 0.02 = 2.75 ETH**.
## Configurations
Edge module has following configuration options:
#### enabled
If true, then Edge will run periodically.
(defaults to false)
#### process_throttle_secs
How often should Edge run in seconds?
(defaults to 3600 so one hour)
#### calculate_since_number_of_days
Number of days of data against which Edge calculates Win Rate, Risk Reward and Expectancy
Note that it downloads historical data so increasing this number would lead to slowing down the bot.
(defaults to 7)
#### capital_available_percentage
This is the percentage of the total capital on exchange in stake currency.
As an example if you have 10 ETH available in your wallet on the exchange and this value is 0.5 (which is 50%), then the bot will use a maximum amount of 5 ETH for trading and considers it as available capital.
(defaults to 0.5)
#### allowed_risk
Percentage of allowed risk per trade.
(defaults to 0.01 so 1%)
#### stoploss_range_min
Minimum stoploss.
(defaults to -0.01)
#### stoploss_range_max
Maximum stoploss.
(defaults to -0.10)
#### stoploss_range_step
As an example if this is set to -0.01 then Edge will test the strategy for \[-0.01, -0,02, -0,03 ..., -0.09, -0.10\] ranges.
Note than having a smaller step means having a bigger range which could lead to slow calculation.
If you set this parameter to -0.001, you then slow down the Edge calculation by a factor of 10.
(defaults to -0.01)
#### minimum_winrate
It filters out pairs which don't have at least minimum_winrate.
This comes handy if you want to be conservative and don't comprise win rate in favour of risk reward ratio.
(defaults to 0.60)
#### minimum_expectancy
It filters out pairs which have the expectancy lower than this number.
Having an expectancy of 0.20 means if you put 10$ on a trade you expect a 12$ return.
(defaults to 0.20)
#### min_trade_number
When calculating *W*, *R* and *E* (expectancy) against historical data, you always want to have a minimum number of trades. The more this number is the more Edge is reliable.
Having a win rate of 100% on a single trade doesn't mean anything at all. But having a win rate of 70% over past 100 trades means clearly something.
(defaults to 10, it is highly recommended not to decrease this number)
#### max_trade_duration_minute
Edge will filter out trades with long duration. If a trade is profitable after 1 month, it is hard to evaluate the strategy based on it. But if most of trades are profitable and they have maximum duration of 30 minutes, then it is clearly a good sign.
**NOTICE:** While configuring this value, you should take into consideration your ticker interval. As an example filtering out trades having duration less than one day for a strategy which has 4h interval does not make sense. Default value is set assuming your strategy interval is relatively small (1m or 5m, etc.).
(defaults to 1 day, i.e. to 60 * 24 = 1440 minutes)
#### remove_pumps
Edge will remove sudden pumps in a given market while going through historical data. However, given that pumps happen very often in crypto markets, we recommend you keep this off.
(defaults to false)
## Running Edge independently
You can run Edge independently in order to see in details the result. Here is an example:
```bash
python3 freqtrade 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 edge --refresh-pairs-cached
```
### Precising stoploss range
```bash
python3 freqtrade edge --stoplosses=-0.01,-0.1,-0.001 #min,max,step
```
### Advanced use of timerange
```bash
python3 freqtrade edge --timerange=20181110-20181113
```
Doing `--timerange=-200` will get the last 200 timeframes from your inputdata. You can also specify specific dates, or a range span indexed by start and stop.
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

@@ -1,71 +1,89 @@
# freqtrade FAQ
# Freqtrade FAQ
### Freqtrade commons
#### I have waited 5 minutes, why hasn't the bot made any trades yet?!
Depending on the buy strategy, the amount of whitelisted coins, the
situation of the market etc, it can take up to hours to find good entry
Depending on the buy strategy, the amount of whitelisted coins, the
situation of the market etc, it can take up to hours to find good entry
position for a trade. Be patient!
#### I have made 12 trades already, why is my total profit negative?!
I understand your disappointment but unfortunately 12 trades is just
not enough to say anything. If you run backtesting, you can see that our
current algorithm does leave you on the plus side, but that is after
thousands of trades and even there, you will be left with losses on
specific coins that you have traded tens if not hundreds of times. We
of course constantly aim to improve the bot but it will _always_ be a
gamble, which should leave you with modest wins on monthly basis but
I understand your disappointment but unfortunately 12 trades is just
not enough to say anything. If you run backtesting, you can see that our
current algorithm does leave you on the plus side, but that is after
thousands of trades and even there, you will be left with losses on
specific coins that you have traded tens if not hundreds of times. We
of course constantly aim to improve the bot but it will _always_ be a
gamble, which should leave you with modest wins on monthly basis but
you can't say much from few trades.
#### Id like to change the stake amount. Can I just stop the bot with
/stop and then change the config.json and run it again?
#### Id like to change the stake amount. Can I just stop the bot with /stop and then change the config.json and run it again?
Not quite. Trades are persisted to a database but the configuration is
currently only read when the bot is killed and restarted. `/stop` more
Not quite. Trades are persisted to a database but the configuration is
currently only read when the bot is killed and restarted. `/stop` more
like pauses. You can stop your bot, adjust settings and start it again.
#### I want to improve the bot with a new strategy
That's great. We have a nice backtesting and hyperoptimizing setup. See
the tutorial [here|Testing-new-strategies-with-Hyperopt](https://github.com/freqtrade/freqtrade/blob/develop/docs/bot-usage.md#hyperopt-commands).
That's great. We have a nice backtesting and hyperoptimizing setup. See
the tutorial [here|Testing-new-strategies-with-Hyperopt](bot-usage.md#hyperopt-commands).
#### Is there a setting to only SELL the coins being held and not
perform anymore BUYS?
#### Is there a setting to only SELL the coins being held and not perform anymore BUYS?
You can use the `/forcesell all` command from Telegram.
You can use the `/forcesell all` command from Telegram.
### How many epoch do I need to get a good Hyperopt result?
Per default Hyperopts without `-e` or `--epochs` parameter will only
run 100 epochs, means 100 evals of your triggers, guards, .... Too few
to find a great result (unless if you are very lucky), so you probably
have to run it for 10.000 or more. But it will take an eternity to
### Hyperopt module
#### How many epoch do I need to get a good Hyperopt result?
Per default Hyperopts without `-e` or `--epochs` parameter will only
run 100 epochs, means 100 evals of your triggers, guards, ... Too few
to find a great result (unless if you are very lucky), so you probably
have to run it for 10.000 or more. But it will take an eternity to
compute.
We recommend you to run it at least 10.000 epochs:
```bash
python3 ./freqtrade/main.py hyperopt -e 10000
python3 freqtrade hyperopt -e 10000
```
or if you want intermediate result to see
```bash
for i in {1..100}; do python3 ./freqtrade/main.py hyperopt -e 100; done
for i in {1..100}; do python3 freqtrade hyperopt -e 100; done
```
#### Why it is so long to run hyperopt?
Finding a great Hyperopt results takes time.
Finding a great Hyperopt results takes time.
If you wonder why it takes a while to find great hyperopt results
This answer was written during the under the release 0.15.1, when we had
:
This answer was written during the under the release 0.15.1, when we had:
- 8 triggers
- 9 guards: let's say we evaluate even 10 values from each
- 1 stoploss calculation: let's say we want 10 values from that too to
be evaluated
- 1 stoploss calculation: let's say we want 10 values from that too to be evaluated
The following calculation is still very rough and not very precise
but it will give the idea. With only these triggers and guards there is
already 8*10^9*10 evaluations. A roughly total of 80 billion evals.
Did you run 100 000 evals? Congrats, you've done roughly 1 / 100 000 th
but it will give the idea. With only these triggers and guards there is
already 8\*10^9\*10 evaluations. A roughly total of 80 billion evals.
Did you run 100 000 evals? Congrats, you've done roughly 1 / 100 000 th
of the search space.
### Edge module
#### Edge implements interesting approach for controlling position size, is there any theory behind it?
The Edge module is mostly a result of brainstorming of [@mishaker](https://github.com/mishaker) and [@creslinux](https://github.com/creslinux) freqtrade team members.
You can find further info on expectancy, winrate, risk management and position size in the following sources:
- https://www.tradeciety.com/ultimate-math-guide-for-traders/
- http://www.vantharp.com/tharp-concepts/expectancy.asp
- https://samuraitradingacademy.com/trading-expectancy/
- https://www.learningmarkets.com/determining-expectancy-in-your-trading/
- http://www.lonestocktrader.com/make-money-trading-positive-expectancy/
- https://www.babypips.com/trading/trade-expectancy-matter

View File

@@ -1,56 +1,87 @@
# Hyperopt
This page explains how to tune your strategy by finding the optimal
parameters, a process called hyperparameter optimization. The bot uses several
This page explains how to tune your strategy by finding the optimal
parameters, a process called hyperparameter optimization. The bot uses several
algorithms included in the `scikit-optimize` package to accomplish this. The
search will burn all your CPU cores, make your laptop sound like a fighter jet
and still take a long time.
*Note:* Hyperopt will crash when used with only 1 CPU Core as found out in [Issue #1133](https://github.com/freqtrade/freqtrade/issues/1133)
## Table of Contents
- [Prepare your Hyperopt](#prepare-hyperopt)
- [Configure your Guards and Triggers](#configure-your-guards-and-triggers)
- [Solving a Mystery](#solving-a-mystery)
- [Adding New Indicators](#adding-new-indicators)
- [Execute Hyperopt](#execute-hyperopt)
- [Understand the 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`.
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".
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)
Hyperoptimization will, for each eval round, pick one trigger and possibly
multiple guards. The constructed strategy will be something like
"*buy exactly when close price touches lower bollinger band, BUT only if
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
"*buy exactly when close price touches lower bollinger band, BUT only if
ADX > 10*".
If you have updated the buy strategy, ie. changed the contents of
`populate_buy_trend()` method you have to update the `guards` and
`populate_buy_trend()` method you have to update the `guards` and
`triggers` hyperopts must use.
#### Sell optimization
Similar to the buy-signal above, sell-signals can also be optimized.
Place the corresponding settings into the following methods
* Inside `sell_indicator_space()` - the parameters hyperopt shall be optimizing.
* Inside `populate_sell_trend()` - applying the parameters.
The configuration and rules are the same than for buy signals.
To avoid naming collisions in the search-space, please prefix all sell-spaces with `sell-`.
## Solving a Mystery
Let's say you are curious: should you use MACD crossings or lower Bollinger
Bands to trigger your buys. And you also wonder should you use RSI or ADX to
help with those buy decisions. If you decide to use RSI or ADX, which values
should I use for them? So let's use hyperparameter optimization to solve this
Let's say you are curious: should you use MACD crossings or lower Bollinger
Bands to trigger your buys. And you also wonder should you use RSI or ADX to
help with those buy decisions. If you decide to use RSI or ADX, which values
should I use for them? So let's use hyperparameter optimization to solve this
mystery.
We will start by defining a search space:
```
```python
def indicator_space() -> List[Dimension]:
"""
Define your Hyperopt space for searching strategy parameters
@@ -64,8 +95,8 @@ We will start by defining a search space:
]
```
Above definition says: I have five parameters I want you to randomly combine
to find the best combination. Two of them are integer values (`adx-value`
Above definition says: I have five parameters I want you to randomly combine
to find the best combination. Two of them are integer values (`adx-value`
and `rsi-value`) and I want you test in the range of values 20 to 40.
Then we have three category variables. First two are either `True` or `False`.
We use these to either enable or disable the ADX and RSI guards. The last
@@ -73,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
@@ -83,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),
@@ -104,57 +136,70 @@ with different value combinations. It will then use the given historical data an
buys based on the buy signals generated with the above function and based on the results
it will end with telling you which paramter combination produced the best profits.
The search for best parameters starts with a few random combinations and then uses a
The search for best parameters starts with a few random combinations and then uses a
regressor algorithm (currently ExtraTreesRegressor) to quickly find a parameter combination
that minimizes the value of the objective function `calculate_loss` in `hyperopt.py`.
The above setup expects to find ADX, RSI and Bollinger Bands in the populated indicators.
When you want to test an indicator that isn't used by the bot currently, remember to
When you want to test an indicator that isn't used by the bot currently, remember to
add it to the `populate_indicators()` method in `hyperopt.py`.
## Execute Hyperopt
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 -c config.json hyperopt --customhyperopt <hyperoptname> -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 hyperopt --timerange -200
```
### Running Hyperopt with Smaller Search Space
Use the `--spaces` argument to limit the search space used by hyperopt.
Letting Hyperopt optimize everything is a huuuuge search space. Often it
might make more sense to start by just searching for initial buy algorithm.
Or maybe you just want to optimize your stoploss or roi table for that awesome
Letting Hyperopt optimize everything is a huuuuge search space. Often it
might make more sense to start by just searching for initial buy algorithm.
Or maybe you just want to optimize your stoploss or roi table for that awesome
new buy strategy you have.
Legal values are:
- `all`: optimize everything
- `buy`: just search for a new buy strategy
- `sell`: just search for a new sell strategy
- `roi`: just optimize the minimal profit table for your strategy
- `stoploss`: search for the best stoploss value
- space-separated list of any of the above values for example `--spaces roi stoploss`
## 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:
@@ -162,26 +207,32 @@ Given the following result from hyperopt:
Best result:
135 trades. Avg profit 0.57%. Total profit 0.03871918 BTC (0.7722Σ%). Avg duration 180.4 mins.
with values:
{'adx-value': 44, 'rsi-value': 29, 'adx-enabled': False, 'rsi-enabled': True, 'trigger': 'bb_lower'}
{ 'adx-value': 44,
'rsi-value': 29,
'adx-enabled': False,
'rsi-enabled': True,
'trigger': 'bb_lower'}
```
You should understand this result like:
- The buy trigger that worked best was `bb_lower`.
- You should not use ADX because `adx-enabled: False`)
- You should not use ADX because `adx-enabled: False`)
- You should **consider** using the RSI indicator (`rsi-enabled: True` and the best value is `29.0` (`rsi-value: 29.0`)
You have to look inside your strategy file into `buy_strategy_generator()`
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:
You have to look inside your strategy file into `buy_strategy_generator()`
method, what those values match to.
So for example you had `rsi-value: 29.0` so we would look at `rsi`-block, that translates to the following code block:
```
(dataframe['rsi'] < 29.0)
```
Translating your whole hyperopt result as the new buy-signal
Translating your whole hyperopt result as the new buy-signal
would then look like:
```
```python
def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame:
dataframe.loc[
(
@@ -192,6 +243,60 @@ 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 flags `--disable-max-market-positions` and `--enable-position-stacking` for backtesting.
This configuration is the default in hyperopt for performance reasons.
You can overwrite position stacking in the configuration by explicitly setting `"position_stacking"=false` or by changing the relevant line in your hyperopt file [here](https://github.com/freqtrade/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L191).
Enabling the market-position for hyperopt is currently not possible.
!!! Note
Dry/live runs will **NOT** use position stacking - therefore it does make sense to also validate the strategy without this as it's closer to reality.
## Next Step
Now you have a perfect bot and want to control it from Telegram. Your
next step is to learn the [Telegram usage](https://github.com/freqtrade/freqtrade/blob/develop/docs/telegram-usage.md).
next step is to learn the [Telegram usage](telegram-usage.md).

BIN
docs/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

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

View File

@@ -1,25 +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)
* [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.
@@ -33,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:
@@ -43,15 +87,15 @@ 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`.
@@ -69,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
@@ -109,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
@@ -140,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.
@@ -148,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
@@ -156,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 \
@@ -167,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
@@ -184,14 +259,15 @@ docker start freqtrade
For more information on how to operate Docker, please refer to the [official Docker documentation](https://docs.docker.com/).
*Note*: You do not need to rebuild the image for configuration changes, it will suffice to edit `config.json` and restart the container.
!!! 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 \
@@ -201,16 +277,17 @@ 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.
OS Specific steps are listed first, the [Common](#common) section below is necessary for all systems.
### Requirements
@@ -236,28 +313,32 @@ sudo apt-get install python3.6 python3.6-venv python3.6-dev build-essential auto
Before installing FreqTrade on a Raspberry Pi running the official Raspbian Image, make sure you have at least Python 3.6 installed. The default image only provides Python 3.5. Probably the easiest way to get a recent version of python is [miniconda](https://repo.continuum.io/miniconda/).
The following assumes that miniconda3 is installed and available in your environment, and is installed.
It's recommended to use (mini)conda for this as installation/compilation of `scipy` and `pandas` takes a long time.
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.
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 install scipy pandas
conda activate freqtrade
conda install scipy pandas numpy
pip install -r requirements.txt
pip install -e .
sudo apt install libffi-dev
python3 -m pip install -r requirements-pi.txt
python3 -m pip install -e .
```
### MacOS
#### Install Python 3.6, git, wget and ta-lib
#### Install Python 3.6, git and wget
```bash
brew install python3 git wget
```
### common
### Common
#### 1. Install TA-Lib
@@ -268,18 +349,20 @@ 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.bak "s|0.00000001|0.000000000000000001 |g" src/ta_func/ta_utility.h
./configure --prefix=/usr
./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.
!!! Note
An already downloaded version of ta-lib is included in the repository, as the sourceforge.net source seems to have problems frequently.
#### 2. Setup your Python virtual environment (virtualenv)
*Note*: This step is optional but strongly recommended to keep your system organized
!!! Note
This step is optional but strongly recommended to keep your system organized
```bash
python3 -m venv .env
@@ -308,7 +391,7 @@ cd freqtrade
cp config.json.example config.json
```
> *To edit the config please refer to [Bot Configuration](https://github.com/freqtrade/freqtrade/blob/develop/docs/configuration.md).*
> *To edit the config please refer to [Bot Configuration](configuration.md).*
#### 5. Install python dependencies
@@ -323,7 +406,7 @@ pip3 install -e .
If this is the first time you run the bot, ensure you are running it in Dry-run `"dry_run": true,` otherwise it will start to buy and sell coins.
```bash
python3.6 ./freqtrade/main.py -c config.json
python3.6 freqtrade -c config.json
```
*Note*: If you run the bot on a server, you should consider using [Docker](#automatic-installation---docker) a terminal multiplexer like `screen` or [`tmux`](https://en.wikipedia.org/wiki/Tmux) to avoid that the bot is stopped on logout.
@@ -344,6 +427,19 @@ For this to be persistent (run when user is logged out) you'll need to enable `l
sudo loginctl enable-linger "$USER"
```
If you run the bot as a service, you can use systemd service manager as a software watchdog monitoring freqtrade bot
state and restarting it in the case of failures. If the `internals.sd_notify` parameter is set to true in the
configuration or the `--sd-notify` command line option is used, the bot will send keep-alive ping messages to systemd
using the sd_notify (systemd notifications) protocol and will also tell systemd its current state (Running or Stopped)
when it changes.
The `freqtrade.service.watchdog` file contains an example of the service unit configuration file which uses systemd
as the watchdog.
!!! Note
The sd_notify communication between the bot and the systemd service manager will not work if the bot runs in a
Docker container.
------
## Windows
@@ -367,7 +463,7 @@ copy paste `config.json` to ``\path\freqtrade-develop\freqtrade`
Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows).
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of inofficial precompiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which needs to be downloaded and installed using `pip install TA_Lib0.4.17cp36cp36mwin32.whl` (make sure to use the version matching your python version)
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), there is also a repository of unofficial precompiled windows Wheels [here](https://www.lfd.uci.edu/~gohlke/pythonlibs/#ta-lib), which needs to be downloaded and installed using `pip install TA_Lib0.4.17cp36cp36mwin32.whl` (make sure to use the version matching your python version)
```cmd
>cd \path\freqtrade-develop
@@ -397,4 +493,4 @@ The easiest way is to download install Microsoft Visual Studio Community [here](
---
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
@@ -48,7 +49,7 @@ To plot trades stored in a database use `--db-url` argument:
python scripts/plot_dataframe.py --db-url sqlite:///tradesv3.dry_run.sqlite -p BTC/ETH
```
To plot a test strategy the strategy should have first be backtested.
To plot a test strategy the strategy should have first be backtested.
The results may then be plotted with the -s argument:
```
python scripts/plot_dataframe.py -s Strategy_Name -p BTC/ETH --datadir user_data/data/<exchange_name>/
@@ -83,5 +84,5 @@ The `-p` pair argument, can be used to plot a single pair
Example
```
python3 scripts/plot_profit.py --datadir ../freqtrade/freqtrade/tests/testdata-20171221/ -p BTC_LTC
python3 scripts/plot_profit.py --datadir ../freqtrade/freqtrade/tests/testdata-20171221/ -p LTC/BTC
```

View File

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

View File

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

View File

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

View File

@@ -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
@@ -44,8 +55,11 @@ Both values can be configured in the main configuration file and requires `"trai
``` json
"trailing_stop_positive": 0.01,
"trailing_stop_positive_offset": 0.011,
"trailing_only_offset_is_reached": false
```
The 0.01 would translate to a 1% stop loss, once you hit 1.1% profit.
You should also make sure to have this value 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.
If `"trailing_only_offset_is_reached": true` then the trailing stoploss is only activated once the offset is reached. Until then, the stoploss remains at the configured`stoploss`.

View File

@@ -1,21 +1,22 @@
# Telegram usage
This page explains how to command your bot with Telegram.
## Prerequisite
## Pre-requisite
To control your bot with Telegram, you need first to
[set up a Telegram bot](https://github.com/freqtrade/freqtrade/blob/develop/docs/pre-requisite.md)
To control your bot with Telegram, you need first to
[set up a Telegram bot](installation.md)
and add your Telegram API keys into your config file.
## Telegram commands
Per default, the Telegram bot shows predefined commands. Some commands
are only available by sending them to the bot. The table below list the
Per default, the Telegram bot shows predefined commands. Some commands
are only available by sending them to the bot. The table below list the
official commands. You can ask at any moment for help with `/help`.
| Command | Default | Description |
|----------|---------|-------------|
| `/start` | | Starts the trader
| `/stop` | | Stops the trader
| `/stopbuy` | | Stops the trader from opening new trades. Gracefully closes open trades according to their rules.
| `/reload_conf` | | Reloads the configuration file
| `/status` | | Lists all open trades
| `/status table` | | List all open trades in a table format
@@ -23,54 +24,77 @@ 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
| `/whitelist` | | Show the current whitelist
| `/blacklist [pair]` | | Show the current blacklist, or adds a pair to the blacklist.
| `/edge` | | Show validated pairs by Edge if it is enabled.
| `/help` | | Show help message
| `/version` | | Show version
## Telegram commands in action
Below, example of Telegram message you will receive for each command.
### /start
> **Status:** `running`
### /stop
> `Stopping trader ...`
> `Stopping trader ...`
> **Status:** `stopped`
## /status
### /stopbuy
> **status:** `Setting max_open_trades to 0. Run /reload_conf to reset.`
Prevents the bot from opening new trades by temporarily setting "max_open_trades" to 0. Open trades will be handled via their regular rules (ROI / Sell-signal, stoploss, ...).
After this, give the bot time to close off open trades (can be checked via `/status table`).
Once all positions are sold, run `/stop` to completely stop the bot.
`/reload_conf` resets "max_open_trades" to the value set in the configuration and resets this command.
!!! warning
The stop-buy signal is ONLY active while the bot is running, and is not persisted anyway, so restarting the bot will cause this to reset.
### /status
For each open trade, the bot will send you the following message.
> **Trade ID:** `123`
> **Current Pair:** CVC/BTC
> **Trade ID:** `123` `(since 1 days ago)`
> **Current Pair:** CVC/BTC
> **Open Since:** `1 days ago`
> **Amount:** `26.64180098`
> **Open Rate:** `0.00007489`
> **Close Rate:** `None`
> **Current Rate:** `0.00007489`
> **Close Profit:** `None`
> **Current Profit:** `12.95%`
> **Open Order:** `None`
> **Stoploss:** `0.00007389 (-0.02%)`
### /status table
## /status table
Return the status of all open trades in a table format.
```
ID Pair Since Profit
---- -------- ------- --------
ID Pair Since Profit
---- -------- ------- --------
67 SC/BTC 1 d 13.33%
123 CVC/BTC 1 h 12.95%
```
## /count
### /count
Return the number of trades used and available.
```
current max
--------- -----
2 10
--------- -----
2 10
```
## /profit
### /profit
Return a summary of your profit/loss and performance.
> **ROI:** Close trades
@@ -79,28 +103,38 @@ Return a summary of your profit/loss and performance.
> **ROI:** All trades
> ∙ `0.00255280 BTC (143.43%)`
> ∙ `33.095 EUR`
>
>
> **Total Trade Count:** `138`
> **First Trade opened:** `3 days ago`
> **Latest Trade opened:** `2 minutes ago`
> **Avg. Duration:** `2:33:45`
> **Best Performing:** `PAY/BTC: 50.23%`
> **Best Performing:** `PAY/BTC: 50.23%`
## /forcesell <trade_id>
### /forcesell <trade_id>
> **BITTREX:** Selling BTC/LTC with limit `0.01650000 (profit: ~-4.07%, -0.00008168)`
## /performance
Return the performance of each crypto-currency the bot has sold.
> Performance:
> 1. `RCN/BTC 57.77%`
> 2. `PAY/BTC 56.91%`
> 3. `VIB/BTC 47.07%`
> 4. `SALT/BTC 30.24%`
> 5. `STORJ/BTC 27.24%`
> ...
### /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.
[More details](configuration.md/#understand-forcebuy_enable)
### /performance
Return the performance of each crypto-currency the bot has sold.
> Performance:
> 1. `RCN/BTC 57.77%`
> 2. `PAY/BTC 56.91%`
> 3. `VIB/BTC 47.07%`
> 4. `SALT/BTC 30.24%`
> 5. `STORJ/BTC 27.24%`
> ...
### /balance
## /balance
Return the balance of all crypto-currency your have on the exchange.
> **Currency:** BTC
@@ -111,27 +145,54 @@ Return the balance of all crypto-currency your have on the exchange.
> **Currency:** CVC
> **Available:** 86.64180098
> **Balance:** 86.64180098
> **Pending:** 0.0
> **Pending:** 0.0
## /daily <n>
Per default `/daily` will return the 7 last days.
### /daily <n>
Per default `/daily` will return the 7 last days.
The example below if for `/daily 3`:
> **Daily Profit over the last 3 days:**
```
Day Profit BTC Profit USD
---------- -------------- ------------
2018-01-03 0.00224175 BTC 29,142 USD
2018-01-02 0.00033131 BTC 4,307 USD
Day Profit BTC Profit USD
---------- -------------- ------------
2018-01-03 0.00224175 BTC 29,142 USD
2018-01-02 0.00033131 BTC 4,307 USD
2018-01-01 0.00269130 BTC 34.986 USD
```
## /version
> **Version:** `0.14.3`
### /whitelist
### using proxy with telegram
Shows the current whitelist
> Using whitelist `StaticPairList` with 22 pairs
> `IOTA/BTC, NEO/BTC, TRX/BTC, VET/BTC, ADA/BTC, ETC/BTC, NCASH/BTC, DASH/BTC, XRP/BTC, XVG/BTC, EOS/BTC, LTC/BTC, OMG/BTC, BTG/BTC, LSK/BTC, ZEC/BTC, HOT/BTC, IOTX/BTC, XMR/BTC, AST/BTC, XLM/BTC, NANO/BTC`
### /blacklist [pair]
Shows the current blacklist.
If Pair is set, then this pair will be added to the pairlist.
Also supports multiple pairs, seperated by a space.
Use `/reload_conf` to reset the blacklist.
> Using blacklist `StaticPairList` with 2 pairs
>`DODGE/BTC`, `HOT/BTC`.
### /edge
Shows pairs validated by Edge along with their corresponding winrate, expectancy and stoploss values.
> **Edge only validated following pairs:**
```
$ export HTTP_PROXY="http://addr:port"
$ export HTTPS_PROXY="http://addr:port"
$ freqtrade
Pair Winrate Expectancy Stoploss
-------- --------- ------------ ----------
DOCK/ETH 0.522727 0.881821 -0.03
PHX/ETH 0.677419 0.560488 -0.03
HOT/ETH 0.733333 0.490492 -0.03
HC/ETH 0.588235 0.280988 -0.02
ARDR/ETH 0.366667 0.143059 -0.01
```
### /version
> **Version:** `0.14.3`

View File

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

View File

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

View File

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

View File

@@ -1,5 +1,5 @@
""" FreqTrade bot """
__version__ = '0.17.1'
__version__ = '0.18.5'
class DependencyException(BaseException):
@@ -17,6 +17,14 @@ class OperationalException(BaseException):
"""
class InvalidOrderException(BaseException):
"""
This is returned when the order is not valid. Example:
If stoploss on exchange order is hit, then trying to cancel the order
should return this exception.
"""
class TemporaryError(BaseException):
"""
Temporary network or exchange related error.

View File

@@ -6,9 +6,7 @@ import argparse
import os
import re
from typing import List, NamedTuple, Optional
import arrow
from freqtrade import __version__, constants
@@ -55,6 +53,11 @@ class Arguments(object):
"""
parsed_arg = self.parser.parse_args(self.args)
# Workaround issue in argparse with action='append' and default value
# (see https://bugs.python.org/issue16399)
if parsed_arg.config is None:
parsed_arg.config = [constants.DEFAULT_CONFIG]
return parsed_arg
def common_args_parser(self) -> None:
@@ -63,11 +66,18 @@ class Arguments(object):
"""
self.parser.add_argument(
'-v', '--verbose',
help='verbose mode (-vv for more, -vvv to get all messages)',
help='Verbose mode (-vv for more, -vvv to get all messages).',
action='count',
dest='loglevel',
default=0,
)
self.parser.add_argument(
'--logfile',
help='Log to the file specified',
dest='logfile',
type=str,
metavar='FILE'
)
self.parser.add_argument(
'--version',
action='version',
@@ -75,15 +85,16 @@ class Arguments(object):
)
self.parser.add_argument(
'-c', '--config',
help='specify configuration file (default: %(default)s)',
help='Specify configuration file (default: %(default)s). '
'Multiple --config options may be used.',
dest='config',
default='config.json',
action='append',
type=str,
metavar='PATH',
)
self.parser.add_argument(
'-d', '--datadir',
help='path to backtest data',
help='Path to backtest data.',
dest='datadir',
default=None,
type=str,
@@ -91,7 +102,7 @@ class Arguments(object):
)
self.parser.add_argument(
'-s', '--strategy',
help='specify strategy class name (default: %(default)s)',
help='Specify strategy class name (default: %(default)s).',
dest='strategy',
default='DefaultStrategy',
type=str,
@@ -99,15 +110,16 @@ class Arguments(object):
)
self.parser.add_argument(
'--strategy-path',
help='specify additional strategy lookup path',
help='Specify additional strategy lookup path.',
dest='strategy_path',
type=str,
metavar='PATH',
)
self.parser.add_argument(
'--dynamic-whitelist',
help='dynamically generate and update whitelist'
' based on 24h BaseVolume (default: %(const)s)',
help='Dynamically generate and update whitelist'
' based on 24h BaseVolume (default: %(const)s).'
' DEPRECATED.',
dest='dynamic_whitelist',
const=constants.DYNAMIC_WHITELIST,
type=int,
@@ -117,26 +129,47 @@ class Arguments(object):
self.parser.add_argument(
'--db-url',
help='Override trades database URL, this is useful if dry_run is enabled'
' or in custom deployments (default: %(default)s)',
' or in custom deployments (default: %(default)s).',
dest='db_url',
type=str,
metavar='PATH',
)
self.parser.add_argument(
'--sd-notify',
help='Notify systemd service manager.',
action='store_true',
dest='sd_notify',
)
@staticmethod
def backtesting_options(parser: argparse.ArgumentParser) -> None:
"""
Parses given arguments for Backtesting scripts.
"""
parser.add_argument(
'--eps', '--enable-position-stacking',
help='Allow buying the same pair multiple times (position stacking).',
action='store_true',
dest='position_stacking',
default=False
)
parser.add_argument(
'--dmmp', '--disable-max-market-positions',
help='Disable applying `max_open_trades` during backtest '
'(same as setting `max_open_trades` to a very high number).',
action='store_false',
dest='use_max_market_positions',
default=True
)
parser.add_argument(
'-l', '--live',
help='using live data',
help='Use live data.',
action='store_true',
dest='live',
)
parser.add_argument(
'-r', '--refresh-pairs-cached',
help='refresh the pairs files in tests/testdata with the latest data from the '
help='Refresh the pairs files in tests/testdata with the latest data from the '
'exchange. Use it if you want to run your backtesting with up-to-date data.',
action='store_true',
dest='refresh_pairs',
@@ -153,8 +186,8 @@ class Arguments(object):
)
parser.add_argument(
'--export',
help='export backtest results, argument are: trades\
Example --export=trades',
help='Export backtest results, argument are: trades. '
'Example --export=trades',
type=str,
default=None,
dest='export',
@@ -171,6 +204,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:
"""
@@ -180,43 +234,67 @@ class Arguments(object):
"""
parser.add_argument(
'-i', '--ticker-interval',
help='specify ticker interval (1m, 5m, 30m, 1h, 1d)',
help='Specify ticker interval (1m, 5m, 30m, 1h, 1d).',
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',
help='specify what timerange of data to use.',
help='Specify what timerange of data to use.',
default=None,
type=str,
dest='timerange',
)
parser.add_argument(
'--max_open_trades',
help='Specify max_open_trades to use.',
default=None,
type=int,
dest='max_open_trades',
)
parser.add_argument(
'--stake_amount',
help='Specify stake_amount.',
default=None,
type=float,
dest='stake_amount',
)
@staticmethod
def hyperopt_options(parser: argparse.ArgumentParser) -> None:
"""
Parses given arguments for Hyperopt scripts.
"""
parser.add_argument(
'--customhyperopt',
help='Specify hyperopt class name (default: %(default)s).',
dest='hyperopt',
default=constants.DEFAULT_HYPEROPT,
type=str,
metavar='NAME',
)
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)',
help='Specify number of epochs (default: %(default)d).',
dest='epochs',
default=constants.HYPEROPT_EPOCH,
type=int,
@@ -225,8 +303,8 @@ class Arguments(object):
parser.add_argument(
'-s', '--spaces',
help='Specify which parameters to hyperopt. Space separate list. \
Default: %(default)s',
choices=['all', 'buy', 'roi', 'stoploss'],
Default: %(default)s.',
choices=['all', 'buy', 'sell', 'roi', 'stoploss'],
default='all',
nargs='+',
dest='spaces',
@@ -237,18 +315,24 @@ 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')
# Add backtesting subcommand
backtesting_cmd = subparsers.add_parser('backtesting', help='backtesting module')
backtesting_cmd = subparsers.add_parser('backtesting', help='Backtesting module.')
backtesting_cmd.set_defaults(func=backtesting.start)
self.optimizer_shared_options(backtesting_cmd)
self.backtesting_options(backtesting_cmd)
# Add edge subcommand
edge_cmd = subparsers.add_parser('edge', help='Edge module.')
edge_cmd.set_defaults(func=edge_cli.start)
self.optimizer_shared_options(edge_cmd)
self.edge_options(edge_cmd)
# Add hyperopt subcommand
hyperopt_cmd = subparsers.add_parser('hyperopt', help='hyperopt module')
hyperopt_cmd = subparsers.add_parser('hyperopt', help='Hyperopt module.')
hyperopt_cmd.set_defaults(func=hyperopt.start)
self.optimizer_shared_options(hyperopt_cmd)
self.hyperopt_options(hyperopt_cmd)
@@ -300,9 +384,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
)
@@ -312,7 +396,7 @@ class Arguments(object):
"""
self.parser.add_argument(
'--pairs-file',
help='File containing a list of pairs to download',
help='File containing a list of pairs to download.',
dest='pairs_file',
default=None,
metavar='PATH',
@@ -320,15 +404,25 @@ class Arguments(object):
self.parser.add_argument(
'--export',
help='Export files to given dir',
help='Export files to given dir.',
dest='export',
default=None,
metavar='PATH',
)
self.parser.add_argument(
'-c', '--config',
help='Specify configuration file (default: %(default)s). '
'Multiple --config options may be used.',
dest='config',
action='append',
type=str,
metavar='PATH',
)
self.parser.add_argument(
'--days',
help='Download data for number of days',
help='Download data for given number of days.',
dest='days',
type=int,
metavar='INT',
@@ -337,7 +431,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'
@@ -346,7 +440,7 @@ class Arguments(object):
self.parser.add_argument(
'-t', '--timeframes',
help='Specify which tickers to download. Space separated list. \
Default: %(default)s',
Default: %(default)s.',
choices=['1m', '3m', '5m', '15m', '30m', '1h', '2h', '4h',
'6h', '8h', '12h', '1d', '3d', '1w'],
default=['1m', '5m'],
@@ -356,7 +450,7 @@ class Arguments(object):
self.parser.add_argument(
'--erase',
help='Clean all existing data for the selected exchange/pairs/timeframes',
help='Clean all existing data for the selected exchange/pairs/timeframes.',
dest='erase',
action='store_true'
)

View File

@@ -4,14 +4,19 @@ This module contains the configuration class
import json
import logging
import os
import sys
from argparse import Namespace
from typing import Any, Dict, Optional
from logging.handlers import RotatingFileHandler
from typing import Any, Dict, List, Optional
import ccxt
from jsonschema import Draft4Validator, validate
from jsonschema.exceptions import ValidationError, best_match
from freqtrade import OperationalException, constants
from freqtrade.misc import deep_merge_dicts
from freqtrade.state import RunMode
logger = logging.getLogger(__name__)
@@ -33,17 +38,30 @@ class Configuration(object):
Class to read and init the bot configuration
Reuse this class for the bot, backtesting, hyperopt and every script that required configuration
"""
def __init__(self, args: Namespace) -> None:
def __init__(self, args: Namespace, runmode: RunMode = None) -> None:
self.args = args
self.config: Optional[Dict[str, Any]] = None
self.runmode = runmode
def load_config(self) -> Dict[str, Any]:
"""
Extract information for sys.argv and load the bot configuration
:return: Configuration dictionary
"""
logger.info('Using config: %s ...', self.args.config)
config = self._load_config_file(self.args.config)
config: Dict[str, Any] = {}
# Now expecting a list of config filenames here, not a string
for path in self.args.config:
logger.info('Using config: %s ...', path)
# Merge config options, overwriting old values
config = deep_merge_dicts(self._load_config_file(path), config)
if 'internals' not in config:
config['internals'] = {}
logger.info('Validating configuration ...')
self._validate_config_schema(config)
self._validate_config_consistency(config)
# Set strategy if not specified in config and or if it's non default
if self.args.strategy != constants.DEFAULT_STRATEGY or not config.get('strategy'):
@@ -58,9 +76,19 @@ class Configuration(object):
# 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]:
@@ -77,11 +105,7 @@ class Configuration(object):
f'Config file "{path}" not found!'
' Please create a config file or check whether it exists.')
if 'internals' not in conf:
conf['internals'] = {}
logger.info('Validating configuration ...')
return self._validate_config(conf)
return conf
def _load_common_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
"""
@@ -94,28 +118,46 @@ class Configuration(object):
config.update({'verbosity': self.args.loglevel})
else:
config.update({'verbosity': 0})
# Log to stdout, not stderr
log_handlers: List[logging.Handler] = [logging.StreamHandler(sys.stdout)]
if 'logfile' in self.args and self.args.logfile:
config.update({'logfile': self.args.logfile})
# Allow setting this as either configuration or argument
if 'logfile' in config:
log_handlers.append(RotatingFileHandler(config['logfile'],
maxBytes=1024 * 1024, # 1Mb
backupCount=10))
logging.basicConfig(
level=logging.INFO if config['verbosity'] < 1 else logging.DEBUG,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=log_handlers
)
set_loggers(config['verbosity'])
logger.info('Verbosity set to %s', config['verbosity'])
# Support for sd_notify
if self.args.sd_notify:
config['internals'].update({'sd_notify': True})
# Add dynamic_whitelist if found
if 'dynamic_whitelist' in self.args and self.args.dynamic_whitelist:
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 and self.args.db_url != constants.DEFAULT_DB_PROD_URL:
config.update({'db_url': self.args.db_url})
logger.info('Parameter --db-url detected ...')
else:
# Set default here
config.update({'db_url': constants.DEFAULT_DB_PROD_URL})
if config.get('dry_run', False):
logger.info('Dry run is enabled')
@@ -127,6 +169,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
@@ -134,15 +183,18 @@ class Configuration(object):
return config
def _create_default_datadir(self, config: Dict[str, Any]) -> str:
exchange_name = config.get('exchange', {}).get('name').lower()
default_path = os.path.join('user_data', 'data', exchange_name)
if not os.path.isdir(default_path):
os.makedirs(default_path)
logger.info(f'Created data directory: {default_path}')
return default_path
def _create_datadir(self, config: Dict[str, Any], datadir: Optional[str] = None) -> str:
if not datadir:
# set datadir
exchange_name = config.get('exchange', {}).get('name').lower()
datadir = os.path.join('user_data', 'data', exchange_name)
def _load_backtesting_config(self, config: Dict[str, Any]) -> Dict[str, Any]:
if not os.path.isdir(datadir):
os.makedirs(datadir)
logger.info(f'Created data directory: {datadir}')
return datadir
def _load_backtesting_config(self, config: Dict[str, Any]) -> Dict[str, Any]: # noqa: C901
"""
Extract information for sys.argv and load Backtesting configuration
:return: configuration as dictionary
@@ -165,14 +217,24 @@ class Configuration(object):
config.update({'position_stacking': True})
logger.info('Parameter --enable-position-stacking detected ...')
# If --disable-max-market-positions is used we add it to the configuration
# If --disable-max-market-positions or --max_open_trades is used we update configuration
if 'use_max_market_positions' in self.args and not self.args.use_max_market_positions:
config.update({'use_max_market_positions': False})
logger.info('Parameter --disable-max-market-positions detected ...')
logger.info('max_open_trades set to unlimited ...')
elif 'max_open_trades' in self.args and self.args.max_open_trades:
config.update({'max_open_trades': self.args.max_open_trades})
logger.info('Parameter --max_open_trades detected, '
'overriding max_open_trades to: %s ...', config.get('max_open_trades'))
else:
logger.info('Using max_open_trades: %s ...', config.get('max_open_trades'))
# If --stake_amount is used we update configuration
if 'stake_amount' in self.args and self.args.stake_amount:
config.update({'stake_amount': self.args.stake_amount})
logger.info('Parameter --stake_amount detected, overriding stake_amount to: %s ...',
config.get('stake_amount'))
# 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})
@@ -180,9 +242,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
@@ -210,11 +272,42 @@ 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
:return: configuration as dictionary
"""
if "hyperopt" in self.args:
# Add the hyperopt file to use
config.update({'hyperopt': self.args.hyperopt})
# If --epochs is used we add it to the configuration
if 'epochs' in self.args and self.args.epochs:
config.update({'epochs': self.args.epochs})
@@ -228,14 +321,14 @@ class Configuration(object):
return config
def _validate_config(self, conf: Dict[str, Any]) -> Dict[str, Any]:
def _validate_config_schema(self, conf: Dict[str, Any]) -> Dict[str, Any]:
"""
Validate the configuration follow the Config Schema
:param conf: Config in JSON format
:return: Returns the config if valid, otherwise throw an exception
"""
try:
validate(conf, constants.CONF_SCHEMA)
validate(conf, constants.CONF_SCHEMA, Draft4Validator)
return conf
except ValidationError as exception:
logger.critical(
@@ -246,6 +339,35 @@ class Configuration(object):
best_match(Draft4Validator(constants.CONF_SCHEMA).iter_errors(conf)).message
)
def _validate_config_consistency(self, conf: Dict[str, Any]) -> None:
"""
Validate the configuration consistency
:param conf: Config in JSON format
:return: Returns None if everything is ok, otherwise throw an OperationalException
"""
# validating trailing stoploss
self._validate_trailing_stoploss(conf)
def _validate_trailing_stoploss(self, conf: Dict[str, Any]) -> None:
# Skip if trailing stoploss is not activated
if not conf.get('trailing_stop', False):
return
tsl_positive = float(conf.get('trailing_stop_positive', 0))
tsl_offset = float(conf.get('trailing_stop_positive_offset', 0))
tsl_only_offset = conf.get('trailing_only_offset_is_reached', False)
if tsl_only_offset:
if tsl_positive == 0.0:
raise OperationalException(
f'The config trailing_only_offset_is_reached needs '
'trailing_stop_positive_offset to be more than 0 in your config.')
if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive:
raise OperationalException(
f'The config trailing_stop_positive_offset needs '
'to be greater than trailing_stop_positive_offset in your config.')
def get_config(self) -> Dict[str, Any]:
"""
Return the config. Use this method to get the bot config

View File

@@ -3,33 +3,30 @@
"""
bot constants
"""
DEFAULT_CONFIG = 'config.json'
DYNAMIC_WHITELIST = 20 # pairs
PROCESS_THROTTLE_SECS = 5 # sec
TICKER_INTERVAL = 5 # min
DEFAULT_TICKER_INTERVAL = 5 # min
HYPEROPT_EPOCH = 100 # epochs
RETRY_TIMEOUT = 30 # sec
DEFAULT_STRATEGY = 'DefaultStrategy'
DEFAULT_HYPEROPT = 'DefaultHyperOpts'
DEFAULT_DB_PROD_URL = 'sqlite:///tradesv3.sqlite'
DEFAULT_DB_DRYRUN_URL = 'sqlite://'
UNLIMITED_STAKE_AMOUNT = 'unlimited'
DEFAULT_AMOUNT_RESERVE_PERCENT = 0.05
REQUIRED_ORDERTIF = ['buy', 'sell']
REQUIRED_ORDERTYPES = ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']
ORDERTYPE_POSSIBILITIES = ['limit', 'market']
ORDERTIF_POSSIBILITIES = ['gtc', 'fok', 'ioc']
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList']
DRY_RUN_WALLET = 999.9
TICKER_INTERVAL_MINUTES = {
'1m': 1,
'3m': 3,
'5m': 5,
'15m': 15,
'30m': 30,
'1h': 60,
'2h': 120,
'4h': 240,
'6h': 360,
'8h': 480,
'12h': 720,
'1d': 1440,
'3d': 4320,
'1w': 10080,
}
TICKER_INTERVALS = [
'1m', '3m', '5m', '15m', '30m',
'1h', '2h', '4h', '6h', '8h', '12h',
'1d', '3d', '1w',
]
SUPPORTED_FIAT = [
"AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK",
@@ -37,14 +34,14 @@ 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},
'ticker_interval': {'type': 'string', 'enum': list(TICKER_INTERVAL_MINUTES.keys())},
'max_open_trades': {'type': 'integer', 'minimum': -1},
'ticker_interval': {'type': 'string', 'enum': TICKER_INTERVALS},
'stake_currency': {'type': 'string', 'enum': ['BTC', 'XBT', 'ETH', 'USDT', 'EUR', 'USD']},
'stake_amount': {
"type": ["number", "string"],
@@ -53,6 +50,7 @@ CONF_SCHEMA = {
},
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
'dry_run': {'type': 'boolean'},
'dry_run_wallet': {'type': 'number'},
'process_only_new_candles': {'type': 'boolean'},
'minimal_roi': {
'type': 'object',
@@ -61,10 +59,12 @@ CONF_SCHEMA = {
},
'minProperties': 1
},
'amount_reserve_percent': {'type': 'number', 'minimum': 0.0, 'maximum': 0.5},
'stoploss': {'type': 'number', 'maximum': 0, 'exclusiveMaximum': True},
'trailing_stop': {'type': 'boolean'},
'trailing_stop_positive': {'type': 'number', 'minimum': 0, 'maximum': 1},
'trailing_stop_positive_offset': {'type': 'number', 'minimum': 0, 'maximum': 1},
'trailing_only_offset_is_reached': {'type': 'boolean'},
'unfilledtimeout': {
'type': 'object',
'properties': {
@@ -101,7 +101,27 @@ CONF_SCHEMA = {
'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': {
@@ -110,6 +130,14 @@ CONF_SCHEMA = {
'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': {
@@ -130,11 +158,13 @@ CONF_SCHEMA = {
},
'db_url': {'type': 'string'},
'initial_state': {'type': 'string', 'enum': ['running', 'stopped']},
'forcebuy_enable': {'type': 'boolean'},
'internals': {
'type': 'object',
'properties': {
'process_throttle_secs': {'type': 'number'},
'interval': {'type': 'integer'}
'interval': {'type': 'integer'},
'sd_notify': {'type': 'boolean'},
}
}
},
@@ -164,9 +194,31 @@ CONF_SCHEMA = {
},
'uniqueItems': True
},
'outdated_offset': {'type': 'integer', 'minimum': 1}
'outdated_offset': {'type': 'integer', 'minimum': 1},
'markets_refresh_interval': {'type': 'integer'},
'ccxt_config': {'type': 'object'},
'ccxt_async_config': {'type': 'object'}
},
'required': ['name', 'key', 'secret', 'pair_whitelist']
},
'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'
]

View File

@@ -0,0 +1,67 @@
"""
Helpers when analyzing backtest data
"""
from pathlib import Path
import numpy as np
import pandas as pd
from freqtrade.misc import json_load
# must align with columns in backtest.py
BT_DATA_COLUMNS = ["pair", "profitperc", "open_time", "close_time", "index", "duration",
"open_rate", "close_rate", "open_at_end", "sell_reason"]
def load_backtest_data(filename) -> pd.DataFrame:
"""
Load backtest data file.
:param filename: pathlib.Path object, or string pointing to the file.
:return a dataframe with the analysis results
"""
if isinstance(filename, str):
filename = Path(filename)
if not filename.is_file():
raise ValueError("File {filename} does not exist.")
with filename.open() as file:
data = json_load(file)
df = pd.DataFrame(data, columns=BT_DATA_COLUMNS)
df['open_time'] = pd.to_datetime(df['open_time'],
unit='s',
utc=True,
infer_datetime_format=True
)
df['close_time'] = pd.to_datetime(df['close_time'],
unit='s',
utc=True,
infer_datetime_format=True
)
df['profitabs'] = df['close_rate'] - df['open_rate']
df = df.sort_values("open_time").reset_index(drop=True)
return df
def evaluate_result_multi(results: pd.DataFrame, freq: str, max_open_trades: int) -> pd.DataFrame:
"""
Find overlapping trades by expanding each trade once per period it was open
and then counting overlaps
:param results: Results Dataframe - can be loaded
:param freq: Frequency used for the backtest
:param max_open_trades: parameter max_open_trades used during backtest run
:return: dataframe with open-counts per time-period in freq
"""
dates = [pd.Series(pd.date_range(row[1].open_time, row[1].close_time, freq=freq))
for row in results[['open_time', 'close_time']].iterrows()]
deltas = [len(x) for x in dates]
dates = pd.Series(pd.concat(dates).values, name='date')
df2 = pd.DataFrame(np.repeat(results.values, deltas, axis=0), columns=results.columns)
df2 = df2.astype(dtype={"open_time": "datetime64", "close_time": "datetime64"})
df2 = pd.concat([dates, df2], axis=1)
df2 = df2.set_index('date')
df_final = df2.resample(freq)[['pair']].count()
return df_final[df_final['pair'] > max_open_trades]

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.misc import timeframe_to_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'
}
ticker_minutes = timeframe_to_minutes(ticker_interval)
# Resample to create "NAN" values
df = dataframe.resample(f'{ticker_minutes}min', on='date').agg(ohlc_dict)
# Forwardfill close for missing columns
df['close'] = df['close'].fillna(method='ffill')
# Use close for "open, high, low"
df.loc[:, ['open', 'high', 'low']] = df[['open', 'high', 'low']].fillna(
value={'open': df['close'],
'high': df['close'],
'low': df['close'],
})
df.reset_index(inplace=True)
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, ticker_interval for which data is currently cached.
Should be whitelist + open trades.
"""
return list(self._exchange._klines.keys())
def ohlcv(self, pair: str, ticker_interval: str = None, copy: bool = True) -> DataFrame:
"""
get ohlcv data for the given pair as DataFrame
Please check `available_pairs` to verify which pairs are currently cached.
:param pair: pair to get the data for
:param ticker_interval: ticker_interval to get pair for
:param copy: copy dataframe before returning.
Use false only for RO operations (where the dataframe is not modified)
"""
if self.runmode in (RunMode.DRY_RUN, RunMode.LIVE):
if ticker_interval:
pairtick = (pair, ticker_interval)
else:
pairtick = (pair, self._config['ticker_interval'])
return self._exchange.klines(pairtick, copy=copy)
else:
return DataFrame()
def historic_ohlcv(self, pair: str, ticker_interval: str) -> DataFrame:
"""
get stored historic ohlcv data
:param pair: pair to get the data for
:param ticker_interval: ticker_interval to get pair for
"""
return load_pair_history(pair=pair,
ticker_interval=ticker_interval,
refresh_pairs=False,
datadir=Path(self._config['datadir']) if self._config.get(
'datadir') else None
)
def ticker(self, pair: str):
"""
Return last ticker data
"""
# TODO: Implement me
pass
def orderbook(self, pair: str, max: int):
"""
return latest orderbook data
"""
# 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))

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

@@ -0,0 +1,237 @@
"""
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, OperationalException
from freqtrade.arguments import TimeRange
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.exchange import Exchange
from freqtrade.misc import timeframe_to_minutes
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,
ticker_interval=ticker_interval,
timerange=timerange)
pairdata = load_tickerdata_file(datadir, pair, ticker_interval, timerange=timerange)
if pairdata:
if timerange.starttype == 'date' and pairdata[0][0] > timerange.startts * 1000:
logger.warning('Missing data at start for pair %s, data starts at %s',
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, ticker_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 * timeframe_to_minutes(ticker_interval)
since_ms = arrow.utcnow().shift(minutes=num_minutes).timestamp * 1000
# read the cached file
if filename.is_file():
with open(filename, "rt") as file:
data = misc.json_load(file)
# 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,
ticker_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 ticker_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}-{ticker_interval}.json')
logger.info('Download the pair: "%s", Interval: %s', pair, ticker_interval)
data, since_ms = load_cached_data_for_updating(filename, ticker_interval, timerange)
logger.debug("Current Start: %s", misc.format_ms_time(data[1][0]) if data else 'None')
logger.debug("Current End: %s", misc.format_ms_time(data[-1][0]) if data else 'None')
# Default since_ms to 30 days if nothing is given
new_data = exchange.get_history(pair=pair, ticker_interval=ticker_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, ticker_interval)
return False

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

@@ -0,0 +1,459 @@
# 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 accepted_pairs(self) -> list:
"""
return a list of accepted pairs along with their winrate, expectancy and stoploss
"""
final = []
for pair, info in self._cached_pairs.items():
if info.expectancy > float(self.edge_config.get('minimum_expectancy', 0.2)) and \
info.winrate > float(self.edge_config.get('minimum_winrate', 0.60)):
final.append({
'Pair': pair,
'Winrate': info.winrate,
'Expectancy': info.expectancy,
'Stoploss': info.stoploss,
})
return final
def _fill_calculable_fields(self, result: DataFrame) -> DataFrame:
"""
The result frame contains a number of columns that are calculable
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):
"""
Iterate through ohlc_columns in order to find the next trade
Next trade opens from the first buy signal noticed to
The sell or stoploss signal after it.
It then cuts OHLC, buy_column, sell_column and date_column.
Cut from (the exit trade index) + 1.
Author: https://github.com/mishaker
"""
result: list = []
start_point = 0
while True:
open_trade_index = utf1st.find_1st(buy_column, 1, utf1st.cmp_equal)
# Return empty if we don't find trade entry (i.e. buy==1) or
# we find a buy but at the end of array
if open_trade_index == -1 or open_trade_index == len(buy_column) - 1:
break
else:
# When a buy signal is seen,
# trade opens in reality on the next candle
open_trade_index += 1
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'):
break
if stop_index <= sell_index:
exit_index = open_trade_index + stop_index
exit_type = SellType.STOP_LOSS
exit_price = stop_price
elif stop_index > sell_index:
# If exit is SELL then we exit at the next candle
exit_index = open_trade_index + sell_index + 1
# Check if we have the next candle
if len(ohlc_columns) - 1 < exit_index:
break
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)
# Giving a view of exit_index till the end of array
buy_column = buy_column[exit_index:]
sell_column = sell_column[exit_index:]
date_column = date_column[exit_index:]
ohlc_columns = ohlc_columns[exit_index:]
start_point += exit_index
return result

View File

@@ -1,648 +1,3 @@
# pragma pylint: disable=W0603
""" Cryptocurrency Exchanges support """
import logging
import inspect
from random import randint
from typing import List, Dict, Tuple, Any, Optional
from datetime import datetime
from math import floor, ceil
import asyncio
import ccxt
import ccxt.async_support as ccxt_async
import arrow
from freqtrade import constants, OperationalException, DependencyException, TemporaryError
logger = logging.getLogger(__name__)
API_RETRY_COUNT = 4
# Urls to exchange markets, insert quote and base with .format()
_EXCHANGE_URLS = {
ccxt.bittrex.__name__: '/Market/Index?MarketName={quote}-{base}',
ccxt.binance.__name__: '/tradeDetail.html?symbol={base}_{quote}'
}
def retrier_async(f):
async def wrapper(*args, **kwargs):
count = kwargs.pop('count', API_RETRY_COUNT)
try:
return await f(*args, **kwargs)
except (TemporaryError, DependencyException) as ex:
logger.warning('%s() returned exception: "%s"', f.__name__, ex)
if count > 0:
count -= 1
kwargs.update({'count': count})
logger.warning('retrying %s() still for %s times', f.__name__, count)
return await wrapper(*args, **kwargs)
else:
logger.warning('Giving up retrying: %s()', f.__name__)
raise ex
return wrapper
def retrier(f):
def wrapper(*args, **kwargs):
count = kwargs.pop('count', API_RETRY_COUNT)
try:
return f(*args, **kwargs)
except (TemporaryError, DependencyException) as ex:
logger.warning('%s() returned exception: "%s"', f.__name__, ex)
if count > 0:
count -= 1
kwargs.update({'count': count})
logger.warning('retrying %s() still for %s times', f.__name__, count)
return wrapper(*args, **kwargs)
else:
logger.warning('Giving up retrying: %s()', f.__name__)
raise ex
return wrapper
class Exchange(object):
# Current selected exchange
_api: ccxt.Exchange = None
_api_async: ccxt_async.Exchange = None
_conf: Dict = {}
# Holds all open sell orders for dry_run
_dry_run_open_orders: Dict[str, Any] = {}
def __init__(self, config: dict) -> None:
"""
Initializes this module with the given config,
it does basic validation whether the specified
exchange and pairs are valid.
:return: None
"""
self._conf.update(config)
self._cached_ticker: Dict[str, Any] = {}
# Holds last candle refreshed time of each pair
self._pairs_last_refresh_time: Dict[str, int] = {}
# Holds candles
self.klines: Dict[str, Any] = {}
if config['dry_run']:
logger.info('Instance is running with dry_run enabled')
exchange_config = config['exchange']
self._api = self._init_ccxt(exchange_config)
self._api_async = self._init_ccxt(exchange_config, ccxt_async)
logger.info('Using Exchange "%s"', self.name)
self.markets = self._load_markets()
# Check if all pairs are available
self.validate_pairs(config['exchange']['pair_whitelist'])
if config.get('ticker_interval'):
# Check if timeframe is available
self.validate_timeframes(config['ticker_interval'])
def __del__(self):
"""
Destructor - clean up async stuff
"""
logger.debug("Exchange object destroyed, closing async loop")
if self._api_async and inspect.iscoroutinefunction(self._api_async.close):
asyncio.get_event_loop().run_until_complete(self._api_async.close())
def _init_ccxt(self, exchange_config: dict, ccxt_module=ccxt) -> ccxt.Exchange:
"""
Initialize ccxt with given config and return valid
ccxt instance.
"""
# Find matching class for the given exchange name
name = exchange_config['name']
if name not in ccxt_module.exchanges:
raise OperationalException(f'Exchange {name} is not supported')
try:
api = getattr(ccxt_module, name.lower())({
'apiKey': exchange_config.get('key'),
'secret': exchange_config.get('secret'),
'password': exchange_config.get('password'),
'uid': exchange_config.get('uid', ''),
'enableRateLimit': exchange_config.get('ccxt_rate_limit', True)
})
except (KeyError, AttributeError):
raise OperationalException(f'Exchange {name} is not supported')
self.set_sandbox(api, exchange_config, name)
return api
@property
def name(self) -> str:
"""exchange Name (from ccxt)"""
return self._api.name
@property
def id(self) -> str:
"""exchange ccxt id"""
return self._api.id
def set_sandbox(self, api, exchange_config: dict, name: str):
if exchange_config.get('sandbox'):
if api.urls.get('test'):
api.urls['api'] = api.urls['test']
logger.info("Enabled Sandbox API on %s", name)
else:
logger.warning(name, "No Sandbox URL in CCXT, exiting. "
"Please check your config.json")
raise OperationalException(f'Exchange {name} does not provide a sandbox api')
def _load_async_markets(self) -> None:
try:
if self._api_async:
asyncio.get_event_loop().run_until_complete(self._api_async.load_markets())
except ccxt.BaseError as e:
logger.warning('Could not load async markets. Reason: %s', e)
return
def _load_markets(self) -> Dict[str, Any]:
""" Initialize markets both sync and async """
try:
markets = self._api.load_markets()
self._load_async_markets()
return markets
except ccxt.BaseError as e:
logger.warning('Unable to initialize markets. Reason: %s', e)
return {}
def validate_pairs(self, pairs: List[str]) -> None:
"""
Checks if all given pairs are tradable on the current exchange.
Raises OperationalException if one pair is not available.
:param pairs: list of pairs
:return: None
"""
if not self.markets:
logger.warning('Unable to validate pairs (assuming they are correct).')
# return
stake_cur = self._conf['stake_currency']
for pair in pairs:
# Note: ccxt has BaseCurrency/QuoteCurrency format for pairs
# TODO: add a support for having coins in BTC/USDT format
if not pair.endswith(stake_cur):
raise OperationalException(
f'Pair {pair} not compatible with stake_currency: {stake_cur}')
if self.markets and pair not in self.markets:
raise OperationalException(
f'Pair {pair} is not available at {self.name}')
def validate_timeframes(self, timeframe: List[str]) -> None:
"""
Checks if ticker interval from config is a supported timeframe on the exchange
"""
timeframes = self._api.timeframes
if timeframe not in timeframes:
raise OperationalException(
f'Invalid ticker {timeframe}, this Exchange supports {timeframes}')
def exchange_has(self, endpoint: str) -> bool:
"""
Checks if exchange implements a specific API endpoint.
Wrapper around ccxt 'has' attribute
:param endpoint: Name of endpoint (e.g. 'fetchOHLCV', 'fetchTickers')
:return: bool
"""
return endpoint in self._api.has and self._api.has[endpoint]
def symbol_amount_prec(self, pair, amount: float):
'''
Returns the amount to buy or sell to a precision the Exchange accepts
Rounded down
'''
if self._api.markets[pair]['precision']['amount']:
symbol_prec = self._api.markets[pair]['precision']['amount']
big_amount = amount * pow(10, symbol_prec)
amount = floor(big_amount) / pow(10, symbol_prec)
return amount
def symbol_price_prec(self, pair, price: float):
'''
Returns the price buying or selling with to the precision the Exchange accepts
Rounds up
'''
if self._api.markets[pair]['precision']['price']:
symbol_prec = self._api.markets[pair]['precision']['price']
big_price = price * pow(10, symbol_prec)
price = ceil(big_price) / pow(10, symbol_prec)
return price
def buy(self, pair: str, rate: float, amount: float) -> 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',
'side': 'buy',
'remaining': 0.0,
'datetime': arrow.utcnow().isoformat(),
'status': 'closed',
'fee': None
}
return {'id': order_id}
try:
# Set the precision for amount and price(rate) as accepted by the exchange
amount = self.symbol_amount_prec(pair, amount)
rate = self.symbol_price_prec(pair, rate)
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}.'
f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not create limit buy order on market {pair}.'
f'Tried to buy amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place buy order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
def sell(self, pair: str, rate: float, amount: float) -> 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',
'side': 'sell',
'remaining': 0.0,
'datetime': arrow.utcnow().isoformat(),
'status': 'closed'
}
return {'id': order_id}
try:
# Set the precision for amount and price(rate) as accepted by the exchange
amount = self.symbol_amount_prec(pair, amount)
rate = self.symbol_price_prec(pair, rate)
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}.'
f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not create limit sell order on market {pair}.'
f'Tried to sell amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place sell order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_balance(self, currency: str) -> float:
if self._conf['dry_run']:
return 999.9
# ccxt exception is already handled by get_balances
balances = self.get_balances()
balance = balances.get(currency)
if balance is None:
raise TemporaryError(
f'Could not get {currency} balance due to malformed exchange response: {balances}')
return balance['free']
@retrier
def get_balances(self) -> dict:
if self._conf['dry_run']:
return {}
try:
balances = self._api.fetch_balance()
# Remove additional info from ccxt results
balances.pop("info", None)
balances.pop("free", None)
balances.pop("total", None)
balances.pop("used", None)
return balances
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get balance due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_tickers(self) -> Dict:
try:
return self._api.fetch_tickers()
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {self._api.name} does not support fetching tickers in batch.'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load tickers due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict:
if refresh or pair not in self._cached_ticker.keys():
try:
data = self._api.fetch_ticker(pair)
try:
self._cached_ticker[pair] = {
'bid': float(data['bid']),
'ask': float(data['ask']),
}
except KeyError:
logger.debug("Could not cache ticker data for %s", pair)
return data
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load ticker due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
else:
logger.info("returning cached ticker-data for %s", pair)
return self._cached_ticker[pair]
def get_history(self, pair: str, tick_interval: str,
since_ms: int) -> List:
"""
Gets candle history using asyncio and returns the list of candles.
Handles all async doing.
"""
return asyncio.get_event_loop().run_until_complete(
self._async_get_history(pair=pair, tick_interval=tick_interval,
since_ms=since_ms))
async def _async_get_history(self, pair: str,
tick_interval: str,
since_ms: int) -> List:
# Assume exchange returns 500 candles
_LIMIT = 500
one_call = constants.TICKER_INTERVAL_MINUTES[tick_interval] * 60 * _LIMIT * 1000
logger.debug("one_call: %s", one_call)
input_coroutines = [self._async_get_candle_history(
pair, tick_interval, since) for since in
range(since_ms, arrow.utcnow().timestamp * 1000, one_call)]
tickers = await asyncio.gather(*input_coroutines, return_exceptions=True)
# Combine tickers
data: List = []
for tick in tickers:
if tick[0] == pair:
data.extend(tick[1])
# Sort data again after extending the result - above calls return in "async order" order
data = sorted(data, key=lambda x: x[0])
logger.info("downloaded %s with length %s.", pair, len(data))
return data
def refresh_tickers(self, pair_list: List[str], ticker_interval: str) -> None:
"""
Refresh tickers asyncronously and return the result.
"""
logger.debug("Refreshing klines for %d pairs", len(pair_list))
asyncio.get_event_loop().run_until_complete(
self.async_get_candles_history(pair_list, ticker_interval))
async def async_get_candles_history(self, pairs: List[str],
tick_interval: str) -> List[Tuple[str, List]]:
"""Download ohlcv history for pair-list asyncronously """
input_coroutines = [self._async_get_candle_history(
symbol, tick_interval) for symbol in pairs]
tickers = await asyncio.gather(*input_coroutines, return_exceptions=True)
return tickers
@retrier_async
async def _async_get_candle_history(self, pair: str, tick_interval: str,
since_ms: Optional[int] = None) -> Tuple[str, List]:
try:
# fetch ohlcv asynchronously
logger.debug("fetching %s since %s ...", pair, since_ms)
# Calculating ticker interval in second
interval_in_sec = constants.TICKER_INTERVAL_MINUTES[tick_interval] * 60
# If (last update time) + (interval in second) is greater or equal than now
# that means we don't have to hit the API as there is no new candle
# so we fetch it from local cache
if (not since_ms and
self._pairs_last_refresh_time.get(pair, 0) + interval_in_sec >=
arrow.utcnow().timestamp):
data = self.klines[pair]
logger.debug("Using cached klines data for %s ...", pair)
else:
data = await self._api_async.fetch_ohlcv(pair, timeframe=tick_interval,
since=since_ms)
# Because some exchange sort Tickers ASC and other DESC.
# Ex: Bittrex returns a list of tickers ASC (oldest first, newest last)
# when GDAX returns a list of tickers DESC (newest first, oldest last)
data = sorted(data, key=lambda x: x[0])
# keeping last candle time as last refreshed time of the pair
if data:
self._pairs_last_refresh_time[pair] = data[-1][0] // 1000
# keeping candles in cache
self.klines[pair] = data
logger.debug("done fetching %s ...", pair)
return pair, data
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {self._api.name} does not support fetching historical candlestick data.'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(f'Could not fetch ticker data. Msg: {e}')
@retrier
def get_candle_history(self, pair: str, tick_interval: str,
since_ms: Optional[int] = None) -> List[Dict]:
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)
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)
# 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])
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.'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(f'Could not fetch ticker data. Msg: {e}')
@retrier
def cancel_order(self, order_id: str, pair: str) -> None:
if self._conf['dry_run']:
return
try:
return self._api.cancel_order(order_id, pair)
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not cancel order. Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not cancel order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_order(self, order_id: str, pair: str) -> Dict:
if self._conf['dry_run']:
order = self._dry_run_open_orders[order_id]
order.update({
'id': order_id
})
return order
try:
return self._api.fetch_order(order_id, pair)
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not get order. Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_order_book(self, pair: str, limit: int = 100) -> dict:
"""
get order book level 2 from exchange
Notes:
20180619: bittrex doesnt support limits -.-
20180619: binance support limits but only on specific range
"""
try:
if self._api.name == 'Binance':
limit_range = [5, 10, 20, 50, 100, 500, 1000]
# get next-higher step in the limit_range list
limit = min(list(filter(lambda x: limit <= x, limit_range)))
# above script works like loop below (but with slightly better performance):
# for limitx in limit_range:
# if limit <= limitx:
# limit = limitx
# break
return self._api.fetch_l2_order_book(pair, limit)
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {self._api.name} does not support fetching order book.'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get order book due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> List:
if self._conf['dry_run']:
return []
if not self.exchange_has('fetchMyTrades'):
return []
try:
# Allow 5s offset to catch slight time offsets (discovered in #1185)
my_trades = self._api.fetch_my_trades(pair, since.timestamp() - 5)
matched_trades = [trade for trade in my_trades if trade['order'] == order_id]
return matched_trades
except ccxt.NetworkError as e:
raise TemporaryError(
f'Could not get trades due to networking error. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
def get_pair_detail_url(self, pair: str) -> str:
try:
url_base = self._api.urls.get('www')
base, quote = pair.split('/')
return url_base + _EXCHANGE_URLS[self._api.id].format(base=base, quote=quote)
except KeyError:
logger.warning('Could not get exchange url for %s', self.name)
return ""
@retrier
def get_markets(self) -> List[dict]:
try:
return self._api.fetch_markets()
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load markets due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_fee(self, symbol='ETH/BTC', type='', side='', amount=1,
price=1, taker_or_maker='maker') -> float:
try:
# validate that markets are loaded before trying to get fee
if self._api.markets is None or len(self._api.markets) == 0:
self._api.load_markets()
return self._api.calculate_fee(symbol=symbol, type=type, side=side, amount=amount,
price=price, takerOrMaker=taker_or_maker)['rate']
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get fee info due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
from freqtrade.exchange.exchange import Exchange # noqa: F401
from freqtrade.exchange.kraken import Kraken # noqa: F401
from freqtrade.exchange.binance import Binance # noqa: F401

View File

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

View File

@@ -0,0 +1,691 @@
# pragma pylint: disable=W0603
""" Cryptocurrency Exchanges support """
import logging
import inspect
from random import randint
from typing import List, Dict, Tuple, Any, Optional
from datetime import datetime
from math import floor, ceil
import arrow
import asyncio
import ccxt
import ccxt.async_support as ccxt_async
from pandas import DataFrame
from freqtrade import (constants, DependencyException, OperationalException,
TemporaryError, InvalidOrderException)
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.misc import timeframe_to_seconds, timeframe_to_msecs
logger = logging.getLogger(__name__)
API_RETRY_COUNT = 4
def retrier_async(f):
async def wrapper(*args, **kwargs):
count = kwargs.pop('count', API_RETRY_COUNT)
try:
return await f(*args, **kwargs)
except (TemporaryError, DependencyException) as ex:
logger.warning('%s() returned exception: "%s"', f.__name__, ex)
if count > 0:
count -= 1
kwargs.update({'count': count})
logger.warning('retrying %s() still for %s times', f.__name__, count)
return await wrapper(*args, **kwargs)
else:
logger.warning('Giving up retrying: %s()', f.__name__)
raise ex
return wrapper
def retrier(f):
def wrapper(*args, **kwargs):
count = kwargs.pop('count', API_RETRY_COUNT)
try:
return f(*args, **kwargs)
except (TemporaryError, DependencyException) as ex:
logger.warning('%s() returned exception: "%s"', f.__name__, ex)
if count > 0:
count -= 1
kwargs.update({'count': count})
logger.warning('retrying %s() still for %s times', f.__name__, count)
return wrapper(*args, **kwargs)
else:
logger.warning('Giving up retrying: %s()', f.__name__)
raise ex
return wrapper
class Exchange(object):
_config: Dict = {}
_params: Dict = {}
# Dict to specify which options each exchange implements
# TODO: this should be merged with attributes from subclasses
# To avoid having to copy/paste this to all subclasses.
_ft_has: Dict = {
"stoploss_on_exchange": False,
"order_time_in_force": ["gtc"],
}
def __init__(self, config: dict) -> None:
"""
Initializes this module with the given config,
it does basic validation whether the specified exchange and pairs are valid.
:return: None
"""
self._config.update(config)
self._cached_ticker: Dict[str, Any] = {}
# Holds last candle refreshed time of each pair
self._pairs_last_refresh_time: Dict[Tuple[str, str], int] = {}
# Timestamp of last markets refresh
self._last_markets_refresh: int = 0
# Holds candles
self._klines: Dict[Tuple[str, str], DataFrame] = {}
# Holds all open sell orders for dry_run
self._dry_run_open_orders: Dict[str, Any] = {}
if config['dry_run']:
logger.info('Instance is running with dry_run enabled')
exchange_config = config['exchange']
self._api: ccxt.Exchange = self._init_ccxt(
exchange_config, ccxt_kwargs=exchange_config.get('ccxt_config'))
self._api_async: ccxt_async.Exchange = self._init_ccxt(
exchange_config, ccxt_async, ccxt_kwargs=exchange_config.get('ccxt_async_config'))
logger.info('Using Exchange "%s"', self.name)
# Converts the interval provided in minutes in config to seconds
self.markets_refresh_interval: int = exchange_config.get(
"markets_refresh_interval", 60) * 60
# Initial markets load
self._load_markets()
# Check if all pairs are available
self.validate_pairs(config['exchange']['pair_whitelist'])
self.validate_ordertypes(config.get('order_types', {}))
self.validate_order_time_in_force(config.get('order_time_in_force', {}))
if config.get('ticker_interval'):
# Check if timeframe is available
self.validate_timeframes(config['ticker_interval'])
def __del__(self):
"""
Destructor - clean up async stuff
"""
logger.debug("Exchange object destroyed, closing async loop")
if self._api_async and inspect.iscoroutinefunction(self._api_async.close):
asyncio.get_event_loop().run_until_complete(self._api_async.close())
def _init_ccxt(self, exchange_config: dict, ccxt_module=ccxt,
ccxt_kwargs: dict = None) -> ccxt.Exchange:
"""
Initialize ccxt with given config and return valid
ccxt instance.
"""
# Find matching class for the given exchange name
name = exchange_config['name']
if 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', ''),
}
if ccxt_kwargs:
logger.info('Applying additional ccxt config: %s', ccxt_kwargs)
ex_config.update(ccxt_kwargs)
try:
api = getattr(ccxt_module, name.lower())(ex_config)
except (KeyError, AttributeError):
raise OperationalException(f'Exchange {name} is not supported')
self.set_sandbox(api, exchange_config, name)
return api
@property
def name(self) -> str:
"""exchange Name (from ccxt)"""
return self._api.name
@property
def id(self) -> str:
"""exchange ccxt id"""
return self._api.id
@property
def markets(self) -> Dict:
"""exchange ccxt markets"""
if not self._api.markets:
logger.warning("Markets were not loaded. Loading them now..")
self._load_markets()
return self._api.markets
def klines(self, pair_interval: Tuple[str, str], copy=True) -> DataFrame:
if pair_interval in self._klines:
return self._klines[pair_interval].copy() if copy else self._klines[pair_interval]
else:
return DataFrame()
def set_sandbox(self, api, exchange_config: dict, name: str):
if exchange_config.get('sandbox'):
if api.urls.get('test'):
api.urls['api'] = api.urls['test']
logger.info("Enabled Sandbox API on %s", name)
else:
logger.warning(name, "No Sandbox URL in CCXT, exiting. "
"Please check your config.json")
raise OperationalException(f'Exchange {name} does not provide a sandbox api')
def _load_async_markets(self, reload=False) -> None:
try:
if self._api_async:
asyncio.get_event_loop().run_until_complete(
self._api_async.load_markets(reload=reload))
except ccxt.BaseError as e:
logger.warning('Could not load async markets. Reason: %s', e)
return
def _load_markets(self) -> None:
""" Initialize markets both sync and async """
try:
self._api.load_markets()
self._load_async_markets()
self._last_markets_refresh = arrow.utcnow().timestamp
except ccxt.BaseError as e:
logger.warning('Unable to initialize markets. Reason: %s', e)
def _reload_markets(self) -> None:
"""Reload markets both sync and async, if refresh interval has passed"""
# Check whether markets have to be reloaded
if (self._last_markets_refresh > 0) and (
self._last_markets_refresh + self.markets_refresh_interval
> arrow.utcnow().timestamp):
return None
logger.debug("Performing scheduled market reload..")
self._api.load_markets(reload=True)
self._last_markets_refresh = arrow.utcnow().timestamp
def validate_pairs(self, pairs: List[str]) -> None:
"""
Checks if all given pairs are tradable on the current exchange.
Raises OperationalException if one pair is not available.
:param pairs: list of pairs
:return: None
"""
if not self.markets:
logger.warning('Unable to validate pairs (assuming they are correct).')
# return
for pair in pairs:
# Note: ccxt has BaseCurrency/QuoteCurrency format for pairs
# TODO: add a support for having coins in BTC/USDT format
if self.markets and pair not in self.markets:
raise OperationalException(
f'Pair {pair} is not available on {self.name}. '
f'Please remove {pair} from your whitelist.')
def validate_timeframes(self, timeframe: List[str]) -> None:
"""
Checks if ticker interval from config is a supported timeframe on the exchange
"""
timeframes = self._api.timeframes
if timeframe not in timeframes:
raise OperationalException(
f'Invalid ticker {timeframe}, this Exchange supports {timeframes}')
def validate_ordertypes(self, order_types: Dict) -> None:
"""
Checks if order-types configured in strategy/config are supported
"""
if any(v == 'market' for k, v in order_types.items()):
if not self.exchange_has('createMarketOrder'):
raise OperationalException(
f'Exchange {self.name} does not support market orders.')
if (order_types.get("stoploss_on_exchange")
and not self._ft_has.get("stoploss_on_exchange", False)):
raise OperationalException(
'On exchange stoploss is not supported for %s.' % self.name
)
def validate_order_time_in_force(self, order_time_in_force: Dict) -> None:
"""
Checks if order time in force configured in strategy/config are supported
"""
if any(v not in self._ft_has["order_time_in_force"]
for k, v in order_time_in_force.items()):
raise OperationalException(
f'Time in force policies are not supported for {self.name} yet.')
def exchange_has(self, endpoint: str) -> bool:
"""
Checks if exchange implements a specific API endpoint.
Wrapper around ccxt 'has' attribute
:param endpoint: Name of endpoint (e.g. 'fetchOHLCV', 'fetchTickers')
:return: bool
"""
return endpoint in self._api.has and self._api.has[endpoint]
def symbol_amount_prec(self, pair, amount: float):
'''
Returns the amount to buy or sell to a precision the Exchange accepts
Rounded down
'''
if self.markets[pair]['precision']['amount']:
symbol_prec = self.markets[pair]['precision']['amount']
big_amount = amount * pow(10, symbol_prec)
amount = floor(big_amount) / pow(10, symbol_prec)
return amount
def symbol_price_prec(self, pair, price: float):
'''
Returns the price buying or selling with to the precision the Exchange accepts
Rounds up
'''
if self.markets[pair]['precision']['price']:
symbol_prec = self.markets[pair]['precision']['price']
big_price = price * pow(10, symbol_prec)
price = ceil(big_price) / pow(10, symbol_prec)
return price
def dry_run_order(self, pair: str, ordertype: str, side: str, amount: float,
rate: float, params: Dict = {}) -> Dict[str, Any]:
order_id = f'dry_run_{side}_{randint(0, 10**6)}'
dry_order = { # TODO: additional entry should be added for stoploss limit
"id": order_id,
'pair': pair,
'price': rate,
'amount': amount,
"cost": amount * rate,
'type': ordertype,
'side': side,
'remaining': amount,
'datetime': arrow.utcnow().isoformat(),
'status': "open",
'fee': None,
"info": {}
}
self._store_dry_order(dry_order)
return dry_order
def _store_dry_order(self, dry_order: Dict) -> None:
closed_order = dry_order.copy()
if closed_order["type"] in ["market", "limit"]:
closed_order.update({
"status": "closed",
"filled": closed_order["amount"],
"remaining": 0
})
self._dry_run_open_orders[closed_order["id"]] = closed_order
def create_order(self, pair: str, ordertype: str, side: str, amount: float,
rate: float, params: Dict = {}) -> Dict:
try:
# Set the precision for amount and price(rate) as accepted by the exchange
amount = self.symbol_amount_prec(pair, amount)
rate = self.symbol_price_prec(pair, rate) if ordertype != 'market' else None
return self._api.create_order(pair, ordertype, side,
amount, rate, params)
except ccxt.InsufficientFunds as e:
raise DependencyException(
f'Insufficient funds to create {ordertype} {side} order on market {pair}.'
f'Tried to {side} amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except ccxt.InvalidOrder as e:
raise DependencyException(
f'Could not create {ordertype} {side} order on market {pair}.'
f'Tried to {side} amount {amount} at rate {rate} (total {rate*amount}).'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not place {side} order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
def buy(self, pair: str, ordertype: str, amount: float,
rate: float, time_in_force) -> Dict:
if self._config['dry_run']:
dry_order = self.dry_run_order(pair, ordertype, "buy", amount, rate)
return dry_order
params = self._params.copy()
if time_in_force != 'gtc' and ordertype != 'market':
params.update({'timeInForce': time_in_force})
return self.create_order(pair, ordertype, 'buy', amount, rate, params)
def sell(self, pair: str, ordertype: str, amount: float,
rate: float, time_in_force='gtc') -> Dict:
if self._config['dry_run']:
dry_order = self.dry_run_order(pair, ordertype, "sell", amount, rate)
return dry_order
params = self._params.copy()
if time_in_force != 'gtc' and ordertype != 'market':
params.update({'timeInForce': time_in_force})
return self.create_order(pair, ordertype, 'sell', amount, rate, params)
def stoploss_limit(self, pair: str, amount: float, stop_price: float, rate: float) -> Dict:
"""
creates a stoploss limit order.
NOTICE: it is not supported by all exchanges. only binance is tested for now.
TODO: implementation maybe needs to be moved to the binance subclass
"""
ordertype = "stop_loss_limit"
stop_price = self.symbol_price_prec(pair, stop_price)
# Ensure rate is less than stop price
if stop_price <= rate:
raise OperationalException(
'In stoploss limit order, stop price should be more than limit price')
if self._config['dry_run']:
dry_order = self.dry_run_order(
pair, ordertype, "sell", amount, stop_price)
return dry_order
params = self._params.copy()
params.update({'stopPrice': stop_price})
order = self.create_order(pair, ordertype, 'sell', amount, rate, params)
logger.info('stoploss limit order added for %s. '
'stop price: %s. limit: %s' % (pair, stop_price, rate))
return order
@retrier
def get_balance(self, currency: str) -> float:
if self._config['dry_run']:
return constants.DRY_RUN_WALLET
# ccxt exception is already handled by get_balances
balances = self.get_balances()
balance = balances.get(currency)
if balance is None:
raise TemporaryError(
f'Could not get {currency} balance due to malformed exchange response: {balances}')
return balance['free']
@retrier
def get_balances(self) -> dict:
if self._config['dry_run']:
return {}
try:
balances = self._api.fetch_balance()
# Remove additional info from ccxt results
balances.pop("info", None)
balances.pop("free", None)
balances.pop("total", None)
balances.pop("used", None)
return balances
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get balance due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_tickers(self) -> Dict:
try:
return self._api.fetch_tickers()
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {self._api.name} does not support fetching tickers in batch.'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load tickers due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_ticker(self, pair: str, refresh: Optional[bool] = True) -> dict:
if refresh or pair not in self._cached_ticker.keys():
try:
if pair not in self._api.markets:
raise DependencyException(f"Pair {pair} not available")
data = self._api.fetch_ticker(pair)
try:
self._cached_ticker[pair] = {
'bid': float(data['bid']),
'ask': float(data['ask']),
}
except KeyError:
logger.debug("Could not cache ticker data for %s", pair)
return data
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load ticker due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
else:
logger.info("returning cached ticker-data for %s", pair)
return self._cached_ticker[pair]
def get_history(self, pair: str, ticker_interval: str,
since_ms: int) -> List:
"""
Gets candle history using asyncio and returns the list of candles.
Handles all async doing.
"""
return asyncio.get_event_loop().run_until_complete(
self._async_get_history(pair=pair, ticker_interval=ticker_interval,
since_ms=since_ms))
async def _async_get_history(self, pair: str,
ticker_interval: str,
since_ms: int) -> List:
# Assume exchange returns 500 candles
_LIMIT = 500
one_call = timeframe_to_msecs(ticker_interval) * _LIMIT
logger.debug("one_call: %s msecs", one_call)
input_coroutines = [self._async_get_candle_history(
pair, ticker_interval, since) for since in
range(since_ms, arrow.utcnow().timestamp * 1000, one_call)]
tickers = await asyncio.gather(*input_coroutines, return_exceptions=True)
# Combine tickers
data: List = []
for p, ticker_interval, ticker in tickers:
if p == pair:
data.extend(ticker)
# Sort data again after extending the result - above calls return in "async order"
data = sorted(data, key=lambda x: x[0])
logger.info("downloaded %s with length %s.", pair, len(data))
return data
def refresh_latest_ohlcv(self, pair_list: List[Tuple[str, str]]) -> List[Tuple[str, List]]:
"""
Refresh in-memory ohlcv asyncronously and set `_klines` with the result
"""
logger.debug("Refreshing ohlcv data for %d pairs", len(pair_list))
input_coroutines = []
# Gather coroutines to run
for pair, ticker_interval in set(pair_list):
if (not ((pair, ticker_interval) in self._klines)
or self._now_is_time_to_refresh(pair, ticker_interval)):
input_coroutines.append(self._async_get_candle_history(pair, ticker_interval))
else:
logger.debug("Using cached ohlcv data for %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]
ticker_interval = res[1]
ticks = res[2]
# keeping last candle time as last refreshed time of the pair
if ticks:
self._pairs_last_refresh_time[(pair, ticker_interval)] = ticks[-1][0] // 1000
# keeping parsed dataframe in cache
self._klines[(pair, ticker_interval)] = parse_ticker_dataframe(
ticks, ticker_interval, fill_missing=True)
return tickers
def _now_is_time_to_refresh(self, pair: str, ticker_interval: str) -> bool:
# Calculating ticker interval in seconds
interval_in_sec = timeframe_to_seconds(ticker_interval)
return not ((self._pairs_last_refresh_time.get((pair, ticker_interval), 0)
+ interval_in_sec) >= arrow.utcnow().timestamp)
@retrier_async
async def _async_get_candle_history(self, pair: str, ticker_interval: str,
since_ms: Optional[int] = None) -> Tuple[str, str, List]:
"""
Asyncronously gets candle histories using fetch_ohlcv
returns tuple: (pair, ticker_interval, ohlcv_list)
"""
try:
# fetch ohlcv asynchronously
logger.debug("fetching %s, %s since %s ...", pair, ticker_interval, since_ms)
data = await self._api_async.fetch_ohlcv(pair, timeframe=ticker_interval,
since=since_ms)
# Because some exchange sort Tickers ASC and other DESC.
# Ex: Bittrex returns a list of tickers ASC (oldest first, newest last)
# when GDAX returns a list of tickers DESC (newest first, oldest last)
# Only sort if necessary to save computing time
try:
if data and data[0][0] > data[-1][0]:
data = sorted(data, key=lambda x: x[0])
except IndexError:
logger.exception("Error loading %s. Result was %s.", pair, data)
return pair, ticker_interval, []
logger.debug("done fetching %s, %s ...", pair, ticker_interval)
return pair, ticker_interval, data
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {self._api.name} does not support fetching historical candlestick data.'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(f'Could not fetch ticker data. Msg: {e}')
@retrier
def cancel_order(self, order_id: str, pair: str) -> None:
if self._config['dry_run']:
return
try:
return self._api.cancel_order(order_id, pair)
except ccxt.InvalidOrder as e:
raise InvalidOrderException(
f'Could not cancel order. Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not cancel order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_order(self, order_id: str, pair: str) -> Dict:
if self._config['dry_run']:
order = self._dry_run_open_orders[order_id]
return order
try:
return self._api.fetch_order(order_id, pair)
except ccxt.InvalidOrder as e:
raise InvalidOrderException(
f'Tried to get an invalid order (id: {order_id}). Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get order due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_order_book(self, pair: str, limit: int = 100) -> dict:
"""
get order book level 2 from exchange
Notes:
20180619: bittrex doesnt support limits -.-
"""
try:
return self._api.fetch_l2_order_book(pair, limit)
except ccxt.NotSupported as e:
raise OperationalException(
f'Exchange {self._api.name} does not support fetching order book.'
f'Message: {e}')
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get order book due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_trades_for_order(self, order_id: str, pair: str, since: datetime) -> List:
if self._config['dry_run']:
return []
if not self.exchange_has('fetchMyTrades'):
return []
try:
# Allow 5s offset to catch slight time offsets (discovered in #1185)
my_trades = self._api.fetch_my_trades(pair, since.timestamp() - 5)
matched_trades = [trade for trade in my_trades if trade['order'] == order_id]
return matched_trades
except ccxt.NetworkError as e:
raise TemporaryError(
f'Could not get trades due to networking error. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
@retrier
def get_fee(self, symbol='ETH/BTC', type='', side='', amount=1,
price=1, taker_or_maker='maker') -> float:
try:
# validate that markets are loaded before trying to get fee
if self._api.markets is None or len(self._api.markets) == 0:
self._api.load_markets()
return self._api.calculate_fee(symbol=symbol, type=type, side=side, amount=amount,
price=price, takerOrMaker=taker_or_maker)['rate']
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not get fee info due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)

View File

@@ -1,58 +0,0 @@
"""
Functions to analyze ticker data with indicators and produce buy and sell signals
"""
import logging
import pandas as pd
from pandas import DataFrame, to_datetime
logger = logging.getLogger(__name__)
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
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,12 @@
""" Kraken exchange subclass """
import logging
from typing import Dict
from freqtrade.exchange import Exchange
logger = logging.getLogger(__name__)
class Kraken(Exchange):
_params: Dict = {"trading_agreement": "agree"}

File diff suppressed because it is too large Load Diff

View File

@@ -10,10 +10,9 @@ from typing import List
from freqtrade import OperationalException
from freqtrade.arguments import Arguments
from freqtrade.configuration import Configuration, set_loggers
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.state import State
from freqtrade.rpc import RPCMessageType
from freqtrade.configuration import set_loggers
from freqtrade.worker import Worker
logger = logging.getLogger('freqtrade')
@@ -25,9 +24,9 @@ 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()
args: Namespace = arguments.get_parsed_arg()
# A subcommand has been issued.
# Means if Backtesting or Hyperopt have been called we exit the bot
@@ -35,20 +34,12 @@ def main(sysargv: List[str]) -> None:
args.func(args)
return
freqtrade = None
worker = None
return_code = 1
try:
# Load and validate configuration
config = Configuration(args).get_config()
# Init the bot
freqtrade = FreqtradeBot(config)
state = None
while 1:
state = freqtrade.worker(old_state=state)
if state == State.RELOAD_CONF:
freqtrade = reconfigure(freqtrade, args)
# Load and run worker
worker = Worker(args)
worker.run()
except KeyboardInterrupt:
logger.info('SIGINT received, aborting ...')
@@ -59,31 +50,11 @@ def main(sysargv: List[str]) -> None:
except BaseException:
logger.exception('Fatal exception!')
finally:
if freqtrade:
freqtrade.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': 'process died'
})
freqtrade.cleanup()
if worker:
worker.exit()
sys.exit(return_code)
def reconfigure(freqtrade: FreqtradeBot, args: Namespace) -> FreqtradeBot:
"""
Cleans up current instance, reloads the configuration and returns the new instance
"""
# Clean up current modules
freqtrade.cleanup()
# Create new instance
freqtrade = FreqtradeBot(Configuration(args).get_config())
freqtrade.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': 'config reloaded'
})
return freqtrade
if __name__ == '__main__':
set_loggers()
main(sys.argv[1:])

View File

@@ -3,14 +3,15 @@ Various tool function for Freqtrade and scripts
"""
import gzip
import json
import logging
import re
from datetime import datetime
from typing import Dict
from ccxt import Exchange
import numpy as np
from pandas import DataFrame
import rapidjson
logger = logging.getLogger(__name__)
@@ -38,12 +39,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 +67,45 @@ def file_dump_json(filename, data, is_zip=False) -> None:
:param data: JSON Data to save
:return:
"""
print(f'dumping json to "{filename}"')
logger.info(f'dumping json to "{filename}"')
if is_zip:
if not filename.endswith('.gz'):
filename = filename + '.gz'
with gzip.open(filename, 'w') as fp:
json.dump(data, fp, default=str)
rapidjson.dump(data, fp, default=str, number_mode=rapidjson.NM_NATIVE)
else:
with open(filename, 'w') as fp:
json.dump(data, fp, default=str)
rapidjson.dump(data, fp, default=str, number_mode=rapidjson.NM_NATIVE)
logger.debug(f'done json to "{filename}"')
def json_load(datafile):
"""
load data with rapidjson
Use this to have a consistent experience,
sete number_mode to "NM_NATIVE" for greatest speed
"""
return rapidjson.load(datafile, number_mode=rapidjson.NM_NATIVE)
def file_load_json(file):
gzipfile = file.with_suffix(file.suffix + '.gz')
# Try gzip file first, otherwise regular json file.
if gzipfile.is_file():
logger.debug('Loading ticker data from file %s', gzipfile)
with gzip.open(gzipfile) as tickerdata:
pairdata = json_load(tickerdata)
elif file.is_file():
logger.debug('Loading ticker data from file %s', file)
with open(file) as tickerdata:
pairdata = json_load(tickerdata)
else:
return None
return pairdata
def format_ms_time(date: int) -> str:
@@ -89,3 +114,44 @@ def format_ms_time(date: int) -> str:
: epoch-string in ms
"""
return datetime.fromtimestamp(date/1000.0).strftime('%Y-%m-%dT%H:%M:%S')
def deep_merge_dicts(source, destination):
"""
>>> a = { 'first' : { 'rows' : { 'pass' : 'dog', 'number' : '1' } } }
>>> b = { 'first' : { 'rows' : { 'fail' : 'cat', 'number' : '5' } } }
>>> merge(b, a) == { 'first' : { 'rows' : { 'pass' : 'dog', 'fail' : 'cat', 'number' : '5' } } }
True
"""
for key, value in source.items():
if isinstance(value, dict):
# get node or create one
node = destination.setdefault(key, {})
deep_merge_dicts(value, node)
else:
destination[key] = value
return destination
def timeframe_to_seconds(ticker_interval: str) -> int:
"""
Translates the timeframe interval value written in the human readable
form ('1m', '5m', '1h', '1d', '1w', etc.) to the number
of seconds for one timeframe interval.
"""
return Exchange.parse_timeframe(ticker_interval)
def timeframe_to_minutes(ticker_interval: str) -> int:
"""
Same as above, but returns minutes.
"""
return Exchange.parse_timeframe(ticker_interval) // 60
def timeframe_to_msecs(ticker_interval: str) -> int:
"""
Same as above, but returns milliseconds.
"""
return Exchange.parse_timeframe(ticker_interval) * 1000

View File

@@ -1,245 +1,49 @@
# pragma pylint: disable=missing-docstring
import gzip
try:
import ujson as json
_UJSON = True
except ImportError:
# see mypy/issues/1153
import json # type: ignore
_UJSON = False
import logging
import os
from typing import Optional, List, Dict, Tuple, Any
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 json_load(data):
"""Try to load data with ujson"""
if _UJSON:
return json.load(data, precise_float=True)
else:
return json.load(data)
def trim_tickerlist(tickerlist: List[Dict], timerange: TimeRange) -> List[Dict]:
if not tickerlist:
return tickerlist
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 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: 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')
# 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)
# 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.misc import file_dump_json
from freqtrade.data import history
from freqtrade.data.dataprovider import DataProvider
from freqtrade.misc import file_dump_json, timeframe_to_minutes
from freqtrade.persistence import Trade
from freqtrade.strategy.interface import SellType
from freqtrade.strategy.resolver import IStrategy, StrategyResolver
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
from freqtrade.state import RunMode
from freqtrade.strategy.interface import SellType, IStrategy
logger = logging.getLogger(__name__)
@@ -65,9 +65,19 @@ class Backtesting(object):
self.config['exchange']['uid'] = ''
self.config['dry_run'] = True
self.strategylist: List[IStrategy] = []
exchange_name = self.config.get('exchange', {}).get('name', 'bittrex').title()
self.exchange = ExchangeResolver(exchange_name, self.config).exchange
self.fee = self.exchange.get_fee()
if self.config.get('runmode') != RunMode.HYPEROPT:
self.dataprovider = DataProvider(self.config, self.exchange)
IStrategy.dp = self.dataprovider
if self.config.get('strategy_list', None):
# Force one interval
self.ticker_interval = str(self.config.get('ticker_interval'))
self.ticker_interval_mins = timeframe_to_minutes(self.ticker_interval)
for strat in list(self.config['strategy_list']):
stratconf = deepcopy(self.config)
stratconf['strategy'] = strat
@@ -79,32 +89,21 @@ class Backtesting(object):
# Load one strategy
self._set_strategy(self.strategylist[0])
self.exchange = Exchange(self.config)
self.fee = self.exchange.get_fee()
def _set_strategy(self, strategy):
"""
Load strategy into backtesting
"""
self.strategy = strategy
self.ticker_interval = self.config.get('ticker_interval')
self.ticker_interval_mins = timeframe_to_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]
# Set stoploss_on_exchange to false for backtesting,
# since a "perfect" stoploss-sell is assumed anyway
# And the regular "stoploss" function would not apply to that case
self.strategy.order_types['stoploss_on_exchange'] = False
def _generate_text_table(self, data: Dict[str, Dict], results: DataFrame,
skip_nan: bool = False) -> str:
@@ -113,11 +112,13 @@ class Backtesting(object):
:return: pretty printed table with tabulate as str
"""
stake_currency = str(self.config.get('stake_currency'))
max_open_trades = self.config.get('max_open_trades')
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', 'd', '.1f', '.1f')
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', '.2f', 'd', '.1f', '.1f')
tabular_data = []
headers = ['pair', 'buy count', 'avg profit %', 'cum profit %',
'total profit ' + stake_currency, 'avg duration', 'profit', 'loss']
'tot profit ' + stake_currency, 'tot profit %', 'avg duration',
'profit', 'loss']
for pair in data:
result = results[results.pair == pair]
if skip_nan and result.profit_abs.isnull().all():
@@ -129,6 +130,7 @@ class Backtesting(object):
result.profit_percent.mean() * 100.0,
result.profit_percent.sum() * 100.0,
result.profit_abs.sum(),
result.profit_percent.sum() * 100.0 / max_open_trades,
str(timedelta(
minutes=round(result.trade_duration.mean()))) if not result.empty else '0:00',
len(result[result.profit_abs > 0]),
@@ -142,12 +144,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:
"""
@@ -164,11 +169,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,
@@ -176,12 +183,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:
@@ -200,6 +210,32 @@ class Backtesting(object):
logger.info('Dumping backtest results to %s', recordfilename)
file_dump_json(recordfilename, records)
def _get_ticker_list(self, processed) -> Dict[str, DataFrame]:
"""
Helper function to convert a processed tickerlist into a list for performance reasons.
Used by backtest() - so keep this optimized for performance.
"""
headers = ['date', 'buy', 'open', 'close', 'sell', 'low', 'high']
ticker: Dict = {}
# Create ticker dict
for pair, pair_data in processed.items():
pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run
ticker_data = self.advise_sell(
self.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy()
# to avoid using data from future, we buy/sell with signal from previous candle
ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1)
ticker_data.loc[:, 'sell'] = ticker_data['sell'].shift(1)
ticker_data.drop(ticker_data.head(1).index, inplace=True)
# Convert from Pandas to list for performance reasons
# (Looping Pandas is slow.)
ticker[pair] = [x for x in ticker_data.itertuples()]
return ticker
def _get_sell_trade_entry(
self, pair: str, buy_row: DataFrame,
partial_ticker: List, trade_count_lock: Dict, args: Dict) -> Optional[BacktestResult]:
@@ -223,21 +259,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:
@@ -277,54 +330,68 @@ class Backtesting(object):
position_stacking: do we allow position stacking? (default: False)
:return: DataFrame
"""
headers = ['date', 'buy', 'open', 'close', 'sell']
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 = {}
for pair, pair_data in processed.items():
pair_data['buy'], pair_data['sell'] = 0, 0 # cleanup from previous run
ticker_data = self.advise_sell(
self.advise_buy(pair_data, {'pair': pair}), {'pair': pair})[headers].copy()
# Dict of ticker-lists for performance (looping lists is a lot faster than dataframes)
ticker: Dict = self._get_ticker_list(processed)
# to avoid using data from future, we buy/sell with signal from previous candle
ticker_data.loc[:, 'buy'] = ticker_data['buy'].shift(1)
ticker_data.loc[:, 'sell'] = ticker_data['sell'].shift(1)
lock_pair_until: Dict = {}
# Indexes per pair, so some pairs are allowed to have a missing start.
indexes: Dict = {}
tmp = start_date + timedelta(minutes=self.ticker_interval_mins)
ticker_data.drop(ticker_data.head(1).index, inplace=True)
# Loop timerange and get candle for each pair at that point in time
while tmp < end_date:
# Convert from Pandas to list for performance reasons
# (Looping Pandas is slow.)
ticker = [x for x in ticker_data.itertuples()]
for i, pair in enumerate(ticker):
if pair not in indexes:
indexes[pair] = 0
try:
row = ticker[pair][indexes[pair]]
except IndexError:
# missing Data for one pair at the end.
# Warnings for this are shown by `validate_backtest_data`
continue
# Waits until the time-counter reaches the start of the data for this pair.
if row.date > tmp.datetime:
continue
indexes[pair] += 1
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:
continue
if (not position_stacking and pair in lock_pair_until
and row.date <= lock_pair_until[pair]):
# without positionstacking, we can only have one open trade per pair.
continue
if max_open_trades > 0:
# Check if max_open_trades has already been reached for the given date
if not trade_count_lock.get(row.date, 0) < max_open_trades:
continue
trade_count_lock[row.date] = trade_count_lock.get(row.date, 0) + 1
trade_entry = self._get_sell_trade_entry(pair, row, ticker[index + 1:],
trade_entry = self._get_sell_trade_entry(pair, row, ticker[pair][indexes[pair]:],
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.datetime
# Move time one configured time_interval ahead.
tmp += timedelta(minutes=self.ticker_interval_mins)
return DataFrame.from_records(trades, columns=BacktestResult._fields)
def start(self) -> None:
@@ -339,15 +406,16 @@ class Backtesting(object):
if self.config.get('live'):
logger.info('Downloading data for all pairs in whitelist ...')
self.exchange.refresh_tickers(pairs, self.ticker_interval)
data = self.exchange.klines
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),
@@ -370,17 +438,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,
timeframe_to_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(
@@ -389,6 +458,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,
}
)
@@ -399,18 +470,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(' 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')
@@ -421,7 +492,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 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 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,7 +307,7 @@ 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()

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,86 @@
"""
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 of pairs the user might want to trade
:return: the list of pairs the user wants to trade without those unavailable or
black_listed
"""
markets = self._freqtrade.exchange.markets
sanitized_whitelist = set()
for pair in whitelist:
# pair is not in the generated dynamic market, or in the blacklist ... ignore it
if (pair in self.blacklist or pair not in markets
or not pair.endswith(self._config['stake_currency'])):
logger.warning(f"Pair {pair} is not compatible with exchange "
f"{self._freqtrade.exchange.name} or contained in "
f"your blacklist. Removing it from whitelist..")
continue
# Check if market is active
market = markets[pair]
if not market['active']:
logger.info(f"Ignoring {pair} from whitelist. Market is not active.")
continue
sanitized_whitelist.add(pair)
# We need to remove pairs that are unknown
return list(sanitized_whitelist)

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,95 @@
"""
Volume PairList 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')
self._precision_filter = self._whitelistconf.get('precision_filter', False)
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
self._whitelist = self._gen_pair_whitelist(
self._config['stake_currency'], self._sort_key)[:self._number_pairs]
logger.info(f"Searching pairs: {self._whitelist}")
@cached(TTLCache(maxsize=1, ttl=1800))
def _gen_pair_whitelist(self, base_currency: str, key: str) -> List[str]:
"""
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
and v[key] is not None)]
sorted_tickers = sorted(tickers, reverse=True, key=lambda t: t[key])
# Validate whitelist to only have active market pairs
valid_pairs = self._validate_whitelist([s['symbol'] for s in sorted_tickers])
valid_tickers = [t for t in sorted_tickers if t["symbol"] in valid_pairs]
if self._freqtrade.strategy.stoploss is not None and self._precision_filter:
stop_prices = [self._freqtrade.get_target_bid(t["symbol"], t)
* (1 - abs(self._freqtrade.strategy.stoploss)) for t in valid_tickers]
rates = [sp * 0.99 for sp in stop_prices]
logger.debug("\n".join([f"{sp} : {r}" for sp, r in zip(stop_prices[:10], rates[:10])]))
for i, t in enumerate(valid_tickers):
sp = self._freqtrade.exchange.symbol_price_prec(t["symbol"], stop_prices[i])
r = self._freqtrade.exchange.symbol_price_prec(t["symbol"], rates[i])
logger.debug(f"{t['symbol']} - {sp} : {r}")
if sp <= r:
logger.info(f"Removed {t['symbol']} from whitelist, "
f"because stop price {sp} would be <= stop limit {r}")
valid_tickers.remove(t)
pairs = [s['symbol'] for s in valid_tickers]
return pairs

View File

View File

@@ -4,8 +4,8 @@ This module contains the class to persist trades into SQLite
import logging
from datetime import datetime
from decimal import Decimal, getcontext
from typing import Any, Dict, Optional
from decimal import Decimal
from typing import Any, Dict, List, Optional
import arrow
from sqlalchemy import (Boolean, Column, DateTime, Float, Integer, String,
@@ -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
@@ -82,7 +83,7 @@ def check_migrate(engine) -> None:
logger.debug(f'trying {table_back_name}')
# Check for latest column
if not has_column(cols, 'ticker_interval'):
if not has_column(cols, 'stop_loss_pct'):
logger.info(f'Running database migration - backup available as {table_back_name}')
fee_open = get_column_def(cols, 'fee_open', 'fee')
@@ -90,14 +91,22 @@ def check_migrate(engine) -> None:
open_rate_requested = get_column_def(cols, 'open_rate_requested', 'null')
close_rate_requested = get_column_def(cols, 'close_rate_requested', 'null')
stop_loss = get_column_def(cols, 'stop_loss', '0.0')
stop_loss_pct = get_column_def(cols, 'stop_loss_pct', 'null')
initial_stop_loss = get_column_def(cols, 'initial_stop_loss', '0.0')
initial_stop_loss_pct = get_column_def(cols, 'initial_stop_loss_pct', 'null')
stoploss_order_id = get_column_def(cols, 'stoploss_order_id', 'null')
stoploss_last_update = get_column_def(cols, 'stoploss_last_update', 'null')
max_rate = get_column_def(cols, 'max_rate', '0.0')
min_rate = get_column_def(cols, 'min_rate', 'null')
sell_reason = get_column_def(cols, 'sell_reason', 'null')
strategy = get_column_def(cols, 'strategy', 'null')
ticker_interval = get_column_def(cols, 'ticker_interval', 'null')
# 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)
@@ -106,7 +115,9 @@ def check_migrate(engine) -> None:
(id, exchange, pair, is_open, fee_open, fee_close, open_rate,
open_rate_requested, close_rate, close_rate_requested, close_profit,
stake_amount, amount, open_date, close_date, open_order_id,
stop_loss, initial_stop_loss, max_rate, sell_reason, strategy,
stop_loss, stop_loss_pct, initial_stop_loss, initial_stop_loss_pct,
stoploss_order_id, stoploss_last_update,
max_rate, min_rate, sell_reason, strategy,
ticker_interval
)
select id, lower(exchange),
@@ -121,9 +132,12 @@ def check_migrate(engine) -> None:
open_rate, {open_rate_requested} open_rate_requested, close_rate,
{close_rate_requested} close_rate_requested, close_profit,
stake_amount, amount, open_date, close_date, open_order_id,
{stop_loss} stop_loss, {initial_stop_loss} initial_stop_loss,
{max_rate} max_rate, {sell_reason} sell_reason, {strategy} strategy,
{ticker_interval} ticker_interval
{stop_loss} stop_loss, {stop_loss_pct} stop_loss_pct,
{initial_stop_loss} initial_stop_loss,
{initial_stop_loss_pct} initial_stop_loss_pct,
{stoploss_order_id} stoploss_order_id, {stoploss_last_update} stoploss_last_update,
{max_rate} max_rate, {min_rate} min_rate, {sell_reason} sell_reason,
{strategy} strategy, {ticker_interval} ticker_interval
from {table_back_name}
""")
@@ -175,10 +189,20 @@ class Trade(_DECL_BASE):
open_order_id = Column(String)
# absolute value of the stop loss
stop_loss = Column(Float, nullable=True, default=0.0)
# percentage value of the stop loss
stop_loss_pct = Column(Float, nullable=True)
# absolute value of the initial stop loss
initial_stop_loss = Column(Float, nullable=True, default=0.0)
# percentage value of the initial stop loss
initial_stop_loss_pct = Column(Float, nullable=True)
# stoploss order id which is on exchange
stoploss_order_id = Column(String, nullable=True, index=True)
# last update time of the stoploss order on exchange
stoploss_last_update = Column(DateTime, nullable=True)
# absolute value of the highest reached price
max_rate = Column(Float, nullable=True, default=0.0)
# Lowest price reached
min_rate = Column(Float, nullable=True)
sell_reason = Column(String, nullable=True)
strategy = Column(String, nullable=True)
ticker_interval = Column(Integer, nullable=True)
@@ -189,8 +213,22 @@ class Trade(_DECL_BASE):
return (f'Trade(id={self.id}, pair={self.pair}, amount={self.amount:.8f}, '
f'open_rate={self.open_rate:.8f}, open_since={open_since})')
def adjust_min_max_rates(self, current_price: float):
"""
Adjust the max_rate and min_rate.
"""
logger.debug("Adjusting min/max rates")
self.max_rate = max(current_price, self.max_rate or self.open_rate)
self.min_rate = min(current_price, self.min_rate or self.open_rate)
def adjust_stop_loss(self, current_price: float, stoploss: float, initial: bool = False):
"""this adjusts the stop loss to it's most recently observed setting"""
"""
This adjusts the stop loss to it's most recently observed setting
:param current_price: Current rate the asset is traded
:param stoploss: Stoploss as factor (sample -0.05 -> -5% below current price).
:param initial: Called to initiate stop_loss.
Skips everything if self.stop_loss is already set.
"""
if initial and not (self.stop_loss is None or self.stop_loss == 0):
# Don't modify if called with initial and nothing to do
@@ -198,23 +236,21 @@ class Trade(_DECL_BASE):
new_loss = float(current_price * (1 - abs(stoploss)))
# keeping track of the highest observed rate for this trade
if self.max_rate is None:
self.max_rate = current_price
else:
if current_price > self.max_rate:
self.max_rate = current_price
# no stop loss assigned yet
if not self.stop_loss:
logger.debug("assigning new stop loss")
self.stop_loss = new_loss
self.stop_loss_pct = -1 * abs(stoploss)
self.initial_stop_loss = new_loss
self.initial_stop_loss_pct = -1 * abs(stoploss)
self.stoploss_last_update = datetime.utcnow()
# evaluate if the stop loss needs to be updated
else:
if new_loss > self.stop_loss: # stop losses only walk up, never down!
self.stop_loss = new_loss
self.stop_loss_pct = -1 * abs(stoploss)
self.stoploss_last_update = datetime.utcnow()
logger.debug("adjusted stop loss")
else:
logger.debug("keeping current stop loss")
@@ -239,17 +275,22 @@ 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
self.close_rate_requested = self.stop_loss
logger.info('STOP_LOSS_LIMIT is hit for %s.', self)
self.close(order['average'])
else:
raise ValueError(f'Unknown order type: {order_type}')
cleanup()
@@ -273,12 +314,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)
@@ -289,14 +329,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
@@ -310,12 +349,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(
@@ -336,7 +375,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(
@@ -345,3 +383,21 @@ 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
@staticmethod
def get_open_trades() -> List[Any]:
"""
Query trades from persistence layer
"""
return Trade.query.filter(Trade.is_open.is_(True)).all()

View File

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

View File

@@ -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,65 @@
# 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)
try:
spec.loader.exec_module(module) # type: ignore # importlib does not use typehints
except (ModuleNotFoundError, SyntaxError) as err:
# Catch errors in case a specific module is not installed
logger.warning(f"Could not import {module_path} due to '{err}'")
valid_objects_gen = (
obj for name, obj in inspect.getmembers(module, inspect.isclass)
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,171 @@
# 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", {"0": 10.0}, False),
("ticker_interval", None, False),
("stoploss", None, False),
("trailing_stop", None, False),
("trailing_stop_positive", None, False),
("trailing_stop_positive_offset", 0.0, False),
("trailing_only_offset_is_reached", None, False),
("process_only_new_candles", None, False),
("order_types", None, False),
("order_time_in_force", None, False),
("stake_currency", None, False),
("stake_amount", None, False),
("use_sell_signal", False, True),
("sell_profit_only", False, True),
("ignore_roi_if_buy_signal", False, True),
]
for attribute, default, experimental in attributes:
if experimental:
self._override_attribute_helper(config['experimental'], attribute, default)
else:
self._override_attribute_helper(config, attribute, default)
# 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)
try:
return import_strategy(strategy, config=config)
except TypeError as e:
logger.warning(
f"Impossible to load strategy '{strategy}' from {_path}. Error: {e}")
except FileNotFoundError:
logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd()))
raise ImportError(
f"Impossible to load Strategy '{strategy_name}'. This class does not exist"
" or contains Python code errors"
)

View File

@@ -10,13 +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 import TemporaryError
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,10 +83,8 @@ class RPC(object):
a remotely exposed function
"""
# 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:
trades = Trade.get_open_trades()
if not trades:
raise RPCException('no active trade')
else:
results = []
@@ -95,38 +93,49 @@ 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.get_sell_rate(trade.pair, False)
except DependencyException:
current_rate = NAN
current_profit = trade.calc_profit_percent(current_rate)
fmt_close_profit = (f'{round(trade.close_profit * 100, 2):.2f}%'
if trade.close_profit else None)
results.append(dict(
trade_id=trade.id,
pair=trade.pair,
market_url=self._freqtrade.exchange.get_pair_detail_url(trade.pair),
base_currency=self._freqtrade.config['stake_currency'],
date=arrow.get(trade.open_date),
open_rate=trade.open_rate,
close_rate=trade.close_rate,
current_rate=current_rate,
amount=round(trade.amount, 8),
stake_amount=round(trade.stake_amount, 8),
close_profit=fmt_close_profit,
current_profit=round(current_profit * 100, 2),
stop_loss=trade.stop_loss,
stop_loss_pct=(trade.stop_loss_pct * 100)
if trade.stop_loss_pct else None,
initial_stop_loss=trade.initial_stop_loss,
initial_stop_loss_pct=(trade.initial_stop_loss_pct * 100)
if trade.initial_stop_loss_pct else None,
open_order='({} {} rem={:.8f})'.format(
order['type'], order['side'], order['remaining']
order['type'], order['side'], order['remaining']
) if order else None,
))
return results
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:
trades = Trade.get_open_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.get_sell_rate(trade.pair, False)
except DependencyException:
current_rate = NAN
trade_perc = (100 * trade.calc_profit_percent(current_rate))
trades_list.append([
trade.id,
@@ -211,7 +220,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.get_sell_rate(trade.pair, False)
except DependencyException:
current_rate = NAN
profit_percent = trade.calc_profit_percent(rate=current_rate)
profit_all_coin.append(
@@ -276,10 +288,10 @@ class RPC(object):
else:
try:
if coin == 'USDT':
rate = 1.0 / self._freqtrade.exchange.get_ticker('BTC/USDT', False)['bid']
rate = 1.0 / self._freqtrade.get_sell_rate('BTC/USDT', False)
else:
rate = self._freqtrade.exchange.get_ticker(coin + '/BTC', False)['bid']
except TemporaryError:
rate = self._freqtrade.get_sell_rate(coin + '/BTC', False)
except (TemporaryError, DependencyException):
continue
est_btc: float = rate * balance['total']
total = total + est_btc
@@ -324,6 +336,16 @@ class RPC(object):
self._freqtrade.state = State.RELOAD_CONF
return {'status': 'reloading config ...'}
def _rpc_stopbuy(self) -> Dict[str, str]:
"""
Handler to stop buying, but handle open trades gracefully.
"""
if self._freqtrade.state == State.RUNNING:
# Set 'max_open_trades' to 0
self._freqtrade.config['max_open_trades'] = 0
return {'status': 'No more buy will occur from now. Run /reload_conf to reset.'}
def _rpc_forcesell(self, trade_id) -> None:
"""
Handler for forcesell <id>.
@@ -352,7 +374,7 @@ class RPC(object):
return
# Get current rate and execute sell
current_rate = self._freqtrade.exchange.get_ticker(trade.pair, False)['bid']
current_rate = self._freqtrade.get_sell_rate(trade.pair, False)
self._freqtrade.execute_sell(trade, current_rate, SellType.FORCE_SELL)
# ---- EOF def _exec_forcesell ----
@@ -361,8 +383,9 @@ class RPC(object):
if trade_id == 'all':
# Execute sell for all open orders
for trade in Trade.query.filter(Trade.is_open.is_(True)).all():
for trade in Trade.get_open_trades():
_exec_forcesell(trade)
Trade.session.flush()
return
# Query for trade
@@ -379,13 +402,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'),
@@ -399,9 +454,43 @@ class RPC(object):
for pair, rate, count in pair_rates
]
def _rpc_count(self) -> List[Trade]:
def _rpc_count(self) -> Dict[str, float]:
""" Returns the number of trades running """
if self._freqtrade.state != State.RUNNING:
raise RPCException('trader is not running')
return Trade.query.filter(Trade.is_open.is_(True)).all()
trades = Trade.get_open_trades()
return {
'current': len(trades),
'max': float(self._freqtrade.config['max_open_trades']),
'total_stake': sum((trade.open_rate * trade.amount) for trade in trades)
}
def _rpc_whitelist(self) -> Dict:
""" Returns the currently active whitelist"""
res = {'method': self._freqtrade.pairlists.name,
'length': len(self._freqtrade.active_pair_whitelist),
'whitelist': self._freqtrade.active_pair_whitelist
}
return res
def _rpc_blacklist(self, add: List[str]) -> Dict:
""" Returns the currently active blacklist"""
if add:
stake_currency = self._freqtrade.config.get('stake_currency')
for pair in add:
if (pair.endswith(stake_currency)
and pair not in self._freqtrade.pairlists.blacklist):
self._freqtrade.pairlists.blacklist.append(pair)
res = {'method': self._freqtrade.pairlists.name,
'length': len(self._freqtrade.pairlists.blacklist),
'blacklist': self._freqtrade.pairlists.blacklist,
}
return res
def _rpc_edge(self) -> List[Dict[str, Any]]:
""" Returns information related to Edge """
if not self._freqtrade.edge:
raise RPCException(f'Edge is not enabled.')
return self._freqtrade.edge.accepted_pairs()

View File

@@ -2,9 +2,9 @@
This module contains class to manage RPC communications (Telegram, Slack, ...)
"""
import logging
from typing import List, Dict, Any
from typing import Any, Dict, List
from freqtrade.rpc import RPC
from freqtrade.rpc import RPC, RPCMessageType
logger = logging.getLogger(__name__)
@@ -51,3 +51,32 @@ 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']
stoploss = config['stoploss']
trailing_stop = config['trailing_stop']
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'*{"Trailing " if trailing_stop else ""}Stoploss:* `{stoploss}`\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

@@ -4,7 +4,7 @@
This module manage Telegram communication
"""
import logging
from typing import Any, Callable, Dict
from typing import Any, Callable, Dict, List
from tabulate import tabulate
from telegram import Bot, ParseMode, ReplyKeyboardMarkup, Update
@@ -12,15 +12,18 @@ 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__)
logger.debug('Included module rpc.telegram ...')
def authorized_only(command_handler: Callable[[Any, Bot, Update], None]) -> Callable[..., Any]:
MAX_TELEGRAM_MESSAGE_LENGTH = 4096
def authorized_only(command_handler: Callable[..., None]) -> Callable[..., Any]:
"""
Decorator to check if the message comes from the correct chat_id
:param command_handler: Telegram CommandHandler
@@ -86,10 +89,15 @@ 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('stopbuy', self._stopbuy),
CommandHandler('whitelist', self._whitelist),
CommandHandler('blacklist', self._blacklist, pass_args=True),
CommandHandler('edge', self._edge),
CommandHandler('help', self._help),
CommandHandler('version', self._version),
]
@@ -123,9 +131,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}\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 +143,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}\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 +157,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)
@@ -188,21 +197,34 @@ class Telegram(RPC):
for result in results:
result['date'] = result['date'].humanize()
messages = [
"*Trade ID:* `{trade_id}`\n"
"*Current Pair:* [{pair}]({market_url})\n"
"*Open Since:* `{date}`\n"
"*Amount:* `{amount}`\n"
"*Open Rate:* `{open_rate:.8f}`\n"
"*Close Rate:* `{close_rate}`\n"
"*Current Rate:* `{current_rate:.8f}`\n"
"*Close Profit:* `{close_profit}`\n"
"*Current Profit:* `{current_profit:.2f}%`\n"
"*Open Order:* `{open_order}`".format(**result)
for result in results
]
messages = []
for r in results:
lines = [
"*Trade ID:* `{trade_id}` `(since {date})`",
"*Current Pair:* {pair}",
"*Amount:* `{amount} ({stake_amount} {base_currency})`",
"*Open Rate:* `{open_rate:.8f}`",
"*Close Rate:* `{close_rate}`" if r['close_rate'] else "",
"*Current Rate:* `{current_rate:.8f}`",
"*Close Profit:* `{close_profit}`" if r['close_profit'] else "",
"*Current Profit:* `{current_profit:.2f}%`",
# Adding initial stoploss only if it is different from stoploss
"*Initial Stoploss:* `{initial_stop_loss:.8f}` " +
("`({initial_stop_loss_pct:.2f}%)`" if r['initial_stop_loss_pct'] else "")
if r['stop_loss'] != r['initial_stop_loss'] else "",
# Adding stoploss and stoploss percentage only if it is not None
"*Stoploss:* `{stop_loss:.8f}` " +
("`({stop_loss_pct:.2f}%)`" if r['stop_loss_pct'] else ""),
"*Open Order:* `{open_order}`" if r['open_order'] else ""
]
messages.append("\n".join(filter(None, lines)).format(**r))
for msg in messages:
self._send_msg(msg, bot=bot)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@@ -243,14 +265,15 @@ class Telegram(RPC):
stake_cur,
fiat_disp_cur
)
stats = tabulate(stats,
headers=[
'Day',
f'Profit {stake_cur}',
f'Profit {fiat_disp_cur}'
],
tablefmt='simple')
message = f'<b>Daily Profit over the last {timescale} days</b>:\n<pre>{stats}</pre>'
stats_tab = tabulate(stats,
headers=[
'Day',
f'Profit {stake_cur}',
f'Profit {fiat_disp_cur}',
f'Trades'
],
tablefmt='simple')
message = f'<b>Daily Profit over the last {timescale} days</b>:\n<pre>{stats_tab}</pre>'
self._send_msg(message, bot=bot, parse_mode=ParseMode.HTML)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@@ -307,11 +330,21 @@ 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:
curr_output = "*{currency}:*\n" \
"\t`Available: {available: .8f}`\n" \
"\t`Balance: {balance: .8f}`\n" \
"\t`Pending: {pending: .8f}`\n" \
"\t`Est. BTC: {est_btc: .8f}`\n".format(**currency)
else:
curr_output = "*{currency}:* not showing <1$ amount \n".format(**currency)
# Handle overflowing messsage length
if len(output + curr_output) >= MAX_TELEGRAM_MESSAGE_LENGTH:
self._send_msg(output, bot=bot)
output = curr_output
else:
output += curr_output
output += "\n*Estimated Value*:\n" \
"\t`BTC: {total: .8f}`\n" \
@@ -356,6 +389,18 @@ class Telegram(RPC):
msg = self._rpc_reload_conf()
self._send_msg('Status: `{status}`'.format(**msg), bot=bot)
@authorized_only
def _stopbuy(self, bot: Bot, update: Update) -> None:
"""
Handler for /stop_buy.
Sets max_open_trades to 0 and gracefully sells all open trades
:param bot: telegram bot
:param update: message update
:return: None
"""
msg = self._rpc_stopbuy()
self._send_msg('Status: `{status}`'.format(**msg), bot=bot)
@authorized_only
def _forcesell(self, bot: Bot, update: Update) -> None:
"""
@@ -372,6 +417,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:
"""
@@ -404,18 +467,65 @@ class Telegram(RPC):
:return: None
"""
try:
trades = self._rpc_count()
message = tabulate({
'current': [len(trades)],
'max': [self._config['max_open_trades']],
'total stake': [sum((trade.open_rate * trade.amount) for trade in trades)]
}, headers=['current', 'max', 'total stake'], tablefmt='simple')
counts = self._rpc_count()
message = tabulate({k: [v] for k, v in counts.items()},
headers=['current', 'max', 'total stake'],
tablefmt='simple')
message = "<pre>{}</pre>".format(message)
logger.debug(message)
self._send_msg(message, parse_mode=ParseMode.HTML)
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 _blacklist(self, bot: Bot, update: Update, args: List[str]) -> None:
"""
Handler for /blacklist
Shows the currently active blacklist
"""
try:
blacklist = self._rpc_blacklist(args)
message = f"Blacklist contains {blacklist['length']} pairs\n"
message += f"`{', '.join(blacklist['blacklist'])}`"
logger.debug(message)
self._send_msg(message)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only
def _edge(self, bot: Bot, update: Update) -> None:
"""
Handler for /edge
Shows information related to Edge
"""
try:
edge_pairs = self._rpc_edge()
edge_pairs_tab = tabulate(edge_pairs, headers='keys', tablefmt='simple')
message = f'<b>Edge only validated following pairs:</b>\n<pre>{edge_pairs_tab}</pre>'
self._send_msg(message, bot=bot, parse_mode=ParseMode.HTML)
except RPCException as e:
self._send_msg(str(e), bot=bot)
@authorized_only
def _help(self, bot: Bot, update: Update) -> None:
"""
@@ -425,6 +535,8 @@ class Telegram(RPC):
:param update: message update
:return: None
"""
forcebuy_text = "*/forcebuy <pair> [<rate>]:* `Instantly buys the given pair. " \
"Optionally takes a rate at which to buy.` \n"
message = "*/start:* `Starts the trader`\n" \
"*/stop:* `Stops the trader`\n" \
"*/status [table]:* `Lists all open trades`\n" \
@@ -432,11 +544,18 @@ class Telegram(RPC):
"*/profit:* `Lists cumulative profit from all finished trades`\n" \
"*/forcesell <trade_id>|all:* `Instantly sells the given trade or all trades, " \
"regardless of profit`\n" \
f"{forcebuy_text if self._config.get('forcebuy_enable', False) else '' }" \
"*/performance:* `Show performance of each finished trade grouped by pair`\n" \
"*/daily <n>:* `Shows profit or loss per day, over the last n days`\n" \
"*/count:* `Show number of trades running compared to allowed number of trades`" \
"\n" \
"*/balance:* `Show account balance per currency`\n" \
"*/stopbuy:* `Stops buying, but handles open trades gracefully` \n" \
"*/reload_conf:* `Reload configuration file` \n" \
"*/whitelist:* `Show current whitelist` \n" \
"*/blacklist [pair]:* `Show current blacklist, or adds one or more pairs " \
"to the blacklist.` \n" \
"*/edge:* `Shows validated pairs by Edge if it is enabeld` \n" \
"*/help:* `This help message`\n" \
"*/version:* `Show version`"

View File

@@ -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

@@ -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

@@ -6,15 +6,16 @@ import logging
from abc import ABC, abstractmethod
from datetime import datetime
from enum import Enum
from typing import Dict, List, NamedTuple, Optional, Tuple
from typing import Dict, List, NamedTuple, Tuple
import warnings
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.misc import timeframe_to_minutes
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,18 +69,43 @@ class IStrategy(ABC):
# associated stoploss
stoploss: float
# trailing stoploss
trailing_stop: bool = False
trailing_stop_positive: float
trailing_stop_positive_offset: float
trailing_only_offset_is_reached = False
# associated ticker interval
ticker_interval: str
# 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
# Dict to determine if analysis is necessary
_last_candle_seen_per_pair: Dict[str, datetime] = {}
# Class level variables (intentional) containing
# the dataprovider (dp) (access to other candles, historic data, ...)
# and wallets - access to the current balance.
dp: DataProvider
wallets: Wallets
def __init__(self, config: dict) -> None:
self.config = config
self._last_candle_seen_per_pair = {}
# Dict to determine if analysis is necessary
self._last_candle_seen_per_pair: Dict[str, datetime] = {}
@abstractmethod
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
@@ -107,58 +134,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)
pair = str(metadata.get('pair'))
# Test if seen this pair and last candle before.
# always run if process_only_new_candles is set to true
# 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.
logging.debug("TA Analysis Launched")
logger.debug("TA Analysis Launched")
dataframe = self.advise_indicators(dataframe, metadata)
dataframe = self.advise_buy(dataframe, metadata)
dataframe = self.advise_sell(dataframe, metadata)
self._last_candle_seen_per_pair[pair] = dataframe.iloc[-1]['date']
else:
logging.debug("Skippinig TA Analysis for already analyzed candle")
logger.debug("Skipping TA Analysis for already analyzed candle")
dataframe['buy'] = 0
dataframe['sell'] = 0
# Other Defs in strategy that want to be called every loop here
# twitter_sell = self.watch_twitter_feed(dataframe, metadata)
logging.debug("Loop Analysis Launched")
logger.debug("Loop Analysis Launched")
return dataframe
def get_signal(self, pair: str, interval: str,
ticker_hist: Optional[List[Dict]]) -> Tuple[bool, bool]:
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',
@@ -182,7 +221,7 @@ class IStrategy(ABC):
# Check if dataframe is out of date
signal_date = arrow.get(latest['date'])
interval_minutes = constants.TICKER_INTERVAL_MINUTES[interval]
interval_minutes = timeframe_to_minutes(interval)
offset = self.config.get('exchange', {}).get('outdated_offset', 5)
if signal_date < (arrow.utcnow().shift(minutes=-(interval_minutes * 2 + offset))):
logger.warning(
@@ -203,18 +242,33 @@ 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.
:param low: Only used during backtesting to simulate stoploss
:param high: Only used during backtesting, to simulate ROI
:param force_stoploss: Externally provided stoploss
:return: True if trade should be sold, False otherwise
"""
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)
trade.adjust_min_max_rates(high or current_rate)
stoplossflag = self.stop_loss_reached(current_rate=current_rate, trade=trade,
current_time=date, current_profit=current_profit,
force_stoploss=force_stoploss, high=high)
if stoplossflag.sell_flag:
return stoplossflag
# Set current rate to high 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):
@@ -236,8 +290,9 @@ class IStrategy(ABC):
return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
def stop_loss_reached(self, current_rate: float, trade: Trade, current_time: datetime,
current_profit: float) -> SellCheckTuple:
def stop_loss_reached(self, current_rate: float, trade: Trade,
current_time: datetime, current_profit: float,
force_stoploss: float, high: float = None) -> SellCheckTuple:
"""
Based on current profit of the trade and configured (trailing) stoploss,
decides to sell or not
@@ -245,13 +300,36 @@ class IStrategy(ABC):
"""
trailing_stop = self.config.get('trailing_stop', False)
stop_loss_value = force_stoploss if force_stoploss else self.stoploss
trade.adjust_stop_loss(trade.open_rate, self.stoploss, initial=True)
# Initiate stoploss with open_rate. Does nothing if stoploss is already set.
trade.adjust_stop_loss(trade.open_rate, stop_loss_value, initial=True)
if trailing_stop:
# trailing stoploss handling
sl_offset = self.config.get('trailing_stop_positive_offset') or 0.0
tsl_only_offset = self.config.get('trailing_only_offset_is_reached', False)
# Don't update stoploss if trailing_only_offset_is_reached is true.
if not (tsl_only_offset and current_profit < sl_offset):
# Specific handling for trailing_stop_positive
if 'trailing_stop_positive' in self.config and current_profit > sl_offset:
# Ignore mypy error check in configuration that this is a float
stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore
logger.debug(f"using positive stop loss: {stop_loss_value} "
f"offset: {sl_offset:.4g} profit: {current_profit:.4f}%")
trade.adjust_stop_loss(high or current_rate, stop_loss_value)
# evaluate if the stoploss was hit if stoploss is not on exchange
if ((self.stoploss is not None) and
(trade.stop_loss >= current_rate) and
(not self.order_types.get('stoploss_on_exchange'))):
# evaluate if the stoploss was hit
if self.stoploss is not None and trade.stop_loss >= current_rate:
selltype = SellType.STOP_LOSS
if trailing_stop:
# If 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}, "
@@ -263,40 +341,23 @@ class IStrategy(ABC):
logger.debug('Stop loss hit.')
return SellCheckTuple(sell_flag=True, sell_type=selltype)
# update the stop loss afterwards, after all by definition it's supposed to be hanging
if trailing_stop:
# check if we have a special stop loss for positive condition
# and if profit is positive
stop_loss_value = self.stoploss
sl_offset = self.config.get('trailing_stop_positive_offset', 0.0)
if 'trailing_stop_positive' in self.config and current_profit > sl_offset:
# Ignore mypy error check in configuration that this is a float
stop_loss_value = self.config.get('trailing_stop_positive') # type: ignore
logger.debug(f"using positive stop loss mode: {stop_loss_value} "
f"with offset {sl_offset:.4g} "
f"since we have profit {current_profit:.4f}%")
trade.adjust_stop_loss(current_rate, stop_loss_value)
return SellCheckTuple(sell_flag=False, sell_type=SellType.NONE)
def min_roi_reached(self, trade: Trade, current_profit: float, current_time: datetime) -> bool:
"""
Based an earlier trade and current price and ROI configuration, decides whether bot should
sell
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
@@ -304,7 +365,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,178 +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: %s.",
config['minimal_roi'])
else:
config['minimal_roi'] = self.strategy.minimal_roi
if 'stoploss' in config:
self.strategy.stoploss = config['stoploss']
logger.info(
"Override strategy 'stoploss' with value in config file: %s.", config['stoploss']
)
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
if 'process_only_new_candles' in config:
self.strategy.process_only_new_candles = config['process_only_new_candles']
logger.info(
"Override process_only_new_candles 'process_only_new_candles' "
"with value in config file: %s.", config['process_only_new_candles']
)
else:
config['process_only_new_candles'] = self.strategy.process_only_new_candles
# Sort and apply type conversions
self.strategy.minimal_roi = OrderedDict(sorted(
{int(key): value for (key, value) in self.strategy.minimal_roi.items()}.items(),
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,18 +1,22 @@
# 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, 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.edge import Edge, PairInfo
from freqtrade.exchange import Exchange
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.resolvers import ExchangeResolver
from freqtrade.worker import Worker
logging.getLogger('').setLevel(logging.INFO)
@@ -25,43 +29,91 @@ def log_has(line, logs):
False)
def patch_exchange(mocker, api_mock=None) -> None:
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_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.name', PropertyMock(return_value="Bittrex"))
mocker.patch('freqtrade.exchange.Exchange.id', PropertyMock(return_value="bittrex"))
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)
exchange = Exchange(config)
def get_patched_exchange(mocker, config, api_mock=None, id='bittrex') -> Exchange:
patch_exchange(mocker, api_mock, id)
config["exchange"]["name"] = id
try:
exchange = ExchangeResolver(id.title(), config).exchange
except ImportError:
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:
def patch_freqtradebot(mocker, config) -> None:
"""
This function patch _init_modules() to not call dependencies
:param mocker: a Mocker object to apply patches
:param config: Config to pass to the bot
:return: None
"""
# 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())
patch_exchange(mocker, None)
mocker.patch('freqtrade.freqtradebot.RPCManager._init', MagicMock())
mocker.patch('freqtrade.freqtradebot.RPCManager.send_msg', MagicMock())
def get_patched_freqtradebot(mocker, config) -> FreqtradeBot:
patch_freqtradebot(mocker, config)
return FreqtradeBot(config)
def patch_coinmarketcap(mocker, value: Optional[Dict[str, float]] = None) -> None:
def get_patched_worker(mocker, config) -> Worker:
patch_freqtradebot(mocker, config)
return Worker(args=None, config=config)
@pytest.fixture(autouse=True)
def patch_coinmarketcap(mocker) -> None:
"""
Mocker to coinmarketcap to speed up tests
:param mocker: mocker to patch coinmarketcap class
@@ -75,7 +127,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,
@@ -127,6 +179,10 @@ def default_conf():
"LTC/BTC",
"XRP/BTC",
"NEO/BTC"
],
"pair_blacklist": [
"DOGE/BTC",
"HOT/BTC",
]
},
"telegram": {
@@ -182,8 +238,8 @@ def ticker_sell_down():
@pytest.fixture
def markets():
return MagicMock(return_value=[
{
return {
'ETH/BTC': {
'id': 'ethbtc',
'symbol': 'ETH/BTC',
'base': 'ETH',
@@ -208,7 +264,7 @@ def markets():
},
'info': '',
},
{
'TKN/BTC': {
'id': 'tknbtc',
'symbol': 'TKN/BTC',
'base': 'TKN',
@@ -233,7 +289,7 @@ def markets():
},
'info': '',
},
{
'BLK/BTC': {
'id': 'blkbtc',
'symbol': 'BLK/BTC',
'base': 'BLK',
@@ -258,7 +314,7 @@ def markets():
},
'info': '',
},
{
'LTC/BTC': {
'id': 'ltcbtc',
'symbol': 'LTC/BTC',
'base': 'LTC',
@@ -283,7 +339,7 @@ def markets():
},
'info': '',
},
{
'XRP/BTC': {
'id': 'xrpbtc',
'symbol': 'XRP/BTC',
'base': 'XRP',
@@ -308,7 +364,7 @@ def markets():
},
'info': '',
},
{
'NEO/BTC': {
'id': 'neobtc',
'symbol': 'NEO/BTC',
'base': 'NEO',
@@ -332,8 +388,80 @@ def markets():
},
},
'info': '',
},
'BTT/BTC': {
'id': 'BTTBTC',
'symbol': 'BTT/BTC',
'base': 'BTT',
'quote': 'BTC',
'active': True,
'precision': {
'base': 8,
'quote': 8,
'amount': 0,
'price': 8
},
'limits': {
'amount': {
'min': 1.0,
'max': 90000000.0
},
'price': {
'min': None,
'max': None
},
'cost': {
'min': 0.001,
'max': None
}
},
'info': "",
},
'ETH/USDT': {
'id': 'USDT-ETH',
'symbol': 'ETH/USDT',
'base': 'ETH',
'quote': 'USDT',
'precision': {
'amount': 8,
'price': 8
},
'limits': {
'amount': {
'min': 0.02214286,
'max': None
},
'price': {
'min': 1e-08,
'max': None
}
},
'active': True,
'info': ""
},
'LTC/USDT': {
'id': 'USDT-LTC',
'symbol': 'LTC/USDT',
'base': 'LTC',
'quote': 'USDT',
'active': True,
'precision': {
'amount': 8,
'price': 8
},
'limits': {
'amount': {
'min': 0.06646786,
'max': None
},
'price': {
'min': 1e-08,
'max': None
}
},
'info': ""
}
])
}
@pytest.fixture
@@ -356,6 +484,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 {
@@ -450,7 +608,7 @@ def order_book_l2():
@pytest.fixture
def ticker_history():
def ticker_history_list():
return [
[
1511686200000, # unix timestamp ms
@@ -479,6 +637,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={
@@ -517,6 +680,7 @@ def tickers():
'vwap': 0.01869197,
'open': 0.018585,
'close': 0.018573,
'last': 0.018799,
'baseVolume': 81058.66,
'quoteVolume': 2247.48374509,
},
@@ -564,6 +728,28 @@ def tickers():
'quoteVolume': 1401.65697943,
'info': {}
},
'BTT/BTC': {
'symbol': 'BTT/BTC',
'timestamp': 1550936557206,
'datetime': '2019-02-23T15:42:37.206Z',
'high': 0.00000026,
'low': 0.00000024,
'bid': 0.00000024,
'bidVolume': 2446894197.0,
'ask': 0.00000025,
'askVolume': 2447913837.0,
'vwap': 0.00000025,
'open': 0.00000026,
'close': 0.00000024,
'last': 0.00000024,
'previousClose': 0.00000026,
'change': -0.00000002,
'percentage': -7.692,
'average': None,
'baseVolume': 4886464537.0,
'quoteVolume': 1215.14489611,
'info': {}
},
'ETH/USDT': {
'symbol': 'ETH/USDT',
'timestamp': 1522014804118,
@@ -658,7 +844,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
@@ -752,3 +938,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,21 @@
import pytest
from pandas import DataFrame
from freqtrade.data.btanalysis import BT_DATA_COLUMNS, load_backtest_data
from freqtrade.data.history import make_testdata_path
def test_load_backtest_data():
filename = make_testdata_path(None) / "backtest-result_test.json"
bt_data = load_backtest_data(filename)
assert isinstance(bt_data, DataFrame)
assert list(bt_data.columns) == BT_DATA_COLUMNS + ["profitabs"]
assert len(bt_data) == 179
# Test loading from string (must yield same result)
bt_data2 = load_backtest_data(str(filename))
assert bt_data.equals(bt_data2)
with pytest.raises(ValueError, match=r"File .* does not exist\."):
load_backtest_data(str("filename") + "nofile")

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

View File

@@ -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',
ticker_interval='1m')
assert download_pair_history(datadir=None, exchange=exchange,
pair='CFI/BTC',
ticker_interval='1m')
assert not exchange._pairs_last_refresh_time
assert os.path.isfile(file1_1) is True
assert os.path.isfile(file2_1) is True
# 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',
ticker_interval='5m')
assert download_pair_history(datadir=None, exchange=exchange,
pair='CFI/BTC',
ticker_interval='5m')
assert not exchange._pairs_last_refresh_time
assert os.path.isfile(file1_5) is True
assert os.path.isfile(file2_5) is True
# 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", ticker_interval='1m')
download_pair_history(None, exchange, pair="UNITTEST/BTC", ticker_interval='3m')
assert json_dump_mock.call_count == 2
def test_download_backtesting_data_exception(ticker_history, mocker, caplog, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_history',
side_effect=BaseException('File Error'))
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',
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_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 arrow.get(res.open_time) == _get_frame_time_from_offset(trade.open_tick)
assert arrow.get(res.close_time) == _get_frame_time_from_offset(trade.close_tick)
def test_adjust(mocker, edge_conf):
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

File diff suppressed because it is too large Load Diff

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

View File

@@ -0,0 +1,47 @@
from typing import NamedTuple, List
import arrow
from pandas import DataFrame
from freqtrade.misc import timeframe_to_minutes
from freqtrade.strategy.interface import SellType
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
trailing_stop: bool = False
def _get_frame_time_from_offset(offset):
return ticker_start_time.shift(minutes=(offset * timeframe_to_minutes(tests_ticker_interval))
).datetime
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,213 @@
# 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 1 Minus 8% Close
# Test with Stop-loss at 1%
# TC1: Stop-Loss Triggered 1% 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, 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 2 Minus 4% Low, minus 1% close
# Test with Stop-Loss at 3%
# TC2: Stop-Loss Triggered 3% 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, 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%
# Test with Stop-Loss at 2%
# TC3: Trade-A: Stop-Loss Triggered 2% Loss
# Trade-B: 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, 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
tc4 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[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 5 / Drops 0.5% Closes +20%
# Set stop-loss at 1% ROI 3%
# TC5: ROI triggers 3% Gain
tc5 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4980, 4987, 6172, 1, 0],
[1, 5000, 5025, 4980, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[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
# Set stop-loss at 2% ROI at 5%
# TC6: Stop-Loss triggers 2% Loss
tc6 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0], # enter trade (signal on last candle)
[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
# Set stop-loss at 2% ROI at 3%
# TC7: ROI Triggers 3% Gain
tc7 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5025, 4975, 4987, 6172, 1, 0],
[1, 5000, 5025, 4975, 4987, 6172, 0, 0],
[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)]
)
# Test 8 - trailing_stop should raise so candle 3 causes a stoploss.
# Set stop-loss at 10%, ROI at 10% (should not apply)
# TC8: Trailing stoploss - stoploss should be adjusted candle 2
tc8 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5050, 4950, 5000, 6172, 0, 0],
[2, 5000, 5250, 4750, 4850, 6172, 0, 0],
[3, 4850, 5050, 4650, 4750, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi=0.10, profit_perc=-0.055, trailing_stop=True,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
)
# Test 9 - trailing_stop should raise - high and low in same candle.
# Candle Data for test 9
# Set stop-loss at 10%, ROI at 10% (should not apply)
# TC9: Trailing stoploss - stoploss should be adjusted candle 2
tc9 = BTContainer(data=[
# D O H L C V B S
[0, 5000, 5050, 4950, 5000, 6172, 1, 0],
[1, 5000, 5050, 4950, 5000, 6172, 0, 0],
[2, 5000, 5050, 4950, 5000, 6172, 0, 0],
[3, 5000, 5200, 4550, 4850, 6172, 0, 0],
[4, 4750, 4950, 4350, 4750, 6172, 0, 0]],
stop_loss=-0.10, roi=0.10, profit_perc=-0.064, trailing_stop=True,
trades=[BTrade(sell_reason=SellType.TRAILING_STOP_LOSS, open_tick=1, close_tick=3)]
)
TESTS = [
tc1,
tc2,
tc3,
tc4,
tc5,
tc6,
tc7,
tc8,
tc9,
]
@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
default_conf["trailing_stop"] = data.trailing_stop
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,19 @@ 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.btanalysis import evaluate_result_multi
from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.data.dataprovider import DataProvider
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 +39,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 +54,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 +65,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 +77,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 +106,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 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 +203,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 +235,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
@@ -307,28 +317,51 @@ def test_start(mocker, fee, default_conf, caplog) -> None:
assert start_mock.call_count == 1
def test_backtesting_init(mocker, default_conf) -> None:
ORDER_TYPES = [
{
'buy': 'limit',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': False
},
{
'buy': 'limit',
'sell': 'limit',
'stoploss': 'limit',
'stoploss_on_exchange': True
}]
@pytest.mark.parametrize("order_types", ORDER_TYPES)
def test_backtesting_init(mocker, default_conf, order_types) -> None:
"""
Check that stoploss_on_exchange is set to False while backtesting
since backtesting assumes a perfect stoploss anyway.
"""
default_conf["order_types"] = order_types
patch_exchange(mocker)
get_fee = mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.5))
backtesting = Backtesting(default_conf)
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)
assert isinstance(backtesting.strategy.dp, DataProvider)
get_fee.assert_called()
assert backtesting.fee == 0.5
assert not backtesting.strategy.order_types["stoploss_on_exchange"]
def test_tickerdata_to_dataframe(default_conf, mocker) -> None:
def test_tickerdata_to_dataframe_bt(default_conf, mocker) -> None:
patch_exchange(mocker)
timerange = TimeRange(None, 'line', 0, -100)
tick = 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 +369,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 +387,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 +429,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 +457,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.refresh_tickers', MagicMock())
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 +505,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.refresh_tickers', MagicMock())
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 +537,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 +557,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 +579,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 +591,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 +616,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 +627,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 +667,86 @@ 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
@pytest.mark.parametrize("pair", ['ADA/BTC', 'LTC/BTC'])
@pytest.mark.parametrize("tres", [0, 20, 30])
def test_backtest_multi_pair(default_conf, fee, mocker, tres, pair):
def _trend_alternate_hold(dataframe=None, metadata=None):
"""
Buy every xth candle - sell every other xth -2 (hold on to pairs a bit)
"""
if metadata['pair'] in('ETH/BTC', 'LTC/BTC'):
multi = 20
else:
multi = 18
dataframe['buy'] = np.where(dataframe.index % multi == 0, 1, 0)
dataframe['sell'] = np.where((dataframe.index + multi - 2) % multi == 0, 1, 0)
return dataframe
mocker.patch('freqtrade.exchange.Exchange.get_fee', fee)
patch_exchange(mocker)
pairs = ['ADA/BTC', 'DASH/BTC', 'ETH/BTC', 'LTC/BTC', 'NXT/BTC']
data = history.load_data(datadir=None, ticker_interval='5m', pairs=pairs)
# Only use 500 lines to increase performance
data = trim_dictlist(data, -500)
# Remove data for one pair from the beginning of the data
data[pair] = data[pair][tres:]
# 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):

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(
@@ -175,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:
@@ -194,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
@@ -241,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
@@ -253,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,
@@ -291,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())
@@ -304,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,66 @@
# 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.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.misc import timeframe_to_minutes
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,
timeframe_to_minutes('1m'))
assert len(caplog.record_tuples) == 1
assert log_has(
"UNITTEST/BTC has missing frames: expected 14396, got 13680, that's 716 missing values",
caplog.record_tuples)
def test_load_data_30min_ticker(ticker_history, mocker, caplog, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_history', return_value=ticker_history)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-30m.json')
_backup_file(file, copy_file=True)
optimize.load_data(None, pairs=['UNITTEST/BTC'], ticker_interval='30m')
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_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_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_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_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_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_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_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,
timeframe_to_minutes('5m'))
assert len(caplog.record_tuples) == 0

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