diff --git a/docs/configuration.md b/docs/configuration.md index 62559a41e..1e144e5af 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -155,6 +155,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 does not support market orders."` + ### What values for exchange.name? Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports 115 cryptocurrency diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 343036d19..602c4ae2f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -21,9 +21,9 @@ from freqtrade.wallets import Wallets from freqtrade.edge import Edge from freqtrade.persistence import Trade from freqtrade.rpc import RPCManager, RPCMessageType +from freqtrade.resolvers import StrategyResolver from freqtrade.state import State -from freqtrade.strategy.interface import SellType -from freqtrade.strategy.resolver import IStrategy, StrategyResolver +from freqtrade.strategy.interface import SellType, IStrategy from freqtrade.exchange.exchange_helpers import order_book_to_dataframe @@ -337,9 +337,7 @@ class FreqtradeBot(object): else: stake_amount = self.config['stake_amount'] - # TODO: should come from the wallet - avaliable_amount = self.exchange.get_balance(self.config['stake_currency']) - # avaliable_amount = self.wallets.wallets[self.config['stake_currency']].free + avaliable_amount = self.wallets.get_free(self.config['stake_currency']) if stake_amount == constants.UNLIMITED_STAKE_AMOUNT: open_trades = len(Trade.query.filter(Trade.is_open.is_(True)).all()) diff --git a/freqtrade/optimize/backtesting.py b/freqtrade/optimize/backtesting.py index 6fcde64fa..c6cf7276f 100644 --- a/freqtrade/optimize/backtesting.py +++ b/freqtrade/optimize/backtesting.py @@ -20,8 +20,8 @@ from freqtrade.configuration import Configuration from freqtrade.exchange import Exchange from freqtrade.misc import file_dump_json from freqtrade.persistence import Trade -from freqtrade.strategy.interface import SellType -from freqtrade.strategy.resolver import IStrategy, StrategyResolver +from freqtrade.resolvers import StrategyResolver +from freqtrade.strategy.interface import SellType, IStrategy logger = logging.getLogger(__name__) diff --git a/freqtrade/optimize/edge_cli.py b/freqtrade/optimize/edge_cli.py index 59bcbc098..a2189f6c1 100644 --- a/freqtrade/optimize/edge_cli.py +++ b/freqtrade/optimize/edge_cli.py @@ -12,7 +12,7 @@ from freqtrade.edge import Edge from freqtrade.configuration import Configuration from freqtrade.arguments import Arguments from freqtrade.exchange import Exchange -from freqtrade.strategy.resolver import StrategyResolver +from freqtrade.resolvers import StrategyResolver logger = logging.getLogger(__name__) diff --git a/freqtrade/optimize/hyperopt.py b/freqtrade/optimize/hyperopt.py index 70d20673c..fcf35acfe 100644 --- a/freqtrade/optimize/hyperopt.py +++ b/freqtrade/optimize/hyperopt.py @@ -22,7 +22,7 @@ from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration from freqtrade.optimize import load_data from freqtrade.optimize.backtesting import Backtesting -from freqtrade.optimize.hyperopt_resolver import HyperOptResolver +from freqtrade.resolvers import HyperOptResolver logger = logging.getLogger(__name__) diff --git a/freqtrade/optimize/hyperopt_resolver.py b/freqtrade/optimize/hyperopt_resolver.py deleted file mode 100644 index 3d019e8df..000000000 --- a/freqtrade/optimize/hyperopt_resolver.py +++ /dev/null @@ -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 diff --git a/freqtrade/resolvers/__init__.py b/freqtrade/resolvers/__init__.py new file mode 100644 index 000000000..84e3bcdcd --- /dev/null +++ b/freqtrade/resolvers/__init__.py @@ -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 diff --git a/freqtrade/resolvers/hyperopt_resolver.py b/freqtrade/resolvers/hyperopt_resolver.py new file mode 100644 index 000000000..da7b65648 --- /dev/null +++ b/freqtrade/resolvers/hyperopt_resolver.py @@ -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) + ) diff --git a/freqtrade/resolvers/iresolver.py b/freqtrade/resolvers/iresolver.py new file mode 100644 index 000000000..aee292926 --- /dev/null +++ b/freqtrade/resolvers/iresolver.py @@ -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 diff --git a/freqtrade/strategy/resolver.py b/freqtrade/resolvers/strategy_resolver.py similarity index 69% rename from freqtrade/strategy/resolver.py rename to freqtrade/resolvers/strategy_resolver.py index 3f25e4838..4576d0ec8 100644 --- a/freqtrade/strategy/resolver.py +++ b/freqtrade/resolvers/strategy_resolver.py @@ -3,24 +3,23 @@ """ This module load custom strategies """ -import importlib.util import inspect import logging -import os import tempfile from base64 import urlsafe_b64decode from collections import OrderedDict from pathlib import Path -from typing import Dict, Optional, Type +from typing import Dict, Optional from freqtrade import constants +from freqtrade.resolvers import IResolver from freqtrade.strategy import import_strategy from freqtrade.strategy.interface import IStrategy logger = logging.getLogger(__name__) -class StrategyResolver(object): +class StrategyResolver(IResolver): """ 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 :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 = [ - os.path.join(os.getcwd(), 'user_data', 'strategies'), + Path.cwd().joinpath('user_data/strategies'), current_path, ] if extra_dir: # 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: 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("__init__.py").touch() - strategy_name = os.path.splitext(name)[0] + strategy_name = strat[0] # 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: - 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: - 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( inspect.getfullargspec(strategy.populate_indicators).args) strategy._buy_fun_len = len( @@ -143,49 +144,9 @@ class StrategyResolver(object): return import_strategy(strategy, config=config) except FileNotFoundError: - logger.warning('Path "%s" does not exist', path) + logger.warning('Path "%s" does not exist', _path.relative_to(Path.cwd())) raise ImportError( "Impossible to load Strategy '{}'. This class does not exist" " 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 diff --git a/freqtrade/tests/conftest.py b/freqtrade/tests/conftest.py index b6c022b45..63655126c 100644 --- a/freqtrade/tests/conftest.py +++ b/freqtrade/tests/conftest.py @@ -45,6 +45,12 @@ def get_patched_exchange(mocker, config, api_mock=None) -> 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: # "ETH/BTC", # "LTC/BTC", diff --git a/freqtrade/tests/optimize/test_hyperopt.py b/freqtrade/tests/optimize/test_hyperopt.py index 85d140b6d..01d2e8b17 100644 --- a/freqtrade/tests/optimize/test_hyperopt.py +++ b/freqtrade/tests/optimize/test_hyperopt.py @@ -7,7 +7,7 @@ import pytest from freqtrade.optimize.__init__ import load_tickerdata_file 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.optimize.test_backtesting import get_args diff --git a/freqtrade/tests/strategy/test_strategy.py b/freqtrade/tests/strategy/test_strategy.py index a38050f24..80bd9e120 100644 --- a/freqtrade/tests/strategy/test_strategy.py +++ b/freqtrade/tests/strategy/test_strategy.py @@ -2,6 +2,7 @@ import logging from base64 import urlsafe_b64encode from os import path +from pathlib import Path import warnings import pytest @@ -10,7 +11,7 @@ from pandas import DataFrame from freqtrade.strategy import import_strategy from freqtrade.strategy.default_strategy import DefaultStrategy from freqtrade.strategy.interface import IStrategy -from freqtrade.strategy.resolver import StrategyResolver +from freqtrade.resolvers import StrategyResolver def test_import_strategy(caplog): @@ -40,21 +41,21 @@ def test_import_strategy(caplog): def test_search_strategy(): default_config = {} - default_location = path.join(path.dirname( - path.realpath(__file__)), '..', '..', 'strategy' - ) + default_location = Path(__file__).parent.parent.joinpath('strategy').resolve() assert isinstance( - StrategyResolver._search_strategy( - default_location, - config=default_config, - strategy_name='DefaultStrategy' + StrategyResolver._search_object( + directory=default_location, + object_type=IStrategy, + kwargs={'config': default_config}, + object_name='DefaultStrategy' ), IStrategy ) - assert StrategyResolver._search_strategy( - default_location, - config=default_config, - strategy_name='NotFoundStrategy' + assert StrategyResolver._search_object( + directory=default_location, + object_type=IStrategy, + kwargs={'config': default_config}, + object_name='NotFoundStrategy' ) is None @@ -77,7 +78,7 @@ def test_load_strategy_invalid_directory(result, caplog): resolver._load_strategy('TestStrategy', config={}, extra_dir=extra_dir) assert ( - 'freqtrade.strategy.resolver', + 'freqtrade.resolvers.strategy_resolver', logging.WARNING, 'Path "{}" does not exist'.format(extra_dir), ) in caplog.record_tuples @@ -128,7 +129,7 @@ def test_strategy_override_minimal_roi(caplog): resolver = StrategyResolver(config) assert resolver.strategy.minimal_roi[0] == 0.5 - assert ('freqtrade.strategy.resolver', + assert ('freqtrade.resolvers.strategy_resolver', logging.INFO, "Override strategy 'minimal_roi' with value in config file: {'0': 0.5}." ) in caplog.record_tuples @@ -143,7 +144,7 @@ def test_strategy_override_stoploss(caplog): resolver = StrategyResolver(config) assert resolver.strategy.stoploss == -0.5 - assert ('freqtrade.strategy.resolver', + assert ('freqtrade.resolvers.strategy_resolver', logging.INFO, "Override strategy 'stoploss' with value in config file: -0.5." ) in caplog.record_tuples @@ -159,7 +160,7 @@ def test_strategy_override_ticker_interval(caplog): resolver = StrategyResolver(config) assert resolver.strategy.ticker_interval == 60 - assert ('freqtrade.strategy.resolver', + assert ('freqtrade.resolvers.strategy_resolver', logging.INFO, "Override strategy 'ticker_interval' with value in config file: 60." ) in caplog.record_tuples @@ -175,7 +176,7 @@ def test_strategy_override_process_only_new_candles(caplog): resolver = StrategyResolver(config) assert resolver.strategy.process_only_new_candles - assert ('freqtrade.strategy.resolver', + assert ('freqtrade.resolvers.strategy_resolver', logging.INFO, "Override process_only_new_candles 'process_only_new_candles' " "with value in config file: True." @@ -201,7 +202,7 @@ def test_strategy_override_order_types(caplog): for method in ['buy', 'sell', 'stoploss']: assert resolver.strategy.order_types[method] == order_types[method] - assert ('freqtrade.strategy.resolver', + assert ('freqtrade.resolvers.strategy_resolver', logging.INFO, "Override strategy 'order_types' with value in config file:" " {'buy': 'market', 'sell': 'limit', 'stoploss': 'limit'}." diff --git a/freqtrade/tests/test_dataframe.py b/freqtrade/tests/test_dataframe.py index dc030d630..6afb83a3f 100644 --- a/freqtrade/tests/test_dataframe.py +++ b/freqtrade/tests/test_dataframe.py @@ -3,7 +3,7 @@ import pandas from freqtrade.optimize import load_data -from freqtrade.strategy.resolver import StrategyResolver +from freqtrade.resolvers import StrategyResolver _pairs = ['ETH/BTC'] diff --git a/freqtrade/tests/test_freqtradebot.py b/freqtrade/tests/test_freqtradebot.py index cef89c250..a36ae2cd8 100644 --- a/freqtrade/tests/test_freqtradebot.py +++ b/freqtrade/tests/test_freqtradebot.py @@ -18,7 +18,7 @@ from freqtrade.persistence import Trade from freqtrade.rpc import RPCMessageType from freqtrade.state import State 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 @@ -181,17 +181,10 @@ def test_get_trade_stake_amount(default_conf, ticker, limit_buy_order, fee, mock assert result == default_conf['stake_amount'] -def test_get_trade_stake_amount_no_stake_amount(default_conf, - ticker, - limit_buy_order, - fee, - mocker) -> None: +def test_get_trade_stake_amount_no_stake_amount(default_conf, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) - mocker.patch.multiple( - 'freqtrade.exchange.Exchange', - get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5) - ) + patch_wallet(mocker, free=default_conf['stake_amount'] * 0.5) freqtrade = FreqtradeBot(default_conf) with pytest.raises(DependencyException, match=r'.*stake amount.*'): @@ -206,12 +199,12 @@ def test_get_trade_stake_amount_unlimited_amount(default_conf, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) + patch_wallet(mocker, free=default_conf['stake_amount']) mocker.patch.multiple( 'freqtrade.exchange.Exchange', validate_pairs=MagicMock(), get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), - get_balance=MagicMock(return_value=default_conf['stake_amount']), get_fee=fee, get_markets=markets ) @@ -521,11 +514,11 @@ def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, fee, markets, mocker) -> None: patch_RPCManager(mocker) patch_exchange(mocker) + patch_wallet(mocker, free=default_conf['stake_amount'] * 0.5) mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_ticker=ticker, buy=MagicMock(return_value={'id': limit_buy_order['id']}), - get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5), get_fee=fee, get_markets=markets ) diff --git a/freqtrade/tests/test_wallets.py b/freqtrade/tests/test_wallets.py index cc10d665c..e6a17ecbf 100644 --- a/freqtrade/tests/test_wallets.py +++ b/freqtrade/tests/test_wallets.py @@ -4,6 +4,7 @@ from unittest.mock import MagicMock def test_sync_wallet_at_boot(mocker, default_conf): + default_conf['dry_run'] = False mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value={ @@ -58,6 +59,7 @@ def test_sync_wallet_at_boot(mocker, default_conf): def test_sync_wallet_missing_data(mocker, default_conf): + default_conf['dry_run'] = False mocker.patch.multiple( 'freqtrade.exchange.Exchange', get_balances=MagicMock(return_value={ diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py index 82f527d2c..b8b37907d 100644 --- a/freqtrade/wallets.py +++ b/freqtrade/wallets.py @@ -29,6 +29,17 @@ class Wallets(object): self.wallets: Dict[str, Any] = {} 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: balances = self.exchange.get_balances() diff --git a/requirements.txt b/requirements.txt index 1b271e09e..fa550195d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -ccxt==1.17.522 +ccxt==1.17.536 SQLAlchemy==1.2.14 python-telegram-bot==11.1.0 arrow==0.12.1 @@ -7,13 +7,13 @@ requests==2.20.1 urllib3==1.24.1 wrapt==1.10.11 pandas==0.23.4 -scikit-learn==0.20.0 +scikit-learn==0.20.1 joblib==0.13.0 scipy==1.1.0 jsonschema==2.6.0 numpy==1.15.4 TA-Lib==0.4.17 -pytest==4.0.0 +pytest==4.0.1 pytest-mock==1.10.0 pytest-asyncio==0.9.0 pytest-cov==2.6.0 diff --git a/scripts/plot_dataframe.py b/scripts/plot_dataframe.py index 68713f296..8fd3a43bd 100755 --- a/scripts/plot_dataframe.py +++ b/scripts/plot_dataframe.py @@ -44,7 +44,7 @@ from freqtrade.arguments import Arguments, TimeRange from freqtrade.exchange import Exchange from freqtrade.optimize.backtesting import setup_configuration from freqtrade.persistence import Trade -from freqtrade.strategy.resolver import StrategyResolver +from freqtrade.resolvers import StrategyResolver logger = logging.getLogger(__name__) _CONF: Dict[str, Any] = {} diff --git a/scripts/plot_profit.py b/scripts/plot_profit.py index 9c3468c74..53f14ca3c 100755 --- a/scripts/plot_profit.py +++ b/scripts/plot_profit.py @@ -27,7 +27,7 @@ import plotly.graph_objs as go from freqtrade.arguments import Arguments from freqtrade.configuration import Configuration from freqtrade import constants -from freqtrade.strategy.resolver import StrategyResolver +from freqtrade.resolvers import StrategyResolver import freqtrade.optimize as optimize import freqtrade.misc as misc