From 960d088debec36f67834ffecc1c1a35bfa7d166c Mon Sep 17 00:00:00 2001 From: Jean-Baptiste LE STANG Date: Sat, 20 Jan 2018 15:03:12 +0100 Subject: [PATCH 01/32] Fixing the 'BV' key being missing for USDT --- freqtrade/analyze.py | 3 ++- freqtrade/tests/conftest.py | 3 --- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index cee5d175a..fc2395feb 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -30,8 +30,9 @@ def parse_ticker_dataframe(ticker: list) -> DataFrame: """ columns = {'C': 'close', 'V': 'volume', 'O': 'open', 'H': 'high', 'L': 'low', 'T': 'date'} frame = DataFrame(ticker) \ - .drop('BV', 1) \ .rename(columns=columns) + if 'BV' in frame: + frame.drop('BV', 1, inplace=True) frame['date'] = to_datetime(frame['date'], utc=True, infer_datetime_format=True) frame.sort_values('date', inplace=True) return frame diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 37dc3e894..6bfb529c9 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -196,7 +196,6 @@ def ticker_history(): "C": 8.88e-05, "V": 991.09056638, "T": "2017-11-26T08:50:00", - "BV": 0.0877869 }, { "O": 8.88e-05, @@ -205,7 +204,6 @@ def ticker_history(): "C": 8.893e-05, "V": 658.77935965, "T": "2017-11-26T08:55:00", - "BV": 0.05874751 }, { "O": 8.891e-05, @@ -214,6 +212,5 @@ def ticker_history(): "C": 8.877e-05, "V": 7920.73570705, "T": "2017-11-26T09:00:00", - "BV": 0.7039405 } ] From c0d3ac5534ec40704017e28769059669f98c46ed Mon Sep 17 00:00:00 2001 From: Jean-Baptiste LE STANG Date: Sun, 21 Jan 2018 14:25:15 +0100 Subject: [PATCH 02/32] With a better unit test thanks @glonlas --- freqtrade/tests/conftest.py | 33 +++++++++++++++++++++++++++++++++ freqtrade/tests/test_analyze.py | 13 +++++++++++++ 2 files changed, 46 insertions(+) diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 6bfb529c9..7709eae30 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -196,6 +196,7 @@ def ticker_history(): "C": 8.88e-05, "V": 991.09056638, "T": "2017-11-26T08:50:00", + "BV": 0.0877869 }, { "O": 8.88e-05, @@ -204,6 +205,7 @@ def ticker_history(): "C": 8.893e-05, "V": 658.77935965, "T": "2017-11-26T08:55:00", + "BV": 0.05874751 }, { "O": 8.891e-05, @@ -212,5 +214,36 @@ def ticker_history(): "C": 8.877e-05, "V": 7920.73570705, "T": "2017-11-26T09:00:00", + "BV": 0.7039405 + } + ] + + +@pytest.fixture +def ticker_history_without_bv(): + return [ + { + "O": 8.794e-05, + "H": 8.948e-05, + "L": 8.794e-05, + "C": 8.88e-05, + "V": 991.09056638, + "T": "2017-11-26T08:50:00" + }, + { + "O": 8.88e-05, + "H": 8.942e-05, + "L": 8.88e-05, + "C": 8.893e-05, + "V": 658.77935965, + "T": "2017-11-26T08:55:00" + }, + { + "O": 8.891e-05, + "H": 8.893e-05, + "L": 8.875e-05, + "C": 8.877e-05, + "V": 7920.73570705, + "T": "2017-11-26T09:00:00" } ] diff --git a/freqtrade/tests/test_analyze.py b/freqtrade/tests/test_analyze.py index 8da38fcd7..4c8378b3e 100644 --- a/freqtrade/tests/test_analyze.py +++ b/freqtrade/tests/test_analyze.py @@ -72,3 +72,16 @@ def test_get_signal_handles_exceptions(mocker): side_effect=Exception('invalid ticker history ')) assert get_signal('BTC-ETH', 5) == (False, False) + + +def test_parse_ticker_dataframe(ticker_history, ticker_history_without_bv): + + columns = ['close', 'high', 'low', 'open', 'date', 'volume'] + + # Test file with BV data + dataframe = parse_ticker_dataframe(ticker_history) + assert dataframe.columns.tolist() == columns + + # Test file without BV data + dataframe = parse_ticker_dataframe(ticker_history_without_bv) + assert dataframe.columns.tolist() == columns From 28b1ecb109bccd426f9eb739aaa558a0cf104c8d Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Sun, 21 Jan 2018 15:32:49 -0800 Subject: [PATCH 03/32] Convert CryptoToFiatConverter into a Singleton Result in a speed up of the unittest from 60s to 4s Because it cost time to load Pymarketcap() every time we create a CryptoToFiatConverter, it worth it to change it into a Singleton. --- freqtrade/fiat_convert.py | 19 +++++++++++++------ freqtrade/tests/rpc/test_rpc_telegram.py | 12 +++--------- freqtrade/tests/test_fiat_convert.py | 6 +++--- freqtrade/tests/test_main.py | 8 ++------ 4 files changed, 21 insertions(+), 24 deletions(-) diff --git a/freqtrade/fiat_convert.py b/freqtrade/fiat_convert.py index 0132e531d..91e04fff9 100644 --- a/freqtrade/fiat_convert.py +++ b/freqtrade/fiat_convert.py @@ -48,7 +48,10 @@ class CryptoFiat(): return self._expiration - time.time() <= 0 -class CryptoToFiatConverter(): +class CryptoToFiatConverter(object): + __instance = None + _coinmarketcap = None + # Constants SUPPORTED_FIAT = [ "AUD", "BRL", "CAD", "CHF", "CLP", "CNY", "CZK", "DKK", @@ -57,12 +60,16 @@ class CryptoToFiatConverter(): "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD" ] - def __init__(self) -> None: - try: - self._coinmarketcap = Pymarketcap() - except BaseException: - self._coinmarketcap = None + def __new__(cls): + if CryptoToFiatConverter.__instance is None: + CryptoToFiatConverter.__instance = object.__new__(cls) + try: + CryptoToFiatConverter._coinmarketcap = Pymarketcap() + except BaseException: + CryptoToFiatConverter._coinmarketcap = None + return CryptoToFiatConverter.__instance + def __init__(self) -> None: self._pairs = [] def convert_amount(self, crypto_amount: float, crypto_symbol: str, fiat_symbol: str) -> float: diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 9c99be342..df2624815 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -219,9 +219,7 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker): mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker) - mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', - ticker=MagicMock(return_value={'price_usd': 15000.0}), - _cache_symbols=MagicMock(return_value={'BTC': 1})) + mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) init(default_conf, create_engine('sqlite://')) # Create some test data @@ -256,9 +254,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, m mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker) - mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', - ticker=MagicMock(return_value={'price_usd': 15000.0}), - _cache_symbols=MagicMock(return_value={'BTC': 1})) + mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) init(default_conf, create_engine('sqlite://')) # Create some test data @@ -317,9 +313,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, mocker): mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker) - mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', - ticker=MagicMock(return_value={'price_usd': 15000.0}), - _cache_symbols=MagicMock(return_value={'BTC': 1})) + mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) init(default_conf, create_engine('sqlite://')) # Create some test data diff --git a/freqtrade/tests/test_fiat_convert.py b/freqtrade/tests/test_fiat_convert.py index ddc1c8e29..2d112f921 100644 --- a/freqtrade/tests/test_fiat_convert.py +++ b/freqtrade/tests/test_fiat_convert.py @@ -116,9 +116,9 @@ def test_fiat_convert_get_price(mocker): assert fiat_convert._pairs[0]._expiration is not expiration -def test_fiat_convert_without_network(mocker): - pymarketcap = MagicMock(side_effect=ImportError('Oh boy, you have no network!')) - mocker.patch('freqtrade.fiat_convert.Pymarketcap', pymarketcap) +def test_fiat_convert_without_network(): + # Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap + CryptoToFiatConverter._coinmarketcap = None fiat_convert = CryptoToFiatConverter() assert fiat_convert._coinmarketcap is None diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index a61342480..7ff569e8f 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -525,9 +525,7 @@ def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker): mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker) - mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', - ticker=MagicMock(return_value={'price_usd': 15000.0}), - _cache_symbols=MagicMock(return_value={'BTC': 1})) + mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) init(default_conf, create_engine('sqlite://')) # Create some test data @@ -562,9 +560,7 @@ def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker): mocker.patch.multiple('freqtrade.main.exchange', validate_pairs=MagicMock(), get_ticker=ticker) - mocker.patch.multiple('freqtrade.fiat_convert.Pymarketcap', - ticker=MagicMock(return_value={'price_usd': 15000.0}), - _cache_symbols=MagicMock(return_value={'BTC': 1})) + mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0) init(default_conf, create_engine('sqlite://')) # Create some test data From bd356f3eb45045ddef0c153c2694fc7bd7a44414 Mon Sep 17 00:00:00 2001 From: Samuel Husso Date: Sat, 20 Jan 2018 17:22:25 +0200 Subject: [PATCH 04/32] when selling, show more information about the trade in the message --- freqtrade/main.py | 19 ++++++++++++++++--- freqtrade/rpc/telegram.py | 6 +++--- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/freqtrade/main.py b/freqtrade/main.py index 27f3dfd9a..9e621bcbb 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -191,12 +191,25 @@ def execute_sell(trade: Trade, limit: float) -> None: fmt_exp_profit = round(trade.calc_profit_percent(rate=limit) * 100, 2) profit_trade = trade.calc_profit(rate=limit) - - message = '*{exchange}:* Selling [{pair}]({pair_url}) with limit `{limit:.8f}`'.format( + current_rate = exchange.get_ticker(trade.pair, False)['bid'] + current_profit = trade.calc_profit_percent(current_rate) + + message = """*{exchange}:* Selling +*Current Pair:* [{pair}]({pair_url}) +*Limit:* `{limit}` +*Amount:* `{amount}` +*Open Rate:* `{open_rate:.8f}` +*Current Rate:* `{current_rate:.8f}` +*Current Profit:* `{current_profit:.2f}%` + """.format( exchange=trade.exchange, pair=trade.pair.replace('_', '/'), pair_url=exchange.get_pair_detail_url(trade.pair), - limit=limit + limit=limit, + open_rate=trade.open_rate, + current_rate=current_rate, + amount=round(trade.amount, 8), + current_profit=round(current_profit * 100, 2), ) # For regular case, when the configuration exists diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 0fdc734f4..596dd70dc 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -147,7 +147,7 @@ def _status(bot: Bot, update: Update) -> None: ) if trade.close_profit else None message = """ *Trade ID:* `{trade_id}` -*Current Pair:* [{pair}]({market_url}) +*Current Pair:* [{pair}]({pair_url}) *Open Since:* `{date}` *Amount:* `{amount}` *Open Rate:* `{open_rate:.8f}` @@ -158,8 +158,8 @@ def _status(bot: Bot, update: Update) -> None: *Open Order:* `{open_order}` """.format( trade_id=trade.id, - pair=trade.pair, - market_url=exchange.get_pair_detail_url(trade.pair), + pair=trade.pair.replace('_', '/'), + pair_url=exchange.get_pair_detail_url(trade.pair), date=arrow.get(trade.open_date).humanize(), open_rate=trade.open_rate, close_rate=trade.close_rate, From ddd62277c28eebe7b9b60503d16093f754e20534 Mon Sep 17 00:00:00 2001 From: Samuel Husso Date: Sat, 20 Jan 2018 17:50:23 +0200 Subject: [PATCH 05/32] add total amount of trades to /status --- freqtrade/rpc/telegram.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 596dd70dc..409427615 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -156,6 +156,7 @@ def _status(bot: Bot, update: Update) -> None: *Close Profit:* `{close_profit}` *Current Profit:* `{current_profit:.2f}%` *Open Order:* `{open_order}` +*Total Open Trades:* `{total_trades}` """.format( trade_id=trade.id, pair=trade.pair.replace('_', '/'), @@ -170,6 +171,7 @@ def _status(bot: Bot, update: Update) -> None: open_order='({} rem={:.8f})'.format( order['type'], order['remaining'] ) if order else None, + total_trades=len(trades) ) send_msg(message, bot=bot) From 6abbf45042eb49276b43bd46642a4a5620895b20 Mon Sep 17 00:00:00 2001 From: Samuel Husso Date: Sun, 21 Jan 2018 19:33:19 +0200 Subject: [PATCH 06/32] Update tests to reflect new selling msg --- freqtrade/main.py | 2 +- freqtrade/tests/rpc/test_rpc_telegram.py | 10 +++++++--- freqtrade/tests/test_main.py | 13 ++++++++++--- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/freqtrade/main.py b/freqtrade/main.py index 9e621bcbb..1d7f87585 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -193,7 +193,7 @@ def execute_sell(trade: Trade, limit: float) -> None: profit_trade = trade.calc_profit(rate=limit) current_rate = exchange.get_ticker(trade.pair, False)['bid'] current_profit = trade.calc_profit_percent(current_rate) - + message = """*{exchange}:* Selling *Current Pair:* [{pair}]({pair_url}) *Limit:* `{limit}` diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index 9c99be342..da616ae65 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -107,7 +107,7 @@ def test_status_handle(default_conf, update, ticker, mocker): _status(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 - assert '[BTC_ETH]' in msg_mock.call_args_list[0][0][0] + assert '[BTC/ETH]' in msg_mock.call_args_list[0][0][0] def test_status_table_handle(default_conf, update, ticker, mocker): @@ -239,7 +239,9 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker): _forcesell(bot=MagicMock(), update=update) assert rpc_mock.call_count == 2 - assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert 'Selling' in rpc_mock.call_args_list[-1][0][0] + assert '[BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert 'profit: 6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0] assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0] @@ -276,7 +278,9 @@ def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, m _forcesell(bot=MagicMock(), update=update) assert rpc_mock.call_count == 2 - assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert 'Selling' in rpc_mock.call_args_list[-1][0][0] + assert '[BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] assert '-0.824 USD' in rpc_mock.call_args_list[-1][0][0] diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index a61342480..b8801df85 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -544,7 +544,10 @@ def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker): execute_sell(trade=trade, limit=ticker_sell_up()['bid']) assert rpc_mock.call_count == 2 - assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert 'Selling' in rpc_mock.call_args_list[-1][0][0] + assert '[BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert 'Amount' in rpc_mock.call_args_list[-1][0][0] + assert 'Current Profit' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert 'profit: 6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0] assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0] @@ -581,7 +584,9 @@ def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker): execute_sell(trade=trade, limit=ticker_sell_down()['bid']) assert rpc_mock.call_count == 2 - assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert 'Selling' in rpc_mock.call_args_list[-1][0][0] + assert '[BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] assert '-0.824 USD' in rpc_mock.call_args_list[-1][0][0] @@ -644,7 +649,9 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, ticker_sell_up, execute_sell(trade=trade, limit=ticker_sell_up()['bid']) assert rpc_mock.call_count == 2 - assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert 'Selling' in rpc_mock.call_args_list[-1][0][0] + assert '[BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert '(profit: 6.11%, 0.00006126)' in rpc_mock.call_args_list[-1][0][0] assert 'USD' not in rpc_mock.call_args_list[-1][0][0] From bce6a7be61dfc35c187a57df935db40cbf6f80e8 Mon Sep 17 00:00:00 2001 From: Samuel Husso Date: Mon, 22 Jan 2018 09:39:11 +0200 Subject: [PATCH 07/32] rebase develop and update tests --- freqtrade/tests/test_main.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index b8801df85..4c20f824d 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -616,10 +616,9 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, ticker_sell_d execute_sell(trade=trade, limit=ticker_sell_down()['bid']) - print(rpc_mock.call_args_list[-1][0][0]) - assert rpc_mock.call_count == 2 - assert 'Selling [BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert 'Selling' in rpc_mock.call_args_list[-1][0][0] + assert '[BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] From 757a46ab12761e7519cb172b495cc3aa2c62e754 Mon Sep 17 00:00:00 2001 From: Samuel Husso Date: Mon, 22 Jan 2018 10:39:26 +0200 Subject: [PATCH 08/32] ticker_interval as int (instead of string) --- config.json.example | 2 +- docs/configuration.md | 2 +- freqtrade/main.py | 2 +- freqtrade/misc.py | 2 +- freqtrade/tests/conftest.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/config.json.example b/config.json.example index 0f4271001..37980447d 100644 --- a/config.json.example +++ b/config.json.example @@ -4,7 +4,7 @@ "stake_amount": 0.05, "fiat_display_currency": "USD", "dry_run": false, - "ticker_interval": "5", + "ticker_interval": 5, "minimal_roi": { "40": 0.0, "30": 0.01, diff --git a/docs/configuration.md b/docs/configuration.md index 7a41644ee..5d1204efd 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -17,7 +17,7 @@ The table below will list all configuration parameters. | `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. -| `ticker_interval` | ["1", "5", "30, "60", "1440"] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Defaut is 5 minutes +| `ticker_interval` | [1, 5, 30, 60, 1440] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Defaut is 5 minutes | `fiat_display_currency` | USD | Yes | Fiat currency used to show your profits. More information below. | `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode. | `minimal_roi` | See below | Yes | Set the threshold in percent the bot will use to sell a trade. More information below. diff --git a/freqtrade/main.py b/freqtrade/main.py index 27f3dfd9a..095f7671a 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -476,7 +476,7 @@ def main(sysargv=sys.argv[1:]) -> None: _process, min_secs=_CONF['internals'].get('process_throttle_secs', 10), nb_assets=args.dynamic_whitelist, - interval=int(_CONF.get('ticker_interval', "5")) + interval=int(_CONF.get('ticker_interval', 5)) ) old_state = new_state except KeyboardInterrupt: diff --git a/freqtrade/misc.py b/freqtrade/misc.py index a91062e4e..cab861044 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -284,7 +284,7 @@ CONF_SCHEMA = { 'type': 'object', 'properties': { 'max_open_trades': {'type': 'integer', 'minimum': 1}, - 'ticker_interval': {'type': 'string', 'enum': ['1', '5', '30', '60', '1440']}, + 'ticker_interval': {'type': 'integer', 'enum': [1, 5, 30, 60, 1440]}, 'stake_currency': {'type': 'string', 'enum': ['BTC', 'ETH', 'USDT']}, 'stake_amount': {'type': 'number', 'minimum': 0.0005}, 'fiat_display_currency': {'type': 'string', 'enum': ['AUD', 'BRL', 'CAD', 'CHF', diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index 7709eae30..70249fd7d 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -18,7 +18,7 @@ def default_conf(): "stake_currency": "BTC", "stake_amount": 0.001, "fiat_display_currency": "USD", - "ticker_interval": "5", + "ticker_interval": 5, "dry_run": True, "minimal_roi": { "40": 0.0, From c46d78b4b985ae5f7e3418be80661b991fc9cc13 Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Mon, 15 Jan 2018 00:35:11 -0800 Subject: [PATCH 09/32] Decouple strategy from analyse.py --- .gitignore | 4 +- freqtrade/analyze.py | 214 +------------- freqtrade/main.py | 14 +- freqtrade/misc.py | 9 +- freqtrade/optimize/backtesting.py | 6 + freqtrade/optimize/hyperopt.py | 132 ++------- freqtrade/strategy/__init__.py | 0 freqtrade/strategy/default_strategy.py | 262 ++++++++++++++++++ freqtrade/strategy/interface.py | 56 ++++ freqtrade/strategy/strategy.py | 165 +++++++++++ .../tests/strategy/test_default_strategy.py | 36 +++ freqtrade/tests/strategy/test_strategy.py | 132 +++++++++ freqtrade/tests/test_analyze.py | 7 + user_data/data/.gitkeep | 0 user_data/strategies/__init__.py | 0 user_data/strategies/test_strategy.py | 129 +++++++++ 16 files changed, 839 insertions(+), 327 deletions(-) create mode 100644 freqtrade/strategy/__init__.py create mode 100644 freqtrade/strategy/default_strategy.py create mode 100644 freqtrade/strategy/interface.py create mode 100644 freqtrade/strategy/strategy.py create mode 100644 freqtrade/tests/strategy/test_default_strategy.py create mode 100644 freqtrade/tests/strategy/test_strategy.py create mode 100644 user_data/data/.gitkeep create mode 100644 user_data/strategies/__init__.py create mode 100644 user_data/strategies/test_strategy.py diff --git a/.gitignore b/.gitignore index c81b55222..7c7102874 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,8 @@ config.json *.sqlite .hyperopt logfile.txt +hyperopt_trials.pickle +user_data/ # Byte-compiled / optimized / DLL files __pycache__/ @@ -85,5 +87,3 @@ target/ .venv .idea .vscode - -hyperopt_trials.pickle diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index fc2395feb..0481c7f3c 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -7,11 +7,10 @@ from enum import Enum from typing import Dict, List import arrow -import talib.abstract as ta from pandas import DataFrame, to_datetime -import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.exchange import get_ticker_history +from freqtrade.strategy.strategy import Strategy logger = logging.getLogger(__name__) @@ -46,182 +45,8 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame: you are using. Let uncomment only the indicator you are using in your strategies or your hyperopt configuration, otherwise you will waste your memory and CPU usage. """ - - # Momentum Indicator - # ------------------------------------ - - # ADX - dataframe['adx'] = ta.ADX(dataframe) - - # Awesome oscillator - dataframe['ao'] = qtpylib.awesome_oscillator(dataframe) - """ - # Commodity Channel Index: values Oversold:<-100, Overbought:>100 - dataframe['cci'] = ta.CCI(dataframe) - """ - # MACD - macd = ta.MACD(dataframe) - dataframe['macd'] = macd['macd'] - dataframe['macdsignal'] = macd['macdsignal'] - dataframe['macdhist'] = macd['macdhist'] - - # MFI - dataframe['mfi'] = ta.MFI(dataframe) - - # Minus Directional Indicator / Movement - dataframe['minus_dm'] = ta.MINUS_DM(dataframe) - dataframe['minus_di'] = ta.MINUS_DI(dataframe) - - # Plus Directional Indicator / Movement - dataframe['plus_dm'] = ta.PLUS_DM(dataframe) - dataframe['plus_di'] = ta.PLUS_DI(dataframe) - dataframe['minus_di'] = ta.MINUS_DI(dataframe) - - """ - # ROC - dataframe['roc'] = ta.ROC(dataframe) - """ - # RSI - dataframe['rsi'] = ta.RSI(dataframe) - """ - # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy) - rsi = 0.1 * (dataframe['rsi'] - 50) - dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1) - - # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy) - dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1) - - # Stoch - stoch = ta.STOCH(dataframe) - dataframe['slowd'] = stoch['slowd'] - dataframe['slowk'] = stoch['slowk'] - """ - # Stoch fast - stoch_fast = ta.STOCHF(dataframe) - dataframe['fastd'] = stoch_fast['fastd'] - dataframe['fastk'] = stoch_fast['fastk'] - """ - # Stoch RSI - stoch_rsi = ta.STOCHRSI(dataframe) - dataframe['fastd_rsi'] = stoch_rsi['fastd'] - dataframe['fastk_rsi'] = stoch_rsi['fastk'] - """ - - # Overlap Studies - # ------------------------------------ - - # Previous Bollinger bands - # Because ta.BBANDS implementation is broken with small numbers, it actually - # returns middle band for all the three bands. Switch to qtpylib.bollinger_bands - # and use middle band instead. - dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband'] - """ - # Bollinger bands - """ - bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) - dataframe['bb_lowerband'] = bollinger['lower'] - dataframe['bb_middleband'] = bollinger['mid'] - dataframe['bb_upperband'] = bollinger['upper'] - - # EMA - Exponential Moving Average - dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) - dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) - dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) - dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) - dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) - - # SAR Parabol - dataframe['sar'] = ta.SAR(dataframe) - - # SMA - Simple Moving Average - dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) - - # TEMA - Triple Exponential Moving Average - dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) - - # Cycle Indicator - # ------------------------------------ - # Hilbert Transform Indicator - SineWave - hilbert = ta.HT_SINE(dataframe) - dataframe['htsine'] = hilbert['sine'] - dataframe['htleadsine'] = hilbert['leadsine'] - - # Pattern Recognition - Bullish candlestick patterns - # ------------------------------------ - """ - # Hammer: values [0, 100] - dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe) - - # Inverted Hammer: values [0, 100] - dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe) - - # Dragonfly Doji: values [0, 100] - dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe) - - # Piercing Line: values [0, 100] - dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100] - - # Morningstar: values [0, 100] - dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100] - - # Three White Soldiers: values [0, 100] - dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100] - """ - - # Pattern Recognition - Bearish candlestick patterns - # ------------------------------------ - """ - # Hanging Man: values [0, 100] - dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe) - - # Shooting Star: values [0, 100] - dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe) - - # Gravestone Doji: values [0, 100] - dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe) - - # Dark Cloud Cover: values [0, 100] - dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe) - - # Evening Doji Star: values [0, 100] - dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe) - - # Evening Star: values [0, 100] - dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe) - """ - - # Pattern Recognition - Bullish/Bearish candlestick patterns - # ------------------------------------ - """ - # Three Line Strike: values [0, -100, 100] - dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe) - - # Spinning Top: values [0, -100, 100] - dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100] - - # Engulfing: values [0, -100, 100] - dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100] - - # Harami: values [0, -100, 100] - dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100] - - # Three Outside Up/Down: values [0, -100, 100] - dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100] - - # Three Inside Up/Down: values [0, -100, 100] - dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100] - """ - - # Chart type - # ------------------------------------ - # Heikinashi stategy - heikinashi = qtpylib.heikinashi(dataframe) - dataframe['ha_open'] = heikinashi['open'] - dataframe['ha_close'] = heikinashi['close'] - dataframe['ha_high'] = heikinashi['high'] - dataframe['ha_low'] = heikinashi['low'] - - return dataframe + strategy = Strategy() + return strategy.populate_indicators(dataframe=dataframe) def populate_buy_trend(dataframe: DataFrame) -> DataFrame: @@ -230,20 +55,8 @@ def populate_buy_trend(dataframe: DataFrame) -> DataFrame: :param dataframe: DataFrame :return: DataFrame with buy column """ - dataframe.loc[ - ( - (dataframe['rsi'] < 35) & - (dataframe['fastd'] < 35) & - (dataframe['adx'] > 30) & - (dataframe['plus_di'] > 0.5) - ) | - ( - (dataframe['adx'] > 65) & - (dataframe['plus_di'] > 0.5) - ), - 'buy'] = 1 - - return dataframe + strategy = Strategy() + return strategy.populate_buy_trend(dataframe=dataframe) def populate_sell_trend(dataframe: DataFrame) -> DataFrame: @@ -252,21 +65,8 @@ def populate_sell_trend(dataframe: DataFrame) -> DataFrame: :param dataframe: DataFrame :return: DataFrame with buy column """ - dataframe.loc[ - ( - ( - (qtpylib.crossed_above(dataframe['rsi'], 70)) | - (qtpylib.crossed_above(dataframe['fastd'], 70)) - ) & - (dataframe['adx'] > 10) & - (dataframe['minus_di'] > 0) - ) | - ( - (dataframe['adx'] > 70) & - (dataframe['minus_di'] > 0.5) - ), - 'sell'] = 1 - return dataframe + strategy = Strategy() + return strategy.populate_sell_trend(dataframe=dataframe) def analyze_ticker(ticker_history: List[Dict]) -> DataFrame: diff --git a/freqtrade/main.py b/freqtrade/main.py index 095f7671a..9cce277e1 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -19,6 +19,7 @@ from freqtrade.fiat_convert import CryptoToFiatConverter from freqtrade.misc import (State, get_state, load_config, parse_args, throttle, update_state) from freqtrade.persistence import Trade +from freqtrade.strategy.strategy import Strategy logger = logging.getLogger('freqtrade') @@ -235,14 +236,16 @@ def min_roi_reached(trade: Trade, current_rate: float, current_time: datetime) - Based an earlier trade and current price and ROI configuration, decides whether bot should sell :return True if bot should sell at current rate """ + strategy = Strategy() + current_profit = trade.calc_profit_percent(current_rate) - if 'stoploss' in _CONF and current_profit < float(_CONF['stoploss']): + if strategy.stoploss is not None and current_profit < float(strategy.stoploss): logger.debug('Stop loss hit.') return True # Check if time matches and current rate is above threshold time_diff = (current_time - trade.open_date).total_seconds() / 60 - for duration, threshold in sorted(_CONF['minimal_roi'].items()): + for duration, threshold in sorted(strategy.minimal_roi.items()): if time_diff > float(duration) and current_profit > threshold: return True @@ -378,6 +381,9 @@ def init(config: dict, db_url: Optional[str] = None) -> None: persistence.init(config, db_url) exchange.init(config) + strategy = Strategy() + strategy.init(config) + # Set initial application state initial_state = config.get('initial_state') if initial_state: @@ -445,6 +451,9 @@ def main(sysargv=sys.argv[1:]) -> None: # Load and validate configuration _CONF = load_config(args.config) + # Add the strategy file to use + _CONF.update({'strategy': args.strategy}) + # Initialize all modules and start main loop if args.dynamic_whitelist: logger.info('Using dynamically generated whitelist. (--dynamic-whitelist detected)') @@ -462,6 +471,7 @@ def main(sysargv=sys.argv[1:]) -> None: try: init(_CONF) old_state = None + while True: new_state = get_state() # Log state transition diff --git a/freqtrade/misc.py b/freqtrade/misc.py index cab861044..a0669ae19 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -124,6 +124,14 @@ def common_args_parser(description: str): type=str, metavar='PATH', ) + parser.add_argument( + '-s', '--strategy', + help='specify strategy file (default: freqtrade/strategy/default_strategy.py)', + dest='strategy', + default='.default_strategy', + type=str, + metavar='PATH', + ) return parser @@ -380,7 +388,6 @@ CONF_SCHEMA = { 'stake_amount', 'fiat_display_currency', 'dry_run', - 'minimal_roi', 'bid_strategy', 'telegram' ] diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 25e1e6231..93ff295c5 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -14,6 +14,7 @@ from freqtrade.analyze import populate_buy_trend, populate_sell_trend from freqtrade.exchange import Bittrex from freqtrade.main import min_roi_reached from freqtrade.persistence import Trade +from freqtrade.strategy.strategy import Strategy logger = logging.getLogger(__name__) @@ -199,6 +200,11 @@ def start(args): logger.info('Using max_open_trades: %s ...', config['max_open_trades']) max_open_trades = config['max_open_trades'] + # init the strategy to use + config.update({'strategy': args.strategy}) + strategy = Strategy() + strategy.init(config) + # Monkey patch config from freqtrade import main main._CONF = config diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index b9780c13a..041f9a7a4 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -7,11 +7,10 @@ import sys import pickle import signal import os -from functools import reduce from math import exp from operator import itemgetter -from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, hp, space_eval, tpe +from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, space_eval, tpe from hyperopt.mongoexp import MongoTrials from pandas import DataFrame @@ -21,7 +20,7 @@ from freqtrade.exchange import Bittrex from freqtrade.misc import load_config from freqtrade.optimize.backtesting import backtest from freqtrade.optimize.hyperopt_conf import hyperopt_optimize_conf -from freqtrade.vendor.qtpylib.indicators import crossed_above +from freqtrade.strategy.strategy import Strategy # Remove noisy log messages logging.getLogger('hyperopt.mongoexp').setLevel(logging.WARNING) @@ -57,63 +56,6 @@ from freqtrade import main # noqa main._CONF = OPTIMIZE_CONFIG -SPACE = { - 'macd_below_zero': hp.choice('macd_below_zero', [ - {'enabled': False}, - {'enabled': True} - ]), - 'mfi': hp.choice('mfi', [ - {'enabled': False}, - {'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)} - ]), - 'fastd': hp.choice('fastd', [ - {'enabled': False}, - {'enabled': True, 'value': hp.quniform('fastd-value', 10, 50, 1)} - ]), - 'adx': hp.choice('adx', [ - {'enabled': False}, - {'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)} - ]), - 'rsi': hp.choice('rsi', [ - {'enabled': False}, - {'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)} - ]), - 'uptrend_long_ema': hp.choice('uptrend_long_ema', [ - {'enabled': False}, - {'enabled': True} - ]), - 'uptrend_short_ema': hp.choice('uptrend_short_ema', [ - {'enabled': False}, - {'enabled': True} - ]), - 'over_sar': hp.choice('over_sar', [ - {'enabled': False}, - {'enabled': True} - ]), - 'green_candle': hp.choice('green_candle', [ - {'enabled': False}, - {'enabled': True} - ]), - 'uptrend_sma': hp.choice('uptrend_sma', [ - {'enabled': False}, - {'enabled': True} - ]), - 'trigger': hp.choice('trigger', [ - {'type': 'lower_bb'}, - {'type': 'lower_bb_tema'}, - {'type': 'faststoch10'}, - {'type': 'ao_cross_zero'}, - {'type': 'ema3_cross_ema10'}, - {'type': 'macd_cross_signal'}, - {'type': 'sar_reversal'}, - {'type': 'ht_sine'}, - {'type': 'heiken_reversal_bull'}, - {'type': 'di_cross'}, - ]), - 'stoploss': hp.uniform('stoploss', -0.5, -0.02), -} - - def save_trials(trials, trials_path=TRIALS_FILE): """Save hyperopt trials to file""" logger.info('Saving Trials to \'{}\''.format(trials_path)) @@ -162,7 +104,9 @@ def optimizer(params): global _CURRENT_TRIES from freqtrade.optimize import backtesting - backtesting.populate_buy_trend = buy_strategy_generator(params) + + strategy = Strategy() + backtesting.populate_buy_trend = strategy.buy_strategy_generator(params) results = backtest({'stake_amount': OPTIMIZE_CONFIG['stake_amount'], 'processed': PROCESSED, @@ -208,59 +152,8 @@ def format_results(results: DataFrame): results.duration.mean() * 5, ) - -def buy_strategy_generator(params): - def populate_buy_trend(dataframe: DataFrame) -> DataFrame: - conditions = [] - # GUARDS AND TRENDS - if params['uptrend_long_ema']['enabled']: - conditions.append(dataframe['ema50'] > dataframe['ema100']) - if params['macd_below_zero']['enabled']: - conditions.append(dataframe['macd'] < 0) - if params['uptrend_short_ema']['enabled']: - conditions.append(dataframe['ema5'] > dataframe['ema10']) - if params['mfi']['enabled']: - conditions.append(dataframe['mfi'] < params['mfi']['value']) - if params['fastd']['enabled']: - conditions.append(dataframe['fastd'] < params['fastd']['value']) - if params['adx']['enabled']: - conditions.append(dataframe['adx'] > params['adx']['value']) - if params['rsi']['enabled']: - conditions.append(dataframe['rsi'] < params['rsi']['value']) - if params['over_sar']['enabled']: - conditions.append(dataframe['close'] > dataframe['sar']) - if params['green_candle']['enabled']: - conditions.append(dataframe['close'] > dataframe['open']) - if params['uptrend_sma']['enabled']: - prevsma = dataframe['sma'].shift(1) - conditions.append(dataframe['sma'] > prevsma) - - # TRIGGERS - triggers = { - 'lower_bb': (dataframe['close'] < dataframe['bb_lowerband']), - 'lower_bb_tema': (dataframe['tema'] < dataframe['bb_lowerband']), - 'faststoch10': (crossed_above(dataframe['fastd'], 10.0)), - 'ao_cross_zero': (crossed_above(dataframe['ao'], 0.0)), - 'ema3_cross_ema10': (crossed_above(dataframe['ema3'], dataframe['ema10'])), - 'macd_cross_signal': (crossed_above(dataframe['macd'], dataframe['macdsignal'])), - 'sar_reversal': (crossed_above(dataframe['close'], dataframe['sar'])), - 'ht_sine': (crossed_above(dataframe['htleadsine'], dataframe['htsine'])), - 'heiken_reversal_bull': (crossed_above(dataframe['ha_close'], dataframe['ha_open'])) & - (dataframe['ha_low'] == dataframe['ha_open']), - 'di_cross': (crossed_above(dataframe['plus_di'], dataframe['minus_di'])), - } - conditions.append(triggers.get(params['trigger']['type'])) - - dataframe.loc[ - reduce(lambda x, y: x & y, conditions), - 'buy'] = 1 - - return dataframe - return populate_buy_trend - - def start(args): - global TOTAL_TRIES, PROCESSED, SPACE, TRIALS, _CURRENT_TRIES + global TOTAL_TRIES, PROCESSED, TRIALS, _CURRENT_TRIES TOTAL_TRIES = args.epochs @@ -275,6 +168,12 @@ def start(args): logger.info('Using config: %s ...', args.config) config = load_config(args.config) pairs = config['exchange']['pair_whitelist'] + + # init the strategy to use + config.update({'strategy': args.strategy}) + strategy = Strategy() + strategy.init(config) + timerange = misc.parse_timerange(args.timerange) data = optimize.load_data(args.datadir, pairs=pairs, ticker_interval=args.ticker_interval, @@ -303,7 +202,7 @@ def start(args): try: best_parameters = fmin( fn=optimizer, - space=SPACE, + space=strategy.hyperopt_space(), algo=tpe.suggest, max_evals=TOTAL_TRIES, trials=TRIALS @@ -319,7 +218,10 @@ def start(args): # Improve best parameter logging display if best_parameters: - best_parameters = space_eval(SPACE, best_parameters) + best_parameters = space_eval( + strategy.hyperopt_space(), + best_parameters + ) logger.info('Best parameters:\n%s', json.dumps(best_parameters, indent=4)) logger.info('Best Result:\n%s', best_result) diff --git a/freqtrade/strategy/__init__.py b/freqtrade/strategy/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/freqtrade/strategy/default_strategy.py b/freqtrade/strategy/default_strategy.py new file mode 100644 index 000000000..670cc2abe --- /dev/null +++ b/freqtrade/strategy/default_strategy.py @@ -0,0 +1,262 @@ +import talib.abstract as ta +import freqtrade.vendor.qtpylib.indicators as qtpylib +from freqtrade.strategy.interface import IStrategy +from pandas import DataFrame +from hyperopt import hp +from functools import reduce +from typing import Dict, List + + +class_name = 'DefaultStrategy' + + +class DefaultStrategy(IStrategy): + """ + Default Strategy provided by freqtrade bot. + You can override it with your own strategy + """ + + # Minimal ROI designed for the strategy + minimal_roi = { + "40": 0.0, + "30": 0.01, + "20": 0.02, + "0": 0.04 + } + + # Optimal stoploss designed for the strategy + stoploss = -0.10 + + + def populate_indicators(self, dataframe: DataFrame) -> DataFrame: + """ + Adds several different TA indicators to the given DataFrame + + Performance Note: For the best performance be frugal on the number of indicators + you are using. Let uncomment only the indicator you are using in your strategies + or your hyperopt configuration, otherwise you will waste your memory and CPU usage. + """ + + # Momentum Indicator + # ------------------------------------ + + # ADX + dataframe['adx'] = ta.ADX(dataframe) + + # Awesome oscillator + dataframe['ao'] = qtpylib.awesome_oscillator(dataframe) + + # MACD + macd = ta.MACD(dataframe) + dataframe['macd'] = macd['macd'] + dataframe['macdsignal'] = macd['macdsignal'] + dataframe['macdhist'] = macd['macdhist'] + + # MFI + dataframe['mfi'] = ta.MFI(dataframe) + + # Minus Directional Indicator / Movement + dataframe['minus_dm'] = ta.MINUS_DM(dataframe) + dataframe['minus_di'] = ta.MINUS_DI(dataframe) + + # Plus Directional Indicator / Movement + dataframe['plus_dm'] = ta.PLUS_DM(dataframe) + dataframe['plus_di'] = ta.PLUS_DI(dataframe) + + # RSI + dataframe['rsi'] = ta.RSI(dataframe) + + # Stoch fast + stoch_fast = ta.STOCHF(dataframe) + dataframe['fastd'] = stoch_fast['fastd'] + dataframe['fastk'] = stoch_fast['fastk'] + + # Overlap Studies + # ------------------------------------ + + # Previous Bollinger bands + # Because ta.BBANDS implementation is broken with small numbers, it actually + # returns middle band for all the three bands. Switch to qtpylib.bollinger_bands + # and use middle band instead. + dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband'] + """ + # Bollinger bands + bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) + dataframe['bb_lowerband'] = bollinger['lower'] + dataframe['bb_middleband'] = bollinger['mid'] + dataframe['bb_upperband'] = bollinger['upper'] + """ + + # EMA - Exponential Moving Average + dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) + dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) + dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) + dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) + + # SAR Parabol + dataframe['sar'] = ta.SAR(dataframe) + + # SMA - Simple Moving Average + dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) + + # TEMA - Triple Exponential Moving Average + dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) + + # Cycle Indicator + # ------------------------------------ + # Hilbert Transform Indicator - SineWave + hilbert = ta.HT_SINE(dataframe) + dataframe['htsine'] = hilbert['sine'] + dataframe['htleadsine'] = hilbert['leadsine'] + + return dataframe + + def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: + """ + Based on TA indicators, populates the buy signal for the given dataframe + :param dataframe: DataFrame + :return: DataFrame with buy column + """ + dataframe.loc[ + ( + (dataframe['rsi'] < 35) & + (dataframe['fastd'] < 35) & + (dataframe['adx'] > 30) & + (dataframe['plus_di'] > 0.5) + ) | + ( + (dataframe['adx'] > 65) & + (dataframe['plus_di'] > 0.5) + ), + 'buy'] = 1 + + return dataframe + + def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: + """ + Based on TA indicators, populates the sell signal for the given dataframe + :param dataframe: DataFrame + :return: DataFrame with buy column + """ + dataframe.loc[ + ( + ( + (qtpylib.crossed_above(dataframe['rsi'], 70)) | + (qtpylib.crossed_above(dataframe['fastd'], 70)) + ) & + (dataframe['adx'] > 10) & + (dataframe['minus_di'] > 0) + ) | + ( + (dataframe['adx'] > 70) & + (dataframe['minus_di'] > 0.5) + ), + 'sell'] = 1 + return dataframe + + def hyperopt_space(self) -> List[Dict]: + """ + Define your Hyperopt space for the strategy + """ + space = { + 'mfi': hp.choice('mfi', [ + {'enabled': False}, + {'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)} + ]), + 'fastd': hp.choice('fastd', [ + {'enabled': False}, + {'enabled': True, 'value': hp.quniform('fastd-value', 10, 50, 1)} + ]), + 'adx': hp.choice('adx', [ + {'enabled': False}, + {'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)} + ]), + 'rsi': hp.choice('rsi', [ + {'enabled': False}, + {'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)} + ]), + 'uptrend_long_ema': hp.choice('uptrend_long_ema', [ + {'enabled': False}, + {'enabled': True} + ]), + 'uptrend_short_ema': hp.choice('uptrend_short_ema', [ + {'enabled': False}, + {'enabled': True} + ]), + 'over_sar': hp.choice('over_sar', [ + {'enabled': False}, + {'enabled': True} + ]), + 'green_candle': hp.choice('green_candle', [ + {'enabled': False}, + {'enabled': True} + ]), + 'uptrend_sma': hp.choice('uptrend_sma', [ + {'enabled': False}, + {'enabled': True} + ]), + 'trigger': hp.choice('trigger', [ + {'type': 'lower_bb'}, + {'type': 'faststoch10'}, + {'type': 'ao_cross_zero'}, + {'type': 'ema5_cross_ema10'}, + {'type': 'macd_cross_signal'}, + {'type': 'sar_reversal'}, + {'type': 'stochf_cross'}, + {'type': 'ht_sine'}, + ]), + 'stoploss': hp.uniform('stoploss', -0.5, -0.02), + } + return space + + def buy_strategy_generator(self, params) -> None: + """ + Define the buy strategy parameters to be used by hyperopt + """ + def populate_buy_trend(dataframe: DataFrame) -> DataFrame: + conditions = [] + # GUARDS AND TRENDS + if params['uptrend_long_ema']['enabled']: + conditions.append(dataframe['ema50'] > dataframe['ema100']) + if params['uptrend_short_ema']['enabled']: + conditions.append(dataframe['ema5'] > dataframe['ema10']) + if params['mfi']['enabled']: + conditions.append(dataframe['mfi'] < params['mfi']['value']) + if params['fastd']['enabled']: + conditions.append(dataframe['fastd'] < params['fastd']['value']) + if params['adx']['enabled']: + conditions.append(dataframe['adx'] > params['adx']['value']) + if params['rsi']['enabled']: + conditions.append(dataframe['rsi'] < params['rsi']['value']) + if params['over_sar']['enabled']: + conditions.append(dataframe['close'] > dataframe['sar']) + if params['green_candle']['enabled']: + conditions.append(dataframe['close'] > dataframe['open']) + if params['uptrend_sma']['enabled']: + prevsma = dataframe['sma'].shift(1) + conditions.append(dataframe['sma'] > prevsma) + + # TRIGGERS + triggers = { + 'lower_bb': dataframe['tema'] <= dataframe['blower'], + 'faststoch10': (qtpylib.crossed_above(dataframe['fastd'], 10.0)), + 'ao_cross_zero': (qtpylib.crossed_above(dataframe['ao'], 0.0)), + 'ema5_cross_ema10': ( + qtpylib.crossed_above(dataframe['ema5'], dataframe['ema10']) + ), + 'macd_cross_signal': ( + qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal']) + ), + 'sar_reversal': (qtpylib.crossed_above(dataframe['close'], dataframe['sar'])), + 'stochf_cross': (qtpylib.crossed_above(dataframe['fastk'], dataframe['fastd'])), + 'ht_sine': (qtpylib.crossed_above(dataframe['htleadsine'], dataframe['htsine'])), + } + conditions.append(triggers.get(params['trigger']['type'])) + + dataframe.loc[ + reduce(lambda x, y: x & y, conditions), + 'buy'] = 1 + + return dataframe + + return populate_buy_trend diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py new file mode 100644 index 000000000..4870b14db --- /dev/null +++ b/freqtrade/strategy/interface.py @@ -0,0 +1,56 @@ +from abc import ABC, abstractmethod +from pandas import DataFrame +from typing import Dict + + +class IStrategy(ABC): + @property + def name(self) -> str: + """ + Name of the strategy. + :return: str representation of the class name + """ + return self.__class__.__name__ + + """ + Attributes you can use: + minimal_roi -> Dict: Minimal ROI designed for the strategy + stoploss -> float: ptimal stoploss designed for the strategy + """ + + @abstractmethod + def populate_indicators(self, dataframe: DataFrame) -> 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 + """ + + @abstractmethod + def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: + """ + Based on TA indicators, populates the buy signal for the given dataframe + :param dataframe: DataFrame + :return: DataFrame with buy column + :return: + """ + + @abstractmethod + def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: + """ + Based on TA indicators, populates the sell signal for the given dataframe + :param dataframe: DataFrame + :return: DataFrame with buy column + """ + + @abstractmethod + def hyperopt_space(self) -> Dict: + """ + Define your Hyperopt space for the strategy + """ + + @abstractmethod + def buy_strategy_generator(self, params) -> None: + """ + Define the buy strategy parameters to be used by hyperopt + """ diff --git a/freqtrade/strategy/strategy.py b/freqtrade/strategy/strategy.py new file mode 100644 index 000000000..19db069ea --- /dev/null +++ b/freqtrade/strategy/strategy.py @@ -0,0 +1,165 @@ +import os +import sys +import logging +import importlib + +from pandas import DataFrame +from typing import Dict +from freqtrade.strategy.interface import IStrategy + + +sys.path.insert(0, r'../../user_data/strategies') + + +class Strategy(object): + __instance = None + + DEFAULT_STRATEGY = 'default_strategy' + + def __new__(cls): + if Strategy.__instance is None: + Strategy.__instance = object.__new__(cls) + return Strategy.__instance + + def init(self, config): + self.logger = logging.getLogger(__name__) + + # Verify the strategy is in the configuration, otherwise fallback to the default strategy + if 'strategy' in config: + strategy = config['strategy'] + else: + strategy = self.DEFAULT_STRATEGY + + # Load the strategy + self._load_strategy(strategy) + + # Set attributes + # Check if we need to override configuration + if 'minimal_roi' in config: + self.custom_strategy.minimal_roi = config['minimal_roi'] + self.logger.info("Override strategy \'minimal_roi\' with value in config file.") + + if 'stoploss' in config: + self.custom_strategy.stoploss = config['stoploss'] + self.logger.info("Override strategy \'stoploss\' with value in config file.") + + self.minimal_roi = self.custom_strategy.minimal_roi + self.stoploss = self.custom_strategy.stoploss + + def _load_strategy(self, strategy_name: str) -> None: + """ + Search and load the custom strategy. If no strategy found, fallback on the default strategy + Set the object into self.custom_strategy + :param strategy_name: name of the module to import + :return: None + """ + + try: + # Start by sanitizing the file name (remove any extensions) + strategy_name = self._sanitize_module_name(filename=strategy_name) + + # Search where can be the strategy file + path = self._search_strategy(filename=strategy_name) + + # Load the strategy + self.custom_strategy = self._load_class(path + strategy_name) + + # Fallback to the default strategy + except (ImportError, TypeError): + self.custom_strategy = self._load_class('.' + self.DEFAULT_STRATEGY) + + def _load_class(self, filename: str) -> IStrategy: + """ + Import a strategy as a module + :param filename: path to the strategy (path from freqtrade/strategy/) + :return: return the strategy class + """ + module = importlib.import_module(filename, __package__) + custom_strategy = getattr(module, module.class_name) + + self.logger.info("Load strategy class: {} ({}.py)".format(module.class_name, filename)) + return custom_strategy() + + @staticmethod + def _sanitize_module_name(filename: str) -> str: + """ + Remove any extension from filename + :param filename: filename to sanatize + :return: return the filename without extensions + """ + filename = os.path.basename(filename) + filename = os.path.splitext(filename)[0] + return filename + + @staticmethod + def _search_strategy(filename: str) -> str: + """ + Search for the Strategy file in different folder + 1. search into the user_data/strategies folder + 2. search into the freqtrade/strategy folder + 3. if nothing found, return None + :param strategy_name: module name to search + :return: module path where is the strategy + """ + pwd = os.path.dirname(os.path.realpath(__file__)) + '/' + user_data = os.path.join(pwd, '..', '..', 'user_data', 'strategies', filename + '.py') + strategy_folder = os.path.join(pwd, filename + '.py') + + path = None + if os.path.isfile(user_data): + path = 'user_data.strategies.' + elif os.path.isfile(strategy_folder): + path = '.' + + return path + + def minimal_roi(self) -> Dict: + """ + Minimal ROI designed for the strategy + :return: Dict: Value for the Minimal ROI + """ + return + + def stoploss(self) -> float: + """ + Optimal stoploss designed for the strategy + :return: float | return None to disable it + """ + return self.custom_strategy.stoploss + + def populate_indicators(self, dataframe: DataFrame) -> 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 + """ + return self.custom_strategy.populate_indicators(dataframe) + + def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: + """ + Based on TA indicators, populates the buy signal for the given dataframe + :param dataframe: DataFrame + :return: DataFrame with buy column + :return: + """ + return self.custom_strategy.populate_buy_trend(dataframe) + + def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: + """ + Based on TA indicators, populates the sell signal for the given dataframe + :param dataframe: DataFrame + :return: DataFrame with buy column + """ + return self.custom_strategy.populate_sell_trend(dataframe) + + def hyperopt_space(self) -> Dict: + """ + Define your Hyperopt space for the strategy + """ + return self.custom_strategy.hyperopt_space() + + def buy_strategy_generator(self, params) -> None: + """ + Define the buy strategy parameters to be used by hyperopt + """ + return self.custom_strategy.buy_strategy_generator(params) diff --git a/freqtrade/tests/strategy/test_default_strategy.py b/freqtrade/tests/strategy/test_default_strategy.py new file mode 100644 index 000000000..cbf2bee2c --- /dev/null +++ b/freqtrade/tests/strategy/test_default_strategy.py @@ -0,0 +1,36 @@ +import json +import pytest +from pandas import DataFrame +from freqtrade.strategy.default_strategy import DefaultStrategy, class_name +from freqtrade.analyze import parse_ticker_dataframe + + +@pytest.fixture +def result(): + with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file: + return parse_ticker_dataframe(json.load(data_file)) + + +def test_default_strategy_class_name(): + assert class_name == DefaultStrategy.__name__ + +def test_default_strategy_structure(): + assert hasattr(DefaultStrategy, 'minimal_roi') + assert hasattr(DefaultStrategy, 'stoploss') + assert hasattr(DefaultStrategy, 'populate_indicators') + assert hasattr(DefaultStrategy, 'populate_buy_trend') + assert hasattr(DefaultStrategy, 'populate_sell_trend') + assert hasattr(DefaultStrategy, 'hyperopt_space') + assert hasattr(DefaultStrategy, 'buy_strategy_generator') + +def test_default_strategy(result): + strategy = DefaultStrategy() + + assert type(strategy.minimal_roi) is dict + assert type(strategy.stoploss) is float + indicators = strategy.populate_indicators(result) + assert type(indicators) is DataFrame + assert type(strategy.populate_buy_trend(indicators)) is DataFrame + assert type(strategy.populate_sell_trend(indicators)) is DataFrame + assert type(strategy.hyperopt_space()) is dict + assert callable(strategy.buy_strategy_generator({})) diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py new file mode 100644 index 000000000..b9e20d397 --- /dev/null +++ b/freqtrade/tests/strategy/test_strategy.py @@ -0,0 +1,132 @@ +import json +import logging +import pytest +from freqtrade.strategy.strategy import Strategy +from freqtrade.analyze import parse_ticker_dataframe + + +@pytest.fixture +def result(): + with open('freqtrade/tests/testdata/BTC_ETH-1.json') as data_file: + return parse_ticker_dataframe(json.load(data_file)) + + +def test_sanitize_module_name(): + assert Strategy._sanitize_module_name('default_strategy') == 'default_strategy' + assert Strategy._sanitize_module_name('default_strategy.py') == 'default_strategy' + assert Strategy._sanitize_module_name('../default_strategy.py') == 'default_strategy' + assert Strategy._sanitize_module_name('../default_strategy') == 'default_strategy' + assert Strategy._sanitize_module_name('.default_strategy') == '.default_strategy' + assert Strategy._sanitize_module_name('foo-bar') == 'foo-bar' + assert Strategy._sanitize_module_name('foo/bar') == 'bar' + + +def test_search_strategy(): + assert Strategy._search_strategy('default_strategy') == '.' + assert Strategy._search_strategy('super_duper') is None + + +def test_strategy_structure(): + assert hasattr(Strategy, 'init') + assert hasattr(Strategy, 'minimal_roi') + assert hasattr(Strategy, 'stoploss') + assert hasattr(Strategy, 'populate_indicators') + assert hasattr(Strategy, 'populate_buy_trend') + assert hasattr(Strategy, 'populate_sell_trend') + assert hasattr(Strategy, 'hyperopt_space') + assert hasattr(Strategy, 'buy_strategy_generator') + + +def test_load_strategy(result): + strategy = Strategy() + strategy.logger = logging.getLogger(__name__) + + assert not hasattr(Strategy, 'custom_strategy') + strategy._load_strategy('default_strategy') + + assert not hasattr(Strategy, 'custom_strategy') + + assert hasattr(strategy.custom_strategy, 'populate_indicators') + assert 'adx' in strategy.populate_indicators(result) + + +def test_strategy(result): + strategy = Strategy() + strategy.init({'strategy': 'default_strategy'}) + + assert hasattr(strategy.custom_strategy, 'minimal_roi') + assert strategy.minimal_roi['0'] == 0.04 + + assert hasattr(strategy.custom_strategy, 'stoploss') + assert strategy.stoploss == -0.10 + + assert hasattr(strategy.custom_strategy, 'populate_indicators') + assert 'adx' in strategy.populate_indicators(result) + + assert hasattr(strategy.custom_strategy, 'populate_buy_trend') + dataframe = strategy.populate_buy_trend(strategy.populate_indicators(result)) + assert 'buy' in dataframe.columns + + assert hasattr(strategy.custom_strategy, 'populate_sell_trend') + dataframe = strategy.populate_sell_trend(strategy.populate_indicators(result)) + assert 'sell' in dataframe.columns + + assert hasattr(strategy.custom_strategy, 'hyperopt_space') + assert 'adx' in strategy.hyperopt_space() + + assert hasattr(strategy.custom_strategy, 'buy_strategy_generator') + assert callable(strategy.buy_strategy_generator({})) + + +def test_strategy_override_minimal_roi(caplog): + config = { + 'strategy': 'default_strategy', + 'minimal_roi': { + "0": 0.5 + } + } + strategy = Strategy() + strategy.init(config) + + assert hasattr(strategy.custom_strategy, 'minimal_roi') + assert strategy.minimal_roi['0'] == 0.5 + assert ('freqtrade.strategy.strategy', + logging.INFO, + 'Override strategy \'minimal_roi\' with value in config file.' + ) in caplog.record_tuples + + +def test_strategy_override_stoploss(caplog): + config = { + 'strategy': 'default_strategy', + 'stoploss': -0.5 + } + strategy = Strategy() + strategy.init(config) + + assert hasattr(strategy.custom_strategy, 'stoploss') + assert strategy.stoploss == -0.5 + assert ('freqtrade.strategy.strategy', + logging.INFO, + 'Override strategy \'stoploss\' with value in config file.' + ) in caplog.record_tuples + + +def test_strategy_fallback_default_strategy(): + strategy = Strategy() + strategy.logger = logging.getLogger(__name__) + + assert not hasattr(Strategy, 'custom_strategy') + strategy._load_strategy('../../super_duper') + assert not hasattr(Strategy, 'custom_strategy') + +def test_strategy_singleton(): + strategy1 = Strategy() + strategy1.init({'strategy': 'default_strategy'}) + + assert hasattr(strategy1.custom_strategy, 'minimal_roi') + assert strategy1.minimal_roi['0'] == 0.04 + + strategy2 = Strategy() + assert hasattr(strategy2.custom_strategy, 'minimal_roi') + assert strategy2.minimal_roi['0'] == 0.04 diff --git a/freqtrade/tests/test_analyze.py b/freqtrade/tests/test_analyze.py index 4c8378b3e..472b5eff5 100644 --- a/freqtrade/tests/test_analyze.py +++ b/freqtrade/tests/test_analyze.py @@ -9,6 +9,7 @@ from pandas import DataFrame from freqtrade.analyze import (get_signal, parse_ticker_dataframe, populate_buy_trend, populate_indicators, populate_sell_trend) +from freqtrade.strategy.strategy import Strategy @pytest.fixture @@ -27,11 +28,17 @@ def test_dataframe_correct_length(result): def test_populates_buy_trend(result): + # Load the default strategy for the unit test, because this logic is done in main.py + Strategy().init({'strategy': 'default_strategy'}) + dataframe = populate_buy_trend(populate_indicators(result)) assert 'buy' in dataframe.columns def test_populates_sell_trend(result): + # Load the default strategy for the unit test, because this logic is done in main.py + Strategy().init({'strategy': 'default_strategy'}) + dataframe = populate_sell_trend(populate_indicators(result)) assert 'sell' in dataframe.columns diff --git a/user_data/data/.gitkeep b/user_data/data/.gitkeep new file mode 100644 index 000000000..e69de29bb diff --git a/user_data/strategies/__init__.py b/user_data/strategies/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/user_data/strategies/test_strategy.py b/user_data/strategies/test_strategy.py new file mode 100644 index 000000000..728cc6328 --- /dev/null +++ b/user_data/strategies/test_strategy.py @@ -0,0 +1,129 @@ + +# --- Do not remove these libs --- +from freqtrade.strategy.interface import IStrategy +from typing import Dict, List +from hyperopt import hp +from functools import reduce +from pandas import DataFrame +# -------------------------------- + +# Add your lib to import here +import talib.abstract as ta + + +# Update this variable if you change the class name +class_name = 'TestStrategy' + + +class TestStrategy(IStrategy): + """ + This is a test strategy to inspire you. + You can: + - Rename the class name (Do not forget to update class_name) + - Add any methods you want to build your strategy + - Add any lib you need to build your strategy + + You must keep: + - the lib in the section "Do not remove these libs" + - the prototype for the methods: minimal_roi, stoploss, populate_indicators, populate_buy_trend, + populate_sell_trend, hyperopt_space, buy_strategy_generator + """ + + # Minimal ROI designed for the strategy. + # This attribute will be overridden if the config file contains "minimal_roi" + minimal_roi = { + "40": 0.0, + "30": 0.01, + "20": 0.02, + "0": 0.04 + } + + # Optimal stoploss designed for the strategy + # This attribute will be overridden if the config file contains "stoploss" + stoploss = -0.10 + + def populate_indicators(self, dataframe: DataFrame) -> DataFrame: + """ + Adds several different TA indicators to the given DataFrame + + Performance Note: For the best performance be frugal on the number of indicators + you are using. Let uncomment only the indicator you are using in your strategies + or your hyperopt configuration, otherwise you will waste your memory and CPU usage. + """ + + dataframe['adx'] = ta.ADX(dataframe) + dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband'] + dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) + + return dataframe + + def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: + """ + Based on TA indicators, populates the buy signal for the given dataframe + :param dataframe: DataFrame + :return: DataFrame with buy column + """ + dataframe.loc[ + ( + (dataframe['adx'] > 30) & + (dataframe['tema'] <= dataframe['blower']) & + (dataframe['tema'] > dataframe['tema'].shift(1)) + ), + 'buy'] = 1 + + return dataframe + + def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: + """ + Based on TA indicators, populates the sell signal for the given dataframe + :param dataframe: DataFrame + :return: DataFrame with buy column + """ + dataframe.loc[ + ( + (dataframe['adx'] > 70) & + (dataframe['tema'] > dataframe['blower']) & + (dataframe['tema'] < dataframe['tema'].shift(1)) + ), + 'sell'] = 1 + return dataframe + + def hyperopt_space(self) -> List[Dict]: + """ + Define your Hyperopt space for the strategy + """ + space = { + 'adx': hp.choice('adx', [ + {'enabled': False}, + {'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)} + ]), + 'trigger': hp.choice('trigger', [ + {'type': 'lower_bb'}, + ]), + 'stoploss': hp.uniform('stoploss', -0.5, -0.02), + } + return space + + def buy_strategy_generator(self, params) -> None: + """ + Define the buy strategy parameters to be used by hyperopt + """ + def populate_buy_trend(dataframe: DataFrame) -> DataFrame: + conditions = [] + # GUARDS AND TRENDS + if params['adx']['enabled']: + conditions.append(dataframe['adx'] > params['adx']['value']) + + # TRIGGERS + triggers = { + 'lower_bb': dataframe['tema'] <= dataframe['blower'], + } + conditions.append(triggers.get(params['trigger']['type'])) + + dataframe.loc[ + reduce(lambda x, y: x & y, conditions), + 'buy'] = 1 + + return dataframe + + return populate_buy_trend From dfd61bbf1d8a4dc0ddf3142ff8efe8190e802295 Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Wed, 17 Jan 2018 21:44:37 -0800 Subject: [PATCH 10/32] Implement More triggers and guards from PR#394 --- freqtrade/strategy/default_strategy.py | 143 ++++++++++++++++++++++--- 1 file changed, 127 insertions(+), 16 deletions(-) diff --git a/freqtrade/strategy/default_strategy.py b/freqtrade/strategy/default_strategy.py index 670cc2abe..9be7a67f8 100644 --- a/freqtrade/strategy/default_strategy.py +++ b/freqtrade/strategy/default_strategy.py @@ -45,7 +45,10 @@ class DefaultStrategy(IStrategy): # Awesome oscillator dataframe['ao'] = qtpylib.awesome_oscillator(dataframe) - + """ + # Commodity Channel Index: values Oversold:<-100, Overbought:>100 + dataframe['cci'] = ta.CCI(dataframe) + """ # MACD macd = ta.MACD(dataframe) dataframe['macd'] = macd['macd'] @@ -62,14 +65,35 @@ class DefaultStrategy(IStrategy): # Plus Directional Indicator / Movement dataframe['plus_dm'] = ta.PLUS_DM(dataframe) dataframe['plus_di'] = ta.PLUS_DI(dataframe) + dataframe['minus_di'] = ta.MINUS_DI(dataframe) + """ + # ROC + dataframe['roc'] = ta.ROC(dataframe) + """ # RSI dataframe['rsi'] = ta.RSI(dataframe) - + """ + # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy) + rsi = 0.1 * (dataframe['rsi'] - 50) + dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1) + # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy) + dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1) + # Stoch + stoch = ta.STOCH(dataframe) + dataframe['slowd'] = stoch['slowd'] + dataframe['slowk'] = stoch['slowk'] + """ # Stoch fast stoch_fast = ta.STOCHF(dataframe) dataframe['fastd'] = stoch_fast['fastd'] dataframe['fastk'] = stoch_fast['fastk'] + """ + # Stoch RSI + stoch_rsi = ta.STOCHRSI(dataframe) + dataframe['fastd_rsi'] = stoch_rsi['fastd'] + dataframe['fastk_rsi'] = stoch_rsi['fastk'] + """ # Overlap Studies # ------------------------------------ @@ -79,15 +103,15 @@ class DefaultStrategy(IStrategy): # returns middle band for all the three bands. Switch to qtpylib.bollinger_bands # and use middle band instead. dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband'] - """ + # Bollinger bands bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) dataframe['bb_lowerband'] = bollinger['lower'] dataframe['bb_middleband'] = bollinger['mid'] dataframe['bb_upperband'] = bollinger['upper'] - """ # EMA - Exponential Moving Average + dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) @@ -109,6 +133,66 @@ class DefaultStrategy(IStrategy): dataframe['htsine'] = hilbert['sine'] dataframe['htleadsine'] = hilbert['leadsine'] + # Pattern Recognition - Bullish candlestick patterns + # ------------------------------------ + """ + # Hammer: values [0, 100] + dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe) + # Inverted Hammer: values [0, 100] + dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe) + # Dragonfly Doji: values [0, 100] + dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe) + # Piercing Line: values [0, 100] + dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100] + # Morningstar: values [0, 100] + dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100] + # Three White Soldiers: values [0, 100] + dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100] + """ + + # Pattern Recognition - Bearish candlestick patterns + # ------------------------------------ + """ + # Hanging Man: values [0, 100] + dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe) + # Shooting Star: values [0, 100] + dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe) + # Gravestone Doji: values [0, 100] + dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe) + # Dark Cloud Cover: values [0, 100] + dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe) + # Evening Doji Star: values [0, 100] + dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe) + # Evening Star: values [0, 100] + dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe) + """ + + # Pattern Recognition - Bullish/Bearish candlestick patterns + # ------------------------------------ + """ + # Three Line Strike: values [0, -100, 100] + dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe) + # Spinning Top: values [0, -100, 100] + dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100] + # Engulfing: values [0, -100, 100] + dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100] + # Harami: values [0, -100, 100] + dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100] + # Three Outside Up/Down: values [0, -100, 100] + dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100] + # Three Inside Up/Down: values [0, -100, 100] + dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100] + """ + + # Chart type + # ------------------------------------ + # Heikinashi stategy + heikinashi = qtpylib.heikinashi(dataframe) + dataframe['ha_open'] = heikinashi['open'] + dataframe['ha_close'] = heikinashi['close'] + dataframe['ha_high'] = heikinashi['high'] + dataframe['ha_low'] = heikinashi['low'] + return dataframe def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: @@ -159,6 +243,10 @@ class DefaultStrategy(IStrategy): Define your Hyperopt space for the strategy """ space = { + 'macd_below_zero': hp.choice('macd_below_zero', [ + {'enabled': False}, + {'enabled': True} + ]), 'mfi': hp.choice('mfi', [ {'enabled': False}, {'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)} @@ -197,13 +285,15 @@ class DefaultStrategy(IStrategy): ]), 'trigger': hp.choice('trigger', [ {'type': 'lower_bb'}, + {'type': 'lower_bb_tema'}, {'type': 'faststoch10'}, {'type': 'ao_cross_zero'}, - {'type': 'ema5_cross_ema10'}, + {'type': 'ema3_cross_ema10'}, {'type': 'macd_cross_signal'}, {'type': 'sar_reversal'}, - {'type': 'stochf_cross'}, {'type': 'ht_sine'}, + {'type': 'heiken_reversal_bull'}, + {'type': 'di_cross'}, ]), 'stoploss': hp.uniform('stoploss', -0.5, -0.02), } @@ -218,6 +308,8 @@ class DefaultStrategy(IStrategy): # GUARDS AND TRENDS if params['uptrend_long_ema']['enabled']: conditions.append(dataframe['ema50'] > dataframe['ema100']) + if params['macd_below_zero']['enabled']: + conditions.append(dataframe['macd'] < 0) if params['uptrend_short_ema']['enabled']: conditions.append(dataframe['ema5'] > dataframe['ema10']) if params['mfi']['enabled']: @@ -238,18 +330,37 @@ class DefaultStrategy(IStrategy): # TRIGGERS triggers = { - 'lower_bb': dataframe['tema'] <= dataframe['blower'], - 'faststoch10': (qtpylib.crossed_above(dataframe['fastd'], 10.0)), - 'ao_cross_zero': (qtpylib.crossed_above(dataframe['ao'], 0.0)), - 'ema5_cross_ema10': ( - qtpylib.crossed_above(dataframe['ema5'], dataframe['ema10']) + 'lower_bb': ( + dataframe['close'] < dataframe['bb_lowerband'] ), - 'macd_cross_signal': ( - qtpylib.crossed_above(dataframe['macd'], dataframe['macdsignal']) + 'lower_bb_tema': ( + dataframe['tema'] < dataframe['bb_lowerband'] ), - 'sar_reversal': (qtpylib.crossed_above(dataframe['close'], dataframe['sar'])), - 'stochf_cross': (qtpylib.crossed_above(dataframe['fastk'], dataframe['fastd'])), - 'ht_sine': (qtpylib.crossed_above(dataframe['htleadsine'], dataframe['htsine'])), + 'faststoch10': (qtpylib.crossed_above( + dataframe['fastd'], 10.0 + )), + 'ao_cross_zero': (qtpylib.crossed_above( + dataframe['ao'], 0.0 + )), + 'ema3_cross_ema10': (qtpylib.crossed_above( + dataframe['ema3'], dataframe['ema10'] + )), + 'macd_cross_signal': (qtpylib.crossed_above( + dataframe['macd'], dataframe['macdsignal'] + )), + 'sar_reversal': (qtpylib.crossed_above( + dataframe['close'], dataframe['sar'] + )), + 'ht_sine': (qtpylib.crossed_above( + dataframe['htleadsine'], dataframe['htsine'] + )), + 'heiken_reversal_bull': ( + (qtpylib.crossed_above(dataframe['ha_close'], dataframe['ha_open'])) & + (dataframe['ha_low'] == dataframe['ha_open']) + ), + 'di_cross': (qtpylib.crossed_above( + dataframe['plus_di'], dataframe['minus_di'] + )), } conditions.append(triggers.get(params['trigger']['type'])) From be75522507da5a944f8eb688f718a24f07bada05 Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Wed, 17 Jan 2018 21:50:12 -0800 Subject: [PATCH 11/32] Fix flake8 --- freqtrade/optimize/hyperopt.py | 1 + freqtrade/strategy/default_strategy.py | 1 - freqtrade/tests/strategy/test_default_strategy.py | 2 ++ freqtrade/tests/strategy/test_strategy.py | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 041f9a7a4..4aee0dadc 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -152,6 +152,7 @@ def format_results(results: DataFrame): results.duration.mean() * 5, ) + def start(args): global TOTAL_TRIES, PROCESSED, TRIALS, _CURRENT_TRIES diff --git a/freqtrade/strategy/default_strategy.py b/freqtrade/strategy/default_strategy.py index 9be7a67f8..f603b52c0 100644 --- a/freqtrade/strategy/default_strategy.py +++ b/freqtrade/strategy/default_strategy.py @@ -27,7 +27,6 @@ class DefaultStrategy(IStrategy): # Optimal stoploss designed for the strategy stoploss = -0.10 - def populate_indicators(self, dataframe: DataFrame) -> DataFrame: """ Adds several different TA indicators to the given DataFrame diff --git a/freqtrade/tests/strategy/test_default_strategy.py b/freqtrade/tests/strategy/test_default_strategy.py index cbf2bee2c..a197452c8 100644 --- a/freqtrade/tests/strategy/test_default_strategy.py +++ b/freqtrade/tests/strategy/test_default_strategy.py @@ -14,6 +14,7 @@ def result(): def test_default_strategy_class_name(): assert class_name == DefaultStrategy.__name__ + def test_default_strategy_structure(): assert hasattr(DefaultStrategy, 'minimal_roi') assert hasattr(DefaultStrategy, 'stoploss') @@ -23,6 +24,7 @@ def test_default_strategy_structure(): assert hasattr(DefaultStrategy, 'hyperopt_space') assert hasattr(DefaultStrategy, 'buy_strategy_generator') + def test_default_strategy(result): strategy = DefaultStrategy() diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index b9e20d397..c71d3bc89 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -120,6 +120,7 @@ def test_strategy_fallback_default_strategy(): strategy._load_strategy('../../super_duper') assert not hasattr(Strategy, 'custom_strategy') + def test_strategy_singleton(): strategy1 = Strategy() strategy1.init({'strategy': 'default_strategy'}) From a5853681e3713e892364130a5af154780ad89287 Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Wed, 17 Jan 2018 23:06:37 -0800 Subject: [PATCH 12/32] Update documentation --- docs/bot-optimization.md | 98 +++++++++------ docs/bot-usage.md | 45 +++++-- docs/configuration.md | 12 +- docs/hyperopt.md | 46 +++---- user_data/strategies/test_strategy.py | 167 ++++++++++++++++++++++++++ 5 files changed, 297 insertions(+), 71 deletions(-) diff --git a/docs/bot-optimization.md b/docs/bot-optimization.md index 424bab989..f94bb5206 100644 --- a/docs/bot-optimization.md +++ b/docs/bot-optimization.md @@ -3,21 +3,55 @@ This page explains where to customize your strategies, and add new indicators. ## Table of Contents -- [Change your strategy](#change-your-strategy) +- [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 +`user_data/strategies`. + +Let guess you have a strategy file `awesome-strategy.py`: +1. Move your file into `user_data/strategies` (you should have `user_data/strategies/awesome-strategy.py` +2. Start the bot with the param `--strategy awesome-strategy` (the parameter is the name of the file without '.py') + +```bash +python3 ./freqtrade/main.py --strategy awesome_strategy +``` ## Change your strategy -The bot is using buy and sell strategies to buy and sell your trades. -Both are customizable. +The bot includes a default strategy file. However, we recommend you to +use your own file to not have to lose your parameters everytime the default +strategy file will be updated on Github. Put your custom strategy file +into the folder `user_data/strategies`. -### Buy strategy -The default buy strategy is located in the file -[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L73-L92). -Edit the function `populate_buy_trend()` to update your buy strategy. +A strategy file contains all the information needed to build a good strategy: +- Buy strategy rules +- Sell strategy rules +- Minimal ROI recommended +- Stoploss recommended +- Hyperopt parameter -Sample: +The bot also include a sample strategy you can update: `user_data/strategies/test_strategy.py`. +You can test it with the parameter: `--strategy test_strategy` + +```bash +python3 ./freqtrade/main.py --strategy awesome_strategy +``` + +**For the following section we will use the [user_data/strategies/test_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py) +file as reference.** + +### Buy strategy +Edit the method `populate_buy_trend()` into your strategy file to +update your buy strategy. + +Sample from `user_data/strategies/test_strategy.py`: ```python -def populate_buy_trend(dataframe: DataFrame) -> DataFrame: +def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: """ Based on TA indicators, populates the buy signal for the given dataframe :param dataframe: DataFrame @@ -25,14 +59,9 @@ def populate_buy_trend(dataframe: DataFrame) -> DataFrame: """ dataframe.loc[ ( - (dataframe['rsi'] < 35) & - (dataframe['fastd'] < 35) & (dataframe['adx'] > 30) & - (dataframe['plus_di'] > 0.5) - ) | - ( - (dataframe['adx'] > 65) & - (dataframe['plus_di'] > 0.5) + (dataframe['tema'] <= dataframe['blower']) & + (dataframe['tema'] > dataframe['tema'].shift(1)) ), 'buy'] = 1 @@ -40,41 +69,31 @@ def populate_buy_trend(dataframe: DataFrame) -> DataFrame: ``` ### Sell strategy -The default buy strategy is located in the file -[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L95-L115) -Edit the function `populate_sell_trend()` to update your buy strategy. +Edit the method `populate_sell_trend()` into your strategy file to +update your sell strategy. -Sample: +Sample from `user_data/strategies/test_strategy.py`: ```python -def populate_sell_trend(dataframe: DataFrame) -> DataFrame: +def populate_sell_trend(self, dataframe: DataFrame) -> DataFrame: """ Based on TA indicators, populates the sell signal for the given dataframe :param dataframe: DataFrame :return: DataFrame with buy column """ dataframe.loc[ - ( - ( - (crossed_above(dataframe['rsi'], 70)) | - (crossed_above(dataframe['fastd'], 70)) - ) & - (dataframe['adx'] > 10) & - (dataframe['minus_di'] > 0) - ) | ( (dataframe['adx'] > 70) & - (dataframe['minus_di'] > 0.5) + (dataframe['tema'] > dataframe['blower']) & + (dataframe['tema'] < dataframe['tema'].shift(1)) ), 'sell'] = 1 return dataframe ``` ## Add more Indicator -As you have seen, buy and sell strategies need indicators. You can see -the indicators in the file -[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L95-L115). -Of course you can add more indicators by extending the list contained in -the function `populate_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. Sample: ```python @@ -111,6 +130,15 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame: return dataframe ``` +**Want more indicators example?** +Look into the [user_data/strategies/test_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py). +Then uncomment indicateur you need. + + +### Where is the default strategy? +The default buy strategy is located in the file +[freqtrade/default_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/strategy/default_strategy.py). + ## Next step Now you have a perfect strategy you probably want to backtesting it. diff --git a/docs/bot-usage.md b/docs/bot-usage.md index d3dcf8659..ca92d74c4 100644 --- a/docs/bot-usage.md +++ b/docs/bot-usage.md @@ -22,19 +22,21 @@ positional arguments: optional arguments: -h, --help show this help message and exit - -c PATH, --config PATH - specify configuration file (default: config.json) -v, --verbose be verbose --version show program's version number and exit - -dd PATH, --datadir PATH - Path is from where backtesting and hyperopt will load the - ticker data files (default freqdata/tests/testdata). - --dynamic-whitelist [INT] - dynamically generate and update whitelist based on 24h - BaseVolume (Default 20 currencies) + -c PATH, --config PATH + specify configuration file (default: config.json) + -s PATH, --strategy PATH + specify strategy file (default: + freqtrade/strategy/default_strategy.py) --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. + -dd PATH, --datadir PATH + path to backtest data (default freqdata/tests/testdata + --dynamic-whitelist [INT] + dynamically generate and update whitelist based on 24h + BaseVolume (Default 20 currencies) ``` ### How to use a different config file? @@ -45,6 +47,33 @@ default, the bot will load the file `./config.json` python3 ./freqtrade/main.py -c path/far/far/away/config.json ``` +### How to use --strategy? +This parameter will allow you to load your custom strategy file. Per +default without `--strategy` or `-s` the bol will load the +`default_strategy` included with the bot (`freqtrade/strategy/default_strategy.py`). + +The bot will search your strategy file into `user_data/strategies` and +`freqtrade/strategy`. + +To load a strategy, simply pass the file name (without .py) in this +parameters. + +**Example:** +In `user_data/strategies` you have a file `my_awesome_strategy.py` to +load it: +```bash +python3 ./freqtrade/main.py --strategy my_awesome_strategy +``` + +If the bot does not find your strategy file, it will fallback to the +`default_strategy`. + +Learn more about strategy file in [optimize your bot](https://github.com/gcarq/freqtrade/blob/develop/docs/bot-optimization.md). + +#### How to install a strategy? +This is very simple. Copy paste your strategy file into the folder +`user_data/strategies`. And voila, the bot is ready to use it. + ### 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. diff --git a/docs/configuration.md b/docs/configuration.md index 5d1204efd..84c8f53ba 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -20,8 +20,8 @@ The table below will list all configuration parameters. | `ticker_interval` | [1, 5, 30, 60, 1440] | No | The ticker interval to use (1min, 5 min, 30 min, 1 hour or 1 day). Defaut is 5 minutes | `fiat_display_currency` | USD | Yes | Fiat currency used to show your profits. More information below. | `dry_run` | true | Yes | Define if the bot must be in Dry-run or production mode. -| `minimal_roi` | See below | Yes | Set the threshold in percent the bot will use to sell a trade. More information below. -| `stoploss` | -0.10 | No | Value of the stoploss in percent used by the bot. More information below. +| `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. | `unfilledtimeout` | 0 | No | How long (in minutes) the bot will wait for an unfilled order to complete, after which the order will be cancelled. | `bid_strategy.ask_last_balance` | 0.0 | Yes | Set the bidding price. More information below. | `exchange.name` | bittrex | Yes | Name of the exchange class to use. @@ -53,11 +53,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 +`minimal_roi` value from the strategy file. + ### Understand stoploss `stoploss` is loss in percentage that should trigger a sale. For example value `-0.10` will cause immediate sell if the profit dips below -10% for a given trade. This parameter is optional. +Most of the strategy files already include the optimal `stoploss` +value. This parameter is optional. If you use it, it will take over the +`stoploss` value from the strategy file. + ### Understand initial_state `initial_state` is an optional field that defines the initial application state. Possible values are `running` or `stopped`. (default=`running`) diff --git a/docs/hyperopt.md b/docs/hyperopt.md index af564f0b6..3c3cb7d25 100644 --- a/docs/hyperopt.md +++ b/docs/hyperopt.md @@ -14,14 +14,13 @@ parameters with Hyperopt. ## Prepare Hyperopt Before we start digging in Hyperopt, we recommend you to take a look at -out Hyperopt file -[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py) +your strategy file located into [user_data/strategies/](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py) ### 1. Configure your Guards and Triggers -There are two places you need to change to add a new buy strategy for -testing: -- Inside the [populate_buy_trend()](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L167-L207). -- Inside the [SPACE dict](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L47-L94). +There are two places you need to change in your strategy file to add a +new buy strategy for testing: +- Inside [populate_buy_trend()](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L278-L294). +- Inside [hyperopt_space()](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L244-L297) known as `SPACE`. There you have two different type of indicators: 1. `guards` and 2. `triggers`. @@ -38,10 +37,10 @@ ADX > 10*". If you have updated the buy strategy, means change the content of -`populate_buy_trend()` function you have to update the `guards` and +`populate_buy_trend()` method you have to update the `guards` and `triggers` hyperopts must used. -As for an example if your `populate_buy_trend()` function is: +As for an example if your `populate_buy_trend()` method is: ```python def populate_buy_trend(dataframe: DataFrame) -> DataFrame: dataframe.loc[ @@ -56,10 +55,10 @@ Your hyperopt file must contains `guards` to find the right value for `(dataframe['adx'] > 65)` & and `(dataframe['plus_di'] > 0.5)`. That means you will need to enable/disable triggers. -In our case the `SPACE` and `populate_buy_trend` in hyperopt.py file +In our case the `SPACE` and `populate_buy_trend` in your strategy file will be look like: ```python -SPACE = { +space = { 'rsi': hp.choice('rsi', [ {'enabled': False}, {'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)} @@ -82,7 +81,7 @@ SPACE = { ... -def populate_buy_trend(dataframe: DataFrame) -> DataFrame: +def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: conditions = [] # GUARDS AND TRENDS if params['adx']['enabled']: @@ -111,13 +110,13 @@ cannot use your config file. It is also made on purpose to allow you testing your strategy with different configurations. The Hyperopt configuration is located in -[freqtrade/optimize/hyperopt_conf.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt_conf.py). +[user_data/hyperopt_conf.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/hyperopt_conf.py). ## Advanced notions ### Understand the Guards and Triggers When you need to add the new guards and triggers to be hyperopt -parameters, you do this by adding them into the [SPACE dict](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L47-L94). +parameters, you do this by adding them into the [hyperopt_space()](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py#L244-L297). If it's a trigger, you add one line to the 'trigger' choice group and that's it. @@ -149,9 +148,8 @@ for best working algo. ### Add a new Indicators If you want to test an indicator that isn't used by the bot currently, -you need to add it to -[freqtrade/analyze.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L40-L70) -inside the `populate_indicators` function. +you need to add it to your strategy file (example: [user_data/strategies/test_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py)) +inside the `populate_indicators()` method. ## Execute Hyperopt Once you have updated your hyperopt configuration you can run it. @@ -165,8 +163,8 @@ python3 ./freqtrade/main.py -c config.json hyperopt ### Execute hyperopt with different ticker-data source If you would like to learn parameters using an alternate ticke-data that -you have on-disk, use the --datadir PATH option. Default hyperopt will -use data from directory freqtrade/tests/testdata. +you have on-disk, use the `--datadir PATH` option. Default hyperopt will +use data from directory `user_data/data`. ### Running hyperopt with smaller testset @@ -270,15 +268,11 @@ customizable value. - and so on... -You have to look from -[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L170-L200) -what those values match to. +You have to look inside your strategy file into `buy_strategy_generator()` +method, what those values match to. So for example you had `adx:` with the `value: 15.0` so we would look -at `adx`-block from -[freqtrade/optimize/hyperopt.py](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/optimize/hyperopt.py#L178-L179). -That translates to the following code block to -[analyze.populate_buy_trend()](https://github.com/gcarq/freqtrade/blob/develop/freqtrade/analyze.py#L73) +at `adx`-block, that translates to the following code block: ``` (dataframe['adx'] > 15.0) ``` @@ -286,7 +280,7 @@ That translates to the following code block to So translating your whole hyperopt result to as the new buy-signal would be the following: ``` -def populate_buy_trend(dataframe: DataFrame) -> DataFrame: +def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: dataframe.loc[ ( (dataframe['adx'] > 15.0) & # adx-value diff --git a/user_data/strategies/test_strategy.py b/user_data/strategies/test_strategy.py index 728cc6328..69c0f4bcd 100644 --- a/user_data/strategies/test_strategy.py +++ b/user_data/strategies/test_strategy.py @@ -9,15 +9,20 @@ from pandas import DataFrame # Add your lib to import here import talib.abstract as ta +import freqtrade.vendor.qtpylib.indicators as qtpylib +import numpy # noqa # Update this variable if you change the class name class_name = 'TestStrategy' +# This class is a sample. Feel free to customize it. class TestStrategy(IStrategy): """ This is a test strategy to inspire you. + More information in https://github.com/gcarq/freqtrade/blob/develop/docs/bot-optimization.md + You can: - Rename the class name (Do not forget to update class_name) - Add any methods you want to build your strategy @@ -51,10 +56,171 @@ class TestStrategy(IStrategy): or your hyperopt configuration, otherwise you will waste your memory and CPU usage. """ + # Momentum Indicator + # ------------------------------------ + + # ADX dataframe['adx'] = ta.ADX(dataframe) + + """ + # Awesome oscillator + dataframe['ao'] = qtpylib.awesome_oscillator(dataframe) + + # Commodity Channel Index: values Oversold:<-100, Overbought:>100 + dataframe['cci'] = ta.CCI(dataframe) + + # MACD + macd = ta.MACD(dataframe) + dataframe['macd'] = macd['macd'] + dataframe['macdsignal'] = macd['macdsignal'] + dataframe['macdhist'] = macd['macdhist'] + + # MFI + dataframe['mfi'] = ta.MFI(dataframe) + + # Minus Directional Indicator / Movement + dataframe['minus_dm'] = ta.MINUS_DM(dataframe) + dataframe['minus_di'] = ta.MINUS_DI(dataframe) + + # Plus Directional Indicator / Movement + dataframe['plus_dm'] = ta.PLUS_DM(dataframe) + dataframe['plus_di'] = ta.PLUS_DI(dataframe) + dataframe['minus_di'] = ta.MINUS_DI(dataframe) + + # ROC + dataframe['roc'] = ta.ROC(dataframe) + + # RSI + dataframe['rsi'] = ta.RSI(dataframe) + + # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy) + rsi = 0.1 * (dataframe['rsi'] - 50) + dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1) + + # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy) + dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1) + + # Stoch + stoch = ta.STOCH(dataframe) + dataframe['slowd'] = stoch['slowd'] + dataframe['slowk'] = stoch['slowk'] + + # Stoch fast + stoch_fast = ta.STOCHF(dataframe) + dataframe['fastd'] = stoch_fast['fastd'] + dataframe['fastk'] = stoch_fast['fastk'] + + # Stoch RSI + stoch_rsi = ta.STOCHRSI(dataframe) + dataframe['fastd_rsi'] = stoch_rsi['fastd'] + dataframe['fastk_rsi'] = stoch_rsi['fastk'] + """ + + # Overlap Studies + # ------------------------------------ + + """ + # Previous Bollinger bands + # Because ta.BBANDS implementation is broken with small numbers, it actually + # returns middle band for all the three bands. Switch to qtpylib.bollinger_bands + # and use middle band instead. + dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband'] + """ + + # Bollinger bands + bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) + dataframe['bb_lowerband'] = bollinger['lower'] + dataframe['bb_middleband'] = bollinger['mid'] + dataframe['bb_upperband'] = bollinger['upper'] + + """ + # EMA - Exponential Moving Average + dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) + dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) + dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) + dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) + dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) + + # SAR Parabol + dataframe['sar'] = ta.SAR(dataframe) + + # SMA - Simple Moving Average + dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) + """ + + # TEMA - Triple Exponential Moving Average dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) + # Cycle Indicator + # ------------------------------------ + # Hilbert Transform Indicator - SineWave + hilbert = ta.HT_SINE(dataframe) + dataframe['htsine'] = hilbert['sine'] + dataframe['htleadsine'] = hilbert['leadsine'] + + # Pattern Recognition - Bullish candlestick patterns + # ------------------------------------ + """ + # Hammer: values [0, 100] + dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe) + # Inverted Hammer: values [0, 100] + dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe) + # Dragonfly Doji: values [0, 100] + dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe) + # Piercing Line: values [0, 100] + dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100] + # Morningstar: values [0, 100] + dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100] + # Three White Soldiers: values [0, 100] + dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100] + """ + + # Pattern Recognition - Bearish candlestick patterns + # ------------------------------------ + """ + # Hanging Man: values [0, 100] + dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe) + # Shooting Star: values [0, 100] + dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe) + # Gravestone Doji: values [0, 100] + dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe) + # Dark Cloud Cover: values [0, 100] + dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe) + # Evening Doji Star: values [0, 100] + dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe) + # Evening Star: values [0, 100] + dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe) + """ + + # Pattern Recognition - Bullish/Bearish candlestick patterns + # ------------------------------------ + """ + # Three Line Strike: values [0, -100, 100] + dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe) + # Spinning Top: values [0, -100, 100] + dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100] + # Engulfing: values [0, -100, 100] + dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100] + # Harami: values [0, -100, 100] + dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100] + # Three Outside Up/Down: values [0, -100, 100] + dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100] + # Three Inside Up/Down: values [0, -100, 100] + dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100] + """ + + # Chart type + # ------------------------------------ + """ + # Heikinashi stategy + heikinashi = qtpylib.heikinashi(dataframe) + dataframe['ha_open'] = heikinashi['open'] + dataframe['ha_close'] = heikinashi['close'] + dataframe['ha_high'] = heikinashi['high'] + dataframe['ha_low'] = heikinashi['low'] + """ + return dataframe def populate_buy_trend(self, dataframe: DataFrame) -> DataFrame: @@ -91,6 +257,7 @@ class TestStrategy(IStrategy): def hyperopt_space(self) -> List[Dict]: """ Define your Hyperopt space for the strategy + :return: Dict """ space = { 'adx': hp.choice('adx', [ From baae374899074d1d70f768abc3da3e19a54d080d Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Wed, 17 Jan 2018 23:10:48 -0800 Subject: [PATCH 13/32] Move hyperopt_conf.py into user_data/ --- freqtrade/optimize/__init__.py | 3 ++- freqtrade/optimize/hyperopt.py | 2 +- freqtrade/tests/optimize/test_hyperopt_config.py | 2 +- {freqtrade/optimize => user_data}/hyperopt_conf.py | 0 4 files changed, 4 insertions(+), 3 deletions(-) rename {freqtrade/optimize => user_data}/hyperopt_conf.py (100%) diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index 0ac808424..08d10c83e 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -6,9 +6,10 @@ import os from typing import Optional, List, Dict from pandas import DataFrame from freqtrade.exchange import get_ticker_history -from freqtrade.optimize.hyperopt_conf import hyperopt_optimize_conf from freqtrade.analyze import populate_indicators, parse_ticker_dataframe + from freqtrade import misc +from user_data.hyperopt_conf import hyperopt_optimize_conf logger = logging.getLogger(__name__) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 4aee0dadc..91a2530e9 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -19,8 +19,8 @@ from freqtrade import exchange, optimize from freqtrade.exchange import Bittrex from freqtrade.misc import load_config from freqtrade.optimize.backtesting import backtest -from freqtrade.optimize.hyperopt_conf import hyperopt_optimize_conf from freqtrade.strategy.strategy import Strategy +from user_data.hyperopt_conf import hyperopt_optimize_conf # Remove noisy log messages logging.getLogger('hyperopt.mongoexp').setLevel(logging.WARNING) diff --git a/freqtrade/tests/optimize/test_hyperopt_config.py b/freqtrade/tests/optimize/test_hyperopt_config.py index e06c1f2eb..aa9424826 100644 --- a/freqtrade/tests/optimize/test_hyperopt_config.py +++ b/freqtrade/tests/optimize/test_hyperopt_config.py @@ -1,6 +1,6 @@ # pragma pylint: disable=missing-docstring,W0212 -from freqtrade.optimize.hyperopt_conf import hyperopt_optimize_conf +from user_data.hyperopt_conf import hyperopt_optimize_conf def test_hyperopt_optimize_conf(): diff --git a/freqtrade/optimize/hyperopt_conf.py b/user_data/hyperopt_conf.py similarity index 100% rename from freqtrade/optimize/hyperopt_conf.py rename to user_data/hyperopt_conf.py From 1c7da95fedd51f20827c5777881d42d0102b5d4e Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Wed, 17 Jan 2018 23:16:44 -0800 Subject: [PATCH 14/32] Move hyperopt_trials.pickle to user_data/ --- freqtrade/optimize/hyperopt.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 91a2530e9..3e174d892 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -48,7 +48,7 @@ PROCESSED = None # optimize.preprocess(optimize.load_data()) OPTIMIZE_CONFIG = hyperopt_optimize_conf() # Hyperopt Trials -TRIALS_FILE = os.path.join('freqtrade', 'optimize', 'hyperopt_trials.pickle') +TRIALS_FILE = os.path.join('user_data', 'hyperopt_trials.pickle') TRIALS = Trials() # Monkey patch config From 3e8088d99ccf7c0de8f8249e8212981b412137df Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Thu, 18 Jan 2018 21:25:14 -0800 Subject: [PATCH 15/32] Avoid hyperopt to fail if a guard was removed from SPACE but still defined in populate_buy_trend() --- freqtrade/strategy/default_strategy.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/freqtrade/strategy/default_strategy.py b/freqtrade/strategy/default_strategy.py index f603b52c0..927148fea 100644 --- a/freqtrade/strategy/default_strategy.py +++ b/freqtrade/strategy/default_strategy.py @@ -305,25 +305,25 @@ class DefaultStrategy(IStrategy): def populate_buy_trend(dataframe: DataFrame) -> DataFrame: conditions = [] # GUARDS AND TRENDS - if params['uptrend_long_ema']['enabled']: + if 'uptrend_long_ema' in params and params['uptrend_long_ema']['enabled']: conditions.append(dataframe['ema50'] > dataframe['ema100']) - if params['macd_below_zero']['enabled']: + if 'macd_below_zero' in params and params['macd_below_zero']['enabled']: conditions.append(dataframe['macd'] < 0) - if params['uptrend_short_ema']['enabled']: + if 'uptrend_short_ema' in params and params['uptrend_short_ema']['enabled']: conditions.append(dataframe['ema5'] > dataframe['ema10']) - if params['mfi']['enabled']: + if 'mfi' in params and params['mfi']['enabled']: conditions.append(dataframe['mfi'] < params['mfi']['value']) - if params['fastd']['enabled']: + if 'fastd' in params and params['fastd']['enabled']: conditions.append(dataframe['fastd'] < params['fastd']['value']) - if params['adx']['enabled']: + if 'adx' in params and params['adx']['enabled']: conditions.append(dataframe['adx'] > params['adx']['value']) - if params['rsi']['enabled']: + if 'rsi' in params and params['rsi']['enabled']: conditions.append(dataframe['rsi'] < params['rsi']['value']) - if params['over_sar']['enabled']: + if 'over_sar' in params and params['over_sar']['enabled']: conditions.append(dataframe['close'] > dataframe['sar']) - if params['green_candle']['enabled']: + if 'green_candle' in params and params['green_candle']['enabled']: conditions.append(dataframe['close'] > dataframe['open']) - if params['uptrend_sma']['enabled']: + if 'uptrend_sma' in params and params['uptrend_sma']['enabled']: prevsma = dataframe['sma'].shift(1) conditions.append(dataframe['sma'] > prevsma) From 04010548f825b00e4646ac4da596852245f653d8 Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Thu, 18 Jan 2018 21:27:23 -0800 Subject: [PATCH 16/32] Update hyperopt params in test_strategy.py --- user_data/strategies/test_strategy.py | 98 ++++++++++++++++++++++++++- 1 file changed, 96 insertions(+), 2 deletions(-) diff --git a/user_data/strategies/test_strategy.py b/user_data/strategies/test_strategy.py index 69c0f4bcd..410fba697 100644 --- a/user_data/strategies/test_strategy.py +++ b/user_data/strategies/test_strategy.py @@ -260,12 +260,57 @@ class TestStrategy(IStrategy): :return: Dict """ space = { + 'macd_below_zero': hp.choice('macd_below_zero', [ + {'enabled': False}, + {'enabled': True} + ]), + 'mfi': hp.choice('mfi', [ + {'enabled': False}, + {'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)} + ]), + 'fastd': hp.choice('fastd', [ + {'enabled': False}, + {'enabled': True, 'value': hp.quniform('fastd-value', 10, 50, 1)} + ]), 'adx': hp.choice('adx', [ {'enabled': False}, {'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)} ]), + 'rsi': hp.choice('rsi', [ + {'enabled': False}, + {'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)} + ]), + 'uptrend_long_ema': hp.choice('uptrend_long_ema', [ + {'enabled': False}, + {'enabled': True} + ]), + 'uptrend_short_ema': hp.choice('uptrend_short_ema', [ + {'enabled': False}, + {'enabled': True} + ]), + 'over_sar': hp.choice('over_sar', [ + {'enabled': False}, + {'enabled': True} + ]), + 'green_candle': hp.choice('green_candle', [ + {'enabled': False}, + {'enabled': True} + ]), + 'uptrend_sma': hp.choice('uptrend_sma', [ + {'enabled': False}, + {'enabled': True} + ]), 'trigger': hp.choice('trigger', [ {'type': 'lower_bb'}, + {'type': 'lower_bb_tema'}, + {'type': 'faststoch10'}, + {'type': 'ao_cross_zero'}, + {'type': 'ema3_cross_ema10'}, + {'type': 'macd_cross_signal'}, + {'type': 'sar_reversal'}, + {'type': 'ht_sine'}, + {'type': 'heiken_reversal_bull'}, + {'type': 'di_cross'}, ]), 'stoploss': hp.uniform('stoploss', -0.5, -0.02), } @@ -278,12 +323,61 @@ class TestStrategy(IStrategy): def populate_buy_trend(dataframe: DataFrame) -> DataFrame: conditions = [] # GUARDS AND TRENDS - if params['adx']['enabled']: + if 'uptrend_long_ema' in params and params['uptrend_long_ema']['enabled']: + conditions.append(dataframe['ema50'] > dataframe['ema100']) + if 'macd_below_zero' in params and params['macd_below_zero']['enabled']: + conditions.append(dataframe['macd'] < 0) + if 'uptrend_short_ema' in params and params['uptrend_short_ema']['enabled']: + conditions.append(dataframe['ema5'] > dataframe['ema10']) + if 'mfi' in params and params['mfi']['enabled']: + conditions.append(dataframe['mfi'] < params['mfi']['value']) + if 'fastd' in params and params['fastd']['enabled']: + conditions.append(dataframe['fastd'] < params['fastd']['value']) + if 'adx' in params and params['adx']['enabled']: conditions.append(dataframe['adx'] > params['adx']['value']) + if 'rsi' in params and params['rsi']['enabled']: + conditions.append(dataframe['rsi'] < params['rsi']['value']) + if 'over_sar' in params and params['over_sar']['enabled']: + conditions.append(dataframe['close'] > dataframe['sar']) + if 'green_candle' in params and params['green_candle']['enabled']: + conditions.append(dataframe['close'] > dataframe['open']) + if 'uptrend_sma' in params and params['uptrend_sma']['enabled']: + prevsma = dataframe['sma'].shift(1) + conditions.append(dataframe['sma'] > prevsma) # TRIGGERS triggers = { - 'lower_bb': dataframe['tema'] <= dataframe['blower'], + 'lower_bb': ( + dataframe['close'] < dataframe['bb_lowerband'] + ), + 'lower_bb_tema': ( + dataframe['tema'] < dataframe['bb_lowerband'] + ), + 'faststoch10': (qtpylib.crossed_above( + dataframe['fastd'], 10.0 + )), + 'ao_cross_zero': (qtpylib.crossed_above( + dataframe['ao'], 0.0 + )), + 'ema3_cross_ema10': (qtpylib.crossed_above( + dataframe['ema3'], dataframe['ema10'] + )), + 'macd_cross_signal': (qtpylib.crossed_above( + dataframe['macd'], dataframe['macdsignal'] + )), + 'sar_reversal': (qtpylib.crossed_above( + dataframe['close'], dataframe['sar'] + )), + 'ht_sine': (qtpylib.crossed_above( + dataframe['htleadsine'], dataframe['htsine'] + )), + 'heiken_reversal_bull': ( + (qtpylib.crossed_above(dataframe['ha_close'], dataframe['ha_open'])) & + (dataframe['ha_low'] == dataframe['ha_open']) + ), + 'di_cross': (qtpylib.crossed_above( + dataframe['plus_di'], dataframe['minus_di'] + )), } conditions.append(triggers.get(params['trigger']['type'])) From eac6e0539224908d6cdf6da2bd5767219999bbbf Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Thu, 18 Jan 2018 22:37:06 -0800 Subject: [PATCH 17/32] Fix error when config does not have stoploss --- freqtrade/optimize/backtesting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 93ff295c5..a21bdb61a 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -222,7 +222,7 @@ def start(args): 'realistic': args.realistic_simulation, 'sell_profit_only': sell_profit_only, 'use_sell_signal': use_sell_signal, - 'stoploss': config.get('stoploss'), + 'stoploss': strategy.stoploss, 'record': args.export }) logger.info( From 1792aebaf6d4b0ecb590d80b91f60b0558adf249 Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Sat, 20 Jan 2018 00:18:55 -0800 Subject: [PATCH 18/32] Fix doc feedbacks --- docs/bot-optimization.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/bot-optimization.md b/docs/bot-optimization.md index f94bb5206..7900e6dd2 100644 --- a/docs/bot-optimization.md +++ b/docs/bot-optimization.md @@ -14,7 +14,7 @@ Since the version `0.16.0` the bot allows using custom strategy file. This is very simple. Copy paste your strategy file into the folder `user_data/strategies`. -Let guess you have a strategy file `awesome-strategy.py`: +Let assume you have a strategy file `awesome-strategy.py`: 1. Move your file into `user_data/strategies` (you should have `user_data/strategies/awesome-strategy.py` 2. Start the bot with the param `--strategy awesome-strategy` (the parameter is the name of the file without '.py') @@ -132,7 +132,7 @@ def populate_indicators(dataframe: DataFrame) -> DataFrame: **Want more indicators example?** Look into the [user_data/strategies/test_strategy.py](https://github.com/gcarq/freqtrade/blob/develop/user_data/strategies/test_strategy.py). -Then uncomment indicateur you need. +Then uncomment indicators you need. ### Where is the default strategy? From 5eb7aa07a16cea7b1b6ce46562e707704ec044bc Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Sat, 20 Jan 2018 10:05:57 -0800 Subject: [PATCH 19/32] Update bot version to 0.16.0 This commit is major core upgrade and introduce breaking change. --- freqtrade/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/__init__.py b/freqtrade/__init__.py index d1671e4c6..cd4515a3b 100644 --- a/freqtrade/__init__.py +++ b/freqtrade/__init__.py @@ -1,5 +1,5 @@ """ FreqTrade bot """ -__version__ = '0.15.1' +__version__ = '0.16.0' class DependencyException(BaseException): From 41aa8f18fbae1bb68981e2bc9f56b1c4529a491d Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Sat, 20 Jan 2018 14:40:41 -0800 Subject: [PATCH 20/32] Add ticker_interval support in strategy class --- freqtrade/misc.py | 1 - freqtrade/strategy/default_strategy.py | 3 +++ freqtrade/strategy/interface.py | 3 ++- freqtrade/strategy/strategy.py | 15 ++++++++++++++- .../tests/strategy/test_default_strategy.py | 2 ++ freqtrade/tests/strategy/test_strategy.py | 18 +++++++++++++++++- user_data/strategies/test_strategy.py | 3 +++ 7 files changed, 41 insertions(+), 4 deletions(-) diff --git a/freqtrade/misc.py b/freqtrade/misc.py index a0669ae19..f6c9af8d9 100644 --- a/freqtrade/misc.py +++ b/freqtrade/misc.py @@ -383,7 +383,6 @@ CONF_SCHEMA = { ], 'required': [ 'max_open_trades', - 'ticker_interval', 'stake_currency', 'stake_amount', 'fiat_display_currency', diff --git a/freqtrade/strategy/default_strategy.py b/freqtrade/strategy/default_strategy.py index 927148fea..95423fe70 100644 --- a/freqtrade/strategy/default_strategy.py +++ b/freqtrade/strategy/default_strategy.py @@ -27,6 +27,9 @@ class DefaultStrategy(IStrategy): # Optimal stoploss designed for the strategy stoploss = -0.10 + # Optimal ticker interval for the strategy + ticker_interval = 5 + def populate_indicators(self, dataframe: DataFrame) -> DataFrame: """ Adds several different TA indicators to the given DataFrame diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 4870b14db..70ea43a15 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -15,7 +15,8 @@ class IStrategy(ABC): """ Attributes you can use: minimal_roi -> Dict: Minimal ROI designed for the strategy - stoploss -> float: ptimal stoploss 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 """ @abstractmethod diff --git a/freqtrade/strategy/strategy.py b/freqtrade/strategy/strategy.py index 19db069ea..859a52797 100644 --- a/freqtrade/strategy/strategy.py +++ b/freqtrade/strategy/strategy.py @@ -41,10 +41,23 @@ class Strategy(object): if 'stoploss' in config: self.custom_strategy.stoploss = config['stoploss'] - self.logger.info("Override strategy \'stoploss\' with value in config file.") + self.logger.info( + "Override strategy \'stoploss\' with value in config file: {}.".format( + config['stoploss'] + ) + ) + + if 'ticker_interval' in config: + self.custom_strategy.ticker_interval = config['ticker_interval'] + self.logger.info( + "Override strategy \'ticker_interval\' with value in config file: {}.".format( + config['ticker_interval'] + ) + ) self.minimal_roi = self.custom_strategy.minimal_roi self.stoploss = self.custom_strategy.stoploss + self.ticker_interval = self.custom_strategy.ticker_interval def _load_strategy(self, strategy_name: str) -> None: """ diff --git a/freqtrade/tests/strategy/test_default_strategy.py b/freqtrade/tests/strategy/test_default_strategy.py index a197452c8..669e1ad84 100644 --- a/freqtrade/tests/strategy/test_default_strategy.py +++ b/freqtrade/tests/strategy/test_default_strategy.py @@ -18,6 +18,7 @@ def test_default_strategy_class_name(): def test_default_strategy_structure(): assert hasattr(DefaultStrategy, 'minimal_roi') assert hasattr(DefaultStrategy, 'stoploss') + assert hasattr(DefaultStrategy, 'ticker_interval') assert hasattr(DefaultStrategy, 'populate_indicators') assert hasattr(DefaultStrategy, 'populate_buy_trend') assert hasattr(DefaultStrategy, 'populate_sell_trend') @@ -30,6 +31,7 @@ def test_default_strategy(result): assert type(strategy.minimal_roi) is dict assert type(strategy.stoploss) is float + assert type(strategy.ticker_interval) is int indicators = strategy.populate_indicators(result) assert type(indicators) is DataFrame assert type(strategy.populate_buy_trend(indicators)) is DataFrame diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index c71d3bc89..2655800d5 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -108,7 +108,23 @@ def test_strategy_override_stoploss(caplog): assert strategy.stoploss == -0.5 assert ('freqtrade.strategy.strategy', logging.INFO, - 'Override strategy \'stoploss\' with value in config file.' + 'Override strategy \'stoploss\' with value in config file: -0.5.' + ) in caplog.record_tuples + + +def test_strategy_override_ticker_interval(caplog): + config = { + 'strategy': 'default_strategy', + 'ticker_interval': 60 + } + strategy = Strategy() + strategy.init(config) + + assert hasattr(strategy.custom_strategy, 'ticker_interval') + assert strategy.ticker_interval == 60 + assert ('freqtrade.strategy.strategy', + logging.INFO, + 'Override strategy \'ticker_interval\' with value in config file: 60.' ) in caplog.record_tuples diff --git a/user_data/strategies/test_strategy.py b/user_data/strategies/test_strategy.py index 410fba697..e1966c042 100644 --- a/user_data/strategies/test_strategy.py +++ b/user_data/strategies/test_strategy.py @@ -47,6 +47,9 @@ class TestStrategy(IStrategy): # This attribute will be overridden if the config file contains "stoploss" stoploss = -0.10 + # Optimal ticker interval for the strategy + ticker_interval = 5 + def populate_indicators(self, dataframe: DataFrame) -> DataFrame: """ Adds several different TA indicators to the given DataFrame From 00f1c57279240c356252ed5928ac1f41a1176e8e Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Mon, 22 Jan 2018 21:09:40 -0800 Subject: [PATCH 21/32] Add support of custom strategy into plot_dataframe.py --- scripts/plot_dataframe.py | 53 +++++++++++++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index d2c32d754..415ca1882 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -1,15 +1,18 @@ #!/usr/bin/env python3 import sys -import argparse import matplotlib # Install PYQT5 manually if you want to test this helper function matplotlib.use("Qt5Agg") import matplotlib.pyplot as plt +import talib.abstract as ta +import freqtrade.vendor.qtpylib.indicators as qtpylib +from pandas import DataFrame from freqtrade import exchange, analyze from freqtrade.misc import common_args_parser +from freqtrade.strategy.strategy import Strategy -def plot_parse_args(args ): +def plot_parse_args(args): parser = common_args_parser(description='Graph utility') parser.add_argument( '-p', '--pair', @@ -22,26 +25,28 @@ def plot_parse_args(args ): '-i', '--interval', help = 'what interval to use', dest = 'interval', - default = '5', + default = 5, type = int, ) return parser.parse_args(args) -def plot_analyzed_dataframe(args): +def plot_analyzed_dataframe(args) -> None: """ Calls analyze() and plots the returned dataframe :param pair: pair as str :return: None """ + # Init strategy + strategy = Strategy() + strategy.init({'strategy': args.strategy}) # Init Bittrex to use public API exchange._API = exchange.Bittrex({'key': '', 'secret': ''}) ticker = exchange.get_ticker_history(args.pair,args.interval) dataframe = analyze.analyze_ticker(ticker) - dataframe.loc[dataframe['buy'] == 1, 'buy_price'] = dataframe['close'] - dataframe.loc[dataframe['sell'] == 1, 'sell_price'] = dataframe['close'] + dataframe = populate_indicator(dataframe) # Two subplots sharing x axis fig, (ax1, ax2, ax3) = plt.subplots(3, sharex=True) @@ -50,7 +55,7 @@ def plot_analyzed_dataframe(args): # ax1.plot(dataframe.index.values, dataframe['sell'], 'ro', label='sell') ax1.plot(dataframe.index.values, dataframe['sma'], '--', label='SMA') ax1.plot(dataframe.index.values, dataframe['tema'], ':', label='TEMA') - ax1.plot(dataframe.index.values, dataframe['blower'], '-.', label='BB low') + ax1.plot(dataframe.index.values, dataframe['bb_lowerband'], '-.', label='BB low') ax1.plot(dataframe.index.values, dataframe['buy_price'], 'bo', label='buy') ax1.legend() @@ -70,6 +75,40 @@ def plot_analyzed_dataframe(args): plt.setp([a.get_xticklabels() for a in fig.axes[:-1]], visible=False) plt.show() +def populate_indicator(dataframe: DataFrame) -> DataFrame: + + dataframe.loc[dataframe['buy'] == 1, 'buy_price'] = dataframe['close'] + dataframe.loc[dataframe['sell'] == 1, 'sell_price'] = dataframe['close'] + + # ADX + if 'adx' not in dataframe: + dataframe['adx'] = ta.ADX(dataframe) + + # Bollinger bands + if 'bb_lowerband' not in dataframe: + bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) + dataframe['bb_lowerband'] = bollinger['lower'] + + # Stoch fast + if 'fastd' not in dataframe or 'fastk' not in dataframe: + stoch_fast = ta.STOCHF(dataframe) + dataframe['fastd'] = stoch_fast['fastd'] + dataframe['fastk'] = stoch_fast['fastk'] + + # MFI + if 'mfi' not in dataframe: + dataframe['mfi'] = ta.MFI(dataframe) + + # SMA - Simple Moving Average + if 'sma' not in dataframe: + dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) + + # TEMA - Triple Exponential Moving Average + if 'tema' not in dataframe: + dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) + + return dataframe + if __name__ == '__main__': args = plot_parse_args(sys.argv[1:]) From fcb29c6da5a1653d1fa7879d10c2b167181f87d7 Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Mon, 22 Jan 2018 21:12:48 -0800 Subject: [PATCH 22/32] Make plot_dataframe.py flake8 compliant --- scripts/plot_dataframe.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 415ca1882..50e2f77fc 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -1,32 +1,33 @@ #!/usr/bin/env python3 import sys -import matplotlib # Install PYQT5 manually if you want to test this helper function -matplotlib.use("Qt5Agg") -import matplotlib.pyplot as plt import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib from pandas import DataFrame from freqtrade import exchange, analyze from freqtrade.misc import common_args_parser from freqtrade.strategy.strategy import Strategy +import matplotlib.pyplot as plt +import matplotlib # Install PYQT5 manually if you want to test this helper function + +matplotlib.use("Qt5Agg") def plot_parse_args(args): parser = common_args_parser(description='Graph utility') parser.add_argument( '-p', '--pair', - help = 'What currency pair', - dest = 'pair', - default = 'BTC_ETH', - type = str, + help='What currency pair', + dest='pair', + default='BTC_ETH', + type=str, ) parser.add_argument( '-i', '--interval', - help = 'what interval to use', - dest = 'interval', - default = 5, - type = int, + help='what interval to use', + dest='interval', + default=5, + type=int, ) return parser.parse_args(args) @@ -43,7 +44,7 @@ def plot_analyzed_dataframe(args) -> None: # Init Bittrex to use public API exchange._API = exchange.Bittrex({'key': '', 'secret': ''}) - ticker = exchange.get_ticker_history(args.pair,args.interval) + ticker = exchange.get_ticker_history(args.pair, args.interval) dataframe = analyze.analyze_ticker(ticker) dataframe = populate_indicator(dataframe) @@ -75,6 +76,7 @@ def plot_analyzed_dataframe(args) -> None: plt.setp([a.get_xticklabels() for a in fig.axes[:-1]], visible=False) plt.show() + def populate_indicator(dataframe: DataFrame) -> DataFrame: dataframe.loc[dataframe['buy'] == 1, 'buy_price'] = dataframe['close'] From 6d8252e2b67855c9abe6b11644e93c476d65fdcb Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Mon, 22 Jan 2018 21:17:54 -0800 Subject: [PATCH 23/32] Add support of custom strategy in plot_profit.py --- scripts/plot_profit.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/scripts/plot_profit.py b/scripts/plot_profit.py index e8bdbee5c..3f753cac9 100755 --- a/scripts/plot_profit.py +++ b/scripts/plot_profit.py @@ -10,6 +10,7 @@ import freqtrade.optimize as optimize import freqtrade.misc as misc import freqtrade.exchange as exchange import freqtrade.analyze as analyze +from freqtrade.strategy.strategy import Strategy def plot_parse_args(args ): @@ -70,14 +71,21 @@ def plot_profit(args) -> None: filter_pairs = args.pair config = misc.load_config(args.config) + config.update({'strategy': args.strategy}) + + # Init strategy + strategy = Strategy() + strategy.init(config) + pairs = config['exchange']['pair_whitelist'] + if filter_pairs: filter_pairs = filter_pairs.split(',') pairs = list(set(pairs) & set(filter_pairs)) print('Filter, keep pairs %s' % pairs) tickers = optimize.load_data(args.datadir, pairs=pairs, - ticker_interval=args.ticker_interval, + ticker_interval=strategy.ticker_interval, refresh_pairs=False) dataframes = optimize.preprocess(tickers) From 5c499d16a508677acac08efb719a67cfde2937c7 Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Mon, 22 Jan 2018 21:20:17 -0800 Subject: [PATCH 24/32] Make plot_profit.py flake8 compliant --- scripts/plot_profit.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/scripts/plot_profit.py b/scripts/plot_profit.py index 3f753cac9..1bdce62a1 100755 --- a/scripts/plot_profit.py +++ b/scripts/plot_profit.py @@ -1,27 +1,24 @@ #!/usr/bin/env python3 import sys -import argparse import json import matplotlib.pyplot as plt import numpy as np import freqtrade.optimize as optimize import freqtrade.misc as misc -import freqtrade.exchange as exchange -import freqtrade.analyze as analyze from freqtrade.strategy.strategy import Strategy -def plot_parse_args(args ): +def plot_parse_args(args): parser = misc.common_args_parser('Graph utility') # FIX: perhaps delete those backtesting options that are not feasible (shows up in -h) misc.backtesting_options(parser) parser.add_argument( '-p', '--pair', - help = 'Show profits for only this pairs. Pairs are comma-separated.', - dest = 'pair', - default = None + help='Show profits for only this pairs. Pairs are comma-separated.', + dest='pair', + default=None ) return parser.parse_args(args) @@ -48,7 +45,7 @@ def make_profit_array(data, px, filter_pairs=[]): # total profits at each timeframe # to accumulated profits pa = 0 - for x in range(0,len(pg)): + for x in range(0, len(pg)): p = pg[x] # Get current total percent pa += p # Add to the accumulated percent pg[x] = pa # write back to save memory @@ -104,7 +101,7 @@ def plot_profit(args) -> None: # if max_x != n: # raise Exception('Please rerun script. Input data has different lengths %s' # %('Different pair length: %s <=> %s' %(max_x, n))) - print('max_x: %s' %(max_x)) + print('max_x: %s' % (max_x)) # We are essentially saying: # array <- sum dataframes[*]['close'] / num_items dataframes @@ -114,7 +111,7 @@ def plot_profit(args) -> None: for pair, pair_data in dataframes.items(): close = pair_data['close'] maxprice = max(close) # Normalize price to [0,1] - print('Pair %s has length %s' %(pair, len(close))) + print('Pair %s has length %s' % (pair, len(close))) for x in range(0, len(close)): avgclose[x] += close[x] / maxprice # avgclose += close @@ -126,7 +123,7 @@ def plot_profit(args) -> None: filename = 'backtest-result.json' with open(filename) as file: - data = json.load(file) + data = json.load(file) pg = make_profit_array(data, max_x, filter_pairs) # From e220ad53890dca9c5dfb3d1cccc1ab9716021b12 Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Mon, 22 Jan 2018 21:38:22 -0800 Subject: [PATCH 25/32] Remove useless USDT_BTC filename conversion --- freqtrade/optimize/__init__.py | 1 - 1 file changed, 1 deletion(-) diff --git a/freqtrade/optimize/__init__.py b/freqtrade/optimize/__init__.py index 0ac808424..df07c51c1 100644 --- a/freqtrade/optimize/__init__.py +++ b/freqtrade/optimize/__init__.py @@ -127,7 +127,6 @@ def download_backtesting_testdata(datadir: str, pair: str, interval: int = 5) -> pair=filepair, interval=interval, )) - filename = filename.replace('USDT_BTC', 'BTC_FAKEBULL') if os.path.isfile(filename): with open(filename, "rt") as fp: From 93bd63cfbed967f2a854a9ad0fc3335b9028726f Mon Sep 17 00:00:00 2001 From: Samuel Husso Date: Tue, 23 Jan 2018 08:55:22 +0200 Subject: [PATCH 26/32] get rid of / replacements, minor edit to outgoing msg --- freqtrade/main.py | 8 ++++---- freqtrade/rpc/telegram.py | 2 +- freqtrade/tests/rpc/test_rpc_telegram.py | 6 +++--- freqtrade/tests/test_main.py | 10 +++++----- 4 files changed, 13 insertions(+), 13 deletions(-) diff --git a/freqtrade/main.py b/freqtrade/main.py index 1d7f87585..de64a6bd6 100755 --- a/freqtrade/main.py +++ b/freqtrade/main.py @@ -192,7 +192,7 @@ def execute_sell(trade: Trade, limit: float) -> None: fmt_exp_profit = round(trade.calc_profit_percent(rate=limit) * 100, 2) profit_trade = trade.calc_profit(rate=limit) current_rate = exchange.get_ticker(trade.pair, False)['bid'] - current_profit = trade.calc_profit_percent(current_rate) + profit = trade.calc_profit_percent(current_rate) message = """*{exchange}:* Selling *Current Pair:* [{pair}]({pair_url}) @@ -200,16 +200,16 @@ def execute_sell(trade: Trade, limit: float) -> None: *Amount:* `{amount}` *Open Rate:* `{open_rate:.8f}` *Current Rate:* `{current_rate:.8f}` -*Current Profit:* `{current_profit:.2f}%` +*Profit:* `{profit:.2f}%` """.format( exchange=trade.exchange, - pair=trade.pair.replace('_', '/'), + pair=trade.pair, pair_url=exchange.get_pair_detail_url(trade.pair), limit=limit, open_rate=trade.open_rate, current_rate=current_rate, amount=round(trade.amount, 8), - current_profit=round(current_profit * 100, 2), + profit=round(profit * 100, 2), ) # For regular case, when the configuration exists diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 409427615..70d5a78f3 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -159,7 +159,7 @@ def _status(bot: Bot, update: Update) -> None: *Total Open Trades:* `{total_trades}` """.format( trade_id=trade.id, - pair=trade.pair.replace('_', '/'), + pair=trade.pair, pair_url=exchange.get_pair_detail_url(trade.pair), date=arrow.get(trade.open_date).humanize(), open_rate=trade.open_rate, diff --git a/freqtrade/tests/rpc/test_rpc_telegram.py b/freqtrade/tests/rpc/test_rpc_telegram.py index da616ae65..5e73fc911 100644 --- a/freqtrade/tests/rpc/test_rpc_telegram.py +++ b/freqtrade/tests/rpc/test_rpc_telegram.py @@ -107,7 +107,7 @@ def test_status_handle(default_conf, update, ticker, mocker): _status(bot=MagicMock(), update=update) assert msg_mock.call_count == 1 - assert '[BTC/ETH]' in msg_mock.call_args_list[0][0][0] + assert '[BTC_ETH]' in msg_mock.call_args_list[0][0][0] def test_status_table_handle(default_conf, update, ticker, mocker): @@ -240,7 +240,7 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker): assert rpc_mock.call_count == 2 assert 'Selling' in rpc_mock.call_args_list[-1][0][0] - assert '[BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert 'profit: 6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0] @@ -279,7 +279,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, m assert rpc_mock.call_count == 2 assert 'Selling' in rpc_mock.call_args_list[-1][0][0] - assert '[BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] diff --git a/freqtrade/tests/test_main.py b/freqtrade/tests/test_main.py index 4c20f824d..ceaa64969 100644 --- a/freqtrade/tests/test_main.py +++ b/freqtrade/tests/test_main.py @@ -545,9 +545,9 @@ def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker): assert rpc_mock.call_count == 2 assert 'Selling' in rpc_mock.call_args_list[-1][0][0] - assert '[BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in rpc_mock.call_args_list[-1][0][0] - assert 'Current Profit' in rpc_mock.call_args_list[-1][0][0] + assert 'Profit' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert 'profit: 6.11%, 0.00006126' in rpc_mock.call_args_list[-1][0][0] assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0] @@ -585,7 +585,7 @@ def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker): assert rpc_mock.call_count == 2 assert 'Selling' in rpc_mock.call_args_list[-1][0][0] - assert '[BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] @@ -618,7 +618,7 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker, ticker_sell_d assert rpc_mock.call_count == 2 assert 'Selling' in rpc_mock.call_args_list[-1][0][0] - assert '[BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] assert '0.00001044' in rpc_mock.call_args_list[-1][0][0] assert 'loss: -5.48%, -0.00005492' in rpc_mock.call_args_list[-1][0][0] @@ -649,7 +649,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, ticker_sell_up, assert rpc_mock.call_count == 2 assert 'Selling' in rpc_mock.call_args_list[-1][0][0] - assert '[BTC/ETH]' in rpc_mock.call_args_list[-1][0][0] + assert '[BTC_ETH]' in rpc_mock.call_args_list[-1][0][0] assert 'Amount' in rpc_mock.call_args_list[-1][0][0] assert '0.00001172' in rpc_mock.call_args_list[-1][0][0] assert '(profit: 6.11%, 0.00006126)' in rpc_mock.call_args_list[-1][0][0] From f4298a7323c047aa97c9537efdc40aa533890077 Mon Sep 17 00:00:00 2001 From: Gerald Lonlas Date: Mon, 22 Jan 2018 23:23:29 -0800 Subject: [PATCH 27/32] Fix dry_run db issue when open_order_id exist --- freqtrade/persistence.py | 15 +++++++++ freqtrade/tests/test_persistence.py | 49 ++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 1 deletion(-) diff --git a/freqtrade/persistence.py b/freqtrade/persistence.py index 0cac3bbe9..79903d66f 100644 --- a/freqtrade/persistence.py +++ b/freqtrade/persistence.py @@ -47,6 +47,10 @@ def init(config: dict, engine: Optional[Engine] = None) -> None: Trade.query = session.query_property() _DECL_BASE.metadata.create_all(engine) + # Clean dry_run DB + if _CONF.get('dry_run', False) and _CONF.get('dry_run_db', False): + clean_dry_run_db() + def cleanup() -> None: """ @@ -56,6 +60,17 @@ def cleanup() -> None: Trade.session.flush() +def clean_dry_run_db() -> None: + """ + Remove open_order_id from a Dry_run DB + :return: None + """ + for trade in Trade.query.filter(Trade.open_order_id.isnot(None)).all(): + # Check we are updating only a dry_run order not a prod one + if 'dry_run' in trade.open_order_id: + trade.open_order_id = None + + class Trade(_DECL_BASE): __tablename__ = 'trades' diff --git a/freqtrade/tests/test_persistence.py b/freqtrade/tests/test_persistence.py index 70797f960..bfc16064c 100644 --- a/freqtrade/tests/test_persistence.py +++ b/freqtrade/tests/test_persistence.py @@ -4,7 +4,7 @@ import os import pytest from freqtrade.exchange import Exchanges -from freqtrade.persistence import Trade, init +from freqtrade.persistence import Trade, init, clean_dry_run_db def test_init_create_session(default_conf, mocker): @@ -310,3 +310,50 @@ def test_calc_profit_percent(limit_buy_order, limit_sell_order): # Test with a custom fee rate on the close trade assert trade.calc_profit_percent(fee=0.003) == 0.0614782 + + +def test_clean_dry_run_db(default_conf, mocker): + init(default_conf) + + # Simulate dry_run entries + trade = Trade( + pair='BTC_ETH', + stake_amount=0.001, + amount=123.0, + fee=0.0025, + open_rate=0.123, + exchange='BITTREX', + open_order_id='dry_run_buy_12345' + ) + Trade.session.add(trade) + + trade = Trade( + pair='BTC_ETC', + stake_amount=0.001, + amount=123.0, + fee=0.0025, + open_rate=0.123, + exchange='BITTREX', + open_order_id='dry_run_sell_12345' + ) + Trade.session.add(trade) + + # Simulate prod entry + trade = Trade( + pair='BTC_ETC', + stake_amount=0.001, + amount=123.0, + fee=0.0025, + open_rate=0.123, + exchange='BITTREX', + open_order_id='prod_buy_12345' + ) + Trade.session.add(trade) + + # We have 3 entries: 2 dry_run, 1 prod + assert len(Trade.query.filter(Trade.open_order_id.isnot(None)).all()) == 3 + + clean_dry_run_db() + + # We have now only the prod + assert len(Trade.query.filter(Trade.open_order_id.isnot(None)).all()) == 1 From c400d15ed1e5c8468f3ff94c6a0929819b507a22 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Tue, 23 Jan 2018 16:56:12 +0200 Subject: [PATCH 28/32] rip out hyperopt things from strategy, add indicator populating to hyperopt --- freqtrade/optimize/hyperopt.py | 285 +++++++++++++++++- freqtrade/strategy/default_strategy.py | 137 --------- freqtrade/strategy/interface.py | 13 - freqtrade/strategy/strategy.py | 12 - .../tests/strategy/test_default_strategy.py | 4 - freqtrade/tests/strategy/test_strategy.py | 8 - 6 files changed, 272 insertions(+), 187 deletions(-) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 3e174d892..62dcdf68f 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -3,21 +3,28 @@ import json import logging -import sys +import os import pickle import signal -import os +import sys +from functools import reduce from math import exp from operator import itemgetter +from typing import Dict, List -from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, space_eval, tpe +import numpy +import talib.abstract as ta +from hyperopt import STATUS_FAIL, STATUS_OK, Trials, fmin, hp, space_eval, tpe from hyperopt.mongoexp import MongoTrials from pandas import DataFrame -from freqtrade import main, misc # noqa -from freqtrade import exchange, optimize +import freqtrade.vendor.qtpylib.indicators as qtpylib +# Monkey patch config +from freqtrade import main # noqa; noqa +from freqtrade import exchange, misc, optimize from freqtrade.exchange import Bittrex from freqtrade.misc import load_config +from freqtrade.optimize import backtesting from freqtrade.optimize.backtesting import backtest from freqtrade.strategy.strategy import Strategy from user_data.hyperopt_conf import hyperopt_optimize_conf @@ -51,11 +58,129 @@ OPTIMIZE_CONFIG = hyperopt_optimize_conf() TRIALS_FILE = os.path.join('user_data', 'hyperopt_trials.pickle') TRIALS = Trials() -# Monkey patch config -from freqtrade import main # noqa main._CONF = OPTIMIZE_CONFIG +def populate_indicators(dataframe: DataFrame) -> DataFrame: + """ + Adds several different TA indicators to the given DataFrame + """ + dataframe['adx'] = ta.ADX(dataframe) + dataframe['ao'] = qtpylib.awesome_oscillator(dataframe) + dataframe['cci'] = ta.CCI(dataframe) + macd = ta.MACD(dataframe) + dataframe['macd'] = macd['macd'] + dataframe['macdsignal'] = macd['macdsignal'] + dataframe['macdhist'] = macd['macdhist'] + dataframe['mfi'] = ta.MFI(dataframe) + dataframe['minus_dm'] = ta.MINUS_DM(dataframe) + dataframe['minus_di'] = ta.MINUS_DI(dataframe) + dataframe['plus_dm'] = ta.PLUS_DM(dataframe) + dataframe['plus_di'] = ta.PLUS_DI(dataframe) + dataframe['roc'] = ta.ROC(dataframe) + dataframe['rsi'] = ta.RSI(dataframe) + # Inverse Fisher transform on RSI, values [-1.0, 1.0] (https://goo.gl/2JGGoy) + rsi = 0.1 * (dataframe['rsi'] - 50) + dataframe['fisher_rsi'] = (numpy.exp(2 * rsi) - 1) / (numpy.exp(2 * rsi) + 1) + # Inverse Fisher transform on RSI normalized, value [0.0, 100.0] (https://goo.gl/2JGGoy) + dataframe['fisher_rsi_norma'] = 50 * (dataframe['fisher_rsi'] + 1) + # Stoch + stoch = ta.STOCH(dataframe) + dataframe['slowd'] = stoch['slowd'] + dataframe['slowk'] = stoch['slowk'] + # Stoch fast + stoch_fast = ta.STOCHF(dataframe) + dataframe['fastd'] = stoch_fast['fastd'] + dataframe['fastk'] = stoch_fast['fastk'] + # Stoch RSI + stoch_rsi = ta.STOCHRSI(dataframe) + dataframe['fastd_rsi'] = stoch_rsi['fastd'] + dataframe['fastk_rsi'] = stoch_rsi['fastk'] + # Bollinger bands + bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) + dataframe['bb_lowerband'] = bollinger['lower'] + dataframe['bb_middleband'] = bollinger['mid'] + dataframe['bb_upperband'] = bollinger['upper'] + # EMA - Exponential Moving Average + dataframe['ema3'] = ta.EMA(dataframe, timeperiod=3) + dataframe['ema5'] = ta.EMA(dataframe, timeperiod=5) + dataframe['ema10'] = ta.EMA(dataframe, timeperiod=10) + dataframe['ema50'] = ta.EMA(dataframe, timeperiod=50) + dataframe['ema100'] = ta.EMA(dataframe, timeperiod=100) + # SAR Parabolic + dataframe['sar'] = ta.SAR(dataframe) + # SMA - Simple Moving Average + dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) + # TEMA - Triple Exponential Moving Average + dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) + # Hilbert Transform Indicator - SineWave + hilbert = ta.HT_SINE(dataframe) + dataframe['htsine'] = hilbert['sine'] + dataframe['htleadsine'] = hilbert['leadsine'] + + # Pattern Recognition - Bullish candlestick patterns + # ------------------------------------ + """ + # Hammer: values [0, 100] + dataframe['CDLHAMMER'] = ta.CDLHAMMER(dataframe) + # Inverted Hammer: values [0, 100] + dataframe['CDLINVERTEDHAMMER'] = ta.CDLINVERTEDHAMMER(dataframe) + # Dragonfly Doji: values [0, 100] + dataframe['CDLDRAGONFLYDOJI'] = ta.CDLDRAGONFLYDOJI(dataframe) + # Piercing Line: values [0, 100] + dataframe['CDLPIERCING'] = ta.CDLPIERCING(dataframe) # values [0, 100] + # Morningstar: values [0, 100] + dataframe['CDLMORNINGSTAR'] = ta.CDLMORNINGSTAR(dataframe) # values [0, 100] + # Three White Soldiers: values [0, 100] + dataframe['CDL3WHITESOLDIERS'] = ta.CDL3WHITESOLDIERS(dataframe) # values [0, 100] + """ + + # Pattern Recognition - Bearish candlestick patterns + # ------------------------------------ + """ + # Hanging Man: values [0, 100] + dataframe['CDLHANGINGMAN'] = ta.CDLHANGINGMAN(dataframe) + # Shooting Star: values [0, 100] + dataframe['CDLSHOOTINGSTAR'] = ta.CDLSHOOTINGSTAR(dataframe) + # Gravestone Doji: values [0, 100] + dataframe['CDLGRAVESTONEDOJI'] = ta.CDLGRAVESTONEDOJI(dataframe) + # Dark Cloud Cover: values [0, 100] + dataframe['CDLDARKCLOUDCOVER'] = ta.CDLDARKCLOUDCOVER(dataframe) + # Evening Doji Star: values [0, 100] + dataframe['CDLEVENINGDOJISTAR'] = ta.CDLEVENINGDOJISTAR(dataframe) + # Evening Star: values [0, 100] + dataframe['CDLEVENINGSTAR'] = ta.CDLEVENINGSTAR(dataframe) + """ + + # Pattern Recognition - Bullish/Bearish candlestick patterns + # ------------------------------------ + """ + # Three Line Strike: values [0, -100, 100] + dataframe['CDL3LINESTRIKE'] = ta.CDL3LINESTRIKE(dataframe) + # Spinning Top: values [0, -100, 100] + dataframe['CDLSPINNINGTOP'] = ta.CDLSPINNINGTOP(dataframe) # values [0, -100, 100] + # Engulfing: values [0, -100, 100] + dataframe['CDLENGULFING'] = ta.CDLENGULFING(dataframe) # values [0, -100, 100] + # Harami: values [0, -100, 100] + dataframe['CDLHARAMI'] = ta.CDLHARAMI(dataframe) # values [0, -100, 100] + # Three Outside Up/Down: values [0, -100, 100] + dataframe['CDL3OUTSIDE'] = ta.CDL3OUTSIDE(dataframe) # values [0, -100, 100] + # Three Inside Up/Down: values [0, -100, 100] + dataframe['CDL3INSIDE'] = ta.CDL3INSIDE(dataframe) # values [0, -100, 100] + """ + + # Chart type + # ------------------------------------ + # Heikinashi stategy + heikinashi = qtpylib.heikinashi(dataframe) + dataframe['ha_open'] = heikinashi['open'] + dataframe['ha_close'] = heikinashi['close'] + dataframe['ha_high'] = heikinashi['high'] + dataframe['ha_low'] = heikinashi['low'] + + return dataframe + + def save_trials(trials, trials_path=TRIALS_FILE): """Save hyperopt trials to file""" logger.info('Saving Trials to \'{}\''.format(trials_path)) @@ -100,13 +225,146 @@ def calculate_loss(total_profit: float, trade_count: int, trade_duration: float) return trade_loss + profit_loss + duration_loss +def hyperopt_space() -> List[Dict]: + """ + Define your Hyperopt space for searching strategy parameters + """ + space = { + 'macd_below_zero': hp.choice('macd_below_zero', [ + {'enabled': False}, + {'enabled': True} + ]), + 'mfi': hp.choice('mfi', [ + {'enabled': False}, + {'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)} + ]), + 'fastd': hp.choice('fastd', [ + {'enabled': False}, + {'enabled': True, 'value': hp.quniform('fastd-value', 10, 50, 1)} + ]), + 'adx': hp.choice('adx', [ + {'enabled': False}, + {'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)} + ]), + 'rsi': hp.choice('rsi', [ + {'enabled': False}, + {'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)} + ]), + 'uptrend_long_ema': hp.choice('uptrend_long_ema', [ + {'enabled': False}, + {'enabled': True} + ]), + 'uptrend_short_ema': hp.choice('uptrend_short_ema', [ + {'enabled': False}, + {'enabled': True} + ]), + 'over_sar': hp.choice('over_sar', [ + {'enabled': False}, + {'enabled': True} + ]), + 'green_candle': hp.choice('green_candle', [ + {'enabled': False}, + {'enabled': True} + ]), + 'uptrend_sma': hp.choice('uptrend_sma', [ + {'enabled': False}, + {'enabled': True} + ]), + 'trigger': hp.choice('trigger', [ + {'type': 'lower_bb'}, + {'type': 'lower_bb_tema'}, + {'type': 'faststoch10'}, + {'type': 'ao_cross_zero'}, + {'type': 'ema3_cross_ema10'}, + {'type': 'macd_cross_signal'}, + {'type': 'sar_reversal'}, + {'type': 'ht_sine'}, + {'type': 'heiken_reversal_bull'}, + {'type': 'di_cross'}, + ]), + 'stoploss': hp.uniform('stoploss', -0.5, -0.02), + } + return space + + +def buy_strategy_generator(params) -> None: + """ + Define the buy strategy parameters to be used by hyperopt + """ + def populate_buy_trend(dataframe: DataFrame) -> DataFrame: + conditions = [] + # GUARDS AND TRENDS + if 'uptrend_long_ema' in params and params['uptrend_long_ema']['enabled']: + conditions.append(dataframe['ema50'] > dataframe['ema100']) + if 'macd_below_zero' in params and params['macd_below_zero']['enabled']: + conditions.append(dataframe['macd'] < 0) + if 'uptrend_short_ema' in params and params['uptrend_short_ema']['enabled']: + conditions.append(dataframe['ema5'] > dataframe['ema10']) + if 'mfi' in params and params['mfi']['enabled']: + conditions.append(dataframe['mfi'] < params['mfi']['value']) + if 'fastd' in params and params['fastd']['enabled']: + conditions.append(dataframe['fastd'] < params['fastd']['value']) + if 'adx' in params and params['adx']['enabled']: + conditions.append(dataframe['adx'] > params['adx']['value']) + if 'rsi' in params and params['rsi']['enabled']: + conditions.append(dataframe['rsi'] < params['rsi']['value']) + if 'over_sar' in params and params['over_sar']['enabled']: + conditions.append(dataframe['close'] > dataframe['sar']) + if 'green_candle' in params and params['green_candle']['enabled']: + conditions.append(dataframe['close'] > dataframe['open']) + if 'uptrend_sma' in params and params['uptrend_sma']['enabled']: + prevsma = dataframe['sma'].shift(1) + conditions.append(dataframe['sma'] > prevsma) + + # TRIGGERS + triggers = { + 'lower_bb': ( + dataframe['close'] < dataframe['bb_lowerband'] + ), + 'lower_bb_tema': ( + dataframe['tema'] < dataframe['bb_lowerband'] + ), + 'faststoch10': (qtpylib.crossed_above( + dataframe['fastd'], 10.0 + )), + 'ao_cross_zero': (qtpylib.crossed_above( + dataframe['ao'], 0.0 + )), + 'ema3_cross_ema10': (qtpylib.crossed_above( + dataframe['ema3'], dataframe['ema10'] + )), + 'macd_cross_signal': (qtpylib.crossed_above( + dataframe['macd'], dataframe['macdsignal'] + )), + 'sar_reversal': (qtpylib.crossed_above( + dataframe['close'], dataframe['sar'] + )), + 'ht_sine': (qtpylib.crossed_above( + dataframe['htleadsine'], dataframe['htsine'] + )), + 'heiken_reversal_bull': ( + (qtpylib.crossed_above(dataframe['ha_close'], dataframe['ha_open'])) & + (dataframe['ha_low'] == dataframe['ha_open']) + ), + 'di_cross': (qtpylib.crossed_above( + dataframe['plus_di'], dataframe['minus_di'] + )), + } + conditions.append(triggers.get(params['trigger']['type'])) + + dataframe.loc[ + reduce(lambda x, y: x & y, conditions), + 'buy'] = 1 + + return dataframe + + return populate_buy_trend + + def optimizer(params): global _CURRENT_TRIES - from freqtrade.optimize import backtesting - - strategy = Strategy() - backtesting.populate_buy_trend = strategy.buy_strategy_generator(params) + backtesting.populate_buy_trend = buy_strategy_generator(params) results = backtest({'stake_amount': OPTIMIZE_CONFIG['stake_amount'], 'processed': PROCESSED, @@ -179,6 +437,7 @@ def start(args): data = optimize.load_data(args.datadir, pairs=pairs, ticker_interval=args.ticker_interval, timerange=timerange) + optimize.populate_indicators = populate_indicators PROCESSED = optimize.tickerdata_to_dataframe(data) if args.mongodb: @@ -203,7 +462,7 @@ def start(args): try: best_parameters = fmin( fn=optimizer, - space=strategy.hyperopt_space(), + space=hyperopt_space(), algo=tpe.suggest, max_evals=TOTAL_TRIES, trials=TRIALS @@ -220,7 +479,7 @@ def start(args): # Improve best parameter logging display if best_parameters: best_parameters = space_eval( - strategy.hyperopt_space(), + hyperopt_space(), best_parameters ) diff --git a/freqtrade/strategy/default_strategy.py b/freqtrade/strategy/default_strategy.py index 95423fe70..c89b20527 100644 --- a/freqtrade/strategy/default_strategy.py +++ b/freqtrade/strategy/default_strategy.py @@ -2,9 +2,6 @@ import talib.abstract as ta import freqtrade.vendor.qtpylib.indicators as qtpylib from freqtrade.strategy.interface import IStrategy from pandas import DataFrame -from hyperopt import hp -from functools import reduce -from typing import Dict, List class_name = 'DefaultStrategy' @@ -239,137 +236,3 @@ class DefaultStrategy(IStrategy): ), 'sell'] = 1 return dataframe - - def hyperopt_space(self) -> List[Dict]: - """ - Define your Hyperopt space for the strategy - """ - space = { - 'macd_below_zero': hp.choice('macd_below_zero', [ - {'enabled': False}, - {'enabled': True} - ]), - 'mfi': hp.choice('mfi', [ - {'enabled': False}, - {'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)} - ]), - 'fastd': hp.choice('fastd', [ - {'enabled': False}, - {'enabled': True, 'value': hp.quniform('fastd-value', 10, 50, 1)} - ]), - 'adx': hp.choice('adx', [ - {'enabled': False}, - {'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)} - ]), - 'rsi': hp.choice('rsi', [ - {'enabled': False}, - {'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)} - ]), - 'uptrend_long_ema': hp.choice('uptrend_long_ema', [ - {'enabled': False}, - {'enabled': True} - ]), - 'uptrend_short_ema': hp.choice('uptrend_short_ema', [ - {'enabled': False}, - {'enabled': True} - ]), - 'over_sar': hp.choice('over_sar', [ - {'enabled': False}, - {'enabled': True} - ]), - 'green_candle': hp.choice('green_candle', [ - {'enabled': False}, - {'enabled': True} - ]), - 'uptrend_sma': hp.choice('uptrend_sma', [ - {'enabled': False}, - {'enabled': True} - ]), - 'trigger': hp.choice('trigger', [ - {'type': 'lower_bb'}, - {'type': 'lower_bb_tema'}, - {'type': 'faststoch10'}, - {'type': 'ao_cross_zero'}, - {'type': 'ema3_cross_ema10'}, - {'type': 'macd_cross_signal'}, - {'type': 'sar_reversal'}, - {'type': 'ht_sine'}, - {'type': 'heiken_reversal_bull'}, - {'type': 'di_cross'}, - ]), - 'stoploss': hp.uniform('stoploss', -0.5, -0.02), - } - return space - - def buy_strategy_generator(self, params) -> None: - """ - Define the buy strategy parameters to be used by hyperopt - """ - def populate_buy_trend(dataframe: DataFrame) -> DataFrame: - conditions = [] - # GUARDS AND TRENDS - if 'uptrend_long_ema' in params and params['uptrend_long_ema']['enabled']: - conditions.append(dataframe['ema50'] > dataframe['ema100']) - if 'macd_below_zero' in params and params['macd_below_zero']['enabled']: - conditions.append(dataframe['macd'] < 0) - if 'uptrend_short_ema' in params and params['uptrend_short_ema']['enabled']: - conditions.append(dataframe['ema5'] > dataframe['ema10']) - if 'mfi' in params and params['mfi']['enabled']: - conditions.append(dataframe['mfi'] < params['mfi']['value']) - if 'fastd' in params and params['fastd']['enabled']: - conditions.append(dataframe['fastd'] < params['fastd']['value']) - if 'adx' in params and params['adx']['enabled']: - conditions.append(dataframe['adx'] > params['adx']['value']) - if 'rsi' in params and params['rsi']['enabled']: - conditions.append(dataframe['rsi'] < params['rsi']['value']) - if 'over_sar' in params and params['over_sar']['enabled']: - conditions.append(dataframe['close'] > dataframe['sar']) - if 'green_candle' in params and params['green_candle']['enabled']: - conditions.append(dataframe['close'] > dataframe['open']) - if 'uptrend_sma' in params and params['uptrend_sma']['enabled']: - prevsma = dataframe['sma'].shift(1) - conditions.append(dataframe['sma'] > prevsma) - - # TRIGGERS - triggers = { - 'lower_bb': ( - dataframe['close'] < dataframe['bb_lowerband'] - ), - 'lower_bb_tema': ( - dataframe['tema'] < dataframe['bb_lowerband'] - ), - 'faststoch10': (qtpylib.crossed_above( - dataframe['fastd'], 10.0 - )), - 'ao_cross_zero': (qtpylib.crossed_above( - dataframe['ao'], 0.0 - )), - 'ema3_cross_ema10': (qtpylib.crossed_above( - dataframe['ema3'], dataframe['ema10'] - )), - 'macd_cross_signal': (qtpylib.crossed_above( - dataframe['macd'], dataframe['macdsignal'] - )), - 'sar_reversal': (qtpylib.crossed_above( - dataframe['close'], dataframe['sar'] - )), - 'ht_sine': (qtpylib.crossed_above( - dataframe['htleadsine'], dataframe['htsine'] - )), - 'heiken_reversal_bull': ( - (qtpylib.crossed_above(dataframe['ha_close'], dataframe['ha_open'])) & - (dataframe['ha_low'] == dataframe['ha_open']) - ), - 'di_cross': (qtpylib.crossed_above( - dataframe['plus_di'], dataframe['minus_di'] - )), - } - conditions.append(triggers.get(params['trigger']['type'])) - - dataframe.loc[ - reduce(lambda x, y: x & y, conditions), - 'buy'] = 1 - - return dataframe - - return populate_buy_trend diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py index 70ea43a15..ce5f08cd2 100644 --- a/freqtrade/strategy/interface.py +++ b/freqtrade/strategy/interface.py @@ -1,6 +1,5 @@ from abc import ABC, abstractmethod from pandas import DataFrame -from typing import Dict class IStrategy(ABC): @@ -43,15 +42,3 @@ class IStrategy(ABC): :param dataframe: DataFrame :return: DataFrame with buy column """ - - @abstractmethod - def hyperopt_space(self) -> Dict: - """ - Define your Hyperopt space for the strategy - """ - - @abstractmethod - def buy_strategy_generator(self, params) -> None: - """ - Define the buy strategy parameters to be used by hyperopt - """ diff --git a/freqtrade/strategy/strategy.py b/freqtrade/strategy/strategy.py index 859a52797..2545e378c 100644 --- a/freqtrade/strategy/strategy.py +++ b/freqtrade/strategy/strategy.py @@ -164,15 +164,3 @@ class Strategy(object): :return: DataFrame with buy column """ return self.custom_strategy.populate_sell_trend(dataframe) - - def hyperopt_space(self) -> Dict: - """ - Define your Hyperopt space for the strategy - """ - return self.custom_strategy.hyperopt_space() - - def buy_strategy_generator(self, params) -> None: - """ - Define the buy strategy parameters to be used by hyperopt - """ - return self.custom_strategy.buy_strategy_generator(params) diff --git a/freqtrade/tests/strategy/test_default_strategy.py b/freqtrade/tests/strategy/test_default_strategy.py index 669e1ad84..f23c1fa48 100644 --- a/freqtrade/tests/strategy/test_default_strategy.py +++ b/freqtrade/tests/strategy/test_default_strategy.py @@ -22,8 +22,6 @@ def test_default_strategy_structure(): assert hasattr(DefaultStrategy, 'populate_indicators') assert hasattr(DefaultStrategy, 'populate_buy_trend') assert hasattr(DefaultStrategy, 'populate_sell_trend') - assert hasattr(DefaultStrategy, 'hyperopt_space') - assert hasattr(DefaultStrategy, 'buy_strategy_generator') def test_default_strategy(result): @@ -36,5 +34,3 @@ def test_default_strategy(result): assert type(indicators) is DataFrame assert type(strategy.populate_buy_trend(indicators)) is DataFrame assert type(strategy.populate_sell_trend(indicators)) is DataFrame - assert type(strategy.hyperopt_space()) is dict - assert callable(strategy.buy_strategy_generator({})) diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index 2655800d5..79f045a6d 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -33,8 +33,6 @@ def test_strategy_structure(): assert hasattr(Strategy, 'populate_indicators') assert hasattr(Strategy, 'populate_buy_trend') assert hasattr(Strategy, 'populate_sell_trend') - assert hasattr(Strategy, 'hyperopt_space') - assert hasattr(Strategy, 'buy_strategy_generator') def test_load_strategy(result): @@ -71,12 +69,6 @@ def test_strategy(result): dataframe = strategy.populate_sell_trend(strategy.populate_indicators(result)) assert 'sell' in dataframe.columns - assert hasattr(strategy.custom_strategy, 'hyperopt_space') - assert 'adx' in strategy.hyperopt_space() - - assert hasattr(strategy.custom_strategy, 'buy_strategy_generator') - assert callable(strategy.buy_strategy_generator({})) - def test_strategy_override_minimal_roi(caplog): config = { From 30abebfe65badbd5083f0d0b7b07a082afad7667 Mon Sep 17 00:00:00 2001 From: Janne Sinivirta Date: Tue, 23 Jan 2018 17:01:13 +0200 Subject: [PATCH 29/32] remove hyperopt things from test_strategy --- user_data/strategies/test_strategy.py | 151 +------------------------- 1 file changed, 2 insertions(+), 149 deletions(-) diff --git a/user_data/strategies/test_strategy.py b/user_data/strategies/test_strategy.py index e1966c042..a164812c4 100644 --- a/user_data/strategies/test_strategy.py +++ b/user_data/strategies/test_strategy.py @@ -1,9 +1,6 @@ # --- Do not remove these libs --- from freqtrade.strategy.interface import IStrategy -from typing import Dict, List -from hyperopt import hp -from functools import reduce from pandas import DataFrame # -------------------------------- @@ -122,15 +119,6 @@ class TestStrategy(IStrategy): # Overlap Studies # ------------------------------------ - """ - # Previous Bollinger bands - # Because ta.BBANDS implementation is broken with small numbers, it actually - # returns middle band for all the three bands. Switch to qtpylib.bollinger_bands - # and use middle band instead. - - dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband'] - """ - # Bollinger bands bollinger = qtpylib.bollinger_bands(qtpylib.typical_price(dataframe), window=20, stds=2) dataframe['bb_lowerband'] = bollinger['lower'] @@ -235,7 +223,7 @@ class TestStrategy(IStrategy): dataframe.loc[ ( (dataframe['adx'] > 30) & - (dataframe['tema'] <= dataframe['blower']) & + (dataframe['tema'] <= dataframe['bb_middleband']) & (dataframe['tema'] > dataframe['tema'].shift(1)) ), 'buy'] = 1 @@ -251,143 +239,8 @@ class TestStrategy(IStrategy): dataframe.loc[ ( (dataframe['adx'] > 70) & - (dataframe['tema'] > dataframe['blower']) & + (dataframe['tema'] > dataframe['bb_middleband']) & (dataframe['tema'] < dataframe['tema'].shift(1)) ), 'sell'] = 1 return dataframe - - def hyperopt_space(self) -> List[Dict]: - """ - Define your Hyperopt space for the strategy - :return: Dict - """ - space = { - 'macd_below_zero': hp.choice('macd_below_zero', [ - {'enabled': False}, - {'enabled': True} - ]), - 'mfi': hp.choice('mfi', [ - {'enabled': False}, - {'enabled': True, 'value': hp.quniform('mfi-value', 5, 25, 1)} - ]), - 'fastd': hp.choice('fastd', [ - {'enabled': False}, - {'enabled': True, 'value': hp.quniform('fastd-value', 10, 50, 1)} - ]), - 'adx': hp.choice('adx', [ - {'enabled': False}, - {'enabled': True, 'value': hp.quniform('adx-value', 15, 50, 1)} - ]), - 'rsi': hp.choice('rsi', [ - {'enabled': False}, - {'enabled': True, 'value': hp.quniform('rsi-value', 20, 40, 1)} - ]), - 'uptrend_long_ema': hp.choice('uptrend_long_ema', [ - {'enabled': False}, - {'enabled': True} - ]), - 'uptrend_short_ema': hp.choice('uptrend_short_ema', [ - {'enabled': False}, - {'enabled': True} - ]), - 'over_sar': hp.choice('over_sar', [ - {'enabled': False}, - {'enabled': True} - ]), - 'green_candle': hp.choice('green_candle', [ - {'enabled': False}, - {'enabled': True} - ]), - 'uptrend_sma': hp.choice('uptrend_sma', [ - {'enabled': False}, - {'enabled': True} - ]), - 'trigger': hp.choice('trigger', [ - {'type': 'lower_bb'}, - {'type': 'lower_bb_tema'}, - {'type': 'faststoch10'}, - {'type': 'ao_cross_zero'}, - {'type': 'ema3_cross_ema10'}, - {'type': 'macd_cross_signal'}, - {'type': 'sar_reversal'}, - {'type': 'ht_sine'}, - {'type': 'heiken_reversal_bull'}, - {'type': 'di_cross'}, - ]), - 'stoploss': hp.uniform('stoploss', -0.5, -0.02), - } - return space - - def buy_strategy_generator(self, params) -> None: - """ - Define the buy strategy parameters to be used by hyperopt - """ - def populate_buy_trend(dataframe: DataFrame) -> DataFrame: - conditions = [] - # GUARDS AND TRENDS - if 'uptrend_long_ema' in params and params['uptrend_long_ema']['enabled']: - conditions.append(dataframe['ema50'] > dataframe['ema100']) - if 'macd_below_zero' in params and params['macd_below_zero']['enabled']: - conditions.append(dataframe['macd'] < 0) - if 'uptrend_short_ema' in params and params['uptrend_short_ema']['enabled']: - conditions.append(dataframe['ema5'] > dataframe['ema10']) - if 'mfi' in params and params['mfi']['enabled']: - conditions.append(dataframe['mfi'] < params['mfi']['value']) - if 'fastd' in params and params['fastd']['enabled']: - conditions.append(dataframe['fastd'] < params['fastd']['value']) - if 'adx' in params and params['adx']['enabled']: - conditions.append(dataframe['adx'] > params['adx']['value']) - if 'rsi' in params and params['rsi']['enabled']: - conditions.append(dataframe['rsi'] < params['rsi']['value']) - if 'over_sar' in params and params['over_sar']['enabled']: - conditions.append(dataframe['close'] > dataframe['sar']) - if 'green_candle' in params and params['green_candle']['enabled']: - conditions.append(dataframe['close'] > dataframe['open']) - if 'uptrend_sma' in params and params['uptrend_sma']['enabled']: - prevsma = dataframe['sma'].shift(1) - conditions.append(dataframe['sma'] > prevsma) - - # TRIGGERS - triggers = { - 'lower_bb': ( - dataframe['close'] < dataframe['bb_lowerband'] - ), - 'lower_bb_tema': ( - dataframe['tema'] < dataframe['bb_lowerband'] - ), - 'faststoch10': (qtpylib.crossed_above( - dataframe['fastd'], 10.0 - )), - 'ao_cross_zero': (qtpylib.crossed_above( - dataframe['ao'], 0.0 - )), - 'ema3_cross_ema10': (qtpylib.crossed_above( - dataframe['ema3'], dataframe['ema10'] - )), - 'macd_cross_signal': (qtpylib.crossed_above( - dataframe['macd'], dataframe['macdsignal'] - )), - 'sar_reversal': (qtpylib.crossed_above( - dataframe['close'], dataframe['sar'] - )), - 'ht_sine': (qtpylib.crossed_above( - dataframe['htleadsine'], dataframe['htsine'] - )), - 'heiken_reversal_bull': ( - (qtpylib.crossed_above(dataframe['ha_close'], dataframe['ha_open'])) & - (dataframe['ha_low'] == dataframe['ha_open']) - ), - 'di_cross': (qtpylib.crossed_above( - dataframe['plus_di'], dataframe['minus_di'] - )), - } - conditions.append(triggers.get(params['trigger']['type'])) - - dataframe.loc[ - reduce(lambda x, y: x & y, conditions), - 'buy'] = 1 - - return dataframe - - return populate_buy_trend From c83ac5271deae368907e658fe9490328d7e15986 Mon Sep 17 00:00:00 2001 From: pyup-bot Date: Tue, 23 Jan 2018 20:38:41 +0100 Subject: [PATCH 30/32] Update pymarketcap from 3.3.148 to 3.3.150 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d37312268..b574c76ac 100644 --- a/requirements.txt +++ b/requirements.txt @@ -19,7 +19,7 @@ hyperopt==0.1 # do not upgrade networkx before this is fixed https://github.com/hyperopt/hyperopt/issues/325 networkx==1.11 tabulate==0.8.2 -pymarketcap==3.3.148 +pymarketcap==3.3.150 # Required for plotting data #matplotlib==2.1.0 From a14d9d35c7f07ac7bb7059f7bae30976fd70fcda Mon Sep 17 00:00:00 2001 From: kryofly Date: Wed, 24 Jan 2018 10:32:52 +0100 Subject: [PATCH 31/32] tests: run backtest single --- freqtrade/tests/optimize/test_backtesting.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 2872df83f..9e2007aa4 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -9,6 +9,13 @@ from freqtrade.exchange import Bittrex from freqtrade.optimize import preprocess from freqtrade.optimize.backtesting import backtest, generate_text_table, get_timeframe import freqtrade.optimize.backtesting as backtesting +from freqtrade.strategy.strategy import Strategy + + +def setup_strategy(): + strategy = Strategy() + strategy.init({'strategy': 'default_strategy'}) + return strategy def trim_dictlist(dl, num): @@ -46,6 +53,7 @@ def test_get_timeframe(): def test_backtest(default_conf, mocker): + setup_strategy() mocker.patch.dict('freqtrade.main._CONF', default_conf) exchange._API = Bittrex({'key': '', 'secret': ''}) @@ -61,6 +69,7 @@ def test_backtest(default_conf, mocker): def test_backtest_1min_ticker_interval(default_conf, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) exchange._API = Bittrex({'key': '', 'secret': ''}) + setup_strategy() # Run a backtesting for an exiting 5min ticker_interval data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST']) @@ -116,6 +125,7 @@ def load_data_test(what): def simple_backtest(config, contour, num_results): + setup_strategy() data = load_data_test(contour) processed = optimize.preprocess(data) assert isinstance(processed, dict) @@ -132,6 +142,7 @@ def simple_backtest(config, contour, num_results): def test_backtest2(default_conf, mocker): + setup_strategy() mocker.patch.dict('freqtrade.main._CONF', default_conf) data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH']) data = trim_dictlist(data, -200) @@ -143,6 +154,7 @@ def test_backtest2(default_conf, mocker): def test_processed(default_conf, mocker): + setup_strategy() mocker.patch.dict('freqtrade.main._CONF', default_conf) dict_of_tickerrows = load_data_test('raise') dataframes = optimize.preprocess(dict_of_tickerrows) From 30ca078cecbe28ab46bca9cf5d935721bff7bdd6 Mon Sep 17 00:00:00 2001 From: kryofly Date: Wed, 24 Jan 2018 11:05:27 +0100 Subject: [PATCH 32/32] test: use pytest fixture --- freqtrade/tests/optimize/test_backtesting.py | 21 +++++++++----------- 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/freqtrade/tests/optimize/test_backtesting.py b/freqtrade/tests/optimize/test_backtesting.py index 9e2007aa4..f88cdd9b9 100644 --- a/freqtrade/tests/optimize/test_backtesting.py +++ b/freqtrade/tests/optimize/test_backtesting.py @@ -3,6 +3,7 @@ import logging import math import pandas as pd +import pytest from unittest.mock import MagicMock from freqtrade import exchange, optimize from freqtrade.exchange import Bittrex @@ -12,7 +13,8 @@ import freqtrade.optimize.backtesting as backtesting from freqtrade.strategy.strategy import Strategy -def setup_strategy(): +@pytest.fixture +def default_strategy(): strategy = Strategy() strategy.init({'strategy': 'default_strategy'}) return strategy @@ -44,7 +46,7 @@ def test_generate_text_table(): 'TOTAL 2 15.00 0.60000000 100.0 2 0') # noqa -def test_get_timeframe(): +def test_get_timeframe(default_strategy): data = preprocess(optimize.load_data( None, ticker_interval=1, pairs=['BTC_UNITEST'])) min_date, max_date = get_timeframe(data) @@ -52,8 +54,7 @@ def test_get_timeframe(): assert max_date.isoformat() == '2017-11-14T22:59:00+00:00' -def test_backtest(default_conf, mocker): - setup_strategy() +def test_backtest(default_strategy, default_conf, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) exchange._API = Bittrex({'key': '', 'secret': ''}) @@ -66,10 +67,9 @@ def test_backtest(default_conf, mocker): assert not results.empty -def test_backtest_1min_ticker_interval(default_conf, mocker): +def test_backtest_1min_ticker_interval(default_strategy, default_conf, mocker): mocker.patch.dict('freqtrade.main._CONF', default_conf) exchange._API = Bittrex({'key': '', 'secret': ''}) - setup_strategy() # Run a backtesting for an exiting 5min ticker_interval data = optimize.load_data(None, ticker_interval=1, pairs=['BTC_UNITEST']) @@ -125,7 +125,6 @@ def load_data_test(what): def simple_backtest(config, contour, num_results): - setup_strategy() data = load_data_test(contour) processed = optimize.preprocess(data) assert isinstance(processed, dict) @@ -141,8 +140,7 @@ def simple_backtest(config, contour, num_results): # loaded by freqdata/optimize/__init__.py::load_data() -def test_backtest2(default_conf, mocker): - setup_strategy() +def test_backtest2(default_conf, mocker, default_strategy): mocker.patch.dict('freqtrade.main._CONF', default_conf) data = optimize.load_data(None, ticker_interval=5, pairs=['BTC_ETH']) data = trim_dictlist(data, -200) @@ -153,8 +151,7 @@ def test_backtest2(default_conf, mocker): assert not results.empty -def test_processed(default_conf, mocker): - setup_strategy() +def test_processed(default_conf, mocker, default_strategy): mocker.patch.dict('freqtrade.main._CONF', default_conf) dict_of_tickerrows = load_data_test('raise') dataframes = optimize.preprocess(dict_of_tickerrows) @@ -166,7 +163,7 @@ def test_processed(default_conf, mocker): assert col in cols -def test_backtest_pricecontours(default_conf, mocker): +def test_backtest_pricecontours(default_conf, mocker, default_strategy): mocker.patch.dict('freqtrade.main._CONF', default_conf) tests = [['raise', 17], ['lower', 0], ['sine', 17]] for [contour, numres] in tests: