Merge branch 'develop' into align_userdata

This commit is contained in:
Matthias 2019-08-01 19:33:45 +02:00
commit bcccdda7c0
13 changed files with 160 additions and 116 deletions

View File

@ -12,8 +12,8 @@ Special fields for the documentation (like Note boxes, ...) can be found [here](
## Developer setup ## Developer setup
To configure a development environment, use best use the `setup.sh` script and answer "y" when asked "Do you want to install dependencies for dev [y/N]? ". To configure a development environment, best use the `setup.sh` script and answer "y" when asked "Do you want to install dependencies for dev [y/N]? ".
Alternatively (if your system is not supported by the setup.sh script), follow the manual installation process and run `pip3 install -r requirements-dev.txt`. Alternatively (if your system is not supported by the setup.sh script), follow the manual installation process and run `pip3 install -e .[all]`.
This will install all required tools for development, including `pytest`, `flake8`, `mypy`, and `coveralls`. This will install all required tools for development, including `pytest`, `flake8`, `mypy`, and `coveralls`.
@ -156,6 +156,8 @@ git log --oneline --no-decorate --no-merges master..develop
### Create github release / tag ### Create github release / tag
Once the PR against master is merged (best right after merging):
* Use the button "Draft a new release" in the Github UI (subsection releases) * Use the button "Draft a new release" in the Github UI (subsection releases)
* Use the version-number specified as tag. * Use the version-number specified as tag.
* Use "master" as reference (this step comes after the above PR is merged). * Use "master" as reference (this step comes after the above PR is merged).

View File

@ -1,5 +1,5 @@
""" FreqTrade bot """ """ FreqTrade bot """
__version__ = '2019.6-dev' __version__ = '2019.7-dev'
class DependencyException(Exception): class DependencyException(Exception):

View File

@ -67,7 +67,6 @@ def evaluate_result_multi(results: pd.DataFrame, freq: str, max_open_trades: int
dates = pd.Series(pd.concat(dates).values, name='date') dates = pd.Series(pd.concat(dates).values, name='date')
df2 = pd.DataFrame(np.repeat(results.values, deltas, axis=0), columns=results.columns) df2 = pd.DataFrame(np.repeat(results.values, deltas, axis=0), columns=results.columns)
df2 = df2.astype(dtype={"open_time": "datetime64", "close_time": "datetime64"})
df2 = pd.concat([dates, df2], axis=1) df2 = pd.concat([dates, df2], axis=1)
df2 = df2.set_index('date') df2 = df2.set_index('date')
df_final = df2.resample(freq)[['pair']].count() df_final = df2.resample(freq)[['pair']].count()

View File

@ -51,7 +51,8 @@ class Hyperopt(Backtesting):
'hyperopt_results' / 'hyperopt_results.pickle') 'hyperopt_results' / 'hyperopt_results.pickle')
self.tickerdata_pickle = (self.config['user_data_dir'] / self.tickerdata_pickle = (self.config['user_data_dir'] /
'hyperopt_results' / 'hyperopt_tickerdata.pkl') 'hyperopt_results' / 'hyperopt_tickerdata.pkl')
self.total_tries = config.get('epochs', 0) self.total_epochs = config.get('epochs', 0)
self.current_best_loss = 100 self.current_best_loss = 100
if not self.config.get('hyperopt_continue'): if not self.config.get('hyperopt_continue'):
@ -128,13 +129,12 @@ class Hyperopt(Backtesting):
""" """
results = sorted(self.trials, key=itemgetter('loss')) results = sorted(self.trials, key=itemgetter('loss'))
best_result = results[0] best_result = results[0]
logger.info(
'Best result:\n%s\nwith values:\n', log_str = self.format_results_logstring(best_result)
best_result['result'] print(f"\nBest result:\n{log_str}\nwith values:")
)
pprint(best_result['params'], indent=4) pprint(best_result['params'], indent=4)
if 'roi_t1' in best_result['params']: if 'roi_t1' in best_result['params']:
logger.info('ROI table:') print("ROI table:")
pprint(self.custom_hyperopt.generate_roi_table(best_result['params']), indent=4) pprint(self.custom_hyperopt.generate_roi_table(best_result['params']), indent=4)
def log_results(self, results) -> None: def log_results(self, results) -> None:
@ -143,22 +143,26 @@ class Hyperopt(Backtesting):
""" """
print_all = self.config.get('print_all', False) print_all = self.config.get('print_all', False)
if print_all or results['loss'] < self.current_best_loss: if print_all or results['loss'] < self.current_best_loss:
# Output human-friendly index here (starting from 1) log_str = self.format_results_logstring(results)
current = results['current_tries'] + 1
total = results['total_tries']
res = results['result']
loss = results['loss']
self.current_best_loss = results['loss']
log_msg = f'{current:5d}/{total}: {res} Objective: {loss:.5f}'
log_msg = f'*{log_msg}' if results['initial_point'] else f' {log_msg}'
if print_all: if print_all:
print(log_msg) print(log_str)
else: else:
print('\n' + log_msg) print('\n' + log_str)
else: else:
print('.', end='') print('.', end='')
sys.stdout.flush() sys.stdout.flush()
def format_results_logstring(self, results) -> str:
# Output human-friendly index here (starting from 1)
current = results['current_epoch'] + 1
total = self.total_epochs
res = results['results_explanation']
loss = results['loss']
self.current_best_loss = results['loss']
log_str = f'{current:5d}/{total}: {res} Objective: {loss:.5f}'
log_str = f'*{log_str}' if results['is_initial_point'] else f' {log_str}'
return log_str
def has_space(self, space: str) -> bool: def has_space(self, space: str) -> bool:
""" """
Tell if a space value is contained in the configuration Tell if a space value is contained in the configuration
@ -218,7 +222,7 @@ class Hyperopt(Backtesting):
'end_date': max_date, 'end_date': max_date,
} }
) )
result_explanation = self.format_results(results) results_explanation = self.format_results(results)
trade_count = len(results.index) trade_count = len(results.index)
@ -230,7 +234,7 @@ class Hyperopt(Backtesting):
return { return {
'loss': MAX_LOSS, 'loss': MAX_LOSS,
'params': params, 'params': params,
'result': result_explanation, 'results_explanation': results_explanation,
} }
loss = self.calculate_loss(results=results, trade_count=trade_count, loss = self.calculate_loss(results=results, trade_count=trade_count,
@ -239,12 +243,12 @@ class Hyperopt(Backtesting):
return { return {
'loss': loss, 'loss': loss,
'params': params, 'params': params,
'result': result_explanation, 'results_explanation': results_explanation,
} }
def format_results(self, results: DataFrame) -> str: def format_results(self, results: DataFrame) -> str:
""" """
Return the format result in a string Return the formatted results explanation in a string
""" """
trades = len(results.index) trades = len(results.index)
avg_profit = results.profit_percent.mean() * 100.0 avg_profit = results.profit_percent.mean() * 100.0
@ -327,25 +331,19 @@ class Hyperopt(Backtesting):
with Parallel(n_jobs=config_jobs) as parallel: with Parallel(n_jobs=config_jobs) as parallel:
jobs = parallel._effective_n_jobs() jobs = parallel._effective_n_jobs()
logger.info(f'Effective number of parallel workers used: {jobs}') logger.info(f'Effective number of parallel workers used: {jobs}')
EVALS = max(self.total_tries // jobs, 1) EVALS = max(self.total_epochs // jobs, 1)
for i in range(EVALS): for i in range(EVALS):
asked = opt.ask(n_points=jobs) asked = opt.ask(n_points=jobs)
f_val = self.run_optimizer_parallel(parallel, asked) f_val = self.run_optimizer_parallel(parallel, asked)
opt.tell(asked, [i['loss'] for i in f_val]) opt.tell(asked, [v['loss'] for v in f_val])
self.trials += f_val
for j in range(jobs): for j in range(jobs):
current = i * jobs + j current = i * jobs + j
self.log_results({ val = f_val[j]
'loss': f_val[j]['loss'], val['current_epoch'] = current
'current_tries': current, val['is_initial_point'] = current < INITIAL_POINTS
'initial_point': current < INITIAL_POINTS, self.log_results(val)
'total_tries': self.total_tries, self.trials.append(val)
'result': f_val[j]['result'], logger.debug(f"Optimizer epoch evaluated: {val}")
})
logger.debug(f"Optimizer params: {f_val[j]['params']}")
for j in range(jobs):
logger.debug(f"Optimizer state: Xi: {opt.Xi[-j-1]}, yi: {opt.yi[-j-1]}")
except KeyboardInterrupt: except KeyboardInterrupt:
print('User interrupted..') print('User interrupted..')

View File

@ -15,9 +15,9 @@ logger = logging.getLogger(__name__)
try: try:
from plotly import tools from plotly.subplots import make_subplots
from plotly.offline import plot from plotly.offline import plot
import plotly.graph_objs as go import plotly.graph_objects as go
except ImportError: except ImportError:
logger.exception("Module plotly not found \n Please install using `pip install plotly`") logger.exception("Module plotly not found \n Please install using `pip install plotly`")
exit(1) exit(1)
@ -62,7 +62,7 @@ def init_plotscript(config):
} }
def add_indicators(fig, row, indicators: List[str], data: pd.DataFrame) -> tools.make_subplots: def add_indicators(fig, row, indicators: List[str], data: pd.DataFrame) -> make_subplots:
""" """
Generator all the indicator selected by the user for a specific row Generator all the indicator selected by the user for a specific row
:param fig: Plot figure to append to :param fig: Plot figure to append to
@ -79,7 +79,7 @@ def add_indicators(fig, row, indicators: List[str], data: pd.DataFrame) -> tools
mode='lines', mode='lines',
name=indicator name=indicator
) )
fig.append_trace(scattergl, row, 1) fig.add_trace(scattergl, row, 1)
else: else:
logger.info( logger.info(
'Indicator "%s" ignored. Reason: This indicator is not found ' 'Indicator "%s" ignored. Reason: This indicator is not found '
@ -90,7 +90,7 @@ def add_indicators(fig, row, indicators: List[str], data: pd.DataFrame) -> tools
return fig return fig
def add_profit(fig, row, data: pd.DataFrame, column: str, name: str) -> tools.make_subplots: def add_profit(fig, row, data: pd.DataFrame, column: str, name: str) -> make_subplots:
""" """
Add profit-plot Add profit-plot
:param fig: Plot figure to append to :param fig: Plot figure to append to
@ -105,12 +105,12 @@ def add_profit(fig, row, data: pd.DataFrame, column: str, name: str) -> tools.ma
y=data[column], y=data[column],
name=name, name=name,
) )
fig.append_trace(profit, row, 1) fig.add_trace(profit, row, 1)
return fig return fig
def plot_trades(fig, trades: pd.DataFrame) -> tools.make_subplots: def plot_trades(fig, trades: pd.DataFrame) -> make_subplots:
""" """
Add trades to "fig" Add trades to "fig"
""" """
@ -145,8 +145,8 @@ def plot_trades(fig, trades: pd.DataFrame) -> tools.make_subplots:
color='red' color='red'
) )
) )
fig.append_trace(trade_buys, 1, 1) fig.add_trace(trade_buys, 1, 1)
fig.append_trace(trade_sells, 1, 1) fig.add_trace(trade_sells, 1, 1)
else: else:
logger.warning("No trades found.") logger.warning("No trades found.")
return fig return fig
@ -167,7 +167,7 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
""" """
# Define the graph # Define the graph
fig = tools.make_subplots( fig = make_subplots(
rows=3, rows=3,
cols=1, cols=1,
shared_xaxes=True, shared_xaxes=True,
@ -189,7 +189,7 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
close=data.close, close=data.close,
name='Price' name='Price'
) )
fig.append_trace(candles, 1, 1) fig.add_trace(candles, 1, 1)
if 'buy' in data.columns: if 'buy' in data.columns:
df_buy = data[data['buy'] == 1] df_buy = data[data['buy'] == 1]
@ -206,7 +206,7 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
color='green', color='green',
) )
) )
fig.append_trace(buys, 1, 1) fig.add_trace(buys, 1, 1)
else: else:
logger.warning("No buy-signals found.") logger.warning("No buy-signals found.")
@ -225,7 +225,7 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
color='red', color='red',
) )
) )
fig.append_trace(sells, 1, 1) fig.add_trace(sells, 1, 1)
else: else:
logger.warning("No sell-signals found.") logger.warning("No sell-signals found.")
@ -244,8 +244,8 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
fillcolor="rgba(0,176,246,0.2)", fillcolor="rgba(0,176,246,0.2)",
line={'color': 'rgba(255,255,255,0)'}, line={'color': 'rgba(255,255,255,0)'},
) )
fig.append_trace(bb_lower, 1, 1) fig.add_trace(bb_lower, 1, 1)
fig.append_trace(bb_upper, 1, 1) fig.add_trace(bb_upper, 1, 1)
# Add indicators to main plot # Add indicators to main plot
fig = add_indicators(fig=fig, row=1, indicators=indicators1, data=data) fig = add_indicators(fig=fig, row=1, indicators=indicators1, data=data)
@ -258,7 +258,7 @@ def generate_candlestick_graph(pair: str, data: pd.DataFrame, trades: pd.DataFra
y=data['volume'], y=data['volume'],
name='Volume' name='Volume'
) )
fig.append_trace(volume, 2, 1) fig.add_trace(volume, 2, 1)
# Add indicators to seperate row # Add indicators to seperate row
fig = add_indicators(fig=fig, row=3, indicators=indicators2, data=data) fig = add_indicators(fig=fig, row=3, indicators=indicators2, data=data)
@ -281,10 +281,10 @@ def generate_profit_graph(pairs: str, tickers: Dict[str, pd.DataFrame],
name='Avg close price', name='Avg close price',
) )
fig = tools.make_subplots(rows=3, cols=1, shared_xaxes=True, row_width=[1, 1, 1]) fig = make_subplots(rows=3, cols=1, shared_xaxes=True, row_width=[1, 1, 1])
fig['layout'].update(title="Profit plot") fig['layout'].update(title="Profit plot")
fig.append_trace(avgclose, 1, 1) fig.add_trace(avgclose, 1, 1)
fig = add_profit(fig, 2, df_comb, 'cum_profit', 'Profit') fig = add_profit(fig, 2, df_comb, 'cum_profit', 'Profit')
for pair in pairs: for pair in pairs:

View File

@ -33,13 +33,13 @@ def get_mock_coro(return_value):
def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name, def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
fun, mock_ccxt_fun, **kwargs): fun, mock_ccxt_fun, **kwargs):
with pytest.raises(TemporaryError): with pytest.raises(TemporaryError):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError) api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeaDBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
getattr(exchange, fun)(**kwargs) getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == API_RETRY_COUNT + 1 assert api_mock.__dict__[mock_ccxt_fun].call_count == API_RETRY_COUNT + 1
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError) api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
getattr(exchange, fun)(**kwargs) getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1 assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
@ -47,13 +47,13 @@ def ccxt_exceptionhandlers(mocker, default_conf, api_mock, exchange_name,
async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fun, **kwargs): async def async_ccxt_exception(mocker, default_conf, api_mock, fun, mock_ccxt_fun, **kwargs):
with pytest.raises(TemporaryError): with pytest.raises(TemporaryError):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError) api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.NetworkError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
await getattr(exchange, fun)(**kwargs) await getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == API_RETRY_COUNT + 1 assert api_mock.__dict__[mock_ccxt_fun].call_count == API_RETRY_COUNT + 1
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError) api_mock.__dict__[mock_ccxt_fun] = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
await getattr(exchange, fun)(**kwargs) await getattr(exchange, fun)(**kwargs)
assert api_mock.__dict__[mock_ccxt_fun].call_count == 1 assert api_mock.__dict__[mock_ccxt_fun].call_count == 1
@ -256,13 +256,13 @@ def test__load_async_markets(default_conf, mocker, caplog):
def test__load_markets(default_conf, mocker, caplog): def test__load_markets(default_conf, mocker, caplog):
caplog.set_level(logging.INFO) caplog.set_level(logging.INFO)
api_mock = MagicMock() api_mock = MagicMock()
api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError()) api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError("SomeError"))
mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock)) mocker.patch('freqtrade.exchange.Exchange._init_ccxt', MagicMock(return_value=api_mock))
mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_pairs', MagicMock())
mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock()) mocker.patch('freqtrade.exchange.Exchange.validate_timeframes', MagicMock())
mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock()) mocker.patch('freqtrade.exchange.Exchange._load_async_markets', MagicMock())
Exchange(default_conf) Exchange(default_conf)
assert log_has('Unable to initialize markets. Reason: ', caplog.record_tuples) assert log_has('Unable to initialize markets. Reason: SomeError', caplog.record_tuples)
expected_return = {'ETH/BTC': 'available'} expected_return = {'ETH/BTC': 'available'}
api_mock = MagicMock() api_mock = MagicMock()
@ -305,7 +305,7 @@ def test__reload_markets_exception(default_conf, mocker, caplog):
caplog.set_level(logging.DEBUG) caplog.set_level(logging.DEBUG)
api_mock = MagicMock() api_mock = MagicMock()
api_mock.load_markets = MagicMock(side_effect=ccxt.NetworkError) api_mock.load_markets = MagicMock(side_effect=ccxt.NetworkError("LoadError"))
default_conf['exchange']['markets_refresh_interval'] = 10 default_conf['exchange']['markets_refresh_interval'] = 10
exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance") exchange = get_patched_exchange(mocker, default_conf, api_mock, id="binance")
@ -634,25 +634,25 @@ def test_buy_prod(default_conf, mocker, exchange_name):
# test exception handling # test exception handling
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds) api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("Not enough funds"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.buy(pair='ETH/BTC', ordertype=order_type, exchange.buy(pair='ETH/BTC', ordertype=order_type,
amount=1, rate=200, time_in_force=time_in_force) amount=1, rate=200, time_in_force=time_in_force)
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder) api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.buy(pair='ETH/BTC', ordertype=order_type, exchange.buy(pair='ETH/BTC', ordertype=order_type,
amount=1, rate=200, time_in_force=time_in_force) amount=1, rate=200, time_in_force=time_in_force)
with pytest.raises(TemporaryError): with pytest.raises(TemporaryError):
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError) api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("Network disconnect"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.buy(pair='ETH/BTC', ordertype=order_type, exchange.buy(pair='ETH/BTC', ordertype=order_type,
amount=1, rate=200, time_in_force=time_in_force) amount=1, rate=200, time_in_force=time_in_force)
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError) api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.buy(pair='ETH/BTC', ordertype=order_type, exchange.buy(pair='ETH/BTC', ordertype=order_type,
amount=1, rate=200, time_in_force=time_in_force) amount=1, rate=200, time_in_force=time_in_force)
@ -758,22 +758,22 @@ def test_sell_prod(default_conf, mocker, exchange_name):
# test exception handling # test exception handling
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds) api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder) api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
with pytest.raises(TemporaryError): with pytest.raises(TemporaryError):
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError) api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No Connection"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError) api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200) exchange.sell(pair='ETH/BTC', ordertype=order_type, amount=1, rate=200)
@ -846,7 +846,7 @@ def test_get_balance_prod(default_conf, mocker, exchange_name):
assert exchange.get_balance(currency='BTC') == 123.4 assert exchange.get_balance(currency='BTC') == 123.4
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError) api_mock.fetch_balance = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.get_balance(currency='BTC') exchange.get_balance(currency='BTC')
@ -919,7 +919,7 @@ def test_get_tickers(default_conf, mocker, exchange_name):
"get_tickers", "fetch_tickers") "get_tickers", "fetch_tickers")
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NotSupported) api_mock.fetch_tickers = MagicMock(side_effect=ccxt.NotSupported("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.get_tickers() exchange.get_tickers()
@ -1101,7 +1101,7 @@ async def test__async_get_candle_history(default_conf, mocker, caplog, exchange_
api_mock = MagicMock() api_mock = MagicMock()
with pytest.raises(OperationalException, match=r'Could not fetch ticker data*'): with pytest.raises(OperationalException, match=r'Could not fetch ticker data*'):
api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError) api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.BaseError("Unknown error"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
await exchange._async_get_candle_history(pair, "5m", await exchange._async_get_candle_history(pair, "5m",
(arrow.utcnow().timestamp - 2000) * 1000) (arrow.utcnow().timestamp - 2000) * 1000)
@ -1173,15 +1173,15 @@ def test_get_order_book(default_conf, mocker, order_book_l2, exchange_name):
def test_get_order_book_exception(default_conf, mocker, exchange_name): def test_get_order_book_exception(default_conf, mocker, exchange_name):
api_mock = MagicMock() api_mock = MagicMock()
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NotSupported) api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NotSupported("Not supported"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.get_order_book(pair='ETH/BTC', limit=50) exchange.get_order_book(pair='ETH/BTC', limit=50)
with pytest.raises(TemporaryError): with pytest.raises(TemporaryError):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NetworkError) api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.NetworkError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.get_order_book(pair='ETH/BTC', limit=50) exchange.get_order_book(pair='ETH/BTC', limit=50)
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.BaseError) api_mock.fetch_l2_order_book = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.get_order_book(pair='ETH/BTC', limit=50) exchange.get_order_book(pair='ETH/BTC', limit=50)
@ -1294,7 +1294,7 @@ def test_cancel_order(default_conf, mocker, exchange_name):
assert exchange.cancel_order(order_id='_', pair='TKN/BTC') == 123 assert exchange.cancel_order(order_id='_', pair='TKN/BTC') == 123
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder) api_mock.cancel_order = MagicMock(side_effect=ccxt.InvalidOrder("Did not find order"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.cancel_order(order_id='_', pair='TKN/BTC') exchange.cancel_order(order_id='_', pair='TKN/BTC')
assert api_mock.cancel_order.call_count == 1 assert api_mock.cancel_order.call_count == 1
@ -1321,7 +1321,7 @@ def test_get_order(default_conf, mocker, exchange_name):
assert exchange.get_order('X', 'TKN/BTC') == 456 assert exchange.get_order('X', 'TKN/BTC') == 456
with pytest.raises(InvalidOrderException): with pytest.raises(InvalidOrderException):
api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder) api_mock.fetch_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name) exchange = get_patched_exchange(mocker, default_conf, api_mock, id=exchange_name)
exchange.get_order(order_id='_', pair='TKN/BTC') exchange.get_order(order_id='_', pair='TKN/BTC')
assert api_mock.fetch_order.call_count == 1 assert api_mock.fetch_order.call_count == 1
@ -1437,22 +1437,22 @@ def test_stoploss_limit_order(default_conf, mocker):
# test exception handling # test exception handling
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds) api_mock.create_order = MagicMock(side_effect=ccxt.InsufficientFunds("0 balance"))
exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
with pytest.raises(DependencyException): with pytest.raises(DependencyException):
api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder) api_mock.create_order = MagicMock(side_effect=ccxt.InvalidOrder("Order not found"))
exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
with pytest.raises(TemporaryError): with pytest.raises(TemporaryError):
api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError) api_mock.create_order = MagicMock(side_effect=ccxt.NetworkError("No connection"))
exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)
with pytest.raises(OperationalException): with pytest.raises(OperationalException):
api_mock.create_order = MagicMock(side_effect=ccxt.BaseError) api_mock.create_order = MagicMock(side_effect=ccxt.BaseError("DeadBeef"))
exchange = get_patched_exchange(mocker, default_conf, api_mock) exchange = get_patched_exchange(mocker, default_conf, api_mock)
exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200) exchange.stoploss_limit(pair='ETH/BTC', amount=1, stop_price=220, rate=200)

