Merge branch 'develop' into order-book

This commit is contained in:
Matthias
2018-08-29 19:32:44 +02:00
40 changed files with 775 additions and 234 deletions

View File

@@ -1,5 +1,5 @@
""" FreqTrade bot """
__version__ = '0.17.1'
__version__ = '0.17.2'
class DependencyException(BaseException):

View File

@@ -142,6 +142,16 @@ class Arguments(object):
action='store_true',
dest='refresh_pairs',
)
parser.add_argument(
'--strategy-list',
help='Provide a commaseparated list of strategies to backtest '
'Please note that ticker-interval needs to be set either in config '
'or via command line. When using this together with --export trades, '
'the strategy-name is injected into the filename '
'(so backtest-data.json becomes backtest-data-DefaultStrategy.json',
nargs='+',
dest='strategy_list',
)
parser.add_argument(
'--export',
help='export backtest results, argument are: trades\

View File

@@ -187,6 +187,14 @@ class Configuration(object):
config.update({'refresh_pairs': True})
logger.info('Parameter -r/--refresh-pairs-cached detected ...')
if 'strategy_list' in self.args and self.args.strategy_list:
config.update({'strategy_list': self.args.strategy_list})
logger.info('Using strategy list of %s Strategies', len(self.args.strategy_list))
if 'ticker_interval' in self.args and self.args.ticker_interval:
config.update({'ticker_interval': self.args.ticker_interval})
logger.info('Overriding ticker interval with Command line argument')
# If --export is used we add it to the configuration
if 'export' in self.args and self.args.export:
config.update({'export': self.args.export})

View File

@@ -36,7 +36,7 @@ SUPPORTED_FIAT = [
"EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY",
"KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN",
"RUB", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR", "USD",
"BTC", "ETH", "XRP", "LTC", "BCH", "USDT"
"BTC", "XBT", "ETH", "XRP", "LTC", "BCH", "USDT"
]
# Required json-schema for user specified config
@@ -45,7 +45,7 @@ CONF_SCHEMA = {
'properties': {
'max_open_trades': {'type': 'integer', 'minimum': 0},
'ticker_interval': {'type': 'string', 'enum': list(TICKER_INTERVAL_MINUTES.keys())},
'stake_currency': {'type': 'string', 'enum': ['BTC', 'ETH', 'USDT', 'EUR', 'USD']},
'stake_currency': {'type': 'string', 'enum': ['BTC', 'XBT', 'ETH', 'USDT', 'EUR', 'USD']},
'stake_amount': {
"type": ["number", "string"],
"minimum": 0.0005,
@@ -162,7 +162,8 @@ CONF_SCHEMA = {
'pattern': '^[0-9A-Z]+/[0-9A-Z]+$'
},
'uniqueItems': True
}
},
'outdated_offset': {'type': 'integer', 'minimum': 1}
},
'required': ['name', 'key', 'secret', 'pair_whitelist']
}

View File

@@ -322,7 +322,7 @@ class Exchange(object):
return data
except (ccxt.NetworkError, ccxt.ExchangeError) as e:
raise TemporaryError(
f'Could not load ticker history due to {e.__class__.__name__}. Message: {e}')
f'Could not load ticker due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
else:
@@ -330,7 +330,7 @@ class Exchange(object):
return self._cached_ticker[pair]
@retrier
def get_ticker_history(self, pair: str, tick_interval: str,
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]
@@ -493,12 +493,3 @@ class Exchange(object):
f'Could not get fee info due to {e.__class__.__name__}. Message: {e}')
except ccxt.BaseError as e:
raise OperationalException(e)
def get_amount_lots(self, pair: str, amount: float) -> float:
"""
get buyable amount rounding, ..
"""
# validate that markets are loaded before trying to get fee
if not self._api.markets:
self._api.load_markets()
return self._api.amount_to_lots(pair, amount)

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_ticker_history
:param ticker: See exchange.get_candle_history
:return: DataFrame
"""
cols = ['date', 'open', 'high', 'low', 'close', 'volume']

View File

@@ -95,6 +95,8 @@ class FreqtradeBot(object):
'status': f'{state.name.lower()}'
})
logger.info('Changing state to: %s', state.name)
if state == State.RUNNING:
self._startup_messages()
if state == State.STOPPED:
time.sleep(1)
@@ -111,6 +113,38 @@ class FreqtradeBot(object):
nb_assets=nb_assets)
return state
def _startup_messages(self) -> None:
if self.config.get('dry_run', False):
self.rpc.send_msg({
'type': RPCMessageType.WARNING_NOTIFICATION,
'status': 'Dry run is enabled. All trades are simulated.'
})
stake_currency = self.config['stake_currency']
stake_amount = self.config['stake_amount']
minimal_roi = self.config['minimal_roi']
ticker_interval = self.config['ticker_interval']
exchange_name = self.config['exchange']['name']
strategy_name = self.config.get('strategy', '')
self.rpc.send_msg({
'type': RPCMessageType.CUSTOM_NOTIFICATION,
'status': f'*Exchange:* `{exchange_name}`\n'
f'*Stake per trade:* `{stake_amount} {stake_currency}`\n'
f'*Minimum ROI:* `{minimal_roi}`\n'
f'*Ticker Interval:* `{ticker_interval}`\n'
f'*Strategy:* `{strategy_name}`'
})
if self.config.get('dynamic_whitelist', False):
top_pairs = 'top ' + str(self.config.get('dynamic_whitelist', 20))
specific_pairs = ''
else:
top_pairs = 'whitelisted'
specific_pairs = '\n' + ', '.join(self.config['exchange'].get('pair_whitelist', ''))
self.rpc.send_msg({
'type': RPCMessageType.STATUS_NOTIFICATION,
'status': f'Searching for {top_pairs} {stake_currency} pairs to buy and sell...'
f'{specific_pairs}'
})
def _throttle(self, func: Callable[..., Any], min_secs: float, *args, **kwargs) -> Any:
"""
Throttles the given callable that it
@@ -355,7 +389,7 @@ class FreqtradeBot(object):
# Pick pair based on buy signals
for _pair in whitelist:
thistory = self.exchange.get_ticker_history(_pair, interval)
thistory = self.exchange.get_candle_history(_pair, interval)
(buy, sell) = self.strategy.get_signal(_pair, interval, thistory)
if buy and not sell:
@@ -547,7 +581,7 @@ class FreqtradeBot(object):
(buy, sell) = (False, False)
experimental = self.config.get('experimental', {})
if experimental.get('use_sell_signal') or experimental.get('ignore_roi_if_buy_signal'):
ticker = self.exchange.get_ticker_history(trade.pair, self.strategy.ticker_interval)
ticker = self.exchange.get_candle_history(trade.pair, self.strategy.ticker_interval)
(buy, sell) = self.strategy.get_signal(trade.pair, self.strategy.ticker_interval,
ticker)

