conflict resolved
This commit is contained in:
commit
86354ed258
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user