View File

@ -9,7 +9,7 @@ from arrow import Arrow
from filelock import Timeout from filelock import Timeout
from pathlib import Path from pathlib import Path
from freqtrade import DependencyException from freqtrade import DependencyException, OperationalException
from freqtrade.data.converter import parse_ticker_dataframe from freqtrade.data.converter import parse_ticker_dataframe
from freqtrade.data.history import load_tickerdata_file from freqtrade.data.history import load_tickerdata_file
from freqtrade.optimize import setup_configuration, start_hyperopt from freqtrade.optimize import setup_configuration, start_hyperopt
@ -189,6 +189,13 @@ def test_hyperoptresolver(mocker, default_conf, caplog) -> None:
assert hasattr(x, "ticker_interval") assert hasattr(x, "ticker_interval")
def test_hyperoptresolver_wrongname(mocker, default_conf, caplog) -> None:
default_conf.update({'hyperopt': "NonExistingHyperoptClass"})
with pytest.raises(OperationalException, match=r'Impossible to load Hyperopt.*'):
HyperOptResolver(default_conf, ).hyperopt
def test_hyperoptlossresolver(mocker, default_conf, caplog) -> None: def test_hyperoptlossresolver(mocker, default_conf, caplog) -> None:
hl = DefaultHyperOptLoss hl = DefaultHyperOptLoss
@ -196,9 +203,15 @@ def test_hyperoptlossresolver(mocker, default_conf, caplog) -> None:
'freqtrade.resolvers.hyperopt_resolver.HyperOptLossResolver._load_hyperoptloss', 'freqtrade.resolvers.hyperopt_resolver.HyperOptLossResolver._load_hyperoptloss',
MagicMock(return_value=hl) MagicMock(return_value=hl)
) )
x = HyperOptResolver(default_conf, ).hyperopt x = HyperOptLossResolver(default_conf, ).hyperoptloss
assert hasattr(x, "populate_indicators") assert hasattr(x, "hyperopt_loss_function")
assert hasattr(x, "ticker_interval")
def test_hyperoptlossresolver_wrongname(mocker, default_conf, caplog) -> None:
default_conf.update({'hyperopt_loss': "NonExistingLossClass"})
with pytest.raises(OperationalException, match=r'Impossible to load HyperoptLoss.*'):
HyperOptLossResolver(default_conf, ).hyperopt
def test_start(mocker, default_conf, caplog) -> None: def test_start(mocker, default_conf, caplog) -> None:
@ -360,13 +373,13 @@ def test_onlyprofit_loss_prefers_higher_profits(default_conf, hyperopt_results)
def test_log_results_if_loss_improves(hyperopt, capsys) -> None: def test_log_results_if_loss_improves(hyperopt, capsys) -> None:
hyperopt.current_best_loss = 2 hyperopt.current_best_loss = 2
hyperopt.total_epochs = 2
hyperopt.log_results( hyperopt.log_results(
{ {
'loss': 1, 'loss': 1,
'current_tries': 1, 'current_epoch': 1,
'total_tries': 2, 'results_explanation': 'foo.',
'result': 'foo.', 'is_initial_point': False
'initial_point': False
} }
) )
out, err = capsys.readouterr() out, err = capsys.readouterr()
@ -423,7 +436,7 @@ def test_roi_table_generation(hyperopt) -> None:
assert hyperopt.custom_hyperopt.generate_roi_table(params) == {0: 6, 15: 3, 25: 1, 30: 0} assert hyperopt.custom_hyperopt.generate_roi_table(params) == {0: 6, 15: 3, 25: 1, 30: 0}
def test_start_calls_optimizer(mocker, default_conf, caplog) -> None: def test_start_calls_optimizer(mocker, default_conf, caplog, capsys) -> None:
dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock()) dumper = mocker.patch('freqtrade.optimize.hyperopt.dump', MagicMock())
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock()) mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
mocker.patch( mocker.patch(
@ -433,7 +446,7 @@ def test_start_calls_optimizer(mocker, default_conf, caplog) -> None:
parallel = mocker.patch( parallel = mocker.patch(
'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel', 'freqtrade.optimize.hyperopt.Hyperopt.run_optimizer_parallel',
MagicMock(return_value=[{'loss': 1, 'result': 'foo result', 'params': {}}]) MagicMock(return_value=[{'loss': 1, 'results_explanation': 'foo result', 'params': {}}])
) )
patch_exchange(mocker) patch_exchange(mocker)
@ -447,8 +460,11 @@ def test_start_calls_optimizer(mocker, default_conf, caplog) -> None:
hyperopt.strategy.tickerdata_to_dataframe = MagicMock() hyperopt.strategy.tickerdata_to_dataframe = MagicMock()
hyperopt.start() hyperopt.start()
parallel.assert_called_once() parallel.assert_called_once()
assert log_has('Best result:\nfoo result\nwith values:\n', caplog.record_tuples)
out, err = capsys.readouterr()
assert 'Best result:\n* 1/1: foo result Objective: 1.00000\nwith values:\n' in out
assert dumper.called assert dumper.called
# Should be called twice, once for tickerdata, once to save evaluations # Should be called twice, once for tickerdata, once to save evaluations
assert dumper.call_count == 2 assert dumper.call_count == 2
@ -588,7 +604,7 @@ def test_generate_optimizer(mocker, default_conf) -> None:
} }
response_expected = { response_expected = {
'loss': 1.9840569076926293, 'loss': 1.9840569076926293,
'result': ' 1 trades. Avg profit 2.31%. Total profit 0.00023300 BTC ' 'results_explanation': ' 1 trades. Avg profit 2.31%. Total profit 0.00023300 BTC '
'( 2.31Σ%). Avg duration 100.0 mins.', '( 2.31Σ%). Avg duration 100.0 mins.',
'params': optimizer_param 'params': optimizer_param
} }

