conflict resolved

This commit is contained in:
misagh 2018-11-26 19:08:58 +01:00
commit 86354ed258
24 changed files with 247 additions and 340 deletions

View File

@ -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?
Freqtrade is based on [CCXT library](https://github.com/ccxt/ccxt) that supports 115 cryptocurrency

View File

@ -537,7 +537,9 @@ class Exchange(object):
# 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 = 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
if data:
@ -559,51 +561,6 @@ class Exchange(object):
except ccxt.BaseError as 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
def cancel_order(self, order_id: str, pair: str) -> None:
if self._conf['dry_run']:

View File

@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
def parse_ticker_dataframe(ticker: list) -> DataFrame:
"""
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
"""
cols = ['date', 'open', 'high', 'low', 'close', 'volume']

View File

@ -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
@ -338,9 +338,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())

View File

@ -45,7 +45,7 @@ def main(sysargv: List[str]) -> None:
freqtrade = FreqtradeBot(config)
state = None
while 1:
while True:
state = freqtrade.worker(old_state=state)
if state == State.RELOAD_CONF:
freqtrade = reconfigure(freqtrade, args)

View File

@ -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__)

View File

@ -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__)

View File

@ -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__)

View File

@ -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

View 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

View 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)
)

View 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

View File

@ -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

View File

@ -4,7 +4,6 @@ import logging
from datetime import datetime
from functools import reduce
from typing import Dict, Optional
from collections import namedtuple
from unittest.mock import MagicMock, PropertyMock
import arrow
@ -13,7 +12,7 @@ from telegram import Chat, Message, Update
from freqtrade.exchange.exchange_helpers import parse_ticker_dataframe
from freqtrade.exchange import Exchange
from freqtrade.edge import Edge
from freqtrade.edge import Edge, PairInfo
from freqtrade.freqtradebot import FreqtradeBot
logging.getLogger('').setLevel(logging.INFO)
@ -46,18 +45,22 @@ def get_patched_exchange(mocker, config, api_mock=None, id='bittrex') -> Exchang
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",
# "XRP/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(
return_value={
'NEO/BTC': pair_info(-0.20, 0.66, 3.71, 0.50, 1.71),
'LTC/BTC': pair_info(-0.21, 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': PairInfo(-0.21, 0.66, 3.71, 0.50, 1.71, 11, 20),
}
))
mocker.patch('freqtrade.edge.Edge.stoploss', MagicMock(return_value=-0.20))

View File

@ -898,65 +898,10 @@ def make_fetch_ohlcv_mock(data):
return fetch_ohlcv_mock
def test_get_candle_history(default_conf, mocker):
api_mock = MagicMock()
tick = [
[
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()
@pytest.mark.asyncio
async def test___async_get_candle_history_sort(default_conf, mocker):
def sort_data(data, key):
return sorted(data, key=key)
# GDAX use-case (real data from GDAX)
# 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],
[1527830400000, 0.07649, 0.07651, 0.07649, 0.07651, 2.5734867]
]
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)
exchange = get_patched_exchange(mocker, default_conf)
exchange._api_async.fetch_ohlcv = get_mock_coro(tick)
sort_mock = mocker.patch('freqtrade.exchange.sorted', MagicMock(side_effect=sort_data))
# 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][1] == 0.07649
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],
[1527830400000, 0.07671, 0.07674399, 0.07629216, 0.07655213, 2.31452783]
]
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)
exchange._api_async.fetch_ohlcv = get_mock_coro(tick)
# Reset sort mock
sort_mock = mocker.patch('freqtrade.exchange.sorted', MagicMock(side_effect=sort_data))
# 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][1] == 0.07659999
assert ticks[0][2] == 0.0766

View File

@ -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

View File

@ -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."
@ -202,7 +203,7 @@ def test_strategy_override_order_types(caplog):
for method in ['buy', 'sell', 'stoploss', 'stoploss_on_exchange']:
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',"

View File

@ -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']

View File

@ -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
)
@ -299,7 +292,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, markets, caplog, mocker,
freqtrade = FreqtradeBot(edge_conf)
freqtrade.active_pair_whitelist = ['NEO/BTC']
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()
trade = Trade.query.first()
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.active_pair_whitelist = ['NEO/BTC']
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()
trade = Trade.query.first()
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:
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
)
@ -1108,7 +1101,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order,
freqtrade = FreqtradeBot(default_conf)
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()
@ -1164,7 +1157,7 @@ def test_handle_trade_roi(default_conf, ticker, limit_buy_order,
freqtrade = FreqtradeBot(default_conf)
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()
@ -1197,7 +1190,7 @@ def test_handle_trade_experimental(
freqtrade = FreqtradeBot(default_conf)
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()
trade = Trade.query.first()
@ -1801,7 +1794,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order,
}
freqtrade = FreqtradeBot(default_conf)
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()
@ -1833,7 +1826,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order,
}
freqtrade = FreqtradeBot(default_conf)
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()
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)
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()
@ -1925,7 +1918,7 @@ def test_ignore_roi_if_buy_signal(default_conf, limit_buy_order, fee, markets, m
}
freqtrade = FreqtradeBot(default_conf)
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()
@ -1957,7 +1950,7 @@ def test_trailing_stop_loss(default_conf, limit_buy_order, fee, markets, caplog,
default_conf['trailing_stop'] = True
freqtrade = FreqtradeBot(default_conf)
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()
@ -1992,7 +1985,7 @@ def test_trailing_stop_loss_positive(default_conf, limit_buy_order, fee, markets
default_conf['trailing_stop_positive'] = 0.01
freqtrade = FreqtradeBot(default_conf)
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()
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
freqtrade = FreqtradeBot(default_conf)
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()
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)
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()

View File

@ -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={
@ -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'].used == 0.0
assert freqtrade.wallets.wallets['GAS'].total == 0.260739
assert freqtrade.wallets.get_free('BNT') == 1.0
mocker.patch.multiple(
'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'].used == 0.1
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):
default_conf['dry_run'] = False
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
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'].used is None
assert freqtrade.wallets.wallets['GAS'].total == 0.260739
assert freqtrade.wallets.get_free('GAS') == 0.260739

View File

@ -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()

View File

@ -1,4 +1,4 @@
ccxt==1.17.522
ccxt==1.17.539
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

View File

@ -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] = {}

View File

@ -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