conflict resolved
This commit is contained in:
		| @@ -156,6 +156,9 @@ The below is the default which is used if this is not configured in either Strat | |||||||
|     }, |     }, | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | **NOTE**: Not all exchanges support "market" orders. | ||||||
|  | The following message will be shown if your exchange does not support market orders: `"Exchange <yourexchange>  does not support market orders."` | ||||||
|  |  | ||||||
| ### What values for exchange.name? | ### What values for exchange.name? | ||||||
|  |  | ||||||
| Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports 115 cryptocurrency | Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports 115 cryptocurrency | ||||||
|   | |||||||
| @@ -537,7 +537,9 @@ class Exchange(object): | |||||||
|             # Because some exchange sort Tickers ASC and other DESC. |             # Because some exchange sort Tickers ASC and other DESC. | ||||||
|             # Ex: Bittrex returns a list of tickers ASC (oldest first, newest last) |             # Ex: Bittrex returns a list of tickers ASC (oldest first, newest last) | ||||||
|             # when GDAX returns a list of tickers DESC (newest first, oldest last) |             # when GDAX returns a list of tickers DESC (newest first, oldest last) | ||||||
|             data = sorted(data, key=lambda x: x[0]) |             # Only sort if necessary to save computing time | ||||||
|  |             if data and data[0][0] > data[-1][0]: | ||||||
|  |                 data = sorted(data, key=lambda x: x[0]) | ||||||
|  |  | ||||||
|             # keeping last candle time as last refreshed time of the pair |             # keeping last candle time as last refreshed time of the pair | ||||||
|             if data: |             if data: | ||||||
| @@ -559,51 +561,6 @@ class Exchange(object): | |||||||
|         except ccxt.BaseError as e: |         except ccxt.BaseError as e: | ||||||
|             raise OperationalException(f'Could not fetch ticker data. Msg: {e}') |             raise OperationalException(f'Could not fetch ticker data. Msg: {e}') | ||||||
|  |  | ||||||
|     @retrier |  | ||||||
|     def get_candle_history(self, pair: str, tick_interval: str, |  | ||||||
|                            since_ms: Optional[int] = None) -> List[Dict]: |  | ||||||
|         try: |  | ||||||
|             # last item should be in the time interval [now - tick_interval, now] |  | ||||||
|             till_time_ms = arrow.utcnow().shift( |  | ||||||
|                             minutes=-constants.TICKER_INTERVAL_MINUTES[tick_interval] |  | ||||||
|                         ).timestamp * 1000 |  | ||||||
|             # it looks as if some exchanges return cached data |  | ||||||
|             # and they update it one in several minute, so 10 mins interval |  | ||||||
|             # is necessary to skeep downloading of an empty array when all |  | ||||||
|             # chached data was already downloaded |  | ||||||
|             till_time_ms = min(till_time_ms, arrow.utcnow().shift(minutes=-10).timestamp * 1000) |  | ||||||
|  |  | ||||||
|             data: List[Dict[Any, Any]] = [] |  | ||||||
|             while not since_ms or since_ms < till_time_ms: |  | ||||||
|                 data_part = self._api.fetch_ohlcv(pair, timeframe=tick_interval, since=since_ms) |  | ||||||
|  |  | ||||||
|                 # Because some exchange sort Tickers ASC and other DESC. |  | ||||||
|                 # Ex: Bittrex returns a list of tickers ASC (oldest first, newest last) |  | ||||||
|                 # when GDAX returns a list of tickers DESC (newest first, oldest last) |  | ||||||
|                 data_part = sorted(data_part, key=lambda x: x[0]) |  | ||||||
|  |  | ||||||
|                 if not data_part: |  | ||||||
|                     break |  | ||||||
|  |  | ||||||
|                 logger.debug('Downloaded data for %s time range [%s, %s]', |  | ||||||
|                              pair, |  | ||||||
|                              arrow.get(data_part[0][0] / 1000).format(), |  | ||||||
|                              arrow.get(data_part[-1][0] / 1000).format()) |  | ||||||
|  |  | ||||||
|                 data.extend(data_part) |  | ||||||
|                 since_ms = data[-1][0] + 1 |  | ||||||
|  |  | ||||||
|             return data |  | ||||||
|         except ccxt.NotSupported as e: |  | ||||||
|             raise OperationalException( |  | ||||||
|                 f'Exchange {self._api.name} does not support fetching historical candlestick data.' |  | ||||||
|                 f'Message: {e}') |  | ||||||
|         except (ccxt.NetworkError, ccxt.ExchangeError) as e: |  | ||||||
|             raise TemporaryError( |  | ||||||
|                 f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}') |  | ||||||
|         except ccxt.BaseError as e: |  | ||||||
|             raise OperationalException(f'Could not fetch ticker data. Msg: {e}') |  | ||||||
|  |  | ||||||
|     @retrier |     @retrier | ||||||
|     def cancel_order(self, order_id: str, pair: str) -> None: |     def cancel_order(self, order_id: str, pair: str) -> None: | ||||||
|         if self._conf['dry_run']: |         if self._conf['dry_run']: | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ logger = logging.getLogger(__name__) | |||||||
| def parse_ticker_dataframe(ticker: list) -> DataFrame: | def parse_ticker_dataframe(ticker: list) -> DataFrame: | ||||||
|     """ |     """ | ||||||
|     Analyses the trend for the given ticker history |     Analyses the trend for the given ticker history | ||||||
|     :param ticker: See exchange.get_candle_history |     :param ticker: ticker list, as returned by exchange.async_get_candle_history | ||||||
|     :return: DataFrame |     :return: DataFrame | ||||||
|     """ |     """ | ||||||
|     cols = ['date', 'open', 'high', 'low', 'close', 'volume'] |     cols = ['date', 'open', 'high', 'low', 'close', 'volume'] | ||||||
|   | |||||||
| @@ -21,9 +21,9 @@ from freqtrade.wallets import Wallets | |||||||
| from freqtrade.edge import Edge | from freqtrade.edge import Edge | ||||||
| from freqtrade.persistence import Trade | from freqtrade.persistence import Trade | ||||||
| from freqtrade.rpc import RPCManager, RPCMessageType | from freqtrade.rpc import RPCManager, RPCMessageType | ||||||
|  | from freqtrade.resolvers import StrategyResolver | ||||||
| from freqtrade.state import State | from freqtrade.state import State | ||||||
| from freqtrade.strategy.interface import SellType | from freqtrade.strategy.interface import SellType, IStrategy | ||||||
| from freqtrade.strategy.resolver import IStrategy, StrategyResolver |  | ||||||
| from freqtrade.exchange.exchange_helpers import order_book_to_dataframe | from freqtrade.exchange.exchange_helpers import order_book_to_dataframe | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -338,9 +338,7 @@ class FreqtradeBot(object): | |||||||
|         else: |         else: | ||||||
|             stake_amount = self.config['stake_amount'] |             stake_amount = self.config['stake_amount'] | ||||||
|  |  | ||||||
|         # TODO: should come from the wallet |         avaliable_amount = self.wallets.get_free(self.config['stake_currency']) | ||||||
|         avaliable_amount = self.exchange.get_balance(self.config['stake_currency']) |  | ||||||
|         # avaliable_amount = self.wallets.wallets[self.config['stake_currency']].free |  | ||||||
|  |  | ||||||
|         if stake_amount == constants.UNLIMITED_STAKE_AMOUNT: |         if stake_amount == constants.UNLIMITED_STAKE_AMOUNT: | ||||||
|             open_trades = len(Trade.query.filter(Trade.is_open.is_(True)).all()) |             open_trades = len(Trade.query.filter(Trade.is_open.is_(True)).all()) | ||||||
|   | |||||||
| @@ -45,7 +45,7 @@ def main(sysargv: List[str]) -> None: | |||||||
|         freqtrade = FreqtradeBot(config) |         freqtrade = FreqtradeBot(config) | ||||||
|  |  | ||||||
|         state = None |         state = None | ||||||
|         while 1: |         while True: | ||||||
|             state = freqtrade.worker(old_state=state) |             state = freqtrade.worker(old_state=state) | ||||||
|             if state == State.RELOAD_CONF: |             if state == State.RELOAD_CONF: | ||||||
|                 freqtrade = reconfigure(freqtrade, args) |                 freqtrade = reconfigure(freqtrade, args) | ||||||
|   | |||||||
| @@ -20,8 +20,8 @@ from freqtrade.configuration import Configuration | |||||||
| from freqtrade.exchange import Exchange | from freqtrade.exchange import Exchange | ||||||
| from freqtrade.misc import file_dump_json | from freqtrade.misc import file_dump_json | ||||||
| from freqtrade.persistence import Trade | from freqtrade.persistence import Trade | ||||||
| from freqtrade.strategy.interface import SellType | from freqtrade.resolvers import StrategyResolver | ||||||
| from freqtrade.strategy.resolver import IStrategy, StrategyResolver | from freqtrade.strategy.interface import SellType, IStrategy | ||||||
|  |  | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ from freqtrade.edge import Edge | |||||||
| from freqtrade.configuration import Configuration | from freqtrade.configuration import Configuration | ||||||
| from freqtrade.arguments import Arguments | from freqtrade.arguments import Arguments | ||||||
| from freqtrade.exchange import Exchange | from freqtrade.exchange import Exchange | ||||||
| from freqtrade.strategy.resolver import StrategyResolver | from freqtrade.resolvers import StrategyResolver | ||||||
|  |  | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ from freqtrade.arguments import Arguments | |||||||
| from freqtrade.configuration import Configuration | from freqtrade.configuration import Configuration | ||||||
| from freqtrade.optimize import load_data | from freqtrade.optimize import load_data | ||||||
| from freqtrade.optimize.backtesting import Backtesting | from freqtrade.optimize.backtesting import Backtesting | ||||||
| from freqtrade.optimize.hyperopt_resolver import HyperOptResolver | from freqtrade.resolvers import HyperOptResolver | ||||||
|  |  | ||||||
|  |  | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
|   | |||||||
| @@ -1,104 +0,0 @@ | |||||||
| # pragma pylint: disable=attribute-defined-outside-init |  | ||||||
|  |  | ||||||
| """ |  | ||||||
| This module load custom hyperopts |  | ||||||
| """ |  | ||||||
| import importlib.util |  | ||||||
| import inspect |  | ||||||
| import logging |  | ||||||
| import os |  | ||||||
| from typing import Optional, Dict, Type |  | ||||||
|  |  | ||||||
| from freqtrade.constants import DEFAULT_HYPEROPT |  | ||||||
| from freqtrade.optimize.hyperopt_interface import IHyperOpt |  | ||||||
|  |  | ||||||
|  |  | ||||||
| logger = logging.getLogger(__name__) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class HyperOptResolver(object): |  | ||||||
|     """ |  | ||||||
|     This class contains all the logic to load custom hyperopt class |  | ||||||
|     """ |  | ||||||
|  |  | ||||||
|     __slots__ = ['hyperopt'] |  | ||||||
|  |  | ||||||
|     def __init__(self, config: Optional[Dict] = None) -> None: |  | ||||||
|         """ |  | ||||||
|         Load the custom class from config parameter |  | ||||||
|         :param config: configuration dictionary or None |  | ||||||
|         """ |  | ||||||
|         config = config or {} |  | ||||||
|  |  | ||||||
|         # Verify the hyperopt is in the configuration, otherwise fallback to the default hyperopt |  | ||||||
|         hyperopt_name = config.get('hyperopt') or DEFAULT_HYPEROPT |  | ||||||
|         self.hyperopt = self._load_hyperopt(hyperopt_name, extra_dir=config.get('hyperopt_path')) |  | ||||||
|  |  | ||||||
|     def _load_hyperopt( |  | ||||||
|             self, hyperopt_name: str, extra_dir: Optional[str] = None) -> IHyperOpt: |  | ||||||
|         """ |  | ||||||
|         Search and loads the specified hyperopt. |  | ||||||
|         :param hyperopt_name: name of the module to import |  | ||||||
|         :param extra_dir: additional directory to search for the given hyperopt |  | ||||||
|         :return: HyperOpt instance or None |  | ||||||
|         """ |  | ||||||
|         current_path = os.path.dirname(os.path.realpath(__file__)) |  | ||||||
|         abs_paths = [ |  | ||||||
|             os.path.join(current_path, '..', '..', 'user_data', 'hyperopts'), |  | ||||||
|             current_path, |  | ||||||
|         ] |  | ||||||
|  |  | ||||||
|         if extra_dir: |  | ||||||
|             # Add extra hyperopt directory on top of search paths |  | ||||||
|             abs_paths.insert(0, extra_dir) |  | ||||||
|  |  | ||||||
|         for path in abs_paths: |  | ||||||
|             hyperopt = self._search_hyperopt(path, hyperopt_name) |  | ||||||
|             if hyperopt: |  | ||||||
|                 logger.info('Using resolved hyperopt %s from \'%s\'', hyperopt_name, path) |  | ||||||
|                 return hyperopt |  | ||||||
|  |  | ||||||
|         raise ImportError( |  | ||||||
|             "Impossible to load Hyperopt '{}'. This class does not exist" |  | ||||||
|             " or contains Python code errors".format(hyperopt_name) |  | ||||||
|         ) |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def _get_valid_hyperopts(module_path: str, hyperopt_name: str) -> Optional[Type[IHyperOpt]]: |  | ||||||
|         """ |  | ||||||
|         Returns a list of all possible hyperopts for the given module_path |  | ||||||
|         :param module_path: absolute path to the module |  | ||||||
|         :param hyperopt_name: Class name of the hyperopt |  | ||||||
|         :return: Tuple with (name, class) or None |  | ||||||
|         """ |  | ||||||
|  |  | ||||||
|         # Generate spec based on absolute path |  | ||||||
|         spec = importlib.util.spec_from_file_location('user_data.hyperopts', module_path) |  | ||||||
|         module = importlib.util.module_from_spec(spec) |  | ||||||
|         spec.loader.exec_module(module)  # type: ignore # importlib does not use typehints |  | ||||||
|  |  | ||||||
|         valid_hyperopts_gen = ( |  | ||||||
|             obj for name, obj in inspect.getmembers(module, inspect.isclass) |  | ||||||
|             if hyperopt_name == name and IHyperOpt in obj.__bases__ |  | ||||||
|         ) |  | ||||||
|         return next(valid_hyperopts_gen, None) |  | ||||||
|  |  | ||||||
|     @staticmethod |  | ||||||
|     def _search_hyperopt(directory: str, hyperopt_name: str) -> Optional[IHyperOpt]: |  | ||||||
|         """ |  | ||||||
|         Search for the hyperopt_name in the given directory |  | ||||||
|         :param directory: relative or absolute directory path |  | ||||||
|         :return: name of the hyperopt class |  | ||||||
|         """ |  | ||||||
|         logger.debug('Searching for hyperopt %s in \'%s\'', hyperopt_name, directory) |  | ||||||
|         for entry in os.listdir(directory): |  | ||||||
|             # Only consider python files |  | ||||||
|             if not entry.endswith('.py'): |  | ||||||
|                 logger.debug('Ignoring %s', entry) |  | ||||||
|                 continue |  | ||||||
|             hyperopt = HyperOptResolver._get_valid_hyperopts( |  | ||||||
|                 os.path.abspath(os.path.join(directory, entry)), hyperopt_name |  | ||||||
|             ) |  | ||||||
|             if hyperopt: |  | ||||||
|                 return hyperopt() |  | ||||||
|         return None |  | ||||||
							
								
								
									
										3
									
								
								freqtrade/resolvers/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								freqtrade/resolvers/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | |||||||