View File

@ -3,8 +3,8 @@ from copy import deepcopy
from pathlib import Path from pathlib import Path
from unittest.mock import MagicMock from unittest.mock import MagicMock
import plotly.graph_objs as go import plotly.graph_objects as go
from plotly import tools from plotly.subplots import make_subplots
from freqtrade.configuration import Arguments, TimeRange from freqtrade.configuration import Arguments, TimeRange
from freqtrade.data import history from freqtrade.data import history
@ -29,7 +29,7 @@ def find_trace_in_fig_data(data, search_string: str):
def generage_empty_figure(): def generage_empty_figure():
return tools.make_subplots( return make_subplots(
rows=3, rows=3,
cols=1, cols=1,
shared_xaxes=True, shared_xaxes=True,

View File

@ -1,9 +1,9 @@
# requirements without requirements installable via conda # requirements without requirements installable via conda
# mainly used for Raspberry pi installs # mainly used for Raspberry pi installs
ccxt==1.18.860 ccxt==1.18.992
SQLAlchemy==1.3.5 SQLAlchemy==1.3.6
python-telegram-bot==11.1.0 python-telegram-bot==11.1.0
arrow==0.14.2 arrow==0.14.3
cachetools==3.1.1 cachetools==3.1.1
requests==2.22.0 requests==2.22.0
urllib3==1.24.2 # pyup: ignore urllib3==1.24.2 # pyup: ignore
@ -29,4 +29,4 @@ python-rapidjson==0.7.2
sdnotify==0.3.2 sdnotify==0.3.2
# Api server # Api server
flask==1.0.3 flask==1.1.1

View File

@ -2,13 +2,13 @@
-r requirements.txt -r requirements.txt
-r requirements-plot.txt -r requirements-plot.txt
flake8==3.7.7 coveralls==1.8.1
flake8==3.7.8
flake8-type-annotations==0.1.0 flake8-type-annotations==0.1.0
flake8-tidy-imports==2.0.0 flake8-tidy-imports==2.0.0
pytest==5.0.0 mypy==0.720
pytest-mock==1.10.4 pytest==5.0.1
pytest-asyncio==0.10.0 pytest-asyncio==0.10.0
pytest-cov==2.7.1 pytest-cov==2.7.1
pytest-mock==1.10.4
pytest-random-order==1.0.4 pytest-random-order==1.0.4
coveralls==1.8.1
mypy==0.711

View File

@ -1,5 +1,5 @@
# Include all requirements to run the bot. # Include all requirements to run the bot.
-r requirements.txt -r requirements.txt
plotly==3.10.0 plotly==4.0.0

View File

@ -1,6 +1,6 @@
# Load common requirements # Load common requirements
-r requirements-common.txt -r requirements-common.txt
numpy==1.16.4 numpy==1.17.0
pandas==0.24.2 pandas==0.25.0
scipy==1.3.0 scipy==1.3.0

View File

@ -8,6 +8,24 @@ if version_info.major == 3 and version_info.minor < 6 or \
from freqtrade import __version__ from freqtrade import __version__
# Requirements used for submodules
api = ['flask']
plot = ['plotly>=4.0']
develop = [
'coveralls',
'flake8',
'flake8-type-annotations',
'flake8-tidy-imports',
'mypy',
'pytest',
'pytest-asyncio',
'pytest-cov',
'pytest-mock',
'pytest-random-order',
]
all_extra = api + plot + develop
setup(name='freqtrade', setup(name='freqtrade',
version=__version__, version=__version__,
@ -20,26 +38,37 @@ setup(name='freqtrade',
setup_requires=['pytest-runner', 'numpy'], setup_requires=['pytest-runner', 'numpy'],
tests_require=['pytest', 'pytest-mock', 'pytest-cov'], tests_require=['pytest', 'pytest-mock', 'pytest-cov'],
install_requires=[ install_requires=[
'ccxt', # from requirements-common.txt
'ccxt>=1.18',
'SQLAlchemy', 'SQLAlchemy',
'python-telegram-bot', 'python-telegram-bot',
'arrow', 'arrow',
'cachetools',
'requests', 'requests',
'urllib3', 'urllib3',
'wrapt', 'wrapt',
'pandas',
'scikit-learn', 'scikit-learn',
'scipy',
'joblib', 'joblib',
'jsonschema', 'jsonschema',
'TA-Lib', 'TA-Lib',
'tabulate', 'tabulate',
'cachetools',
'coinmarketcap', 'coinmarketcap',
'scikit-optimize', 'scikit-optimize',
'filelock',
'py_find_1st',
'python-rapidjson', 'python-rapidjson',
'py_find_1st' 'sdnotify',
# from requirements.txt
'numpy',
'pandas',
'scipy',
], ],
extras_require={
'api': api,
'dev': all_extra,
'plot': plot,
'all': all_extra,
},
include_package_data=True, include_package_data=True,
zip_safe=False, zip_safe=False,
entry_points={ entry_points={