diff --git a/freqtrade/analyze.py b/freqtrade/analyze.py index e3148349f..310754754 100644 --- a/freqtrade/analyze.py +++ b/freqtrade/analyze.py @@ -24,21 +24,23 @@ def parse_ticker_dataframe(ticker: list, minimum_date: arrow.Arrow) -> DataFrame .drop('BV', 1) \ .rename(columns={'C':'close', 'V':'volume', 'O':'open', 'H':'high', 'L':'low', 'T':'date'}) \ .sort_values('date') - return df[df['date'].map(arrow.get) > minimum_date] + return df def populate_indicators(dataframe: DataFrame) -> DataFrame: """ Adds several different TA indicators to the given DataFrame """ + dataframe['sar'] = ta.SAR(dataframe) dataframe['adx'] = ta.ADX(dataframe) stoch = ta.STOCHF(dataframe) dataframe['fastd'] = stoch['fastd'] dataframe['fastk'] = stoch['fastk'] dataframe['blower'] = ta.BBANDS(dataframe, nbdevup=2, nbdevdn=2)['lowerband'] - dataframe['sma'] = ta.SMA(dataframe, timeperiod=30) + dataframe['sma'] = ta.SMA(dataframe, timeperiod=40) dataframe['tema'] = ta.TEMA(dataframe, timeperiod=9) dataframe['mfi'] = ta.MFI(dataframe) + dataframe['cci'] = ta.CCI(dataframe) return dataframe diff --git a/freqtrade/tests/test_hyperopt.py b/freqtrade/tests/test_hyperopt.py new file mode 100644 index 000000000..167e0d9ab --- /dev/null +++ b/freqtrade/tests/test_hyperopt.py @@ -0,0 +1,166 @@ +# pragma pylint: disable=missing-docstring +import json +import logging +import os +from functools import reduce + +import pytest +import arrow +from pandas import DataFrame + +from hyperopt import fmin, tpe, hp + +from freqtrade.analyze import analyze_ticker +from freqtrade.main import should_sell +from freqtrade.persistence import Trade + +logging.disable(logging.DEBUG) # disable debug logs that slow backtesting a lot + +def print_results(results): + print('Made {} buys. Average profit {:.2f}%. Total profit was {:.3f}. Average duration {:.1f} mins.'.format( + len(results.index), + results.profit.mean() * 100.0, + results.profit.sum(), + results.duration.mean() * 5 + )) + +@pytest.fixture +def pairs(): + return ['btc-neo', 'btc-eth', 'btc-omg', 'btc-edg', 'btc-pay', + 'btc-pivx', 'btc-qtum', 'btc-mtl', 'btc-etc', 'btc-ltc'] + +@pytest.fixture +def conf(): + return { + "minimal_roi": { + "40": 0.0, + "30": 0.01, + "20": 0.02, + "0": 0.04 + }, + "stoploss": -0.05 + } + + +def backtest(conf, pairs, mocker, buy_strategy): + trades = [] + mocker.patch.dict('freqtrade.main._CONF', conf) + for pair in pairs: + with open('freqtrade/tests/testdata/'+pair+'.json') as data_file: + data = json.load(data_file) + + mocker.patch('freqtrade.analyze.get_ticker_history', return_value=data) + mocker.patch('arrow.utcnow', return_value=arrow.get('2017-08-20T14:50:00')) + mocker.patch('freqtrade.analyze.populate_buy_trend', side_effect=buy_strategy) + ticker = analyze_ticker(pair) + # for each buy point + for index, row in ticker[ticker.buy == 1].iterrows(): + trade = Trade( + open_rate=row['close'], + open_date=arrow.get(row['date']).datetime, + amount=1, + ) + # calculate win/lose forwards from buy point + for index2, row2 in ticker[index:].iterrows(): + if should_sell(trade, row2['close'], arrow.get(row2['date']).datetime): + current_profit = (row2['close'] - trade.open_rate) / trade.open_rate + + trades.append((pair, current_profit, index2 - index)) + break + + labels = ['currency', 'profit', 'duration'] + results = DataFrame.from_records(trades, columns=labels) + + print_results(results) + + # set the value below to suit your number concurrent trades so its realistic to 20days of data + TARGET_TRADES = 1200 + if results.profit.sum() == 0 or results.profit.mean() == 0: + return 49999999999 # avoid division by zero, return huge value to discard result + return abs(len(results.index) - 1200.1) / (results.profit.sum() ** 2) * results.duration.mean() # the smaller the better + +def buy_strategy_generator(params): + print(params) + def populate_buy_trend(dataframe: DataFrame) -> DataFrame: + conditions = [] + # GUARDS AND TRENDS + if params['below_sma']['enabled']: + conditions.append(dataframe['close'] < dataframe['sma']) + if params['over_sma']['enabled']: + conditions.append(dataframe['close'] > dataframe['sma']) + 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['cci']['enabled']: + conditions.append(dataframe['cci'] < params['cci']['value']) + if params['over_sar']['enabled']: + conditions.append(dataframe['close'] > dataframe['sar']) + if params['uptrend_sma']['enabled']: + prevsma = dataframe['sma'].shift(1) + conditions.append(dataframe['sma'] > prevsma) + + prev_fastd = dataframe['fastd'].shift(1) + # TRIGGERS + triggers = { + 'lower_bb': dataframe['tema'] <= dataframe['blower'], + 'faststoch10': (dataframe['fastd'] >= 10) & (prev_fastd < 10), + } + conditions.append(triggers.get(params['trigger']['type'])) + + dataframe.loc[ + reduce(lambda x, y: x & y, conditions), + 'buy'] = 1 + dataframe.loc[dataframe['buy'] == 1, 'buy_price'] = dataframe['close'] + + return dataframe + return populate_buy_trend + +@pytest.mark.skipif(not os.environ.get('BACKTEST', False), reason="BACKTEST not set") +def test_hyperopt(conf, pairs, mocker): + + def optimizer(params): + return backtest(conf, pairs, mocker, buy_strategy_generator(params)) + + space = { + 'mfi': hp.choice('mfi', [ + {'enabled': False}, + {'enabled': True, 'value': hp.uniform('mfi-value', 2, 40)} + ]), + 'fastd': hp.choice('fastd', [ + {'enabled': False}, + {'enabled': True, 'value': hp.uniform('fastd-value', 2, 40)} + ]), + 'adx': hp.choice('adx', [ + {'enabled': False}, + {'enabled': True, 'value': hp.uniform('adx-value', 2, 40)} + ]), + 'cci': hp.choice('cci', [ + {'enabled': False}, + {'enabled': True, 'value': hp.uniform('cci-value', -200, -100)} + ]), + 'below_sma': hp.choice('below_sma', [ + {'enabled': False}, + {'enabled': True} + ]), + 'over_sma': hp.choice('over_sma', [ + {'enabled': False}, + {'enabled': True} + ]), + 'over_sar': hp.choice('over_sar', [ + {'enabled': False}, + {'enabled': True} + ]), + 'uptrend_sma': hp.choice('uptrend_sma', [ + {'enabled': False}, + {'enabled': True} + ]), + 'trigger': hp.choice('trigger', [ + {'type': 'lower_bb'}, + {'type': 'faststoch10'} + ]), + } + + print('Best parameters {}'.format(fmin(fn=optimizer, space=space, algo=tpe.suggest, max_evals=40))) diff --git a/requirements.txt b/requirements.txt index 3483bc50e..69379b025 100644 --- a/requirements.txt +++ b/requirements.txt @@ -14,6 +14,9 @@ TA-Lib==0.4.10 pytest==3.2.3 pytest-mock==1.6.3 pytest-cov==2.5.1 +hyperopt==0.1 +# do not upgrade networkx before this is fixed https://github.com/hyperopt/hyperopt/issues/325 +networkx==1.11 # Required for plotting data #matplotlib==2.1.0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index ea59030de..000000000 --- a/setup.cfg +++ /dev/null @@ -1,5 +0,0 @@ -[aliases] -test=pytest - -[tool:pytest] -addopts = --cov=freqtrade --cov-config=.coveragerc freqtrade/tests/