|  |  |  | @@ -10,10 +10,11 @@ import numpy as np | 
		
	
		
			
				|  |  |  |  | import pytest | 
		
	
		
			
				|  |  |  |  | from pandas import DataFrame, to_datetime | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | from freqtrade import OperationalException | 
		
	
		
			
				|  |  |  |  | from freqtrade.data.converter import parse_ticker_dataframe | 
		
	
		
			
				|  |  |  |  | from freqtrade.edge import Edge, PairInfo | 
		
	
		
			
				|  |  |  |  | from freqtrade.strategy.interface import SellType | 
		
	
		
			
				|  |  |  |  | from freqtrade.tests.conftest import get_patched_freqtradebot | 
		
	
		
			
				|  |  |  |  | from freqtrade.tests.conftest import get_patched_freqtradebot, log_has | 
		
	
		
			
				|  |  |  |  | from freqtrade.tests.optimize import (BTContainer, BTrade, | 
		
	
		
			
				|  |  |  |  |                                       _build_backtest_dataframe, | 
		
	
		
			
				|  |  |  |  |                                       _get_frame_time_from_offset) | 
		
	
	
		
			
				
					
					|  |  |  | @@ -30,7 +31,50 @@ ticker_start_time = arrow.get(2018, 10, 3) | 
		
	
		
			
				|  |  |  |  | ticker_interval_in_minute = 60 | 
		
	
		
			
				|  |  |  |  | _ohlc = {'date': 0, 'buy': 1, 'open': 2, 'high': 3, 'low': 4, 'close': 5, 'sell': 6, 'volume': 7} | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | # Helpers for this test file | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | def _validate_ohlc(buy_ohlc_sell_matrice): | 
		
	
		
			
				|  |  |  |  |     for index, ohlc in enumerate(buy_ohlc_sell_matrice): | 
		
	
		
			
				|  |  |  |  |         # if not high < open < low or not high < close < low | 
		
	
		
			
				|  |  |  |  |         if not ohlc[3] >= ohlc[2] >= ohlc[4] or not ohlc[3] >= ohlc[5] >= ohlc[4]: | 
		
	
		
			
				|  |  |  |  |             raise Exception('Line ' + str(index + 1) + ' of ohlc has invalid values!') | 
		
	
		
			
				|  |  |  |  |     return True | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | def _build_dataframe(buy_ohlc_sell_matrice): | 
		
	
		
			
				|  |  |  |  |     _validate_ohlc(buy_ohlc_sell_matrice) | 
		
	
		
			
				|  |  |  |  |     tickers = [] | 
		
	
		
			
				|  |  |  |  |     for ohlc in buy_ohlc_sell_matrice: | 
		
	
		
			
				|  |  |  |  |         ticker = { | 
		
	
		
			
				|  |  |  |  |             'date': ticker_start_time.shift( | 
		
	
		
			
				|  |  |  |  |                 minutes=( | 
		
	
		
			
				|  |  |  |  |                     ohlc[0] * | 
		
	
		
			
				|  |  |  |  |                     ticker_interval_in_minute)).timestamp * | 
		
	
		
			
				|  |  |  |  |             1000, | 
		
	
		
			
				|  |  |  |  |             'buy': ohlc[1], | 
		
	
		
			
				|  |  |  |  |             'open': ohlc[2], | 
		
	
		
			
				|  |  |  |  |             'high': ohlc[3], | 
		
	
		
			
				|  |  |  |  |             'low': ohlc[4], | 
		
	
		
			
				|  |  |  |  |             'close': ohlc[5], | 
		
	
		
			
				|  |  |  |  |             'sell': ohlc[6]} | 
		
	
		
			
				|  |  |  |  |         tickers.append(ticker) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     frame = DataFrame(tickers) | 
		
	
		
			
				|  |  |  |  |     frame['date'] = to_datetime(frame['date'], | 
		
	
		
			
				|  |  |  |  |                                 unit='ms', | 
		
	
		
			
				|  |  |  |  |                                 utc=True, | 
		
	
		
			
				|  |  |  |  |                                 infer_datetime_format=True) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     return frame | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | def _time_on_candle(number): | 
		
	
		
			
				|  |  |  |  |     return np.datetime64(ticker_start_time.shift( | 
		
	
		
			
				|  |  |  |  |         minutes=(number * ticker_interval_in_minute)).timestamp * 1000, 'ms') | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | # End helper functions | 
		
	
		
			
				|  |  |  |  | # Open trade should be removed from the end | 
		
	
		
			
				|  |  |  |  | tc0 = BTContainer(data=[ | 
		
	
		
			
				|  |  |  |  |     # D  O     H     L     C     V    B  S | 
		
	
	
		
			
				
					
					|  |  |  | @@ -203,46 +247,6 @@ def test_nonexisting_stake_amount(mocker, edge_conf): | 
		
	
		
			
				|  |  |  |  |     assert edge.stake_amount('N/O', 1, 2, 1) == 0.15 | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | def _validate_ohlc(buy_ohlc_sell_matrice): | 
		
	
		
			
				|  |  |  |  |     for index, ohlc in enumerate(buy_ohlc_sell_matrice): | 
		
	
		
			
				|  |  |  |  |         # if not high < open < low or not high < close < low | 
		
	
		
			
				|  |  |  |  |         if not ohlc[3] >= ohlc[2] >= ohlc[4] or not ohlc[3] >= ohlc[5] >= ohlc[4]: | 
		
	
		
			
				|  |  |  |  |             raise Exception('Line ' + str(index + 1) + ' of ohlc has invalid values!') | 
		
	
		
			
				|  |  |  |  |     return True | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | def _build_dataframe(buy_ohlc_sell_matrice): | 
		
	
		
			
				|  |  |  |  |     _validate_ohlc(buy_ohlc_sell_matrice) | 
		
	
		
			
				|  |  |  |  |     tickers = [] | 
		
	
		
			
				|  |  |  |  |     for ohlc in buy_ohlc_sell_matrice: | 
		
	
		
			
				|  |  |  |  |         ticker = { | 
		
	
		
			
				|  |  |  |  |             'date': ticker_start_time.shift( | 
		
	
		
			
				|  |  |  |  |                 minutes=( | 
		
	
		
			
				|  |  |  |  |                     ohlc[0] * | 
		
	
		
			
				|  |  |  |  |                     ticker_interval_in_minute)).timestamp * | 
		
	
		
			
				|  |  |  |  |             1000, | 
		
	
		
			
				|  |  |  |  |             'buy': ohlc[1], | 
		
	
		
			
				|  |  |  |  |             'open': ohlc[2], | 
		
	
		
			
				|  |  |  |  |             'high': ohlc[3], | 
		
	
		
			
				|  |  |  |  |             'low': ohlc[4], | 
		
	
		
			
				|  |  |  |  |             'close': ohlc[5], | 
		
	
		
			
				|  |  |  |  |             'sell': ohlc[6]} | 
		
	
		
			
				|  |  |  |  |         tickers.append(ticker) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     frame = DataFrame(tickers) | 
		
	
		
			
				|  |  |  |  |     frame['date'] = to_datetime(frame['date'], | 
		
	
		
			
				|  |  |  |  |                                 unit='ms', | 
		
	
		
			
				|  |  |  |  |                                 utc=True, | 
		
	
		
			
				|  |  |  |  |                                 infer_datetime_format=True) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     return frame | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | def _time_on_candle(number): | 
		
	
		
			
				|  |  |  |  |     return np.datetime64(ticker_start_time.shift( | 
		
	
		
			
				|  |  |  |  |         minutes=(number * ticker_interval_in_minute)).timestamp * 1000, 'ms') | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | def test_edge_heartbeat_calculate(mocker, edge_conf): | 
		
	
		
			
				|  |  |  |  |     freqtrade = get_patched_freqtradebot(mocker, edge_conf) | 
		
	
		
			
				|  |  |  |  |     edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy) | 
		
	
	
		
			
				
					
					|  |  |  | @@ -298,6 +302,40 @@ def test_edge_process_downloaded_data(mocker, edge_conf): | 
		
	
		
			
				|  |  |  |  |     assert edge._last_updated <= arrow.utcnow().timestamp + 2 | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | def test_edge_process_no_data(mocker, edge_conf, caplog): | 
		
	
		
			
				|  |  |  |  |     edge_conf['datadir'] = None | 
		
	
		
			
				|  |  |  |  |     freqtrade = get_patched_freqtradebot(mocker, edge_conf) | 
		
	
		
			
				|  |  |  |  |     mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001)) | 
		
	
		
			
				|  |  |  |  |     mocker.patch('freqtrade.data.history.load_data', MagicMock(return_value={})) | 
		
	
		
			
				|  |  |  |  |     edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     assert not edge.calculate() | 
		
	
		
			
				|  |  |  |  |     assert len(edge._cached_pairs) == 0 | 
		
	
		
			
				|  |  |  |  |     assert log_has("No data found. Edge is stopped ...", caplog.record_tuples) | 
		
	
		
			
				|  |  |  |  |     assert edge._last_updated == 0 | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | def test_edge_process_no_trades(mocker, edge_conf, caplog): | 
		
	
		
			
				|  |  |  |  |     edge_conf['datadir'] = None | 
		
	
		
			
				|  |  |  |  |     freqtrade = get_patched_freqtradebot(mocker, edge_conf) | 
		
	
		
			
				|  |  |  |  |     mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001)) | 
		
	
		
			
				|  |  |  |  |     mocker.patch('freqtrade.data.history.load_data', mocked_load_data) | 
		
	
		
			
				|  |  |  |  |     # Return empty | 
		
	
		
			
				|  |  |  |  |     mocker.patch('freqtrade.edge.Edge._find_trades_for_stoploss_range', MagicMock(return_value=[])) | 
		
	
		
			
				|  |  |  |  |     edge = Edge(edge_conf, freqtrade.exchange, freqtrade.strategy) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     assert not edge.calculate() | 
		
	
		
			
				|  |  |  |  |     assert len(edge._cached_pairs) == 0 | 
		
	
		
			
				|  |  |  |  |     assert log_has("No trades found.", caplog.record_tuples) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | def test_edge_init_error(mocker, edge_conf,): | 
		
	
		
			
				|  |  |  |  |     edge_conf['stake_amount'] = 0.5 | 
		
	
		
			
				|  |  |  |  |     mocker.patch('freqtrade.exchange.Exchange.get_fee', MagicMock(return_value=0.001)) | 
		
	
		
			
				|  |  |  |  |     with pytest.raises(OperationalException,  match='Edge works only with unlimited stake amount'): | 
		
	
		
			
				|  |  |  |  |         get_patched_freqtradebot(mocker, edge_conf) | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  | def test_process_expectancy(mocker, edge_conf): | 
		
	
		
			
				|  |  |  |  |     edge_conf['edge']['min_trade_number'] = 2 | 
		
	
		
			
				|  |  |  |  |     freqtrade = get_patched_freqtradebot(mocker, edge_conf) | 
		
	
	
		
			
				
					
					|  |  |  | @@ -360,3 +398,11 @@ def test_process_expectancy(mocker, edge_conf): | 
		
	
		
			
				|  |  |  |  |     assert round(final['TEST/BTC'].risk_reward_ratio, 10) == 306.5384615384 | 
		
	
		
			
				|  |  |  |  |     assert round(final['TEST/BTC'].required_risk_reward, 10) == 2.0 | 
		
	
		
			
				|  |  |  |  |     assert round(final['TEST/BTC'].expectancy, 10) == 101.5128205128 | 
		
	
		
			
				|  |  |  |  |  | 
		
	
		
			
				|  |  |  |  |     # Pop last item so no trade is profitable | 
		
	
		
			
				|  |  |  |  |     trades.pop() | 
		
	
		
			
				|  |  |  |  |     trades_df = DataFrame(trades) | 
		
	
		
			
				|  |  |  |  |     trades_df = edge._fill_calculable_fields(trades_df) | 
		
	
		
			
				|  |  |  |  |     final = edge._process_expectancy(trades_df) | 
		
	
		
			
				|  |  |  |  |     assert len(final) == 0 | 
		
	
		
			
				|  |  |  |  |     assert isinstance(final, dict) | 
		
	
	
		
			
				
					
					| 
							
							
							
						 |  |  |   |