|  | from freqtrade.resolvers.iresolver import IResolver  # noqa: F401 | ||||||
|  | from freqtrade.resolvers.hyperopt_resolver import HyperOptResolver  # noqa: F401 | ||||||
|  | from freqtrade.resolvers.strategy_resolver import StrategyResolver  # noqa: F401 | ||||||
							
								
								
									
										64
									
								
								freqtrade/resolvers/hyperopt_resolver.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								freqtrade/resolvers/hyperopt_resolver.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,64 @@ | |||||||
|  | # pragma pylint: disable=attribute-defined-outside-init | ||||||
|  |  | ||||||
|  | """ | ||||||
|  | This module load custom hyperopts | ||||||
|  | """ | ||||||
|  | import logging | ||||||
|  | from pathlib import Path | ||||||
|  | from typing import Optional, Dict | ||||||
|  |  | ||||||
|  | from freqtrade.constants import DEFAULT_HYPEROPT | ||||||
|  | from freqtrade.optimize.hyperopt_interface import IHyperOpt | ||||||
|  | from freqtrade.resolvers import IResolver | ||||||
|  |  | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class HyperOptResolver(IResolver): | ||||||
|  |     """ | ||||||
|  |     This class contains all the logic to load custom hyperopt class | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     __slots__ = ['hyperopt'] | ||||||
|  |  | ||||||
|  |     def __init__(self, config: Optional[Dict] = None) -> None: | ||||||
|  |         """ | ||||||
|  |         Load the custom class from config parameter | ||||||
|  |         :param config: configuration dictionary or None | ||||||
|  |         """ | ||||||
|  |         config = config or {} | ||||||
|  |  | ||||||
|  |         # Verify the hyperopt is in the configuration, otherwise fallback to the default hyperopt | ||||||
|  |         hyperopt_name = config.get('hyperopt') or DEFAULT_HYPEROPT | ||||||
|  |         self.hyperopt = self._load_hyperopt(hyperopt_name, extra_dir=config.get('hyperopt_path')) | ||||||
|  |  | ||||||
|  |     def _load_hyperopt( | ||||||
|  |             self, hyperopt_name: str, extra_dir: Optional[str] = None) -> IHyperOpt: | ||||||
|  |         """ | ||||||
|  |         Search and loads the specified hyperopt. | ||||||
|  |         :param hyperopt_name: name of the module to import | ||||||
|  |         :param extra_dir: additional directory to search for the given hyperopt | ||||||
|  |         :return: HyperOpt instance or None | ||||||
|  |         """ | ||||||
|  |         current_path = Path(__file__).parent.parent.joinpath('optimize').resolve() | ||||||
|  |  | ||||||
|  |         abs_paths = [ | ||||||
|  |             current_path.parent.parent.joinpath('user_data/hyperopts'), | ||||||
|  |             current_path, | ||||||
|  |         ] | ||||||
|  |  | ||||||
|  |         if extra_dir: | ||||||
|  |             # Add extra hyperopt directory on top of search paths | ||||||
|  |             abs_paths.insert(0, Path(extra_dir)) | ||||||
|  |  | ||||||
|  |         for _path in abs_paths: | ||||||
|  |             hyperopt = self._search_object(directory=_path, object_type=IHyperOpt, | ||||||
|  |                                            object_name=hyperopt_name) | ||||||
|  |             if hyperopt: | ||||||
|  |                 logger.info('Using resolved hyperopt %s from \'%s\'', hyperopt_name, _path) | ||||||
|  |                 return hyperopt | ||||||
|  |  | ||||||
|  |         raise ImportError( | ||||||
|  |             "Impossible to load Hyperopt '{}'. This class does not exist" | ||||||
|  |             " or contains Python code errors".format(hyperopt_name) | ||||||
|  |         ) | ||||||
							
								
								
									
										61
									
								
								freqtrade/resolvers/iresolver.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								freqtrade/resolvers/iresolver.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,61 @@ | |||||||
|  | # pragma pylint: disable=attribute-defined-outside-init | ||||||
|  |  | ||||||
|  | """ | ||||||
|  | This module load custom objects | ||||||
|  | """ | ||||||
|  | import importlib.util | ||||||
|  | import inspect | ||||||
|  | import logging | ||||||
|  | from pathlib import Path | ||||||
|  | from typing import Optional, Type, Any | ||||||
|  |  | ||||||
|  | logger = logging.getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class IResolver(object): | ||||||
|  |     """ | ||||||
|  |     This class contains all the logic to load custom classes | ||||||
|  |     """ | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _get_valid_object(object_type, module_path: Path, | ||||||
|  |                           object_name: str) -> Optional[Type[Any]]: | ||||||
|  |         """ | ||||||
|  |         Returns the first object with matching object_type and object_name in the path given. | ||||||
|  |         :param object_type: object_type (class) | ||||||
|  |         :param module_path: absolute path to the module | ||||||
|  |         :param object_name: Class name of the object | ||||||
|  |         :return: class or None | ||||||
|  |         """ | ||||||
|  |  | ||||||
|  |         # Generate spec based on absolute path | ||||||
|  |         spec = importlib.util.spec_from_file_location('unknown', str(module_path)) | ||||||
|  |         module = importlib.util.module_from_spec(spec) | ||||||
|  |         spec.loader.exec_module(module)  # type: ignore # importlib does not use typehints | ||||||
|  |  | ||||||
|  |         valid_objects_gen = ( | ||||||
|  |             obj for name, obj in inspect.getmembers(module, inspect.isclass) | ||||||
|  |             if object_name == name and object_type in obj.__bases__ | ||||||
|  |         ) | ||||||
|  |         return next(valid_objects_gen, None) | ||||||
|  |  | ||||||
|  |     @staticmethod | ||||||
|  |     def _search_object(directory: Path, object_type, object_name: str, | ||||||
|  |                        kwargs: dict = {}) -> Optional[Any]: | ||||||
|  |         """ | ||||||
|  |         Search for the objectname in the given directory | ||||||
|  |         :param directory: relative or absolute directory path | ||||||
|  |         :return: object instance | ||||||
|  |         """ | ||||||
|  |         logger.debug('Searching for %s %s in \'%s\'', object_type.__name__, object_name, directory) | ||||||
|  |         for entry in directory.iterdir(): | ||||||
|  |             # Only consider python files | ||||||
|  |             if not str(entry).endswith('.py'): | ||||||
|  |                 logger.debug('Ignoring %s', entry) | ||||||
|  |                 continue | ||||||
|  |             obj = IResolver._get_valid_object( | ||||||
|  |                 object_type, Path.resolve(directory.joinpath(entry)), object_name | ||||||
|  |             ) | ||||||
|  |             if obj: | ||||||
|  |                 return obj(**kwargs) | ||||||
|  |         return None | ||||||
| @@ -3,24 +3,23 @@ | |||||||
| """ | """ | ||||||
| This module load custom strategies | This module load custom strategies | ||||||
| """ | """ | ||||||
| import importlib.util |  | ||||||
| import inspect | import inspect | ||||||
| import logging | import logging | ||||||
| import os |  | ||||||
| import tempfile | import tempfile | ||||||
| from base64 import urlsafe_b64decode | from base64 import urlsafe_b64decode | ||||||
| from collections import OrderedDict | from collections import OrderedDict | ||||||
| from pathlib import Path | from pathlib import Path | ||||||
| from typing import Dict, Optional, Type | from typing import Dict, Optional | ||||||
| 
 | 
 | ||||||
| from freqtrade import constants | from freqtrade import constants | ||||||
|  | from freqtrade.resolvers import IResolver | ||||||
| from freqtrade.strategy import import_strategy | from freqtrade.strategy import import_strategy | ||||||
| from freqtrade.strategy.interface import IStrategy | from freqtrade.strategy.interface import IStrategy | ||||||
| 
 | 
 | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| 
 | 
 | ||||||
| 
 | 
 | ||||||
| class StrategyResolver(object): | class StrategyResolver(IResolver): | ||||||
|     """ |     """ | ||||||
|     This class contains all the logic to load custom strategy class |     This class contains all the logic to load custom strategy class | ||||||
|     """ |     """ | ||||||
| @@ -103,15 +102,16 @@ class StrategyResolver(object): | |||||||
|         :param extra_dir: additional directory to search for the given strategy |         :param extra_dir: additional directory to search for the given strategy | ||||||
|         :return: Strategy instance or None |         :return: Strategy instance or None | ||||||
|         """ |         """ | ||||||
|         current_path = os.path.dirname(os.path.realpath(__file__)) |         current_path = Path(__file__).parent.parent.joinpath('strategy').resolve() | ||||||
|  | 
 | ||||||
|         abs_paths = [ |         abs_paths = [ | ||||||
|             os.path.join(os.getcwd(), 'user_data', 'strategies'), |             Path.cwd().joinpath('user_data/strategies'), | ||||||
|             current_path, |             current_path, | ||||||
|         ] |         ] | ||||||
| 
 | 
 | ||||||
|         if extra_dir: |         if extra_dir: | ||||||
|             # Add extra strategy directory on top of search paths |             # Add extra strategy directory on top of search paths | ||||||
|             abs_paths.insert(0, extra_dir) |             abs_paths.insert(0, Path(extra_dir).resolve()) | ||||||
| 
 | 
 | ||||||
|         if ":" in strategy_name: |         if ":" in strategy_name: | ||||||
|             logger.info("loading base64 endocded strategy") |             logger.info("loading base64 endocded strategy") | ||||||
| @@ -124,16 +124,17 @@ class StrategyResolver(object): | |||||||
|                 temp.joinpath(name).write_text(urlsafe_b64decode(strat[1]).decode('utf-8')) |                 temp.joinpath(name).write_text(urlsafe_b64decode(strat[1]).decode('utf-8')) | ||||||
|                 temp.joinpath("__init__.py").touch() |                 temp.joinpath("__init__.py").touch() | ||||||
| 
 | 
 | ||||||
|                 strategy_name = os.path.splitext(name)[0] |                 strategy_name = strat[0] | ||||||
| 
 | 
 | ||||||
|                 # register temp path with the bot |                 # register temp path with the bot | ||||||
|                 abs_paths.insert(0, str(temp.resolve())) |                 abs_paths.insert(0, temp.resolve()) | ||||||
| 
 | 
 | ||||||
|         for path in abs_paths: |         for _path in abs_paths: | ||||||
|             try: |             try: | ||||||
|                 strategy = self._search_strategy(path, strategy_name=strategy_name, config=config) |                 strategy = self._search_object(directory=_path, object_type=IStrategy, | ||||||
|  |                                                object_name=strategy_name, kwargs={'config': config}) | ||||||
|                 if strategy: |                 if strategy: | ||||||
|                     logger.info('Using resolved strategy %s from \'%s\'', strategy_name, path) |                     logger.info('Using resolved strategy %s from \'%s\'', strategy_name, _path) | ||||||
|                     strategy._populate_fun_len = len( |                     strategy._populate_fun_len = len( | ||||||
|                         inspect.getfullargspec(strategy.populate_indicators).args) |                         inspect.getfullargspec(strategy.populate_indicators).args) | ||||||
|                     strategy._buy_fun_len = len( |                     strategy._buy_fun_len = len( | ||||||
| @@ -143,49 +144,9 @@ class StrategyResolver(object): | |||||||
| 
 | 
 | ||||||
|                     return import_strategy(strategy, config=config) |                     return import_strategy(strategy, config=config) | ||||||
|             except FileNotFoundError: |             except FileNotFoundError: | ||||||
|                 logger.warning('Path "%s" does not exist', path) |                 logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd())) | ||||||
| 
 | 
 | ||||||
|         raise ImportError( |         raise ImportError( | ||||||
|             "Impossible to load Strategy '{}'. This class does not exist" |             "Impossible to load Strategy '{}'. This class does not exist" | ||||||
|             " or contains Python code errors".format(strategy_name) |             " or contains Python code errors".format(strategy_name) | ||||||
|         ) |         ) | ||||||
| 
 |  | ||||||
|     @staticmethod |  | ||||||
|     def _get_valid_strategies(module_path: str, strategy_name: str) -> Optional[Type[IStrategy]]: |  | ||||||
|         """ |  | ||||||
|         Returns a list of all possible strategies for the given module_path |  | ||||||
|         :param module_path: absolute path to the module |  | ||||||
|         :param strategy_name: Class name of the strategy |  | ||||||
|         :return: Tuple with (name, class) or None |  | ||||||
|         """ |  | ||||||
| 
 |  | ||||||
|         # Generate spec based on absolute path |  | ||||||
|         spec = importlib.util.spec_from_file_location('unknown', module_path) |  | ||||||
|         module = importlib.util.module_from_spec(spec) |  | ||||||
|         spec.loader.exec_module(module)  # type: ignore # importlib does not use typehints |  | ||||||
| 
 |  | ||||||
|         valid_strategies_gen = ( |  | ||||||
|             obj for name, obj in inspect.getmembers(module, inspect.isclass) |  | ||||||
|             if strategy_name == name and IStrategy in obj.__bases__ |  | ||||||
|         ) |  | ||||||
|         return next(valid_strategies_gen, None) |  | ||||||
| 
 |  | ||||||
|     @staticmethod |  | ||||||
|     def _search_strategy(directory: str, strategy_name: str, config: dict) -> Optional[IStrategy]: |  | ||||||
|         """ |  | ||||||
|         Search for the strategy_name in the given directory |  | ||||||
|         :param directory: relative or absolute directory path |  | ||||||
|         :return: name of the strategy class |  | ||||||
|         """ |  | ||||||
|         logger.debug('Searching for strategy %s in \'%s\'', strategy_name, directory) |  | ||||||
|         for entry in os.listdir(directory): |  | ||||||
|             # Only consider python files |  | ||||||
|             if not entry.endswith('.py'): |  | ||||||
|                 logger.debug('Ignoring %s', entry) |  | ||||||
|                 continue |  | ||||||
|             strategy = StrategyResolver._get_valid_strategies( |  | ||||||
|                 os.path.abspath(os.path.join(directory, entry)), strategy_name |  | ||||||
|             ) |  | ||||||
|             if strategy: |  | ||||||
|                 return strategy(config) |  | ||||||
|         return None |  | ||||||
| @@ -4,7 +4,6 @@ import logging | |||||||
| from datetime import datetime | from datetime import datetime | ||||||
| from functools import reduce | from functools import reduce | ||||||
| from typing import Dict, Optional | from typing import Dict, Optional | ||||||
| from collections import namedtuple |  | ||||||
| from unittest.mock import MagicMock, PropertyMock | from unittest.mock import MagicMock, PropertyMock | ||||||
|  |  | ||||||
| import arrow | import arrow | ||||||
| @@ -13,7 +12,7 @@ from telegram import Chat, Message, Update | |||||||
|  |  | ||||||
| from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe | from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe | ||||||
| from freqtrade.exchange import Exchange | from freqtrade.exchange import Exchange | ||||||
| from freqtrade.edge import Edge | from freqtrade.edge import Edge, PairInfo | ||||||
| from freqtrade.freqtradebot import FreqtradeBot | from freqtrade.freqtradebot import FreqtradeBot | ||||||
|  |  | ||||||
| logging.getLogger('').setLevel(logging.INFO) | logging.getLogger('').setLevel(logging.INFO) | ||||||
| @@ -46,18 +45,22 @@ def get_patched_exchange(mocker, config, api_mock=None, id='bittrex') -> Exchang | |||||||
|     return exchange |     return exchange | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def patch_wallet(mocker, free=999.9) -> None: | ||||||
|  |     mocker.patch('freqtrade.wallets.Wallets.get_free', MagicMock( | ||||||
|  |         return_value=free | ||||||
|  |     )) | ||||||
|  |  | ||||||
|  |  | ||||||
| def patch_edge(mocker) -> None: | def patch_edge(mocker) -> None: | ||||||
|     # "ETH/BTC", |     # "ETH/BTC", | ||||||
|     # "LTC/BTC", |     # "LTC/BTC", | ||||||
|     # "XRP/BTC", |     # "XRP/BTC", | ||||||
|     # "NEO/BTC" |     # "NEO/BTC" | ||||||
|     pair_info = namedtuple( |  | ||||||
|         'pair_info', |  | ||||||
|         'stoploss, winrate, risk_reward_ratio, required_risk_reward, expectancy') |  | ||||||
|     mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( |     mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock( | ||||||
|         return_value={ |         return_value={ | ||||||
|             'NEO/BTC': pair_info(-0.20, 0.66, 3.71, 0.50, 1.71), |             'NEO/BTC': PairInfo(-0.20, 0.66, 3.71, 0.50, 1.71, 10, 25), | ||||||
|             'LTC/BTC': pair_info(-0.21, 0.66, 3.71, 0.50, 1.71), |             'LTC/BTC': PairInfo(-0.21, 0.66, 3.71, 0.50, 1.71, 11, 20), | ||||||
|         } |         } | ||||||
|     )) |     )) | ||||||
|     mocker.patch('freqtrade.edge.Edge.stoploss', MagicMock(return_value=-0.20)) |     mocker.patch('freqtrade.edge.Edge.stoploss', MagicMock(return_value=-0.20)) | ||||||
|   | |||||||
| @@ -898,65 +898,10 @@ def make_fetch_ohlcv_mock(data): | |||||||
|     return fetch_ohlcv_mock |     return fetch_ohlcv_mock | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_get_candle_history(default_conf, mocker): | @pytest.mark.asyncio | ||||||
|     api_mock = MagicMock() | async def test___async_get_candle_history_sort(default_conf, mocker): | ||||||
|     tick = [ |     def sort_data(data, key): | ||||||
|         [ |         return sorted(data, key=key) | ||||||
|             1511686200000,  # unix timestamp ms |  | ||||||
|             1,  # open |  | ||||||
|             2,  # high |  | ||||||
|             3,  # low |  | ||||||
|             4,  # close |  | ||||||
|             5,  # volume (in quote currency) |  | ||||||
|         ] |  | ||||||
|     ] |  | ||||||
|     type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) |  | ||||||
|     api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick)) |  | ||||||
|     exchange = get_patched_exchange(mocker, default_conf, api_mock) |  | ||||||
|  |  | ||||||
|     # retrieve original ticker |  | ||||||
|     ticks = exchange.get_candle_history('ETH/BTC', default_conf['ticker_interval']) |  | ||||||
|     assert ticks[0][0] == 1511686200000 |  | ||||||
|     assert ticks[0][1] == 1 |  | ||||||
|     assert ticks[0][2] == 2 |  | ||||||
|     assert ticks[0][3] == 3 |  | ||||||
|     assert ticks[0][4] == 4 |  | ||||||
|     assert ticks[0][5] == 5 |  | ||||||
|  |  | ||||||
|     # change ticker and ensure tick changes |  | ||||||
|     new_tick = [ |  | ||||||
|         [ |  | ||||||
|             1511686210000,  # unix timestamp ms |  | ||||||
|             6,  # open |  | ||||||
|             7,  # high |  | ||||||
|             8,  # low |  | ||||||
|             9,  # close |  | ||||||
|             10,  # volume (in quote currency) |  | ||||||
|         ] |  | ||||||
|     ] |  | ||||||
|     api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(new_tick)) |  | ||||||
|     exchange = get_patched_exchange(mocker, default_conf, api_mock) |  | ||||||
|  |  | ||||||
|     ticks = exchange.get_candle_history('ETH/BTC', default_conf['ticker_interval']) |  | ||||||
|     assert ticks[0][0] == 1511686210000 |  | ||||||
|     assert ticks[0][1] == 6 |  | ||||||
|     assert ticks[0][2] == 7 |  | ||||||
|     assert ticks[0][3] == 8 |  | ||||||
|     assert ticks[0][4] == 9 |  | ||||||
|     assert ticks[0][5] == 10 |  | ||||||
|  |  | ||||||
|     ccxt_exceptionhandlers(mocker, default_conf, api_mock, |  | ||||||
|                            "get_candle_history", "fetch_ohlcv", |  | ||||||
|                            pair='ABCD/BTC', tick_interval=default_conf['ticker_interval']) |  | ||||||
|  |  | ||||||
|     with pytest.raises(OperationalException, match=r'Exchange .* does not support.*'): |  | ||||||
|         api_mock.fetch_ohlcv = MagicMock(side_effect=ccxt.NotSupported) |  | ||||||
|         exchange = get_patched_exchange(mocker, default_conf, api_mock) |  | ||||||
|         exchange.get_candle_history(pair='ABCD/BTC', tick_interval=default_conf['ticker_interval']) |  | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_get_candle_history_sort(default_conf, mocker): |  | ||||||
|     api_mock = MagicMock() |  | ||||||
|  |  | ||||||
|     # GDAX use-case (real data from GDAX) |     # GDAX use-case (real data from GDAX) | ||||||
|     # This ticker history is ordered DESC (newest first, oldest last) |     # This ticker history is ordered DESC (newest first, oldest last) | ||||||
| @@ -972,13 +917,15 @@ def test_get_candle_history_sort(default_conf, mocker): | |||||||
|         [1527830700000, 0.07652, 0.07652, 0.07651, 0.07652, 10.04822687], |         [1527830700000, 0.07652, 0.07652, 0.07651, 0.07652, 10.04822687], | ||||||
|         [1527830400000, 0.07649, 0.07651, 0.07649, 0.07651, 2.5734867] |         [1527830400000, 0.07649, 0.07651, 0.07649, 0.07651, 2.5734867] | ||||||
|     ] |     ] | ||||||
|     type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) |     exchange = get_patched_exchange(mocker, default_conf) | ||||||
|     api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick)) |     exchange._api_async.fetch_ohlcv = get_mock_coro(tick) | ||||||
|  |     sort_mock = mocker.patch('freqtrade.exchange.sorted', MagicMock(side_effect=sort_data)) | ||||||
|     exchange = get_patched_exchange(mocker, default_conf, api_mock) |  | ||||||
|  |  | ||||||
|     # Test the ticker history sort |     # Test the ticker history sort | ||||||
|     ticks = exchange.get_candle_history('ETH/BTC', default_conf['ticker_interval']) |     res = await exchange._async_get_candle_history('ETH/BTC', default_conf['ticker_interval']) | ||||||
|  |     assert res[0] == 'ETH/BTC' | ||||||
|  |     ticks = res[1] | ||||||
|  |  | ||||||
|  |     assert sort_mock.call_count == 1 | ||||||
|     assert ticks[0][0] == 1527830400000 |     assert ticks[0][0] == 1527830400000 | ||||||
|     assert ticks[0][1] == 0.07649 |     assert ticks[0][1] == 0.07649 | ||||||
|     assert ticks[0][2] == 0.07651 |     assert ticks[0][2] == 0.07651 | ||||||
| @@ -1007,11 +954,15 @@ def test_get_candle_history_sort(default_conf, mocker): | |||||||
|         [1527830100000, 0.076695, 0.07671, 0.07624171, 0.07671, 1.80689244], |         [1527830100000, 0.076695, 0.07671, 0.07624171, 0.07671, 1.80689244], | ||||||
|         [1527830400000, 0.07671, 0.07674399, 0.07629216, 0.07655213, 2.31452783] |         [1527830400000, 0.07671, 0.07674399, 0.07629216, 0.07655213, 2.31452783] | ||||||
|     ] |     ] | ||||||
|     type(api_mock).has = PropertyMock(return_value={'fetchOHLCV': True}) |     exchange._api_async.fetch_ohlcv = get_mock_coro(tick) | ||||||
|     api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick)) |     # Reset sort mock | ||||||
|     exchange = get_patched_exchange(mocker, default_conf, api_mock) |     sort_mock = mocker.patch('freqtrade.exchange.sorted', MagicMock(side_effect=sort_data)) | ||||||
|     # Test the ticker history sort |     # Test the ticker history sort | ||||||
|     ticks = exchange.get_candle_history('ETH/BTC', default_conf['ticker_interval']) |     res = await exchange._async_get_candle_history('ETH/BTC', default_conf['ticker_interval']) | ||||||
|  |     assert res[0] == 'ETH/BTC' | ||||||
|  |     ticks = res[1] | ||||||
|  |     # Sorted not called again - data is already in order | ||||||
|  |     assert sort_mock.call_count == 0 | ||||||
|     assert ticks[0][0] == 1527827700000 |     assert ticks[0][0] == 1527827700000 | ||||||
|     assert ticks[0][1] == 0.07659999 |     assert ticks[0][1] == 0.07659999 | ||||||
|     assert ticks[0][2] == 0.0766 |     assert ticks[0][2] == 0.0766 | ||||||
|   | |||||||
| @@ -7,7 +7,7 @@ import pytest | |||||||
|  |  | ||||||
| from freqtrade.optimize.__init__ import load_tickerdata_file | from freqtrade.optimize.__init__ import load_tickerdata_file | ||||||
| from freqtrade.optimize.hyperopt import Hyperopt, start | from freqtrade.optimize.hyperopt import Hyperopt, start | ||||||
| from freqtrade.strategy.resolver import StrategyResolver | from freqtrade.resolvers import StrategyResolver | ||||||
| from freqtrade.tests.conftest import log_has, patch_exchange | from freqtrade.tests.conftest import log_has, patch_exchange | ||||||
| from freqtrade.tests.optimize.test_backtesting import get_args | from freqtrade.tests.optimize.test_backtesting import get_args | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ | |||||||
| import logging | import logging | ||||||
| from base64 import urlsafe_b64encode | from base64 import urlsafe_b64encode | ||||||
| from os import path | from os import path | ||||||
|  | from pathlib import Path | ||||||
| import warnings | import warnings | ||||||
|  |  | ||||||
| import pytest | import pytest | ||||||
| @@ -10,7 +11,7 @@ from pandas import DataFrame | |||||||
| from freqtrade.strategy import import_strategy | from freqtrade.strategy import import_strategy | ||||||
| from freqtrade.strategy.default_strategy import DefaultStrategy | from freqtrade.strategy.default_strategy import DefaultStrategy | ||||||
| from freqtrade.strategy.interface import IStrategy | from freqtrade.strategy.interface import IStrategy | ||||||
| from freqtrade.strategy.resolver import StrategyResolver | from freqtrade.resolvers import StrategyResolver | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_import_strategy(caplog): | def test_import_strategy(caplog): | ||||||
| @@ -40,21 +41,21 @@ def test_import_strategy(caplog): | |||||||
|  |  | ||||||
| def test_search_strategy(): | def test_search_strategy(): | ||||||
|     default_config = {} |     default_config = {} | ||||||
|     default_location = path.join(path.dirname( |     default_location = Path(__file__).parent.parent.joinpath('strategy').resolve() | ||||||
|         path.realpath(__file__)), '..', '..', 'strategy' |  | ||||||
|     ) |  | ||||||
|     assert isinstance( |     assert isinstance( | ||||||
|         StrategyResolver._search_strategy( |         StrategyResolver._search_object( | ||||||
|             default_location, |             directory=default_location, | ||||||
|             config=default_config, |             object_type=IStrategy, | ||||||
|             strategy_name='DefaultStrategy' |             kwargs={'config': default_config}, | ||||||
|  |             object_name='DefaultStrategy' | ||||||
|         ), |         ), | ||||||
|         IStrategy |         IStrategy | ||||||
|     ) |     ) | ||||||
|     assert StrategyResolver._search_strategy( |     assert StrategyResolver._search_object( | ||||||
|         default_location, |         directory=default_location, | ||||||
|         config=default_config, |         object_type=IStrategy, | ||||||
|         strategy_name='NotFoundStrategy' |         kwargs={'config': default_config}, | ||||||
|  |         object_name='NotFoundStrategy' | ||||||
|     ) is None |     ) is None | ||||||
|  |  | ||||||
|  |  | ||||||
| @@ -77,7 +78,7 @@ def test_load_strategy_invalid_directory(result, caplog): | |||||||
|     resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir) |     resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir) | ||||||
|  |  | ||||||
|     assert ( |     assert ( | ||||||
|         'freqtrade.strategy.resolver', |         'freqtrade.resolvers.strategy_resolver', | ||||||
|         logging.WARNING, |         logging.WARNING, | ||||||
|         'Path "{}" does not exist'.format(extra_dir), |         'Path "{}" does not exist'.format(extra_dir), | ||||||
|     ) in caplog.record_tuples |     ) in caplog.record_tuples | ||||||
| @@ -128,7 +129,7 @@ def test_strategy_override_minimal_roi(caplog): | |||||||
|     resolver = StrategyResolver(config) |     resolver = StrategyResolver(config) | ||||||
|  |  | ||||||
|     assert resolver.strategy.minimal_roi[0] == 0.5 |     assert resolver.strategy.minimal_roi[0] == 0.5 | ||||||
|     assert ('freqtrade.strategy.resolver', |     assert ('freqtrade.resolvers.strategy_resolver', | ||||||
|             logging.INFO, |             logging.INFO, | ||||||
|             "Override strategy 'minimal_roi' with value in config file: {'0': 0.5}." |             "Override strategy 'minimal_roi' with value in config file: {'0': 0.5}." | ||||||
|             ) in caplog.record_tuples |             ) in caplog.record_tuples | ||||||
| @@ -143,7 +144,7 @@ def test_strategy_override_stoploss(caplog): | |||||||
|     resolver = StrategyResolver(config) |     resolver = StrategyResolver(config) | ||||||
|  |  | ||||||
|     assert resolver.strategy.stoploss == -0.5 |     assert resolver.strategy.stoploss == -0.5 | ||||||
|     assert ('freqtrade.strategy.resolver', |     assert ('freqtrade.resolvers.strategy_resolver', | ||||||
|             logging.INFO, |             logging.INFO, | ||||||
|             "Override strategy 'stoploss' with value in config file: -0.5." |             "Override strategy 'stoploss' with value in config file: -0.5." | ||||||
|             ) in caplog.record_tuples |             ) in caplog.record_tuples | ||||||
| @@ -159,7 +160,7 @@ def test_strategy_override_ticker_interval(caplog): | |||||||
|     resolver = StrategyResolver(config) |     resolver = StrategyResolver(config) | ||||||
|  |  | ||||||
|     assert resolver.strategy.ticker_interval == 60 |     assert resolver.strategy.ticker_interval == 60 | ||||||
|     assert ('freqtrade.strategy.resolver', |     assert ('freqtrade.resolvers.strategy_resolver', | ||||||
|             logging.INFO, |             logging.INFO, | ||||||
|             "Override strategy 'ticker_interval' with value in config file: 60." |             "Override strategy 'ticker_interval' with value in config file: 60." | ||||||
|             ) in caplog.record_tuples |             ) in caplog.record_tuples | ||||||
| @@ -175,7 +176,7 @@ def test_strategy_override_process_only_new_candles(caplog): | |||||||
|     resolver = StrategyResolver(config) |     resolver = StrategyResolver(config) | ||||||
|  |  | ||||||
|     assert resolver.strategy.process_only_new_candles |     assert resolver.strategy.process_only_new_candles | ||||||
|     assert ('freqtrade.strategy.resolver', |     assert ('freqtrade.resolvers.strategy_resolver', | ||||||
|             logging.INFO, |             logging.INFO, | ||||||
|             "Override process_only_new_candles 'process_only_new_candles' " |             "Override process_only_new_candles 'process_only_new_candles' " | ||||||
|             "with value in config file: True." |             "with value in config file: True." | ||||||
| @@ -202,7 +203,7 @@ def test_strategy_override_order_types(caplog): | |||||||
|     for method in ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']: |     for method in ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']: | ||||||
|         assert resolver.strategy.order_types[method] == order_types[method] |         assert resolver.strategy.order_types[method] == order_types[method] | ||||||
|  |  | ||||||
|     assert ('freqtrade.strategy.resolver', |     assert ('freqtrade.resolvers.strategy_resolver', | ||||||
|             logging.INFO, |             logging.INFO, | ||||||
|             "Override strategy 'order_types' with value in config file:" |             "Override strategy 'order_types' with value in config file:" | ||||||
|             " {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit'," |             " {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit'," | ||||||
|   | |||||||
| @@ -3,7 +3,7 @@ | |||||||
| import pandas | import pandas | ||||||
|  |  | ||||||
| from freqtrade.optimize import load_data | from freqtrade.optimize import load_data | ||||||
| from freqtrade.strategy.resolver import StrategyResolver | from freqtrade.resolvers import StrategyResolver | ||||||
|  |  | ||||||
| _pairs = ['ETH/BTC'] | _pairs = ['ETH/BTC'] | ||||||
|  |  | ||||||
|   | |||||||
| @@ -18,7 +18,7 @@ from freqtrade.persistence import Trade | |||||||
| from freqtrade.rpc import RPCMessageType | from freqtrade.rpc import RPCMessageType | ||||||
| from freqtrade.state import State | from freqtrade.state import State | ||||||
| from freqtrade.strategy.interface import SellType, SellCheckTuple | from freqtrade.strategy.interface import SellType, SellCheckTuple | ||||||
| from freqtrade.tests.conftest import log_has, patch_exchange, patch_edge | from freqtrade.tests.conftest import log_has, patch_exchange, patch_edge, patch_wallet | ||||||
|  |  | ||||||
|  |  | ||||||
| # Functions for recurrent object patching | # Functions for recurrent object patching | ||||||
| @@ -181,17 +181,10 @@ def test_get_trade_stake_amount(default_conf, ticker, limit_buy_order, fee, mock | |||||||
|     assert result == default_conf['stake_amount'] |     assert result == default_conf['stake_amount'] | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_get_trade_stake_amount_no_stake_amount(default_conf, | def test_get_trade_stake_amount_no_stake_amount(default_conf, mocker) -> None: | ||||||
|                                                 ticker, |  | ||||||
|                                                 limit_buy_order, |  | ||||||
|                                                 fee, |  | ||||||
|                                                 mocker) -> None: |  | ||||||
|     patch_RPCManager(mocker) |     patch_RPCManager(mocker) | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|     mocker.patch.multiple( |     patch_wallet(mocker, free=default_conf['stake_amount'] * 0.5) | ||||||
|         'freqtrade.exchange.Exchange', |  | ||||||
|         get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5) |  | ||||||
|     ) |  | ||||||
|     freqtrade = FreqtradeBot(default_conf) |     freqtrade = FreqtradeBot(default_conf) | ||||||
|  |  | ||||||
|     with pytest.raises(DependencyException, match=r'.*stake amount.*'): |     with pytest.raises(DependencyException, match=r'.*stake amount.*'): | ||||||
| @@ -206,12 +199,12 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, | |||||||
|                                                  mocker) -> None: |                                                  mocker) -> None: | ||||||
|     patch_RPCManager(mocker) |     patch_RPCManager(mocker) | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|  |     patch_wallet(mocker, free=default_conf['stake_amount']) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.exchange.Exchange', |         'freqtrade.exchange.Exchange', | ||||||
|         validate_pairs=MagicMock(), |         validate_pairs=MagicMock(), | ||||||
|         get_ticker=ticker, |         get_ticker=ticker, | ||||||
|         buy=MagicMock(return_value={'id': limit_buy_order['id']}), |         buy=MagicMock(return_value={'id': limit_buy_order['id']}), | ||||||
|         get_balance=MagicMock(return_value=default_conf['stake_amount']), |  | ||||||
|         get_fee=fee, |         get_fee=fee, | ||||||
|         get_markets=markets |         get_markets=markets | ||||||
|     ) |     ) | ||||||
| @@ -299,7 +292,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, markets, caplog, mocker, | |||||||
|     freqtrade = FreqtradeBot(edge_conf) |     freqtrade = FreqtradeBot(edge_conf) | ||||||
|     freqtrade.active_pair_whitelist = ['NEO/BTC'] |     freqtrade.active_pair_whitelist = ['NEO/BTC'] | ||||||
|     patch_get_signal(freqtrade) |     patch_get_signal(freqtrade) | ||||||
|     freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False |     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) | ||||||
|     freqtrade.create_trade() |     freqtrade.create_trade() | ||||||
|     trade = Trade.query.first() |     trade = Trade.query.first() | ||||||
|     trade.update(limit_buy_order) |     trade.update(limit_buy_order) | ||||||
| @@ -339,7 +332,7 @@ def test_edge_should_ignore_strategy_stoploss(limit_buy_order, fee, markets, | |||||||
|     freqtrade = FreqtradeBot(edge_conf) |     freqtrade = FreqtradeBot(edge_conf) | ||||||
|     freqtrade.active_pair_whitelist = ['NEO/BTC'] |     freqtrade.active_pair_whitelist = ['NEO/BTC'] | ||||||
|     patch_get_signal(freqtrade) |     patch_get_signal(freqtrade) | ||||||
|     freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False |     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) | ||||||
|     freqtrade.create_trade() |     freqtrade.create_trade() | ||||||
|     trade = Trade.query.first() |     trade = Trade.query.first() | ||||||
|     trade.update(limit_buy_order) |     trade.update(limit_buy_order) | ||||||
| @@ -521,11 +514,11 @@ def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, | |||||||
|                                       fee, markets, mocker) -> None: |                                       fee, markets, mocker) -> None: | ||||||
|     patch_RPCManager(mocker) |     patch_RPCManager(mocker) | ||||||
|     patch_exchange(mocker) |     patch_exchange(mocker) | ||||||
|  |     patch_wallet(mocker, free=default_conf['stake_amount'] * 0.5) | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.exchange.Exchange', |         'freqtrade.exchange.Exchange', | ||||||
|         get_ticker=ticker, |         get_ticker=ticker, | ||||||
|         buy=MagicMock(return_value={'id': limit_buy_order['id']}), |         buy=MagicMock(return_value={'id': limit_buy_order['id']}), | ||||||
|         get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5), |  | ||||||
|         get_fee=fee, |         get_fee=fee, | ||||||
|         get_markets=markets |         get_markets=markets | ||||||
|     ) |     ) | ||||||
| @@ -1108,7 +1101,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, | |||||||
|  |  | ||||||
|     freqtrade = FreqtradeBot(default_conf) |     freqtrade = FreqtradeBot(default_conf) | ||||||
|     patch_get_signal(freqtrade, value=(True, True)) |     patch_get_signal(freqtrade, value=(True, True)) | ||||||
|     freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False |     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) | ||||||
|  |  | ||||||
|     freqtrade.create_trade() |     freqtrade.create_trade() | ||||||
|  |  | ||||||
| @@ -1164,7 +1157,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order, | |||||||
|  |  | ||||||
|     freqtrade = FreqtradeBot(default_conf) |     freqtrade = FreqtradeBot(default_conf) | ||||||
|     patch_get_signal(freqtrade, value=(True, False)) |     patch_get_signal(freqtrade, value=(True, False)) | ||||||
|     freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: True |     freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) | ||||||
|  |  | ||||||
|     freqtrade.create_trade() |     freqtrade.create_trade() | ||||||
|  |  | ||||||
| @@ -1197,7 +1190,7 @@ def test_handle_trade_experimental( | |||||||
|  |  | ||||||
|     freqtrade = FreqtradeBot(default_conf) |     freqtrade = FreqtradeBot(default_conf) | ||||||
|     patch_get_signal(freqtrade) |     patch_get_signal(freqtrade) | ||||||
|     freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False |     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) | ||||||
|     freqtrade.create_trade() |     freqtrade.create_trade() | ||||||
|  |  | ||||||
|     trade = Trade.query.first() |     trade = Trade.query.first() | ||||||
| @@ -1801,7 +1794,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, | |||||||
|     } |     } | ||||||
|     freqtrade = FreqtradeBot(default_conf) |     freqtrade = FreqtradeBot(default_conf) | ||||||
|     patch_get_signal(freqtrade) |     patch_get_signal(freqtrade) | ||||||
|     freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False |     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) | ||||||
|  |  | ||||||
|     freqtrade.create_trade() |     freqtrade.create_trade() | ||||||
|  |  | ||||||
| @@ -1833,7 +1826,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, | |||||||
|     } |     } | ||||||
|     freqtrade = FreqtradeBot(default_conf) |     freqtrade = FreqtradeBot(default_conf) | ||||||
|     patch_get_signal(freqtrade) |     patch_get_signal(freqtrade) | ||||||
|     freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False |     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) | ||||||
|     freqtrade.create_trade() |     freqtrade.create_trade() | ||||||
|  |  | ||||||
|     trade = Trade.query.first() |     trade = Trade.query.first() | ||||||
| @@ -1895,7 +1888,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, fee, marke | |||||||
|  |  | ||||||
|     freqtrade = FreqtradeBot(default_conf) |     freqtrade = FreqtradeBot(default_conf) | ||||||
|     patch_get_signal(freqtrade) |     patch_get_signal(freqtrade) | ||||||
|     freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False |     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) | ||||||
|  |  | ||||||
|     freqtrade.create_trade() |     freqtrade.create_trade() | ||||||
|  |  | ||||||
| @@ -1925,7 +1918,7 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, markets, m | |||||||
|     } |     } | ||||||
|     freqtrade = FreqtradeBot(default_conf) |     freqtrade = FreqtradeBot(default_conf) | ||||||
|     patch_get_signal(freqtrade) |     patch_get_signal(freqtrade) | ||||||
|     freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: True |     freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) | ||||||
|  |  | ||||||
|     freqtrade.create_trade() |     freqtrade.create_trade() | ||||||
|  |  | ||||||
| @@ -1957,7 +1950,7 @@ def test_trailing_stop_loss(default_conf, limit_buy_order, fee, markets, caplog, | |||||||
|     default_conf['trailing_stop'] = True |     default_conf['trailing_stop'] = True | ||||||
|     freqtrade = FreqtradeBot(default_conf) |     freqtrade = FreqtradeBot(default_conf) | ||||||
|     patch_get_signal(freqtrade) |     patch_get_signal(freqtrade) | ||||||
|     freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False |     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) | ||||||
|  |  | ||||||
|     freqtrade.create_trade() |     freqtrade.create_trade() | ||||||
|  |  | ||||||
| @@ -1992,7 +1985,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, markets | |||||||
|     default_conf['trailing_stop_positive'] = 0.01 |     default_conf['trailing_stop_positive'] = 0.01 | ||||||
|     freqtrade = FreqtradeBot(default_conf) |     freqtrade = FreqtradeBot(default_conf) | ||||||
|     patch_get_signal(freqtrade) |     patch_get_signal(freqtrade) | ||||||
|     freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False |     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) | ||||||
|     freqtrade.create_trade() |     freqtrade.create_trade() | ||||||
|  |  | ||||||
|     trade = Trade.query.first() |     trade = Trade.query.first() | ||||||
| @@ -2052,7 +2045,7 @@ def test_trailing_stop_loss_offset(default_conf, limit_buy_order, fee, | |||||||
|     default_conf['trailing_stop_positive_offset'] = 0.011 |     default_conf['trailing_stop_positive_offset'] = 0.011 | ||||||
|     freqtrade = FreqtradeBot(default_conf) |     freqtrade = FreqtradeBot(default_conf) | ||||||
|     patch_get_signal(freqtrade) |     patch_get_signal(freqtrade) | ||||||
|     freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: False |     freqtrade.strategy.min_roi_reached = MagicMock(return_value=False) | ||||||
|     freqtrade.create_trade() |     freqtrade.create_trade() | ||||||
|  |  | ||||||
|     trade = Trade.query.first() |     trade = Trade.query.first() | ||||||
| @@ -2111,7 +2104,7 @@ def test_disable_ignore_roi_if_buy_signal(default_conf, limit_buy_order, | |||||||
|     } |     } | ||||||
|     freqtrade = FreqtradeBot(default_conf) |     freqtrade = FreqtradeBot(default_conf) | ||||||
|     patch_get_signal(freqtrade) |     patch_get_signal(freqtrade) | ||||||
|     freqtrade.strategy.min_roi_reached = lambda trade, current_profit, current_time: True |     freqtrade.strategy.min_roi_reached = MagicMock(return_value=True) | ||||||
|  |  | ||||||
|     freqtrade.create_trade() |     freqtrade.create_trade() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -4,6 +4,7 @@ from unittest.mock import MagicMock | |||||||
|  |  | ||||||
|  |  | ||||||
| def test_sync_wallet_at_boot(mocker, default_conf): | def test_sync_wallet_at_boot(mocker, default_conf): | ||||||
|  |     default_conf['dry_run'] = False | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.exchange.Exchange', |         'freqtrade.exchange.Exchange', | ||||||
|         get_balances=MagicMock(return_value={ |         get_balances=MagicMock(return_value={ | ||||||
| @@ -29,6 +30,7 @@ def test_sync_wallet_at_boot(mocker, default_conf): | |||||||
|     assert freqtrade.wallets.wallets['GAS'].free == 0.260739 |     assert freqtrade.wallets.wallets['GAS'].free == 0.260739 | ||||||
|     assert freqtrade.wallets.wallets['GAS'].used == 0.0 |     assert freqtrade.wallets.wallets['GAS'].used == 0.0 | ||||||
|     assert freqtrade.wallets.wallets['GAS'].total == 0.260739 |     assert freqtrade.wallets.wallets['GAS'].total == 0.260739 | ||||||
|  |     assert freqtrade.wallets.get_free('BNT') == 1.0 | ||||||
|  |  | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.exchange.Exchange', |         'freqtrade.exchange.Exchange', | ||||||
| @@ -55,9 +57,11 @@ def test_sync_wallet_at_boot(mocker, default_conf): | |||||||
|     assert freqtrade.wallets.wallets['GAS'].free == 0.270739 |     assert freqtrade.wallets.wallets['GAS'].free == 0.270739 | ||||||
|     assert freqtrade.wallets.wallets['GAS'].used == 0.1 |     assert freqtrade.wallets.wallets['GAS'].used == 0.1 | ||||||
|     assert freqtrade.wallets.wallets['GAS'].total == 0.260439 |     assert freqtrade.wallets.wallets['GAS'].total == 0.260439 | ||||||
|  |     assert freqtrade.wallets.get_free('GAS') == 0.270739 | ||||||
|  |  | ||||||
|  |  | ||||||
| def test_sync_wallet_missing_data(mocker, default_conf): | def test_sync_wallet_missing_data(mocker, default_conf): | ||||||
|  |     default_conf['dry_run'] = False | ||||||
|     mocker.patch.multiple( |     mocker.patch.multiple( | ||||||
|         'freqtrade.exchange.Exchange', |         'freqtrade.exchange.Exchange', | ||||||
|         get_balances=MagicMock(return_value={ |         get_balances=MagicMock(return_value={ | ||||||
| @@ -82,3 +86,4 @@ def test_sync_wallet_missing_data(mocker, default_conf): | |||||||
|     assert freqtrade.wallets.wallets['GAS'].free == 0.260739 |     assert freqtrade.wallets.wallets['GAS'].free == 0.260739 | ||||||
|     assert freqtrade.wallets.wallets['GAS'].used is None |     assert freqtrade.wallets.wallets['GAS'].used is None | ||||||
|     assert freqtrade.wallets.wallets['GAS'].total == 0.260739 |     assert freqtrade.wallets.wallets['GAS'].total == 0.260739 | ||||||
|  |     assert freqtrade.wallets.get_free('GAS') == 0.260739 | ||||||
|   | |||||||
| @@ -29,6 +29,17 @@ class Wallets(object): | |||||||
|         self.wallets: Dict[str, Any] = {} |         self.wallets: Dict[str, Any] = {} | ||||||
|         self.update() |         self.update() | ||||||
|  |  | ||||||
|  |     def get_free(self, currency) -> float: | ||||||
|  |  | ||||||
|  |         if self.exchange._conf['dry_run']: | ||||||
|  |             return 999.9 | ||||||
|  |  | ||||||
|  |         balance = self.wallets.get(currency) | ||||||
|  |         if balance and balance.free: | ||||||
|  |             return balance.free | ||||||
|  |         else: | ||||||
|  |             return 0 | ||||||
|  |  | ||||||
|     def update(self) -> None: |     def update(self) -> None: | ||||||
|         balances = self.exchange.get_balances() |         balances = self.exchange.get_balances() | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,4 +1,4 @@ | |||||||
| ccxt==1.17.522 | ccxt==1.17.539 | ||||||
| SQLAlchemy==1.2.14 | SQLAlchemy==1.2.14 | ||||||
| python-telegram-bot==11.1.0 | python-telegram-bot==11.1.0 | ||||||
| arrow==0.12.1 | arrow==0.12.1 | ||||||
| @@ -7,13 +7,13 @@ requests==2.20.1 | |||||||
| urllib3==1.24.1 | urllib3==1.24.1 | ||||||
| wrapt==1.10.11 | wrapt==1.10.11 | ||||||
| pandas==0.23.4 | pandas==0.23.4 | ||||||
| scikit-learn==0.20.0 | scikit-learn==0.20.1 | ||||||
| joblib==0.13.0 | joblib==0.13.0 | ||||||
| scipy==1.1.0 | scipy==1.1.0 | ||||||
| jsonschema==2.6.0 | jsonschema==2.6.0 | ||||||
| numpy==1.15.4 | numpy==1.15.4 | ||||||
| TA-Lib==0.4.17 | TA-Lib==0.4.17 | ||||||
| pytest==4.0.0 | pytest==4.0.1 | ||||||
| pytest-mock==1.10.0 | pytest-mock==1.10.0 | ||||||
| pytest-asyncio==0.9.0 | pytest-asyncio==0.9.0 | ||||||
| pytest-cov==2.6.0 | pytest-cov==2.6.0 | ||||||
|   | |||||||
| @@ -44,7 +44,7 @@ from freqtrade.arguments import Arguments, TimeRange | |||||||
| from freqtrade.exchange import Exchange | from freqtrade.exchange import Exchange | ||||||
| from freqtrade.optimize.backtesting import setup_configuration | from freqtrade.optimize.backtesting import setup_configuration | ||||||
| from freqtrade.persistence import Trade | from freqtrade.persistence import Trade | ||||||
| from freqtrade.strategy.resolver import StrategyResolver | from freqtrade.resolvers import StrategyResolver | ||||||
|  |  | ||||||
| logger = logging.getLogger(__name__) | logger = logging.getLogger(__name__) | ||||||
| _CONF: Dict[str, Any] = {} | _CONF: Dict[str, Any] = {} | ||||||
|   | |||||||
| @@ -27,7 +27,7 @@ import plotly.graph_objs as go | |||||||
| from freqtrade.arguments import Arguments | from freqtrade.arguments import Arguments | ||||||
| from freqtrade.configuration import Configuration | from freqtrade.configuration import Configuration | ||||||
| from freqtrade import constants | from freqtrade import constants | ||||||
| from freqtrade.strategy.resolver import StrategyResolver | from freqtrade.resolvers import StrategyResolver | ||||||
| import freqtrade.optimize as optimize | import freqtrade.optimize as optimize | ||||||
| import freqtrade.misc as misc | import freqtrade.misc as misc | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user