View File

@@ -1,7 +1,13 @@
# pragma pylint: disable=missing-docstring
import gzip
import json
try:
import ujson as json
_UJSON = True
except ImportError:
# see mypy/issues/1153
import json # type: ignore
_UJSON = False
import logging
import os
from typing import Optional, List, Dict, Tuple, Any
@@ -14,6 +20,14 @@ from freqtrade.arguments import TimeRange
logger = logging.getLogger(__name__)
def json_load(data):
"""Try to load data with ujson"""
if _UJSON:
return json.load(data, precise_float=True)
else:
return json.load(data)
def trim_tickerlist(tickerlist: List[Dict], timerange: TimeRange) -> List[Dict]:
if not tickerlist:
return tickerlist
@@ -163,7 +177,7 @@ def load_cached_data_for_updating(filename: str,
# read the cached file
if os.path.isfile(filename):
with open(filename, "rt") as file:
data = json.load(file)
data = json_load(file)
# remove the last item, because we are not sure if it is correct
# it could be fetched when the candle was incompleted
if data:
@@ -219,7 +233,7 @@ def download_backtesting_testdata(datadir: str,
logger.debug("Current Start: %s", misc.format_ms_time(data[1][0]) if data else 'None')
logger.debug("Current End: %s", misc.format_ms_time(data[-1][0]) if data else 'None')
new_data = exchange.get_ticker_history(pair=pair, tick_interval=tick_interval,
new_data = exchange.get_candle_history(pair=pair, tick_interval=tick_interval,
since_ms=since_ms)
data.extend(new_data)

View File

@@ -6,7 +6,9 @@ This module contains the backtesting logic
import logging
import operator
from argparse import Namespace
from copy import deepcopy
from datetime import datetime, timedelta
from pathlib import Path
from typing import Any, Dict, List, NamedTuple, Optional, Tuple
import arrow
@@ -52,13 +54,9 @@ class Backtesting(object):
backtesting = Backtesting(config)
backtesting.start()
"""
def __init__(self, config: Dict[str, Any]) -> None:
self.config = config
self.strategy: IStrategy = StrategyResolver(self.config).strategy
self.ticker_interval = self.strategy.ticker_interval
self.tickerdata_to_dataframe = self.strategy.tickerdata_to_dataframe
self.advise_buy = self.strategy.advise_buy
self.advise_sell = self.strategy.advise_sell
# Reset keys for backtesting
self.config['exchange']['key'] = ''
@@ -66,9 +64,36 @@ class Backtesting(object):
self.config['exchange']['password'] = ''
self.config['exchange']['uid'] = ''
self.config['dry_run'] = True
self.strategylist: List[IStrategy] = []
if self.config.get('strategy_list', None):
# Force one interval
self.ticker_interval = str(self.config.get('ticker_interval'))
for strat in list(self.config['strategy_list']):
stratconf = deepcopy(self.config)
stratconf['strategy'] = strat
self.strategylist.append(StrategyResolver(stratconf).strategy)
else:
# only one strategy
strat = StrategyResolver(self.config).strategy
self.strategylist.append(StrategyResolver(self.config).strategy)
# Load one strategy
self._set_strategy(self.strategylist[0])
self.exchange = Exchange(self.config)
self.fee = self.exchange.get_fee()
def _set_strategy(self, strategy):
"""
Load strategy into backtesting
"""
self.strategy = strategy
self.ticker_interval = self.config.get('ticker_interval')
self.tickerdata_to_dataframe = strategy.tickerdata_to_dataframe
self.advise_buy = strategy.advise_buy
self.advise_sell = strategy.advise_sell
@staticmethod
def get_timeframe(data: Dict[str, DataFrame]) -> Tuple[arrow.Arrow, arrow.Arrow]:
"""
@@ -132,7 +157,32 @@ class Backtesting(object):
tabular_data.append([reason.value, count])
return tabulate(tabular_data, headers=headers, tablefmt="pipe")
def _store_backtest_result(self, recordfilename: Optional[str], results: DataFrame) -> None:
def _generate_text_table_strategy(self, all_results: dict) -> str:
"""
Generate summary table per strategy
"""
stake_currency = str(self.config.get('stake_currency'))
floatfmt = ('s', 'd', '.2f', '.2f', '.8f', 'd', '.1f', '.1f')
tabular_data = []
headers = ['Strategy', 'buy count', 'avg profit %', 'cum profit %',
'total profit ' + stake_currency, 'avg duration', 'profit', 'loss']
for strategy, results in all_results.items():
tabular_data.append([
strategy,
len(results.index),
results.profit_percent.mean() * 100.0,
results.profit_percent.sum() * 100.0,
results.profit_abs.sum(),
str(timedelta(
minutes=round(results.trade_duration.mean()))) if not results.empty else '0:00',
len(results[results.profit_abs > 0]),
len(results[results.profit_abs < 0])
])
return tabulate(tabular_data, headers=headers, floatfmt=floatfmt, tablefmt="pipe")
def _store_backtest_result(self, recordfilename: str, results: DataFrame,
strategyname: Optional[str] = None) -> None:
records = [(t.pair, t.profit_percent, t.open_time.timestamp(),
t.close_time.timestamp(), t.open_index - 1, t.trade_duration,
@@ -140,6 +190,11 @@ class Backtesting(object):
for index, t in results.iterrows()]
if records:
if strategyname:
# Inject strategyname to filename
recname = Path(recordfilename)
recordfilename = str(Path.joinpath(
recname.parent, f'{recname.stem}-{strategyname}').with_suffix(recname.suffix))
logger.info('Dumping backtest results to %s', recordfilename)
file_dump_json(recordfilename, records)
@@ -283,7 +338,7 @@ class Backtesting(object):
if self.config.get('live'):
logger.info('Downloading data for all pairs in whitelist ...')
for pair in pairs:
data[pair] = self.exchange.get_ticker_history(pair, self.ticker_interval)
data[pair] = self.exchange.get_candle_history(pair, self.ticker_interval)
else:
logger.info('Using local backtesting data (using whitelist in given config) ...')
@@ -307,62 +362,55 @@ class Backtesting(object):
else:
logger.info('Ignoring max_open_trades (--disable-max-market-positions was used) ...')
max_open_trades = 0
all_results = {}
preprocessed = self.tickerdata_to_dataframe(data)
for strat in self.strategylist:
logger.info("Running backtesting for Strategy %s", strat.get_strategy_name())
self._set_strategy(strat)
# Print timeframe
min_date, max_date = self.get_timeframe(preprocessed)
logger.info(
'Measuring data from %s up to %s (%s days)..',
min_date.isoformat(),
max_date.isoformat(),
(max_date - min_date).days
)
# need to reprocess data every time to populate signals
preprocessed = self.tickerdata_to_dataframe(data)
# Execute backtest and print results
results = self.backtest(
{
'stake_amount': self.config.get('stake_amount'),
'processed': preprocessed,
'max_open_trades': max_open_trades,
'position_stacking': self.config.get('position_stacking', False),
}
)
if self.config.get('export', False):
self._store_backtest_result(self.config.get('exportfilename'), results)
logger.info(
'\n' + '=' * 49 +
' BACKTESTING REPORT ' +
'=' * 50 + '\n'
'%s',
self._generate_text_table(
data,
results
# Print timeframe
min_date, max_date = self.get_timeframe(preprocessed)
logger.info(
'Measuring data from %s up to %s (%s days)..',
min_date.isoformat(),
max_date.isoformat(),
(max_date - min_date).days
)
)
# logger.info(
# results[['sell_reason']].groupby('sell_reason').count()
# )
logger.info(
'\n' +
' SELL READON STATS '.center(119, '=') +
'\n%s \n',
self._generate_text_table_sell_reason(data, results)
)
logger.info(
'\n' +
' LEFT OPEN TRADES REPORT '.center(119, '=') +
'\n%s',
self._generate_text_table(
data,
results.loc[results.open_at_end]
# Execute backtest and print results
all_results[self.strategy.get_strategy_name()] = self.backtest(
{
'stake_amount': self.config.get('stake_amount'),
'processed': preprocessed,
'max_open_trades': max_open_trades,
'position_stacking': self.config.get('position_stacking', False),
}
)
)
for strategy, results in all_results.items():
if self.config.get('export', False):
self._store_backtest_result(self.config['exportfilename'], results,
strategy if len(self.strategylist) > 1 else None)
print(f"Result for strategy {strategy}")
print(' BACKTESTING REPORT '.center(119, '='))
print(self._generate_text_table(data, results))
print(' SELL REASON STATS '.center(119, '='))
print(self._generate_text_table_sell_reason(data, results))
print(' LEFT OPEN TRADES REPORT '.center(119, '='))
print(self._generate_text_table(data, results.loc[results.open_at_end]))
print()
if len(all_results) > 1:
# Print Strategy summary table
print(' Strategy Summary '.center(119, '='))
print(self._generate_text_table_strategy(all_results))
print('\nFor more details, please look at the detail tables above')
def setup_configuration(args: Namespace) -> Dict[str, Any]:

View File

@@ -79,10 +79,12 @@ def check_migrate(engine) -> None:
table_back_name = 'trades_bak'
for i, table_back_name in enumerate(tabs):
table_back_name = f'trades_bak{i}'
logger.info(f'trying {table_back_name}')
logger.debug(f'trying {table_back_name}')
# Check for latest column
if not has_column(cols, 'max_rate'):
if not has_column(cols, 'ticker_interval'):
logger.info(f'Running database migration - backup available as {table_back_name}')
fee_open = get_column_def(cols, 'fee_open', 'fee')
fee_close = get_column_def(cols, 'fee_close', 'fee')
open_rate_requested = get_column_def(cols, 'open_rate_requested', 'null')
@@ -157,8 +159,8 @@ class Trade(_DECL_BASE):
id = Column(Integer, primary_key=True)
exchange = Column(String, nullable=False)
pair = Column(String, nullable=False)
is_open = Column(Boolean, nullable=False, default=True)
pair = Column(String, nullable=False, index=True)
is_open = Column(Boolean, nullable=False, default=True, index=True)
fee_open = Column(Float, nullable=False, default=0.0)
fee_close = Column(Float, nullable=False, default=0.0)
open_rate = Column(Float)

View File

@@ -13,6 +13,7 @@ import sqlalchemy as sql
from numpy import mean, nan_to_num
from pandas import DataFrame
from freqtrade import TemporaryError
from freqtrade.fiat_convert import CryptoToFiatConverter
from freqtrade.misc import shorten_date
from freqtrade.persistence import Trade
@@ -24,6 +25,8 @@ logger = logging.getLogger(__name__)
class RPCMessageType(Enum):
STATUS_NOTIFICATION = 'status'
WARNING_NOTIFICATION = 'warning'
CUSTOM_NOTIFICATION = 'custom'
BUY_NOTIFICATION = 'buy'
SELL_NOTIFICATION = 'sell'
@@ -271,10 +274,13 @@ class RPC(object):
if coin == 'BTC':
rate = 1.0
else:
if coin == 'USDT':
rate = 1.0 / self._freqtrade.exchange.get_ticker('BTC/USDT', False)['bid']
else:
rate = self._freqtrade.exchange.get_ticker(coin + '/BTC', False)['bid']
try:
if coin == 'USDT':
rate = 1.0 / self._freqtrade.exchange.get_ticker('BTC/USDT', False)['bid']
else:
rate = self._freqtrade.exchange.get_ticker(coin + '/BTC', False)['bid']
except TemporaryError:
continue
est_btc: float = rate * balance['total']
total = total + est_btc
output.append({

View File

@@ -154,6 +154,12 @@ class Telegram(RPC):
elif msg['type'] == RPCMessageType.STATUS_NOTIFICATION:
message = '*Status:* `{status}`'.format(**msg)
elif msg['type'] == RPCMessageType.WARNING_NOTIFICATION:
message = '*Warning:* `{status}`'.format(**msg)
elif msg['type'] == RPCMessageType.CUSTOM_NOTIFICATION:
message = '{status}'.format(**msg)
else:
raise NotImplementedError('Unknown message type: {}'.format(msg['type']))

View File

@@ -1,4 +1,5 @@
import logging
import sys
from copy import deepcopy
from freqtrade.strategy.interface import IStrategy
@@ -12,8 +13,18 @@ def import_strategy(strategy: IStrategy, config: dict) -> IStrategy:
Imports given Strategy instance to global scope
of freqtrade.strategy and returns an instance of it
"""
# Copy all attributes from base class and class
attr = deepcopy({**strategy.__class__.__dict__, **strategy.__dict__})
comb = {**strategy.__class__.__dict__, **strategy.__dict__}
# Delete '_abc_impl' from dict as deepcopy fails on 3.7 with
# `TypeError: can't pickle _abc_data objects``
# This will only apply to python 3.7
if sys.version_info.major == 3 and sys.version_info.minor == 7 and '_abc_impl' in comb:
del comb['_abc_impl']
attr = deepcopy(comb)
# Adjust module name
attr['__module__'] = 'freqtrade.strategy'

View File

@@ -155,7 +155,8 @@ class IStrategy(ABC):
# Check if dataframe is out of date
signal_date = arrow.get(latest['date'])
interval_minutes = constants.TICKER_INTERVAL_MINUTES[interval]
if signal_date < (arrow.utcnow().shift(minutes=-(interval_minutes * 2 + 5))):
offset = self.config.get('exchange', {}).get('outdated_offset', 5)
if signal_date < (arrow.utcnow().shift(minutes=-(interval_minutes * 2 + offset))):
logger.warning(
'Outdated history for pair %s. Last tick is %s minutes old',
pair,

View File

@@ -44,14 +44,15 @@ class StrategyResolver(object):
# Check if we need to override configuration
if 'minimal_roi' in config:
self.strategy.minimal_roi = config['minimal_roi']
logger.info("Override strategy \'minimal_roi\' with value in config file.")
logger.info("Override strategy 'minimal_roi' with value in config file: %s.",
config['minimal_roi'])
else:
config['minimal_roi'] = self.strategy.minimal_roi
if 'stoploss' in config:
self.strategy.stoploss = config['stoploss']
logger.info(
"Override strategy \'stoploss\' with value in config file: %s.", config['stoploss']
"Override strategy 'stoploss' with value in config file: %s.", config['stoploss']
)
else:
config['stoploss'] = self.strategy.stoploss
@@ -59,7 +60,7 @@ class StrategyResolver(object):
if 'ticker_interval' in config:
self.strategy.ticker_interval = config['ticker_interval']
logger.info(
"Override strategy \'ticker_interval\' with value in config file: %s.",
"Override strategy 'ticker_interval' with value in config file: %s.",
config['ticker_interval']
)
else:

View File

@@ -553,7 +553,7 @@ def make_fetch_ohlcv_mock(data):
return fetch_ohlcv_mock
def test_get_ticker_history(default_conf, mocker):
def test_get_candle_history(default_conf, mocker):
api_mock = MagicMock()
tick = [
[
@@ -570,7 +570,7 @@ def test_get_ticker_history(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock)
# retrieve original ticker
ticks = exchange.get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
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
@@ -592,7 +592,7 @@ def test_get_ticker_history(default_conf, mocker):
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_ticker_history('ETH/BTC', default_conf['ticker_interval'])
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
@@ -601,16 +601,16 @@ def test_get_ticker_history(default_conf, mocker):
assert ticks[0][5] == 10
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
"get_ticker_history", "fetch_ohlcv",
"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_ticker_history(pair='ABCD/BTC', tick_interval=default_conf['ticker_interval'])
exchange.get_candle_history(pair='ABCD/BTC', tick_interval=default_conf['ticker_interval'])
def test_get_ticker_history_sort(default_conf, mocker):
def test_get_candle_history_sort(default_conf, mocker):
api_mock = MagicMock()
# GDAX use-case (real data from GDAX)
@@ -633,7 +633,7 @@ def test_get_ticker_history_sort(default_conf, mocker):
exchange = get_patched_exchange(mocker, default_conf, api_mock)
# Test the ticker history sort
ticks = exchange.get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
ticks = exchange.get_candle_history('ETH/BTC', default_conf['ticker_interval'])
assert ticks[0][0] == 1527830400000
assert ticks[0][1] == 0.07649
assert ticks[0][2] == 0.07651
@@ -666,7 +666,7 @@ def test_get_ticker_history_sort(default_conf, mocker):
api_mock.fetch_ohlcv = MagicMock(side_effect=make_fetch_ohlcv_mock(tick))
exchange = get_patched_exchange(mocker, default_conf, api_mock)
# Test the ticker history sort
ticks = exchange.get_ticker_history('ETH/BTC', default_conf['ticker_interval'])
ticks = exchange.get_candle_history('ETH/BTC', default_conf['ticker_interval'])
assert ticks[0][0] == 1527827700000
assert ticks[0][1] == 0.07659999
assert ticks[0][2] == 0.0766
@@ -852,15 +852,3 @@ def test_get_fee(default_conf, mocker):
ccxt_exceptionhandlers(mocker, default_conf, api_mock,
'get_fee', 'calculate_fee')
def test_get_amount_lots(default_conf, mocker):
api_mock = MagicMock()
api_mock.amount_to_lots = MagicMock(return_value=1.0)
api_mock.markets = None
marketmock = MagicMock()
api_mock.load_markets = marketmock
exchange = get_patched_exchange(mocker, default_conf, api_mock)
assert exchange.get_amount_lots('LTC/BTC', 1.54) == 1
assert marketmock.call_count == 1

View File

@@ -110,7 +110,7 @@ def mocked_load_data(datadir, pairs=[], ticker_interval='0m', refresh_pairs=Fals
return pairdata
# use for mock freqtrade.exchange.get_ticker_history'
# use for mock freqtrade.exchange.get_candle_history'
def _load_pair_as_ticks(pair, tickfreq):
ticks = optimize.load_data(None, ticker_interval=tickfreq, pairs=[pair])
ticks = trim_dictlist(ticks, -201)
@@ -406,12 +406,56 @@ def test_generate_text_table_sell_reason(default_conf, mocker):
data={'ETH/BTC': {}}, results=results) == result_str
def test_generate_text_table_strategyn(default_conf, mocker):
"""
Test Backtesting.generate_text_table_sell_reason() method
"""
patch_exchange(mocker)
backtesting = Backtesting(default_conf)
results = {}
results['ETH/BTC'] = pd.DataFrame(
{
'pair': ['ETH/BTC', 'ETH/BTC', 'ETH/BTC'],
'profit_percent': [0.1, 0.2, 0.3],
'profit_abs': [0.2, 0.4, 0.5],
'trade_duration': [10, 30, 10],
'profit': [2, 0, 0],
'loss': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}
)
results['LTC/BTC'] = pd.DataFrame(
{
'pair': ['LTC/BTC', 'LTC/BTC', 'LTC/BTC'],
'profit_percent': [0.4, 0.2, 0.3],
'profit_abs': [0.4, 0.4, 0.5],
'trade_duration': [15, 30, 15],
'profit': [4, 1, 0],
'loss': [0, 0, 1],
'sell_reason': [SellType.ROI, SellType.ROI, SellType.STOP_LOSS]
}
)
result_str = (
'| Strategy | buy count | avg profit % | cum profit % '
'| total profit BTC | avg duration | profit | loss |\n'
'|:-----------|------------:|---------------:|---------------:'
'|-------------------:|:---------------|---------:|-------:|\n'
'| ETH/BTC | 3 | 20.00 | 60.00 '
'| 1.10000000 | 0:17:00 | 3 | 0 |\n'
'| LTC/BTC | 3 | 30.00 | 90.00 '
'| 1.30000000 | 0:20:00 | 3 | 0 |'
)
print(backtesting._generate_text_table_strategy(all_results=results))
assert backtesting._generate_text_table_strategy(all_results=results) == result_str
def test_backtesting_start(default_conf, mocker, caplog) -> None:
def get_timeframe(input1, input2):
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
mocker.patch('freqtrade.optimize.load_data', mocked_load_data)
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history')
mocker.patch('freqtrade.exchange.Exchange.get_candle_history')
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.optimize.backtesting.Backtesting',
@@ -446,7 +490,7 @@ def test_backtesting_start_no_data(default_conf, mocker, caplog) -> None:
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
mocker.patch('freqtrade.optimize.load_data', MagicMock(return_value={}))
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history')
mocker.patch('freqtrade.exchange.Exchange.get_candle_history')
patch_exchange(mocker)
mocker.patch.multiple(
'freqtrade.optimize.backtesting.Backtesting',
@@ -654,6 +698,18 @@ def test_backtest_record(default_conf, fee, mocker):
records = records[0]
# Ensure records are of correct type
assert len(records) == 4
# reset test to test with strategy name
names = []
records = []
backtesting._store_backtest_result("backtest-result.json", results, "DefStrat")
assert len(results) == 4
# Assert file_dump_json was only called once
assert names == ['backtest-result-DefStrat.json']
records = records[0]
# Ensure records are of correct type
assert len(records) == 4
# ('UNITTEST/BTC', 0.00331158, '1510684320', '1510691700', 0, 117)
# Below follows just a typecheck of the schema/type of trade-records
oix = None
@@ -677,7 +733,7 @@ def test_backtest_record(default_conf, fee, mocker):
def test_backtest_start_live(default_conf, mocker, caplog):
default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history',
mocker.patch('freqtrade.exchange.Exchange.get_candle_history',
new=lambda s, n, i: _load_pair_as_ticks(n, i))
patch_exchange(mocker)
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock())
@@ -686,15 +742,6 @@ def test_backtest_start_live(default_conf, mocker, caplog):
read_data=json.dumps(default_conf)
))
args = MagicMock()
args.ticker_interval = 1
args.level = 10
args.live = True
args.datadir = None
args.export = None
args.strategy = 'DefaultStrategy'
args.timerange = '-100' # needed due to MagicMock malleability
args = [
'--config', 'config.json',
'--strategy', 'DefaultStrategy',
@@ -725,3 +772,60 @@ def test_backtest_start_live(default_conf, mocker, caplog):
for line in exists:
assert log_has(line, caplog.record_tuples)
def test_backtest_start_multi_strat(default_conf, mocker, caplog):
default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
mocker.patch('freqtrade.exchange.Exchange.get_candle_history',
new=lambda s, n, i: _load_pair_as_ticks(n, i))
patch_exchange(mocker)
backtestmock = MagicMock()
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', backtestmock)
gen_table_mock = MagicMock()
mocker.patch('freqtrade.optimize.backtesting.Backtesting._generate_text_table', gen_table_mock)
gen_strattable_mock = MagicMock()
mocker.patch('freqtrade.optimize.backtesting.Backtesting._generate_text_table_strategy',
gen_strattable_mock)
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf)
))
args = [
'--config', 'config.json',
'--datadir', 'freqtrade/tests/testdata',
'backtesting',
'--ticker-interval', '1m',
'--live',
'--timerange', '-100',
'--enable-position-stacking',
'--disable-max-market-positions',
'--strategy-list',
'DefaultStrategy',
'TestStrategy',
]
args = get_args(args)
start(args)
# 2 backtests, 4 tables
assert backtestmock.call_count == 2
assert gen_table_mock.call_count == 4
assert gen_strattable_mock.call_count == 1
# check the logs, that will contain the backtest result
exists = [
'Parameter -i/--ticker-interval detected ...',
'Using ticker_interval: 1m ...',
'Parameter -l/--live detected ...',
'Ignoring max_open_trades (--disable-max-market-positions was used) ...',
'Parameter --timerange detected: -100 ...',
'Using data folder: freqtrade/tests/testdata ...',
'Using stake_currency: BTC ...',
'Using stake_amount: 0.001 ...',
'Downloading data for all pairs in whitelist ...',
'Measuring data from 2017-11-14T19:31:00+00:00 up to 2017-11-14T22:58:00+00:00 (0 days)..',
'Parameter --enable-position-stacking detected ...',
'Running backtesting for Strategy DefaultStrategy',
'Running backtesting for Strategy TestStrategy',
]
for line in exists:
assert log_has(line, caplog.record_tuples)

View File

@@ -53,7 +53,7 @@ def _clean_test_file(file: str) -> None:
def test_load_data_30min_ticker(ticker_history, mocker, caplog, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history)
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=ticker_history)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-30m.json')
_backup_file(file, copy_file=True)
optimize.load_data(None, pairs=['UNITTEST/BTC'], ticker_interval='30m')
@@ -63,7 +63,7 @@ def test_load_data_30min_ticker(ticker_history, mocker, caplog, default_conf) ->
def test_load_data_5min_ticker(ticker_history, mocker, caplog, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history)
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=ticker_history)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-5m.json')
_backup_file(file, copy_file=True)
@@ -74,7 +74,7 @@ def test_load_data_5min_ticker(ticker_history, mocker, caplog, default_conf) ->
def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history)
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=ticker_history)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-1m.json')
_backup_file(file, copy_file=True)
optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
@@ -87,7 +87,7 @@ def test_load_data_with_new_pair_1min(ticker_history, mocker, caplog, default_co
"""
Test load_data() with 1 min ticker
"""
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history)
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=ticker_history)
exchange = get_patched_exchange(mocker, default_conf)
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
@@ -118,7 +118,7 @@ def test_testdata_path() -> None:
def test_download_pairs(ticker_history, mocker, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history)
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=ticker_history)
exchange = get_patched_exchange(mocker, default_conf)
file1_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
file1_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-5m.json')
@@ -261,7 +261,7 @@ def test_load_cached_data_for_updating(mocker) -> None:
def test_download_pairs_exception(ticker_history, mocker, caplog, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history)
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=ticker_history)
mocker.patch('freqtrade.optimize.__init__.download_backtesting_testdata',
side_effect=BaseException('File Error'))
exchange = get_patched_exchange(mocker, default_conf)
@@ -279,7 +279,7 @@ def test_download_pairs_exception(ticker_history, mocker, caplog, default_conf)
def test_download_backtesting_testdata(ticker_history, mocker, default_conf) -> None:
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=ticker_history)
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=ticker_history)
exchange = get_patched_exchange(mocker, default_conf)
# Download a 1 min ticker file
@@ -304,7 +304,7 @@ def test_download_backtesting_testdata2(mocker, default_conf) -> None:
[1509836580000, 0.00161, 0.00161, 0.00161, 0.00161, 82.390199]
]
json_dump_mock = mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=tick)
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=tick)
exchange = get_patched_exchange(mocker, default_conf)
download_backtesting_testdata(None, exchange, pair="UNITTEST/BTC", tick_interval='1m')
download_backtesting_testdata(None, exchange, pair="UNITTEST/BTC", tick_interval='3m')

View File

@@ -6,6 +6,7 @@ from unittest.mock import MagicMock, ANY
import pytest
from freqtrade import TemporaryError
from freqtrade.fiat_convert import CryptoToFiatConverter
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.persistence import Trade
@@ -285,11 +286,12 @@ def test_rpc_balance_handle(default_conf, mocker):
'used': 2.0,
},
'ETH': {
'free': 0.0,
'total': 0.0,
'used': 0.0,
'free': 1.0,
'total': 5.0,
'used': 4.0,
}
}
# ETH will be skipped due to mocked Error below
mocker.patch.multiple(
'freqtrade.fiat_convert.Market',
@@ -301,7 +303,8 @@ def test_rpc_balance_handle(default_conf, mocker):
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_balances=MagicMock(return_value=mock_balance)
get_balances=MagicMock(return_value=mock_balance),
get_ticker=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx'))
)
freqtradebot = FreqtradeBot(default_conf)
@@ -320,6 +323,7 @@ def test_rpc_balance_handle(default_conf, mocker):
'pending': 2.0,
'est_btc': 12.0,
}]
assert result['total'] == 12.0
def test_rpc_start(mocker, default_conf) -> None:

View File

@@ -1095,6 +1095,38 @@ def test_send_msg_status_notification(default_conf, mocker) -> None:
assert msg_mock.call_args[0][0] == '*Status:* `running`'
def test_warning_notification(default_conf, mocker) -> None:
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
_send_msg=msg_mock
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot)
telegram.send_msg({
'type': RPCMessageType.WARNING_NOTIFICATION,
'status': 'message'
})
assert msg_mock.call_args[0][0] == '*Warning:* `message`'
def test_custom_notification(default_conf, mocker) -> None:
msg_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
_send_msg=msg_mock
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
telegram = Telegram(freqtradebot)
telegram.send_msg({
'type': RPCMessageType.CUSTOM_NOTIFICATION,
'status': '*Custom:* `Hello World`'
})
assert msg_mock.call_args[0][0] == '*Custom:* `Hello World`'
def test_send_msg_unknown_type(default_conf, mocker) -> None:
msg_mock = MagicMock()
mocker.patch.multiple(

View File

@@ -8,6 +8,7 @@ from pandas import DataFrame
from freqtrade.arguments import TimeRange
from freqtrade.optimize.__init__ import load_tickerdata_file
from freqtrade.persistence import Trade
from freqtrade.tests.conftest import get_patched_exchange, log_has
from freqtrade.strategy.default_strategy import DefaultStrategy
@@ -88,7 +89,7 @@ def test_get_signal_old_dataframe(default_conf, mocker, caplog):
def test_get_signal_handles_exceptions(mocker, default_conf):
mocker.patch('freqtrade.exchange.Exchange.get_ticker_history', return_value=MagicMock())
mocker.patch('freqtrade.exchange.Exchange.get_candle_history', return_value=MagicMock())
exchange = get_patched_exchange(mocker, default_conf)
mocker.patch.object(
_STRATEGY, 'analyze_ticker',
@@ -105,3 +106,26 @@ def test_tickerdata_to_dataframe(default_conf) -> None:
tickerlist = {'UNITTEST/BTC': tick}
data = strategy.tickerdata_to_dataframe(tickerlist)
assert len(data['UNITTEST/BTC']) == 99 # partial candle was removed
def test_min_roi_reached(default_conf, fee) -> None:
strategy = DefaultStrategy(default_conf)
strategy.minimal_roi = {0: 0.1, 20: 0.05, 55: 0.01}
trade = Trade(
pair='ETH/BTC',
stake_amount=0.001,
open_date=arrow.utcnow().shift(hours=-1).datetime,
fee_open=fee.return_value,
fee_close=fee.return_value,
exchange='bittrex',
open_rate=1,
)
assert not strategy.min_roi_reached(trade, 0.01, arrow.utcnow().shift(minutes=-55).datetime)
assert strategy.min_roi_reached(trade, 0.12, arrow.utcnow().shift(minutes=-55).datetime)
assert not strategy.min_roi_reached(trade, 0.04, arrow.utcnow().shift(minutes=-39).datetime)
assert strategy.min_roi_reached(trade, 0.06, arrow.utcnow().shift(minutes=-39).datetime)
assert not strategy.min_roi_reached(trade, -0.01, arrow.utcnow().shift(minutes=-1).datetime)
assert strategy.min_roi_reached(trade, 0.02, arrow.utcnow().shift(minutes=-1).datetime)

View File

@@ -130,7 +130,7 @@ def test_strategy_override_minimal_roi(caplog):
assert resolver.strategy.minimal_roi[0] == 0.5
assert ('freqtrade.strategy.resolver',
logging.INFO,
'Override strategy \'minimal_roi\' with value in config file.'
"Override strategy 'minimal_roi' with value in config file: {'0': 0.5}."
) in caplog.record_tuples
@@ -145,7 +145,7 @@ def test_strategy_override_stoploss(caplog):
assert resolver.strategy.stoploss == -0.5
assert ('freqtrade.strategy.resolver',
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
@@ -161,7 +161,7 @@ def test_strategy_override_ticker_interval(caplog):
assert resolver.strategy.ticker_interval == 60
assert ('freqtrade.strategy.resolver',
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

View File

@@ -132,7 +132,11 @@ def test_parse_args_backtesting_custom() -> None:
'backtesting',
'--live',
'--ticker-interval', '1m',
'--refresh-pairs-cached']
'--refresh-pairs-cached',
'--strategy-list',
'DefaultStrategy',
'TestStrategy'
]
call_args = Arguments(args, '').get_parsed_arg()
assert call_args.config == 'test_conf.json'
assert call_args.live is True
@@ -141,6 +145,8 @@ def test_parse_args_backtesting_custom() -> None:
assert call_args.func is not None
assert call_args.ticker_interval == '1m'
assert call_args.refresh_pairs is True
assert type(call_args.strategy_list) is list
assert len(call_args.strategy_list) == 2
def test_parse_args_hyperopt_custom() -> None:

View File

@@ -292,6 +292,61 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
)
def test_setup_configuration_with_stratlist(mocker, default_conf, caplog) -> None:
"""
Test setup_configuration() function
"""
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf)
))
arglist = [
'--config', 'config.json',
'backtesting',
'--ticker-interval', '1m',
'--export', '/bar/foo',
'--strategy-list',
'DefaultStrategy',
'TestStrategy'
]
args = Arguments(arglist, '').get_parsed_arg()
configuration = Configuration(args)
config = configuration.get_config()
assert 'max_open_trades' in config
assert 'stake_currency' in config
assert 'stake_amount' in config
assert 'exchange' in config
assert 'pair_whitelist' in config['exchange']
assert 'datadir' in config
assert log_has(
'Using data folder: {} ...'.format(config['datadir']),
caplog.record_tuples
)
assert 'ticker_interval' in config
assert log_has('Parameter -i/--ticker-interval detected ...', caplog.record_tuples)
assert log_has(
'Using ticker_interval: 1m ...',
caplog.record_tuples
)
assert 'strategy_list' in config
assert log_has('Using strategy list of 2 Strategies', caplog.record_tuples)
assert 'position_stacking' not in config
assert 'use_max_market_positions' not in config
assert 'timerange' not in config
assert 'export' in config
assert log_has(
'Parameter --export detected: {} ...'.format(config['export']),
caplog.record_tuples
)
def test_hyperopt_with_arguments(mocker, default_conf, caplog) -> None:
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
read_data=json.dumps(default_conf)

View File

@@ -14,7 +14,7 @@ def load_dataframe_pair(pairs, strategy):
assert isinstance(pairs[0], str)
dataframe = ld[pairs[0]]
dataframe = strategy.analyze_ticker(dataframe, pairs[0])
dataframe = strategy.analyze_ticker(dataframe, {'pair': pairs[0]})
return dataframe

View File

@@ -43,7 +43,7 @@ def patch_get_signal(freqtrade: FreqtradeBot, value=(True, False)) -> None:
:return: None
"""
freqtrade.strategy.get_signal = lambda e, s, t: value
freqtrade.exchange.get_ticker_history = lambda p, i: None
freqtrade.exchange.get_candle_history = lambda p, i: None
def patch_RPCManager(mocker) -> MagicMock:
@@ -553,7 +553,7 @@ def test_create_trade_no_signal(default_conf, fee, mocker) -> None:
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_pairs=MagicMock(),
get_ticker_history=MagicMock(return_value=20),
get_candle_history=MagicMock(return_value=20),
get_balance=MagicMock(return_value=20),
get_fee=fee,
)
@@ -2070,3 +2070,9 @@ def test_order_book_ask_strategy(default_conf, limit_buy_order, limit_sell_order
patch_get_signal(freqtrade, value=(False, True))
assert freqtrade.handle_trade(trade) is True
def test_startup_messages(default_conf, mocker):
default_conf['dynamic_whitelist'] = 20
freqtrade = get_patched_freqtradebot(mocker, default_conf)
assert freqtrade.state is State.RUNNING

View File

@@ -1,5 +1,6 @@
# pragma pylint: disable=missing-docstring, C0103
from unittest.mock import MagicMock
import logging
import pytest
from sqlalchemy import create_engine
@@ -403,7 +404,9 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
"""
Test Database migration (starting with new pairformat)
"""
caplog.set_level(logging.DEBUG)
amount = 103.223
# Always create all columns apart from the last!
create_table_old = """CREATE TABLE IF NOT EXISTS "trades" (
id INTEGER NOT NULL,
exchange VARCHAR NOT NULL,
@@ -418,14 +421,21 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
open_date DATETIME NOT NULL,
close_date DATETIME,
open_order_id VARCHAR,
stop_loss FLOAT,
initial_stop_loss FLOAT,
max_rate FLOAT,
sell_reason VARCHAR,
strategy VARCHAR,
PRIMARY KEY (id),
CHECK (is_open IN (0, 1))
);"""
insert_table_old = """INSERT INTO trades (exchange, pair, is_open, fee,
open_rate, stake_amount, amount, open_date)
open_rate, stake_amount, amount, open_date,
stop_loss, initial_stop_loss, max_rate)
VALUES ('binance', 'ETC/BTC', 1, {fee},
0.00258580, {stake}, {amount},
'2019-11-28 12:44:24.000000')
'2019-11-28 12:44:24.000000',
0.0, 0.0, 0.0)
""".format(fee=fee.return_value,
stake=default_conf.get("stake_amount"),
amount=amount
@@ -463,12 +473,15 @@ def test_migrate_new(mocker, default_conf, fee, caplog):
assert trade.ticker_interval is None
assert log_has("trying trades_bak1", caplog.record_tuples)
assert log_has("trying trades_bak2", caplog.record_tuples)
assert log_has("Running database migration - backup available as trades_bak2",
caplog.record_tuples)
def test_migrate_mid_state(mocker, default_conf, fee, caplog):
"""
Test Database migration (starting with new pairformat)
"""
caplog.set_level(logging.DEBUG)
amount = 103.223
create_table_old = """CREATE TABLE IF NOT EXISTS "trades" (
id INTEGER NOT NULL,
@@ -522,6 +535,8 @@ def test_migrate_mid_state(mocker, default_conf, fee, caplog):
assert trade.stop_loss == 0.0
assert trade.initial_stop_loss == 0.0
assert log_has("trying trades_bak0", caplog.record_tuples)
assert log_has("Running database migration - backup available as trades_bak0",
caplog.record_tuples)
def test_adjust_stop_loss(limit_buy_order, limit_sell_order, fee):

View File

@@ -0,0 +1,16 @@
import talib.abstract as ta
import pandas as pd
def test_talib_bollingerbands_near_zero_values():
inputs = pd.DataFrame([
{'close': 0.00000010},
{'close': 0.00000011},
{'close': 0.00000012},
{'close': 0.00000013},
{'close': 0.00000014}
])
bollinger = ta.BBANDS(inputs, matype=0, timeperiod=2)
assert (bollinger['upperband'][3] != bollinger['middleband'][3])