Merge pull request #603 from enenn/ccxt-objectify-pr3_1
[3/3] Add support for multiple exchanges with ccxt (objectified version)
This commit is contained in:
commit
42c0d7c7c3
@ -107,7 +107,17 @@ def validate_pairs(pairs: List[str]) -> None:
|
||||
)
|
||||
if pair not in markets:
|
||||
raise OperationalException(
|
||||
'Pair {} is not available at {}'.format(pair, _API.id.lower()))
|
||||
'Pair {} is not available at {}'.format(pair, get_name()))
|
||||
|
||||
|
||||
def exchange_has(endpoint: str) -> bool:
|
||||
"""
|
||||
Checks if exchange implements a specific API endpoint.
|
||||
Wrapper around ccxt 'has' attribute
|
||||
:param endpoint: Name of endpoint (e.g. 'fetchOHLCV', 'fetchTickers')
|
||||
:return: bool
|
||||
"""
|
||||
return endpoint in _API.has and _API.has[endpoint]
|
||||
|
||||
|
||||
def buy(pair: str, rate: float, amount: float) -> Dict:
|
||||
@ -216,6 +226,23 @@ def get_balances() -> dict:
|
||||
raise OperationalException(e)
|
||||
|
||||
|
||||
@retrier
|
||||
def get_tickers() -> Dict:
|
||||
try:
|
||||
return _API.fetch_tickers()
|
||||
except ccxt.NetworkError as e:
|
||||
raise NetworkException(
|
||||
'Could not load tickers due to networking error. Message: {}'.format(e)
|
||||
)
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException(e)
|
||||
except ccxt.NotSupported as e:
|
||||
raise OperationalException(
|
||||
'Exchange {} does not support fetching tickers in batch.'
|
||||
'Message: {}'.format(_API.name, e)
|
||||
)
|
||||
|
||||
|
||||
# TODO: remove refresh argument, keeping it to keep track of where it was intended to be used
|
||||
@retrier
|
||||
def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict:
|
||||
@ -231,11 +258,6 @@ def get_ticker(pair: str, refresh: Optional[bool] = True) -> dict:
|
||||
|
||||
@retrier
|
||||
def get_ticker_history(pair: str, tick_interval: str) -> List[Dict]:
|
||||
if 'fetchOHLCV' not in _API.has or not _API.has['fetchOHLCV']:
|
||||
raise OperationalException(
|
||||
'Exchange {} does not support fetching historical candlestick data.'.format(_API.name)
|
||||
)
|
||||
|
||||
try:
|
||||
return _API.fetch_ohlcv(pair, timeframe=tick_interval)
|
||||
except ccxt.NetworkError as e:
|
||||
@ -244,6 +266,11 @@ def get_ticker_history(pair: str, tick_interval: str) -> List[Dict]:
|
||||
)
|
||||
except ccxt.BaseError as e:
|
||||
raise OperationalException('Could not fetch ticker data. Msg: {}'.format(e))
|
||||
except ccxt.NotSupported as e:
|
||||
raise OperationalException(
|
||||
'Exchange {} does not support fetching historical candlestick data.'
|
||||
'Message: {}'.format(_API.name, e)
|
||||
)
|
||||
|
||||
|
||||
def cancel_order(order_id: str, pair: str) -> None:
|
||||
@ -315,18 +342,11 @@ def get_id() -> str:
|
||||
return _API.id
|
||||
|
||||
|
||||
def get_fee_maker() -> float:
|
||||
return _API.fees['trading']['maker']
|
||||
|
||||
|
||||
def get_fee_taker() -> float:
|
||||
return _API.fees['trading']['taker']
|
||||
|
||||
|
||||
def get_fee() -> float:
|
||||
def get_fee(symbol='ETH/BTC', type='', side='', amount=1,
|
||||
price=1, taker_or_maker='maker') -> float:
|
||||
# validate that markets are loaded before trying to get fee
|
||||
if _API.markets is None or len(_API.markets) == 0:
|
||||
_API.load_markets()
|
||||
|
||||
return _API.calculate_fee(symbol='ETH/BTC', type='', side='', amount=1, price=1)['rate']
|
||||
|
||||
return _API.calculate_fee(symbol=symbol, type=type, side=side, amount=amount,
|
||||
price=price, takerOrMaker=taker_or_maker)['rate']
|
||||
|
@ -107,10 +107,7 @@ class FreqtradeBot(object):
|
||||
Constants.PROCESS_THROTTLE_SECS
|
||||
)
|
||||
|
||||
nb_assets = self.config.get(
|
||||
'dynamic_whitelist',
|
||||
Constants.DYNAMIC_WHITELIST
|
||||
)
|
||||
nb_assets = self.config.get('dynamic_whitelist', None)
|
||||
|
||||
self._throttle(func=self._process,
|
||||
min_secs=min_secs,
|
||||
@ -185,49 +182,60 @@ class FreqtradeBot(object):
|
||||
return state_changed
|
||||
|
||||
@cached(TTLCache(maxsize=1, ttl=1800))
|
||||
def _gen_pair_whitelist(self, base_currency: str, key: str = 'BaseVolume') -> List[str]:
|
||||
def _gen_pair_whitelist(self, base_currency: str, key: str = 'quoteVolume') -> List[str]:
|
||||
"""
|
||||
Updates the whitelist with with a dynamically generated list
|
||||
:param base_currency: base currency as str
|
||||
:param key: sort key (defaults to 'BaseVolume')
|
||||
:param key: sort key (defaults to 'quoteVolume')
|
||||
:return: List of pairs
|
||||
"""
|
||||
summaries = sorted(
|
||||
(v for s, v in exchange.get_market_summaries().items() if v['symbol'].endswith(base_currency)),
|
||||
key=lambda v: v.get('info').get(key) or 0.0,
|
||||
reverse=True
|
||||
)
|
||||
|
||||
return [s['symbol'] for s in summaries]
|
||||
if not exchange.exchange_has('fetchTickers'):
|
||||
raise OperationalException(
|
||||
'Exchange does not support dynamic whitelist.'
|
||||
'Please edit your config and restart the bot'
|
||||
)
|
||||
|
||||
tickers = exchange.get_tickers()
|
||||
# check length so that we make sure that '/' is actually in the string
|
||||
tickers = [v for k, v in tickers.items()
|
||||
if len(k.split('/')) == 2 and k.split('/')[1] == base_currency]
|
||||
|
||||
sorted_tickers = sorted(tickers, reverse=True, key=lambda t: t[key])
|
||||
pairs = [s['symbol'] for s in sorted_tickers]
|
||||
return pairs
|
||||
|
||||
def _refresh_whitelist(self, whitelist: List[str]) -> List[str]:
|
||||
"""
|
||||
Check wallet health and remove pair from whitelist if necessary
|
||||
Check available markets and remove pair from whitelist if necessary
|
||||
:param whitelist: the sorted list (based on BaseVolume) of pairs the user might want to
|
||||
trade
|
||||
:return: the list of pairs the user wants to trade without the one unavailable or
|
||||
black_listed
|
||||
"""
|
||||
sanitized_whitelist = whitelist
|
||||
health = exchange.get_wallet_health()
|
||||
markets = exchange.get_markets()
|
||||
|
||||
markets = [m for m in markets if m['quote'] == self.config['stake_currency']]
|
||||
known_pairs = set()
|
||||
for symbol, status in health.items():
|
||||
pair = f"{status['base']}/{self.config['stake_currency']}"
|
||||
for market in markets:
|
||||
pair = market['symbol']
|
||||
# pair is not int the generated dynamic market, or in the blacklist ... ignore it
|
||||
if pair not in whitelist or pair in self.config['exchange'].get('pair_blacklist', []):
|
||||
continue
|
||||
# else the pair is valid
|
||||
known_pairs.add(pair)
|
||||
# Market is not active
|
||||
if not status['active']:
|
||||
if not market['active']:
|
||||
sanitized_whitelist.remove(pair)
|
||||
self.logger.info(
|
||||
'Ignoring %s from whitelist (reason: %s).',
|
||||
pair, status.get('Notice') or 'wallet is not active'
|
||||
'Ignoring %s from whitelist. Market is not active.',
|
||||
pair
|
||||
)
|
||||
|
||||
# We need to remove pairs that are unknown
|
||||
final_list = [x for x in sanitized_whitelist if x in known_pairs]
|
||||
|
||||
return final_list
|
||||
|
||||
def get_target_bid(self, ticker: Dict[str, float]) -> float:
|
||||
@ -285,7 +293,7 @@ class FreqtradeBot(object):
|
||||
buy_limit = self.get_target_bid(exchange.get_ticker(pair))
|
||||
amount = stake_amount / buy_limit
|
||||
|
||||
order_id = exchange.buy(pair, buy_limit, amount)
|
||||
order_id = exchange.buy(pair, buy_limit, amount)['id']
|
||||
|
||||
stake_amount_fiat = self.fiat_converter.convert_amount(
|
||||
stake_amount,
|
||||
@ -297,7 +305,7 @@ class FreqtradeBot(object):
|
||||
self.rpc.send_msg(
|
||||
'*{}:* Buying [{}]({}) with limit `{:.8f} ({:.6f} {}, {:.3f} {})` '
|
||||
.format(
|
||||
exchange.get_name().upper(),
|
||||
exchange.get_name(),
|
||||
pair.replace('_', '/'),
|
||||
exchange.get_pair_detail_url(pair),
|
||||
buy_limit,
|
||||
@ -312,10 +320,10 @@ class FreqtradeBot(object):
|
||||
pair=pair,
|
||||
stake_amount=stake_amount,
|
||||
amount=amount,
|
||||
fee=exchange.get_fee_maker(),
|
||||
fee=exchange.get_fee(taker_or_maker='maker'),
|
||||
open_rate=buy_limit,
|
||||
open_date=datetime.utcnow(),
|
||||
exchange=exchange.get_name().upper(),
|
||||
exchange=exchange.get_id(),
|
||||
open_order_id=order_id
|
||||
)
|
||||
Trade.session.add(trade)
|
||||
@ -347,7 +355,7 @@ class FreqtradeBot(object):
|
||||
if trade.open_order_id:
|
||||
# Update trade with order values
|
||||
self.logger.info('Found open order for %s', trade)
|
||||
trade.update(exchange.get_order(trade.open_order_id))
|
||||
trade.update(exchange.get_order(trade.open_order_id, trade.pair))
|
||||
|
||||
if trade.is_open and trade.open_order_id is None:
|
||||
# Check if we can sell our current pair
|
||||
@ -386,22 +394,22 @@ class FreqtradeBot(object):
|
||||
|
||||
for trade in Trade.query.filter(Trade.open_order_id.isnot(None)).all():
|
||||
try:
|
||||
order = exchange.get_order(trade.open_order_id)
|
||||
order = exchange.get_order(trade.open_order_id, trade.pair)
|
||||
except requests.exceptions.RequestException:
|
||||
self.logger.info(
|
||||
'Cannot query order for %s due to %s',
|
||||
trade,
|
||||
traceback.format_exc())
|
||||
continue
|
||||
ordertime = arrow.get(order['opened'])
|
||||
ordertime = arrow.get(order['datetime']).datetime
|
||||
|
||||
# Check if trade is still actually open
|
||||
if int(order['remaining']) == 0:
|
||||
continue
|
||||
|
||||
if order['type'] == "LIMIT_BUY" and ordertime < timeoutthreashold:
|
||||
if order['side'] == 'buy' and ordertime < timeoutthreashold:
|
||||
self.handle_timedout_limit_buy(trade, order)
|
||||
elif order['type'] == "LIMIT_SELL" and ordertime < timeoutthreashold:
|
||||
elif order['side'] == 'sell' and ordertime < timeoutthreashold:
|
||||
self.handle_timedout_limit_sell(trade, order)
|
||||
|
||||
# FIX: 20180110, why is cancel.order unconditionally here, whereas
|
||||
@ -463,7 +471,7 @@ class FreqtradeBot(object):
|
||||
:return: None
|
||||
"""
|
||||
# Execute sell and update trade record
|
||||
order_id = exchange.sell(str(trade.pair), limit, trade.amount)
|
||||
order_id = exchange.sell(str(trade.pair), limit, trade.amount)['id']
|
||||
trade.open_order_id = order_id
|
||||
|
||||
fmt_exp_profit = round(trade.calc_profit_percent(rate=limit) * 100, 2)
|
||||
|
@ -6,13 +6,11 @@ This module contains the backtesting logic
|
||||
from argparse import Namespace
|
||||
from typing import Dict, Tuple, Any, List, Optional
|
||||
|
||||
import ccxt
|
||||
import arrow
|
||||
from pandas import DataFrame, Series
|
||||
from tabulate import tabulate
|
||||
|
||||
import freqtrade.optimize as optimize
|
||||
import freqtrade.exchange as exchange
|
||||
from freqtrade import exchange
|
||||
from freqtrade.analyze import Analyze
|
||||
from freqtrade.arguments import Arguments
|
||||
@ -57,6 +55,9 @@ class Backtesting(object):
|
||||
# Reset keys for backtesting
|
||||
self.config['exchange']['key'] = ''
|
||||
self.config['exchange']['secret'] = ''
|
||||
self.config['exchange']['password'] = ''
|
||||
self.config['exchange']['uid'] = ''
|
||||
self.config['dry_run'] = True
|
||||
exchange.init(self.config)
|
||||
|
||||
@staticmethod
|
||||
@ -199,8 +200,8 @@ class Backtesting(object):
|
||||
# record a tuple of pair, current_profit_percent,
|
||||
# entry-date, duration
|
||||
records.append((pair, trade_entry[1],
|
||||
row.date.strftime('%s'),
|
||||
row2.date.strftime('%s'),
|
||||
row.date.timestamp(),
|
||||
row2.date.timestamp(),
|
||||
row.date, trade_entry[3]))
|
||||
# For now export inside backtest(), maybe change so that backtest()
|
||||
# returns a tuple like: (dataframe, records, logs, etc)
|
||||
|
@ -111,20 +111,20 @@ class Trade(_DECL_BASE):
|
||||
:return: None
|
||||
"""
|
||||
# Ignore open and cancelled orders
|
||||
if not order['closed'] or order['rate'] is None:
|
||||
if order['status'] == 'open' or order['price'] is None:
|
||||
return
|
||||
|
||||
logger.info('Updating trade (id=%d) ...', self.id)
|
||||
|
||||
getcontext().prec = 8 # Bittrex do not go above 8 decimal
|
||||
if order['type'] == 'LIMIT_BUY':
|
||||
if order['type'] == 'limit' and order['side'] == 'buy':
|
||||
# Update open rate and actual amount
|
||||
self.open_rate = Decimal(order['rate'])
|
||||
self.open_rate = Decimal(order['price'])
|
||||
self.amount = Decimal(order['amount'])
|
||||
logger.info('LIMIT_BUY has been fulfilled for %s.', self)
|
||||
self.open_order_id = None
|
||||
elif order['type'] == 'LIMIT_SELL':
|
||||
self.close(order['rate'])
|
||||
elif order['type'] == 'limit' and order['side'] == 'sell':
|
||||
self.close(order['price'])
|
||||
else:
|
||||
raise ValueError('Unknown order type: {}'.format(order['type']))
|
||||
cleanup()
|
||||
|
@ -50,7 +50,7 @@ class RPC(object):
|
||||
for trade in trades:
|
||||
order = None
|
||||
if trade.open_order_id:
|
||||
order = exchange.get_order(trade.open_order_id)
|
||||
order = exchange.get_order(trade.open_order_id, trade.pair)
|
||||
# calculate profit and send message to user
|
||||
current_rate = exchange.get_ticker(trade.pair, False)['bid']
|
||||
current_profit = trade.calc_profit_percent(current_rate)
|
||||
@ -78,8 +78,8 @@ class RPC(object):
|
||||
amount=round(trade.amount, 8),
|
||||
close_profit=fmt_close_profit,
|
||||
current_profit=round(current_profit * 100, 2),
|
||||
open_order='({} rem={:.8f})'.format(
|
||||
order['type'], order['remaining']
|
||||
open_order='({} {} rem={:.8f})'.format(
|
||||
order['type'], order['side'], order['remaining']
|
||||
) if order else None,
|
||||
)
|
||||
result.append(message)
|
||||
@ -311,17 +311,21 @@ class RPC(object):
|
||||
def _exec_forcesell(trade: Trade) -> None:
|
||||
# Check if there is there is an open order
|
||||
if trade.open_order_id:
|
||||
order = exchange.get_order(trade.open_order_id)
|
||||
order = exchange.get_order(trade.open_order_id, trade.pair)
|
||||
|
||||
# Cancel open LIMIT_BUY orders and close trade
|
||||
if order and not order['closed'] and order['type'] == 'LIMIT_BUY':
|
||||
exchange.cancel_order(trade.open_order_id)
|
||||
trade.close(order.get('rate') or trade.open_rate)
|
||||
if order and order['status'] == 'open' \
|
||||
and order['type'] == 'limit' \
|
||||
and order['side'] == 'buy':
|
||||
exchange.cancel_order(trade.open_order_id, trade.pair)
|
||||
trade.close(order.get('price') or trade.open_rate)
|
||||
# TODO: sell amount which has been bought already
|
||||
return
|
||||
|
||||
# Ignore trades with an attached LIMIT_SELL order
|
||||
if order and not order['closed'] and order['type'] == 'LIMIT_SELL':
|
||||
if order and order['status'] == 'open' \
|
||||
and order['type'] == 'limit' \
|
||||
and order['side'] == 'sell':
|
||||
return
|
||||
|
||||
# Get current rate and execute sell
|
||||
|
@ -99,6 +99,11 @@ def update():
|
||||
return _update
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def fee():
|
||||
return MagicMock(return_value=0.0025)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ticker():
|
||||
return MagicMock(return_value={
|
||||
@ -127,32 +132,80 @@ def ticker_sell_down():
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def health():
|
||||
return MagicMock(return_value={
|
||||
"ETH/BTC": {
|
||||
def markets():
|
||||
return MagicMock(return_value=[
|
||||
{
|
||||
'id': 'ethbtc',
|
||||
'symbol': 'ETH/BTC',
|
||||
'base': 'ETH',
|
||||
'quote': 'BTC',
|
||||
'active': True,
|
||||
'LastChecked': '2017-11-13T20:15:00.00',
|
||||
'Notice': None
|
||||
'precision': {
|
||||
'price': 8,
|
||||
'amount': 8,
|
||||
'cost': 8,
|
||||
},
|
||||
'lot': 0.00000001,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': 0.01,
|
||||
'max': 1000,
|
||||
},
|
||||
'price': 500000,
|
||||
'cost': 500000,
|
||||
},
|
||||
'info': '',
|
||||
},
|
||||
"TRST/BTC": {
|
||||
'base': 'TRST',
|
||||
{
|
||||
'id': 'tknbtc',
|
||||
'symbol': 'TKN/BTC',
|
||||
'base': 'TKN',
|
||||
'quote': 'BTC',
|
||||
'active': True,
|
||||
'LastChecked': '2017-11-13T20:15:00.00',
|
||||
'Notice': None
|
||||
'precision': {
|
||||
'price': 8,
|
||||
'amount': 8,
|
||||
'cost': 8,
|
||||
},
|
||||
'lot': 0.00000001,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': 0.01,
|
||||
'max': 1000,
|
||||
},
|
||||
'price': 500000,
|
||||
'cost': 500000,
|
||||
},
|
||||
'info': '',
|
||||
},
|
||||
"SWT/BTC": {
|
||||
'base': 'SWT',
|
||||
{
|
||||
'id': 'blkbtc',
|
||||
'symbol': 'BLK/BTC',
|
||||
'base': 'BLK',
|
||||
'quote': 'BTC',
|
||||
'active': True,
|
||||
'LastChecked': '2017-11-13T20:15:00.00',
|
||||
'Notice': None
|
||||
},
|
||||
"BCC/BTC": {
|
||||
'base': 'BCC',
|
||||
'active': False,
|
||||
'LastChecked': '2017-11-13T20:15:00.00',
|
||||
'Notice': None
|
||||
}})
|
||||
'precision': {
|
||||
'price': 8,
|
||||
'amount': 8,
|
||||
'cost': 8,
|
||||
},
|
||||
'lot': 0.00000001,
|
||||
'limits': {
|
||||
'amount': {
|
||||
'min': 0.01,
|
||||
'max': 1000,
|
||||
},
|
||||
'price': 500000,
|
||||
'cost': 500000,
|
||||
},
|
||||
'info': '',
|
||||
}
|
||||
])
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def markets_empty():
|
||||
return MagicMock(return_value=[])
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -231,7 +284,7 @@ def limit_sell_order():
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ticker_history_api():
|
||||
def ticker_history():
|
||||
return [
|
||||
[
|
||||
1511686200000, # unix timestamp ms
|
||||
@ -261,66 +314,179 @@ def ticker_history_api():
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ticker_history():
|
||||
return [
|
||||
{
|
||||
"O": 8.794e-05,
|
||||
"H": 8.948e-05,
|
||||
"L": 8.794e-05,
|
||||
"C": 8.88e-05,
|
||||
"V": 991.09056638,
|
||||
"T": "2017-11-26T08:50:00",
|
||||
"BV": 0.0877869
|
||||
def tickers():
|
||||
return MagicMock(return_value={
|
||||
'ETH/BTC': {
|
||||
'symbol': 'ETH/BTC',
|
||||
'timestamp': 1522014806207,
|
||||
'datetime': '2018-03-25T21:53:26.207Z',
|
||||
'high': 0.061697,
|
||||
'low': 0.060531,
|
||||
'bid': 0.061588,
|
||||
'bidVolume': 3.321,
|
||||
'ask': 0.061655,
|
||||
'askVolume': 0.212,
|
||||
'vwap': 0.06105296,
|
||||
'open': 0.060809,
|
||||
'close': 0.060761,
|
||||
'first': None,
|
||||
'last': 0.061588,
|
||||
'change': 1.281,
|
||||
'percentage': None,
|
||||
'average': None,
|
||||
'baseVolume': 111649.001,
|
||||
'quoteVolume': 6816.50176926,
|
||||
'info': {}
|
||||
},
|
||||
{
|
||||
"O": 8.88e-05,
|
||||
"H": 8.942e-05,
|
||||
"L": 8.88e-05,
|
||||
"C": 8.893e-05,
|
||||
"V": 658.77935965,
|
||||
"T": "2017-11-26T08:55:00",
|
||||
"BV": 0.05874751
|
||||
'TKN/BTC': {
|
||||
'symbol': 'TKN/BTC',
|
||||
'timestamp': 1522014806169,
|
||||
'datetime': '2018-03-25T21:53:26.169Z',
|
||||
'high': 0.01885,
|
||||
'low': 0.018497,
|
||||
'bid': 0.018799,
|
||||
'bidVolume': 8.38,
|
||||
'ask': 0.018802,
|
||||
'askVolume': 15.0,
|
||||
'vwap': 0.01869197,
|
||||
'open': 0.018585,
|
||||
'close': 0.018573,
|
||||
'baseVolume': 81058.66,
|
||||
'quoteVolume': 2247.48374509,
|
||||
},
|
||||
{
|
||||
"O": 8.891e-05,
|
||||
"H": 8.893e-05,
|
||||
"L": 8.875e-05,
|
||||
"C": 8.877e-05,
|
||||
"V": 7920.73570705,
|
||||
"T": "2017-11-26T09:00:00",
|
||||
"BV": 0.7039405
|
||||
'BLK/BTC': {
|
||||
'symbol': 'BLK/BTC',
|
||||
'timestamp': 1522014806072,
|
||||
'datetime': '2018-03-25T21:53:26.720Z',
|
||||
'high': 0.007745,
|
||||
'low': 0.007512,
|
||||
'bid': 0.007729,
|
||||
'bidVolume': 0.01,
|
||||
'ask': 0.007743,
|
||||
'askVolume': 21.37,
|
||||
'vwap': 0.00761466,
|
||||
'open': 0.007653,
|
||||
'close': 0.007652,
|
||||
'first': None,
|
||||
'last': 0.007743,
|
||||
'change': 1.176,
|
||||
'percentage': None,
|
||||
'average': None,
|
||||
'baseVolume': 295152.26,
|
||||
'quoteVolume': 1515.14631229,
|
||||
'info': {}
|
||||
},
|
||||
'LTC/BTC': {
|
||||
'symbol': 'LTC/BTC',
|
||||
'timestamp': 1523787258992,
|
||||
'datetime': '2018-04-15T10:14:19.992Z',
|
||||
'high': 0.015978,
|
||||
'low': 0.0157,
|
||||
'bid': 0.015954,
|
||||
'bidVolume': 12.83,
|
||||
'ask': 0.015957,
|
||||
'askVolume': 0.49,
|
||||
'vwap': 0.01581636,
|
||||
'open': 0.015823,
|
||||
'close': 0.01582,
|
||||
'first': None,
|
||||
'last': 0.015951,
|
||||
'change': 0.809,
|
||||
'percentage': None,
|
||||
'average': None,
|
||||
'baseVolume': 88620.68,
|
||||
'quoteVolume': 1401.65697943,
|
||||
'info': {}
|
||||
},
|
||||
'ETH/USDT': {
|
||||
'symbol': 'ETH/USDT',
|
||||
'timestamp': 1522014804118,
|
||||
'datetime': '2018-03-25T21:53:24.118Z',
|
||||
'high': 530.88,
|
||||
'low': 512.0,
|
||||
'bid': 529.73,
|
||||
'bidVolume': 0.2,
|
||||
'ask': 530.21,
|
||||
'askVolume': 0.2464,
|
||||
'vwap': 521.02438405,
|
||||
'open': 527.27,
|
||||
'close': 528.42,
|
||||
'first': None,
|
||||
'last': 530.21,
|
||||
'change': 0.558,
|
||||
'percentage': None,
|
||||
'average': None,
|
||||
'baseVolume': 72300.0659,
|
||||
'quoteVolume': 37670097.3022171,
|
||||
'info': {}
|
||||
},
|
||||
'TKN/USDT': {
|
||||
'symbol': 'TKN/USDT',
|
||||
'timestamp': 1522014806198,
|
||||
'datetime': '2018-03-25T21:53:26.198Z',
|
||||
'high': 8718.0,
|
||||
'low': 8365.77,
|
||||
'bid': 8603.64,
|
||||
'bidVolume': 0.15846,
|
||||
'ask': 8603.67,
|
||||
'askVolume': 0.069147,
|
||||
'vwap': 8536.35621697,
|
||||
'open': 8680.0,
|
||||
'close': 8680.0,
|
||||
'first': None,
|
||||
'last': 8603.67,
|
||||
'change': -0.879,
|
||||
'percentage': None,
|
||||
'average': None,
|
||||
'baseVolume': 30414.604298,
|
||||
'quoteVolume': 259629896.48584127,
|
||||
'info': {}
|
||||
},
|
||||
'BLK/USDT': {
|
||||
'symbol': 'BLK/USDT',
|
||||
'timestamp': 1522014806145,
|
||||
'datetime': '2018-03-25T21:53:26.145Z',
|
||||
'high': 66.95,
|
||||
'low': 63.38,
|
||||
'bid': 66.473,
|
||||
'bidVolume': 4.968,
|
||||
'ask': 66.54,
|
||||
'askVolume': 2.704,
|
||||
'vwap': 65.0526901,
|
||||
'open': 66.43,
|
||||
'close': 66.383,
|
||||
'first': None,
|
||||
'last': 66.5,
|
||||
'change': 0.105,
|
||||
'percentage': None,
|
||||
'average': None,
|
||||
'baseVolume': 294106.204,
|
||||
'quoteVolume': 19132399.743954,
|
||||
'info': {}
|
||||
},
|
||||
'LTC/USDT': {
|
||||
'symbol': 'LTC/USDT',
|
||||
'timestamp': 1523787257812,
|
||||
'datetime': '2018-04-15T10:14:18.812Z',
|
||||
'high': 129.94,
|
||||
'low': 124.0,
|
||||
'bid': 129.28,
|
||||
'bidVolume': 0.03201,
|
||||
'ask': 129.52,
|
||||
'askVolume': 0.14529,
|
||||
'vwap': 126.92838682,
|
||||
'open': 127.0,
|
||||
'close': 127.1,
|
||||
'first': None,
|
||||
'last': 129.28,
|
||||
'change': 1.795,
|
||||
'percentage': None,
|
||||
'average': None,
|
||||
'baseVolume': 59698.79897,
|
||||
'quoteVolume': 29132399.743954,
|
||||
'info': {}
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def ticker_history_without_bv():
|
||||
return [
|
||||
{
|
||||
"O": 8.794e-05,
|
||||
"H": 8.948e-05,
|
||||
"L": 8.794e-05,
|
||||
"C": 8.88e-05,
|
||||
"V": 991.09056638,
|
||||
"T": "2017-11-26T08:50:00"
|
||||
},
|
||||
{
|
||||
"O": 8.88e-05,
|
||||
"H": 8.942e-05,
|
||||
"L": 8.88e-05,
|
||||
"C": 8.893e-05,
|
||||
"V": 658.77935965,
|
||||
"T": "2017-11-26T08:55:00"
|
||||
},
|
||||
{
|
||||
"O": 8.891e-05,
|
||||
"H": 8.893e-05,
|
||||
"L": 8.875e-05,
|
||||
"C": 8.877e-05,
|
||||
"V": 7920.73570705,
|
||||
"T": "2017-11-26T09:00:00"
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
@ -328,7 +494,6 @@ def result():
|
||||
with open('freqtrade/tests/testdata/UNITTEST_BTC-1m.json') as data_file:
|
||||
return Analyze.parse_ticker_dataframe(json.load(data_file))
|
||||
|
||||
|
||||
# FIX:
|
||||
# Create an fixture/function
|
||||
# that inserts a trade of some type and open-status
|
||||
|
@ -68,7 +68,8 @@ def test_validate_pairs_not_compatible(default_conf, mocker):
|
||||
api_mock.load_markets = MagicMock(return_value={
|
||||
'ETH/BTC': '', 'TKN/BTC': '', 'TRST/BTC': '', 'SWT/BTC': '', 'BCC/BTC': ''
|
||||
})
|
||||
default_conf['stake_currency'] = 'ETH'
|
||||
conf = deepcopy(default_conf)
|
||||
conf['stake_currency'] = 'ETH'
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', conf)
|
||||
with pytest.raises(OperationalException, match=r'not compatible'):
|
||||
@ -78,18 +79,20 @@ def test_validate_pairs_not_compatible(default_conf, mocker):
|
||||
def test_validate_pairs_exception(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
api_mock = MagicMock()
|
||||
api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError())
|
||||
api_mock.name = 'binance'
|
||||
api_mock.name = 'Binance'
|
||||
mocker.patch('freqtrade.exchange._API', api_mock)
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
|
||||
with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available at binance'):
|
||||
api_mock.load_markets = MagicMock(return_value={})
|
||||
with pytest.raises(OperationalException, match=r'Pair ETH/BTC is not available at Binance'):
|
||||
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
||||
|
||||
api_mock.load_markets = MagicMock(side_effect=ccxt.BaseError())
|
||||
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
||||
assert log_has('Unable to validate pairs (assuming they are correct). Reason: ',
|
||||
caplog.record_tuples)
|
||||
|
||||
|
||||
def test_validate_pairs_stake_exception(default_conf, mocker, caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
conf = deepcopy(default_conf)
|
||||
@ -105,6 +108,7 @@ def test_validate_pairs_stake_exception(default_conf, mocker, caplog):
|
||||
):
|
||||
validate_pairs(default_conf['exchange']['pair_whitelist'])
|
||||
|
||||
|
||||
def test_buy_dry_run(default_conf, mocker):
|
||||
default_conf['dry_run'] = True
|
||||
mocker.patch.dict('freqtrade.exchange._CONF', default_conf)
|
||||
@ -113,6 +117,7 @@ def test_buy_dry_run(default_conf, mocker):
|
||||
assert 'id' in order
|
||||
assert 'dry_run_buy_' in order['id']
|
||||
|
||||
|
||||
def test_buy_prod(default_conf, mocker):
|
||||
api_mock = MagicMock()
|
||||
order_id = 'test_prod_buy_{}'.format(randint(0, 10 ** 6))
|
||||
|
@ -6,6 +6,7 @@ import random
|
||||
from copy import deepcopy
|
||||
from typing import List
|
||||
from unittest.mock import MagicMock
|
||||
import pytest
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
@ -15,10 +16,20 @@ from freqtrade import optimize
|
||||
from freqtrade.analyze import Analyze
|
||||
from freqtrade.arguments import Arguments
|
||||
from freqtrade.optimize.backtesting import Backtesting, start, setup_configuration
|
||||
from freqtrade.tests.conftest import default_conf, log_has
|
||||
from freqtrade.tests.conftest import log_has
|
||||
|
||||
# Avoid to reinit the same object again and again
|
||||
_BACKTESTING = Backtesting(default_conf())
|
||||
_BACKTESTING = None
|
||||
_BACKTESTING_INITIALIZED = False
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def init_backtesting(default_conf, mocker):
|
||||
global _BACKTESTING_INITIALIZED, _BACKTESTING
|
||||
if not _BACKTESTING_INITIALIZED:
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
_BACKTESTING = Backtesting(default_conf)
|
||||
_BACKTESTING_INITIALIZED = True
|
||||
|
||||
|
||||
def get_args(args) -> List[str]:
|
||||
@ -34,44 +45,54 @@ def trim_dictlist(dict_list, num):
|
||||
|
||||
def load_data_test(what):
|
||||
timerange = ((None, 'line'), None, -100)
|
||||
data = optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC'], timerange=timerange)
|
||||
data = optimize.load_data(None, ticker_interval='1m',
|
||||
pairs=['UNITTEST/BTC'], timerange=timerange)
|
||||
pair = data['UNITTEST/BTC']
|
||||
datalen = len(pair)
|
||||
# Depending on the what parameter we now adjust the
|
||||
# loaded data looks:
|
||||
# pair :: [{'O': 0.123, 'H': 0.123, 'L': 0.123,
|
||||
# 'C': 0.123, 'V': 123.123,
|
||||
# 'T': '2017-11-04T23:02:00', 'BV': 0.123}]
|
||||
# pair :: [[ 1509836520000, unix timestamp in ms
|
||||
# 0.00162008, open
|
||||
# 0.00162008, high
|
||||
# 0.00162008, low
|
||||
# 0.00162008, close
|
||||
# 108.14853839 base volume
|
||||
# ]]
|
||||
base = 0.001
|
||||
if what == 'raise':
|
||||
return {'UNITTEST/BTC':
|
||||
[{'T': pair[x]['T'], # Keep old dates
|
||||
'V': pair[x]['V'], # Keep old volume
|
||||
'BV': pair[x]['BV'], # keep too
|
||||
'O': x * base, # But replace O,H,L,C
|
||||
'H': x * base + 0.0001,
|
||||
'L': x * base - 0.0001,
|
||||
'C': x * base} for x in range(0, datalen)]}
|
||||
return {'UNITTEST/BTC': [
|
||||
[
|
||||
pair[x][0], # Keep old dates
|
||||
x * base, # But replace O,H,L,C
|
||||
x * base + 0.0001,
|
||||
x * base - 0.0001,
|
||||
x * base,
|
||||
pair[x][5], # Keep old volume
|
||||
] for x in range(0, datalen)
|
||||
]}
|
||||
if what == 'lower':
|
||||
return {'UNITTEST/BTC':
|
||||
[{'T': pair[x]['T'], # Keep old dates
|
||||
'V': pair[x]['V'], # Keep old volume
|
||||
'BV': pair[x]['BV'], # keep too
|
||||
'O': 1 - x * base, # But replace O,H,L,C
|
||||
'H': 1 - x * base + 0.0001,
|
||||
'L': 1 - x * base - 0.0001,
|
||||
'C': 1 - x * base} for x in range(0, datalen)]}
|
||||
return {'UNITTEST/BTC': [
|
||||
[
|
||||
pair[x][0], # Keep old dates
|
||||
1 - x * base, # But replace O,H,L,C
|
||||
1 - x * base + 0.0001,
|
||||
1 - x * base - 0.0001,
|
||||
1 - x * base,
|
||||
pair[x][5] # Keep old volume
|
||||
] for x in range(0, datalen)
|
||||
]}
|
||||
if what == 'sine':
|
||||
hz = 0.1 # frequency
|
||||
return {'UNITTEST/BTC':
|
||||
[{'T': pair[x]['T'], # Keep old dates
|
||||
'V': pair[x]['V'], # Keep old volume
|
||||
'BV': pair[x]['BV'], # keep too
|
||||
# But replace O,H,L,C
|
||||
'O': math.sin(x * hz) / 1000 + base,
|
||||
'H': math.sin(x * hz) / 1000 + base + 0.0001,
|
||||
'L': math.sin(x * hz) / 1000 + base - 0.0001,
|
||||
'C': math.sin(x * hz) / 1000 + base} for x in range(0, datalen)]}
|
||||
return {'UNITTEST/BTC': [
|
||||
[
|
||||
pair[x][0], # Keep old dates
|
||||
math.sin(x * hz) / 1000 + base, # But replace O,H,L,C
|
||||
math.sin(x * hz) / 1000 + base + 0.0001,
|
||||
math.sin(x * hz) / 1000 + base - 0.0001,
|
||||
math.sin(x * hz) / 1000 + base,
|
||||
pair[x][5] # Keep old volume
|
||||
] for x in range(0, datalen)
|
||||
]}
|
||||
return data
|
||||
|
||||
|
||||
@ -247,11 +268,11 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
||||
assert 'live' in config
|
||||
assert log_has('Parameter -l/--live detected ...', caplog.record_tuples)
|
||||
|
||||
assert 'realistic_simulation'in config
|
||||
assert 'realistic_simulation' in config
|
||||
assert log_has('Parameter --realistic-simulation detected ...', caplog.record_tuples)
|
||||
assert log_has('Using max_open_trades: 1 ...', caplog.record_tuples)
|
||||
|
||||
assert 'refresh_pairs'in config
|
||||
assert 'refresh_pairs' in config
|
||||
assert log_has('Parameter -r/--refresh-pairs-cached detected ...', caplog.record_tuples)
|
||||
assert 'timerange' in config
|
||||
assert log_has(
|
||||
@ -266,7 +287,7 @@ def test_setup_configuration_with_arguments(mocker, default_conf, caplog) -> Non
|
||||
)
|
||||
|
||||
|
||||
def test_start(mocker, default_conf, caplog) -> None:
|
||||
def test_start(mocker, init_backtesting, default_conf, caplog) -> None:
|
||||
"""
|
||||
Test start() function
|
||||
"""
|
||||
@ -306,10 +327,11 @@ def test_backtesting__init__(mocker, default_conf) -> None:
|
||||
assert init_mock.call_count == 1
|
||||
|
||||
|
||||
def test_backtesting_init(default_conf) -> None:
|
||||
def test_backtesting_init(mocker, default_conf) -> None:
|
||||
"""
|
||||
Test Backtesting._init() method
|
||||
"""
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
backtesting = Backtesting(default_conf)
|
||||
assert backtesting.config == default_conf
|
||||
assert isinstance(backtesting.analyze, Analyze)
|
||||
@ -319,7 +341,7 @@ def test_backtesting_init(default_conf) -> None:
|
||||
assert callable(backtesting.populate_sell_trend)
|
||||
|
||||
|
||||
def test_tickerdata_to_dataframe(default_conf) -> None:
|
||||
def test_tickerdata_to_dataframe(init_backtesting, default_conf) -> None:
|
||||
"""
|
||||
Test Backtesting.tickerdata_to_dataframe() method
|
||||
"""
|
||||
@ -338,7 +360,7 @@ def test_tickerdata_to_dataframe(default_conf) -> None:
|
||||
assert data['UNITTEST/BTC'].equals(data2['UNITTEST/BTC'])
|
||||
|
||||
|
||||
def test_get_timeframe() -> None:
|
||||
def test_get_timeframe(init_backtesting) -> None:
|
||||
"""
|
||||
Test Backtesting.get_timeframe() method
|
||||
"""
|
||||
@ -356,7 +378,7 @@ def test_get_timeframe() -> None:
|
||||
assert max_date.isoformat() == '2017-11-14T22:59:00+00:00'
|
||||
|
||||
|
||||
def test_generate_text_table():
|
||||
def test_generate_text_table(init_backtesting):
|
||||
"""
|
||||
Test Backtesting.generate_text_table() method
|
||||
"""
|
||||
@ -391,12 +413,14 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
||||
"""
|
||||
Test Backtesting.start() method
|
||||
"""
|
||||
|
||||
def get_timeframe(input1, input2):
|
||||
return Arrow(2017, 11, 14, 21, 17), Arrow(2017, 11, 14, 22, 59)
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.Analyze', MagicMock())
|
||||
mocker.patch('freqtrade.optimize.load_data', mocked_load_data)
|
||||
mocker.patch('freqtrade.exchange.get_ticker_history')
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.optimize.backtesting.Backtesting',
|
||||
backtest=MagicMock(),
|
||||
@ -426,7 +450,7 @@ def test_backtesting_start(default_conf, mocker, caplog) -> None:
|
||||
assert log_has(line, caplog.record_tuples)
|
||||
|
||||
|
||||
def test_backtest(default_conf) -> None:
|
||||
def test_backtest(init_backtesting, default_conf) -> None:
|
||||
"""
|
||||
Test Backtesting.backtest() method
|
||||
"""
|
||||
@ -445,7 +469,7 @@ def test_backtest(default_conf) -> None:
|
||||
assert not results.empty
|
||||
|
||||
|
||||
def test_backtest_1min_ticker_interval(default_conf) -> None:
|
||||
def test_backtest_1min_ticker_interval(init_backtesting, default_conf) -> None:
|
||||
"""
|
||||
Test Backtesting.backtest() method with 1 min ticker
|
||||
"""
|
||||
@ -465,7 +489,7 @@ def test_backtest_1min_ticker_interval(default_conf) -> None:
|
||||
assert not results.empty
|
||||
|
||||
|
||||
def test_processed() -> None:
|
||||
def test_processed(init_backtesting) -> None:
|
||||
"""
|
||||
Test Backtesting.backtest() method with offline data
|
||||
"""
|
||||
@ -481,14 +505,15 @@ def test_processed() -> None:
|
||||
assert col in cols
|
||||
|
||||
|
||||
def test_backtest_pricecontours(default_conf) -> None:
|
||||
def test_backtest_pricecontours(init_backtesting, default_conf, fee, mocker) -> None:
|
||||
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
|
||||
tests = [['raise', 17], ['lower', 0], ['sine', 17]]
|
||||
for [contour, numres] in tests:
|
||||
simple_backtest(default_conf, contour, numres)
|
||||
|
||||
|
||||
# Test backtest using offline data (testdata directory)
|
||||
def test_backtest_ticks(default_conf):
|
||||
def test_backtest_ticks(init_backtesting, default_conf):
|
||||
ticks = [1, 5]
|
||||
fun = _BACKTESTING.populate_buy_trend
|
||||
for tick in ticks:
|
||||
@ -497,7 +522,7 @@ def test_backtest_ticks(default_conf):
|
||||
assert not results.empty
|
||||
|
||||
|
||||
def test_backtest_clash_buy_sell(default_conf):
|
||||
def test_backtest_clash_buy_sell(init_backtesting, default_conf):
|
||||
# Override the default buy trend function in our default_strategy
|
||||
def fun(dataframe=None):
|
||||
buy_value = 1
|
||||
@ -509,7 +534,7 @@ def test_backtest_clash_buy_sell(default_conf):
|
||||
assert results.empty
|
||||
|
||||
|
||||
def test_backtest_only_sell(default_conf):
|
||||
def test_backtest_only_sell(init_backtesting, default_conf):
|
||||
# Override the default buy trend function in our default_strategy
|
||||
def fun(dataframe=None):
|
||||
buy_value = 0
|
||||
@ -521,15 +546,17 @@ def test_backtest_only_sell(default_conf):
|
||||
assert results.empty
|
||||
|
||||
|
||||
def test_backtest_alternate_buy_sell(default_conf):
|
||||
def test_backtest_alternate_buy_sell(init_backtesting, default_conf, fee, mocker):
|
||||
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
|
||||
backtest_conf = _make_backtest_conf(conf=default_conf, pair='UNITTEST/BTC')
|
||||
results = _run_backtest_1(_trend_alternate, backtest_conf)
|
||||
assert len(results) == 3
|
||||
|
||||
|
||||
def test_backtest_record(default_conf, mocker):
|
||||
def test_backtest_record(init_backtesting, default_conf, fee, mocker):
|
||||
names = []
|
||||
records = []
|
||||
mocker.patch('freqtrade.optimize.backtesting.exchange.get_fee', fee)
|
||||
mocker.patch(
|
||||
'freqtrade.optimize.backtesting.file_dump_json',
|
||||
new=lambda n, r: (names.append(n), records.append(r))
|
||||
@ -562,14 +589,16 @@ def test_backtest_record(default_conf, mocker):
|
||||
assert dur > 0
|
||||
|
||||
|
||||
def test_backtest_start_live(default_conf, mocker, caplog):
|
||||
default_conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
|
||||
def test_backtest_start_live(init_backtesting, default_conf, mocker, caplog):
|
||||
conf = deepcopy(default_conf)
|
||||
conf['exchange']['pair_whitelist'] = ['UNITTEST/BTC']
|
||||
mocker.patch('freqtrade.exchange.get_ticker_history',
|
||||
new=lambda n, i: _load_pair_as_ticks(n, i))
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock())
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting.backtest', MagicMock())
|
||||
mocker.patch('freqtrade.optimize.backtesting.Backtesting._generate_text_table', MagicMock())
|
||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
read_data=json.dumps(default_conf)
|
||||
read_data=json.dumps(conf)
|
||||
))
|
||||
|
||||
args = MagicMock()
|
||||
|
@ -1,20 +1,34 @@
|
||||
# pragma pylint: disable=missing-docstring,W0212,C0103
|
||||
import json
|
||||
import os
|
||||
import signal
|
||||
from copy import deepcopy
|
||||
from unittest.mock import MagicMock
|
||||
import pytest
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from freqtrade.optimize.__init__ import load_tickerdata_file
|
||||
from freqtrade.optimize.hyperopt import Hyperopt, start
|
||||
from freqtrade.strategy.strategy import Strategy
|
||||
from freqtrade.tests.conftest import default_conf, log_has
|
||||
from freqtrade.tests.conftest import log_has
|
||||
from freqtrade.tests.optimize.test_backtesting import get_args
|
||||
|
||||
|
||||
# Avoid to reinit the same object again and again
|
||||
_HYPEROPT = Hyperopt(default_conf())
|
||||
_HYPEROPT_INITIALIZED = False
|
||||
_HYPEROPT = None
|
||||
|
||||
|
||||
@pytest.fixture(scope='function')
|
||||
def init_hyperopt(default_conf, mocker):
|
||||
global _HYPEROPT_INITIALIZED, _HYPEROPT
|
||||
if not _HYPEROPT_INITIALIZED:
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf',
|
||||
MagicMock(return_value=default_conf))
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock())
|
||||
_HYPEROPT = Hyperopt(default_conf)
|
||||
_HYPEROPT_INITIALIZED = True
|
||||
|
||||
|
||||
# Functions for recurrent object patching
|
||||
@ -52,9 +66,10 @@ def test_start(mocker, default_conf, caplog) -> None:
|
||||
start_mock = MagicMock()
|
||||
mocker.patch('freqtrade.logger.Logger.set_format', MagicMock())
|
||||
mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.start', start_mock)
|
||||
mocker.patch('freqtrade.configuration.open', mocker.mock_open(
|
||||
read_data=json.dumps(default_conf)
|
||||
))
|
||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf',
|
||||
MagicMock(return_value=default_conf))
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
|
||||
|
||||
args = [
|
||||
'--config', 'config.json',
|
||||
'--strategy', 'default_strategy',
|
||||
@ -75,7 +90,7 @@ def test_start(mocker, default_conf, caplog) -> None:
|
||||
assert start_mock.call_count == 1
|
||||
|
||||
|
||||
def test_loss_calculation_prefer_correct_trade_count() -> None:
|
||||
def test_loss_calculation_prefer_correct_trade_count(init_hyperopt) -> None:
|
||||
"""
|
||||
Test Hyperopt.calculate_loss()
|
||||
"""
|
||||
@ -89,7 +104,7 @@ def test_loss_calculation_prefer_correct_trade_count() -> None:
|
||||
assert under > correct
|
||||
|
||||
|
||||
def test_loss_calculation_prefer_shorter_trades() -> None:
|
||||
def test_loss_calculation_prefer_shorter_trades(init_hyperopt) -> None:
|
||||
"""
|
||||
Test Hyperopt.calculate_loss()
|
||||
"""
|
||||
@ -100,7 +115,7 @@ def test_loss_calculation_prefer_shorter_trades() -> None:
|
||||
assert shorter < longer
|
||||
|
||||
|
||||
def test_loss_calculation_has_limited_profit() -> None:
|
||||
def test_loss_calculation_has_limited_profit(init_hyperopt) -> None:
|
||||
hyperopt = _HYPEROPT
|
||||
|
||||
correct = hyperopt.calculate_loss(hyperopt.expected_max_profit, hyperopt.target_trades, 20)
|
||||
@ -110,7 +125,7 @@ def test_loss_calculation_has_limited_profit() -> None:
|
||||
assert under > correct
|
||||
|
||||
|
||||
def test_log_results_if_loss_improves(caplog) -> None:
|
||||
def test_log_results_if_loss_improves(init_hyperopt, caplog) -> None:
|
||||
hyperopt = _HYPEROPT
|
||||
hyperopt.current_best_loss = 2
|
||||
hyperopt.log_results(
|
||||
@ -124,7 +139,7 @@ def test_log_results_if_loss_improves(caplog) -> None:
|
||||
assert log_has(' 1/2: foo. Loss 1.00000', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_no_log_if_loss_does_not_improve(caplog) -> None:
|
||||
def test_no_log_if_loss_does_not_improve(init_hyperopt, caplog) -> None:
|
||||
hyperopt = _HYPEROPT
|
||||
hyperopt.current_best_loss = 2
|
||||
hyperopt.log_results(
|
||||
@ -135,7 +150,7 @@ def test_no_log_if_loss_does_not_improve(caplog) -> None:
|
||||
assert caplog.record_tuples == []
|
||||
|
||||
|
||||
def test_fmin_best_results(mocker, default_conf, caplog) -> None:
|
||||
def test_fmin_best_results(mocker, init_hyperopt, default_conf, caplog) -> None:
|
||||
fmin_result = {
|
||||
"macd_below_zero": 0,
|
||||
"adx": 1,
|
||||
@ -170,6 +185,7 @@ def test_fmin_best_results(mocker, default_conf, caplog) -> None:
|
||||
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value=fmin_result)
|
||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
||||
mocker.patch('freqtrade.logger.Logger.set_format', MagicMock())
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
|
||||
|
||||
Strategy({'strategy': 'default_strategy'})
|
||||
hyperopt = Hyperopt(conf)
|
||||
@ -204,7 +220,7 @@ def test_fmin_best_results(mocker, default_conf, caplog) -> None:
|
||||
assert line in caplog.text
|
||||
|
||||
|
||||
def test_fmin_throw_value_error(mocker, default_conf, caplog) -> None:
|
||||
def test_fmin_throw_value_error(mocker, init_hyperopt, default_conf, caplog) -> None:
|
||||
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||
mocker.patch('freqtrade.optimize.hyperopt.fmin', side_effect=ValueError())
|
||||
|
||||
@ -215,6 +231,8 @@ def test_fmin_throw_value_error(mocker, default_conf, caplog) -> None:
|
||||
conf.update({'spaces': 'all'})
|
||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
||||
mocker.patch('freqtrade.logger.Logger.set_format', MagicMock())
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
|
||||
|
||||
Strategy({'strategy': 'default_strategy'})
|
||||
hyperopt = Hyperopt(conf)
|
||||
hyperopt.trials = create_trials(mocker)
|
||||
@ -232,7 +250,7 @@ def test_fmin_throw_value_error(mocker, default_conf, caplog) -> None:
|
||||
assert line in caplog.text
|
||||
|
||||
|
||||
def test_resuming_previous_hyperopt_results_succeeds(mocker, default_conf) -> None:
|
||||
def test_resuming_previous_hyperopt_results_succeeds(mocker, init_hyperopt, default_conf) -> None:
|
||||
trials = create_trials(mocker)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
@ -257,6 +275,7 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker, default_conf) -> No
|
||||
mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
||||
mocker.patch('freqtrade.logger.Logger.set_format', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock())
|
||||
|
||||
Strategy({'strategy': 'default_strategy'})
|
||||
hyperopt = Hyperopt(conf)
|
||||
@ -275,7 +294,7 @@ def test_resuming_previous_hyperopt_results_succeeds(mocker, default_conf) -> No
|
||||
assert total_tries == (current_tries + len(trials.results))
|
||||
|
||||
|
||||
def test_save_trials_saves_trials(mocker, caplog) -> None:
|
||||
def test_save_trials_saves_trials(mocker, init_hyperopt, caplog) -> None:
|
||||
create_trials(mocker)
|
||||
mock_dump = mocker.patch('freqtrade.optimize.hyperopt.pickle.dump', return_value=None)
|
||||
|
||||
@ -284,22 +303,24 @@ def test_save_trials_saves_trials(mocker, caplog) -> None:
|
||||
|
||||
hyperopt.save_trials()
|
||||
|
||||
trials_file = os.path.join('freqtrade', 'tests', 'optimize', 'ut_trials.pickle')
|
||||
assert log_has(
|
||||
'Saving Trials to \'freqtrade/tests/optimize/ut_trials.pickle\'',
|
||||
'Saving Trials to \'{}\''.format(trials_file),
|
||||
caplog.record_tuples
|
||||
)
|
||||
mock_dump.assert_called_once()
|
||||
|
||||
|
||||
def test_read_trials_returns_trials_file(mocker, caplog) -> None:
|
||||
def test_read_trials_returns_trials_file(mocker, init_hyperopt, caplog) -> None:
|
||||
trials = create_trials(mocker)
|
||||
mock_load = mocker.patch('freqtrade.optimize.hyperopt.pickle.load', return_value=trials)
|
||||
mock_open = mocker.patch('freqtrade.optimize.hyperopt.open', return_value=mock_load)
|
||||
|
||||
hyperopt = _HYPEROPT
|
||||
hyperopt_trial = hyperopt.read_trials()
|
||||
trials_file = os.path.join('freqtrade', 'tests', 'optimize', 'ut_trials.pickle')
|
||||
assert log_has(
|
||||
'Reading Trials from \'freqtrade/tests/optimize/ut_trials.pickle\'',
|
||||
'Reading Trials from \'{}\''.format(trials_file),
|
||||
caplog.record_tuples
|
||||
)
|
||||
assert hyperopt_trial == trials
|
||||
@ -307,7 +328,7 @@ def test_read_trials_returns_trials_file(mocker, caplog) -> None:
|
||||
mock_load.assert_called_once()
|
||||
|
||||
|
||||
def test_roi_table_generation() -> None:
|
||||
def test_roi_table_generation(init_hyperopt) -> None:
|
||||
params = {
|
||||
'roi_t1': 5,
|
||||
'roi_t2': 10,
|
||||
@ -321,10 +342,11 @@ def test_roi_table_generation() -> None:
|
||||
assert hyperopt.generate_roi_table(params) == {0: 6, 15: 3, 25: 1, 30: 0}
|
||||
|
||||
|
||||
def test_start_calls_fmin(mocker, default_conf) -> None:
|
||||
def test_start_calls_fmin(mocker, init_hyperopt, default_conf) -> None:
|
||||
trials = create_trials(mocker)
|
||||
mocker.patch('freqtrade.optimize.hyperopt.sorted', return_value=trials.results)
|
||||
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock())
|
||||
mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
@ -342,7 +364,7 @@ def test_start_calls_fmin(mocker, default_conf) -> None:
|
||||
mock_fmin.assert_called_once()
|
||||
|
||||
|
||||
def test_start_uses_mongotrials(mocker, default_conf) -> None:
|
||||
def test_start_uses_mongotrials(mocker, init_hyperopt, default_conf) -> None:
|
||||
mocker.patch('freqtrade.optimize.hyperopt.load_data', MagicMock())
|
||||
mock_fmin = mocker.patch('freqtrade.optimize.hyperopt.fmin', return_value={})
|
||||
mock_mongotrials = mocker.patch(
|
||||
@ -357,6 +379,7 @@ def test_start_uses_mongotrials(mocker, default_conf) -> None:
|
||||
conf.update({'timerange': None})
|
||||
conf.update({'spaces': 'all'})
|
||||
mocker.patch('freqtrade.optimize.hyperopt.hyperopt_optimize_conf', return_value=conf)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.validate_pairs', MagicMock())
|
||||
|
||||
hyperopt = Hyperopt(conf)
|
||||
hyperopt.tickerdata_to_dataframe = MagicMock()
|
||||
@ -385,7 +408,7 @@ def test_format_results():
|
||||
assert x.find(' 66.67%')
|
||||
|
||||
|
||||
def test_signal_handler(mocker):
|
||||
def test_signal_handler(mocker, init_hyperopt):
|
||||
"""
|
||||
Test Hyperopt.signal_handler()
|
||||
"""
|
||||
@ -395,11 +418,11 @@ def test_signal_handler(mocker):
|
||||
mocker.patch('freqtrade.optimize.hyperopt.Hyperopt.log_trials_result', m)
|
||||
|
||||
hyperopt = _HYPEROPT
|
||||
hyperopt.signal_handler(9, None)
|
||||
hyperopt.signal_handler(signal.SIGTERM, None)
|
||||
assert m.call_count == 3
|
||||
|
||||
|
||||
def test_has_space():
|
||||
def test_has_space(init_hyperopt):
|
||||
"""
|
||||
Test Hyperopt.has_space() method
|
||||
"""
|
||||
@ -412,7 +435,7 @@ def test_has_space():
|
||||
assert _HYPEROPT.has_space('buy')
|
||||
|
||||
|
||||
def test_populate_indicators() -> None:
|
||||
def test_populate_indicators(init_hyperopt) -> None:
|
||||
"""
|
||||
Test Hyperopt.populate_indicators()
|
||||
"""
|
||||
@ -427,7 +450,7 @@ def test_populate_indicators() -> None:
|
||||
assert 'cci' in dataframe
|
||||
|
||||
|
||||
def test_buy_strategy_generator() -> None:
|
||||
def test_buy_strategy_generator(init_hyperopt) -> None:
|
||||
"""
|
||||
Test Hyperopt.buy_strategy_generator()
|
||||
"""
|
||||
@ -484,7 +507,7 @@ def test_buy_strategy_generator() -> None:
|
||||
assert 1 in result['buy']
|
||||
|
||||
|
||||
def test_generate_optimizer(mocker, default_conf) -> None:
|
||||
def test_generate_optimizer(mocker, init_hyperopt, default_conf) -> None:
|
||||
"""
|
||||
Test Hyperopt.generate_optimizer() function
|
||||
"""
|
||||
@ -503,6 +526,7 @@ def test_generate_optimizer(mocker, default_conf) -> None:
|
||||
'freqtrade.optimize.hyperopt.Hyperopt.backtest',
|
||||
MagicMock(return_value=backtest_result)
|
||||
)
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock())
|
||||
|
||||
optimizer_param = {
|
||||
'adx': {'enabled': False},
|
||||
|
@ -52,11 +52,11 @@ def test_load_data_30min_ticker(ticker_history, mocker, caplog) -> None:
|
||||
"""
|
||||
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history)
|
||||
|
||||
file = 'freqtrade/tests/testdata/UNITTEST_BTC-30m.json'
|
||||
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')
|
||||
assert os.path.isfile(file) is True
|
||||
assert not log_has('Download the pair: "ETH/BTC", Interval: 30 min', caplog.record_tuples)
|
||||
assert not log_has('Download the pair: "UNITTEST/BTC", Interval: 30m', caplog.record_tuples)
|
||||
_clean_test_file(file)
|
||||
|
||||
|
||||
@ -66,11 +66,11 @@ def test_load_data_5min_ticker(ticker_history, mocker, caplog) -> None:
|
||||
"""
|
||||
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history)
|
||||
|
||||
file = 'freqtrade/tests/testdata/ETH_BTC-5m.json'
|
||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-5m.json')
|
||||
_backup_file(file, copy_file=True)
|
||||
optimize.load_data(None, pairs=['ETH/BTC'], ticker_interval='5m')
|
||||
optimize.load_data(None, pairs=['UNITTEST/BTC'], ticker_interval='5m')
|
||||
assert os.path.isfile(file) is True
|
||||
assert not log_has('Download the pair: "ETH/BTC", Interval: 5 min', caplog.record_tuples)
|
||||
assert not log_has('Download the pair: "UNITTEST/BTC", Interval: 5m', caplog.record_tuples)
|
||||
_clean_test_file(file)
|
||||
|
||||
|
||||
@ -80,11 +80,11 @@ def test_load_data_1min_ticker(ticker_history, mocker, caplog) -> None:
|
||||
"""
|
||||
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history)
|
||||
|
||||
file = 'freqtrade/tests/testdata/ETH_BTC-1m.json'
|
||||
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=['ETH/BTC'])
|
||||
optimize.load_data(None, ticker_interval='1m', pairs=['UNITTEST/BTC'])
|
||||
assert os.path.isfile(file) is True
|
||||
assert not log_has('Download the pair: "ETH/BTC", Interval: 1 min', caplog.record_tuples)
|
||||
assert not log_has('Download the pair: "UNITTEST/BTC", Interval: 1m', caplog.record_tuples)
|
||||
_clean_test_file(file)
|
||||
|
||||
|
||||
@ -94,11 +94,12 @@ def test_load_data_with_new_pair_1min(ticker_history, mocker, caplog) -> None:
|
||||
"""
|
||||
mocker.patch('freqtrade.optimize.get_ticker_history', return_value=ticker_history)
|
||||
|
||||
file = 'freqtrade/tests/testdata/MEME_BTC-1m.json'
|
||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'MEME_BTC-1m.json')
|
||||
|
||||
_backup_file(file)
|
||||
optimize.load_data(None, ticker_interval='1m', pairs=['MEME/BTC'])
|
||||
assert os.path.isfile(file) is True
|
||||
assert log_has('Download the pair: "MEME/BTC", Interval: 1 min', caplog.record_tuples)
|
||||
assert log_has('Download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples)
|
||||
_clean_test_file(file)
|
||||
|
||||
|
||||
@ -109,10 +110,10 @@ def test_testdata_path() -> None:
|
||||
def test_download_pairs(ticker_history, mocker) -> None:
|
||||
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history)
|
||||
|
||||
file1_1 = 'freqtrade/tests/testdata/MEME_BTC-1m.json'
|
||||
file1_5 = 'freqtrade/tests/testdata/MEME_BTC-5m.json'
|
||||
file2_1 = 'freqtrade/tests/testdata/CFI_BTC-1m.json'
|
||||
file2_5 = 'freqtrade/tests/testdata/CFI_BTC-5m.json'
|
||||
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')
|
||||
file2_1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'CFI_BTC-1m.json')
|
||||
file2_5 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'CFI_BTC-5m.json')
|
||||
|
||||
_backup_file(file1_1)
|
||||
_backup_file(file1_5)
|
||||
@ -149,8 +150,8 @@ def test_download_pairs_exception(ticker_history, mocker, caplog) -> None:
|
||||
mocker.patch('freqtrade.optimize.__init__.download_backtesting_testdata',
|
||||
side_effect=BaseException('File Error'))
|
||||
|
||||
file1_1 = 'freqtrade/tests/testdata/MEME_BTC-1m.json'
|
||||
file1_5 = 'freqtrade/tests/testdata/MEME_BTC-5m.json'
|
||||
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')
|
||||
_backup_file(file1_1)
|
||||
_backup_file(file1_5)
|
||||
|
||||
@ -158,21 +159,21 @@ def test_download_pairs_exception(ticker_history, mocker, caplog) -> None:
|
||||
# clean files freshly downloaded
|
||||
_clean_test_file(file1_1)
|
||||
_clean_test_file(file1_5)
|
||||
assert log_has('Failed to download the pair: "MEME/BTC", Interval: 1 min', caplog.record_tuples)
|
||||
assert log_has('Failed to download the pair: "MEME/BTC", Interval: 1m', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_download_backtesting_testdata(ticker_history, mocker) -> None:
|
||||
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=ticker_history)
|
||||
|
||||
# Download a 1 min ticker file
|
||||
file1 = 'freqtrade/tests/testdata/XEL_BTC-1m.json'
|
||||
file1 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'XEL_BTC-1m.json')
|
||||
_backup_file(file1)
|
||||
download_backtesting_testdata(None, pair="XEL/BTC", interval='1m')
|
||||
assert os.path.isfile(file1) is True
|
||||
_clean_test_file(file1)
|
||||
|
||||
# Download a 5 min ticker file
|
||||
file2 = 'freqtrade/tests/testdata/STORJ_BTC-5m.json'
|
||||
file2 = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'STORJ_BTC-5m.json')
|
||||
_backup_file(file2)
|
||||
|
||||
download_backtesting_testdata(None, pair="STORJ/BTC", interval='5m')
|
||||
@ -181,7 +182,10 @@ def test_download_backtesting_testdata(ticker_history, mocker) -> None:
|
||||
|
||||
|
||||
def test_download_backtesting_testdata2(mocker) -> None:
|
||||
tick = [{'T': 'bar'}, {'T': 'foo'}]
|
||||
tick = [
|
||||
[1509836520000, 0.00162008, 0.00162008, 0.00162008, 0.00162008, 108.14853839],
|
||||
[1509836580000, 0.00161, 0.00161, 0.00161, 0.00161, 82.390199]
|
||||
]
|
||||
mocker.patch('freqtrade.misc.file_dump_json', return_value=None)
|
||||
mocker.patch('freqtrade.optimize.__init__.get_ticker_history', return_value=tick)
|
||||
assert download_backtesting_testdata(None, pair="UNITTEST/BTC", interval='1m')
|
||||
@ -211,7 +215,8 @@ def test_init(default_conf, mocker) -> None:
|
||||
|
||||
|
||||
def test_trim_tickerlist() -> None:
|
||||
with open('freqtrade/tests/testdata/ETH_BTC-1m.json') as data_file:
|
||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata', 'UNITTEST_BTC-1m.json')
|
||||
with open(file) as data_file:
|
||||
ticker_list = json.load(data_file)
|
||||
ticker_list_len = len(ticker_list)
|
||||
|
||||
@ -260,7 +265,8 @@ def test_file_dump_json() -> None:
|
||||
Test file_dump_json()
|
||||
:return: None
|
||||
"""
|
||||
file = 'freqtrade/tests/testdata/test_{id}.json'.format(id=str(uuid.uuid4()))
|
||||
file = os.path.join(os.path.dirname(__file__), '..', 'testdata',
|
||||
'test_{id}.json'.format(id=str(uuid.uuid4())))
|
||||
data = {'bar': 'foo'}
|
||||
|
||||
# check the file we will create does not exist
|
||||
|
@ -25,7 +25,7 @@ def prec_satoshi(a, b) -> float:
|
||||
|
||||
|
||||
# Unit tests
|
||||
def test_rpc_trade_status(default_conf, ticker, mocker) -> None:
|
||||
def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
"""
|
||||
Test rpc_trade_status() method
|
||||
"""
|
||||
@ -35,7 +35,8 @@ def test_rpc_trade_status(default_conf, ticker, mocker) -> None:
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@ -67,13 +68,13 @@ def test_rpc_trade_status(default_conf, ticker, mocker) -> None:
|
||||
'*Current Rate:* `0.00001098`\n'
|
||||
'*Close Profit:* `None`\n'
|
||||
'*Current Profit:* `-0.59%`\n'
|
||||
'*Open Order:* `(LIMIT_BUY rem=0.00000000)`'
|
||||
'*Open Order:* `(limit buy rem=0.00000000)`'
|
||||
]
|
||||
assert result == result_message
|
||||
assert trade.find('[ETH/BTC]') >= 0
|
||||
|
||||
|
||||
def test_rpc_status_table(default_conf, ticker, mocker) -> None:
|
||||
def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||
"""
|
||||
Test rpc_status_table() method
|
||||
"""
|
||||
@ -83,7 +84,8 @@ def test_rpc_status_table(default_conf, ticker, mocker) -> None:
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@ -106,8 +108,8 @@ def test_rpc_status_table(default_conf, ticker, mocker) -> None:
|
||||
assert '-0.59%' in result['Profit'].all()
|
||||
|
||||
|
||||
def test_rpc_daily_profit(default_conf, update, ticker, limit_buy_order, limit_sell_order, mocker)\
|
||||
-> None:
|
||||
def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
||||
limit_buy_order, limit_sell_order, mocker) -> None:
|
||||
"""
|
||||
Test rpc_daily_profit() method
|
||||
"""
|
||||
@ -117,7 +119,8 @@ def test_rpc_daily_profit(default_conf, update, ticker, limit_buy_order, limit_s
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@ -158,8 +161,8 @@ def test_rpc_daily_profit(default_conf, update, ticker, limit_buy_order, limit_s
|
||||
assert days.find('must be an integer greater than 0') >= 0
|
||||
|
||||
|
||||
def test_rpc_trade_statistics(
|
||||
default_conf, ticker, ticker_sell_up, limit_buy_order, limit_sell_order, mocker) -> None:
|
||||
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
||||
limit_buy_order, limit_sell_order, mocker) -> None:
|
||||
"""
|
||||
Test rpc_trade_statistics() method
|
||||
"""
|
||||
@ -173,7 +176,8 @@ def test_rpc_trade_statistics(
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@ -220,8 +224,8 @@ def test_rpc_trade_statistics(
|
||||
|
||||
# Test that rpc_trade_statistics can handle trades that lacks
|
||||
# trade.open_rate (it is set to None)
|
||||
def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, ticker_sell_up, limit_buy_order,
|
||||
limit_sell_order):
|
||||
def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
|
||||
ticker_sell_up, limit_buy_order, limit_sell_order):
|
||||
"""
|
||||
Test rpc_trade_statistics() method
|
||||
"""
|
||||
@ -235,7 +239,8 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, ticker_sell_u
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@ -253,7 +258,8 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, ticker_sell_u
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker_sell_up
|
||||
get_ticker=ticker_sell_up,
|
||||
get_fee=fee
|
||||
)
|
||||
trade.update(limit_sell_order)
|
||||
trade.close_date = datetime.utcnow()
|
||||
@ -401,8 +407,9 @@ def test_rpc_forcesell(default_conf, ticker, mocker) -> None:
|
||||
cancel_order=cancel_order_mock,
|
||||
get_order=MagicMock(
|
||||
return_value={
|
||||
'closed': True,
|
||||
'type': 'LIMIT_BUY',
|
||||
'status': 'closed',
|
||||
'type': 'limit',
|
||||
'side': 'buy'
|
||||
}
|
||||
)
|
||||
)
|
||||
@ -448,8 +455,9 @@ def test_rpc_forcesell(default_conf, ticker, mocker) -> None:
|
||||
mocker.patch(
|
||||
'freqtrade.freqtradebot.exchange.get_order',
|
||||
return_value={
|
||||
'closed': None,
|
||||
'type': 'LIMIT_BUY'
|
||||
'status': 'open',
|
||||
'type': 'limit',
|
||||
'side': 'buy'
|
||||
}
|
||||
)
|
||||
# check that the trade is called, which is done
|
||||
@ -464,8 +472,9 @@ def test_rpc_forcesell(default_conf, ticker, mocker) -> None:
|
||||
mocker.patch(
|
||||
'freqtrade.freqtradebot.exchange.get_order',
|
||||
return_value={
|
||||
'closed': None,
|
||||
'type': 'LIMIT_SELL'
|
||||
'status': 'open',
|
||||
'type': 'limit',
|
||||
'side': 'sell'
|
||||
}
|
||||
)
|
||||
(error, res) = rpc.rpc_forcesell('2')
|
||||
@ -475,7 +484,7 @@ def test_rpc_forcesell(default_conf, ticker, mocker) -> None:
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
|
||||
def test_performance_handle(default_conf, ticker, limit_buy_order,
|
||||
def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
||||
limit_sell_order, mocker) -> None:
|
||||
"""
|
||||
Test rpc_performance() method
|
||||
@ -487,7 +496,8 @@ def test_performance_handle(default_conf, ticker, limit_buy_order,
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
@ -333,7 +333,7 @@ def test_status_table_handle(default_conf, update, ticker, mocker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_order_id')
|
||||
buy=MagicMock(return_value={'id': 'mocked_order_id'})
|
||||
)
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
@ -374,7 +374,7 @@ def test_status_table_handle(default_conf, update, ticker, mocker) -> None:
|
||||
assert msg_mock.call_count == 1
|
||||
|
||||
|
||||
def test_daily_handle(default_conf, update, ticker, limit_buy_order,
|
||||
def test_daily_handle(default_conf, update, ticker, limit_buy_order, fee,
|
||||
limit_sell_order, mocker) -> None:
|
||||
"""
|
||||
Test _daily() method
|
||||
@ -389,7 +389,7 @@ def test_daily_handle(default_conf, update, ticker, limit_buy_order,
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
get_pair_detail_url=MagicMock()
|
||||
get_fee=fee
|
||||
)
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
@ -486,7 +486,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
|
||||
assert str('Daily Profit over the last 7 days') in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_profit_handle(default_conf, update, ticker, ticker_sell_up,
|
||||
def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
||||
limit_buy_order, limit_sell_order, mocker) -> None:
|
||||
"""
|
||||
Test _profit() method
|
||||
@ -497,7 +497,8 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
@ -749,7 +750,7 @@ def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
|
||||
assert 'already stopped' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker) -> None:
|
||||
def test_forcesell_handle(default_conf, update, ticker, fee, ticker_sell_up, mocker) -> None:
|
||||
"""
|
||||
Test _forcesell() method
|
||||
"""
|
||||
@ -761,7 +762,8 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@ -788,7 +790,7 @@ def test_forcesell_handle(default_conf, update, ticker, ticker_sell_up, mocker)
|
||||
assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, mocker) -> None:
|
||||
def test_forcesell_down_handle(default_conf, update, ticker, fee, ticker_sell_down, mocker) -> None:
|
||||
"""
|
||||
Test _forcesell() method
|
||||
"""
|
||||
@ -800,7 +802,8 @@ def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, m
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@ -831,7 +834,7 @@ def test_forcesell_down_handle(default_conf, update, ticker, ticker_sell_down, m
|
||||
assert '-0.824 USD' in rpc_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
def test_forcesell_all_handle(default_conf, update, ticker, mocker) -> None:
|
||||
def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
"""
|
||||
Test _forcesell() method
|
||||
"""
|
||||
@ -844,7 +847,8 @@ def test_forcesell_all_handle(default_conf, update, ticker, mocker) -> None:
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@ -907,8 +911,8 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
|
||||
assert 'Invalid argument.' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_performance_handle(default_conf, update, ticker, limit_buy_order,
|
||||
limit_sell_order, mocker) -> None:
|
||||
def test_performance_handle(default_conf, update, ticker, fee,
|
||||
limit_buy_order, limit_sell_order, mocker) -> None:
|
||||
"""
|
||||
Test _performance() method
|
||||
"""
|
||||
@ -923,7 +927,8 @@ def test_performance_handle(default_conf, update, ticker, limit_buy_order,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
mocker.patch('freqtrade.freqtradebot.RPCManager', MagicMock())
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@ -987,7 +992,7 @@ def test_count_handle(default_conf, update, ticker, mocker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_order_id')
|
||||
buy=MagicMock(return_value={'id': 'mocked_order_id'})
|
||||
)
|
||||
freqtradebot = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
telegram = Telegram(freqtradebot)
|
||||
|
@ -1,18 +1,8 @@
|
||||
import json
|
||||
|
||||
import pytest
|
||||
from pandas import DataFrame
|
||||
|
||||
from freqtrade.analyze import Analyze
|
||||
from freqtrade.strategy.default_strategy import DefaultStrategy, class_name
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def result():
|
||||
with open('freqtrade/tests/testdata/ETH_BTC-1m.json') as data_file:
|
||||
return Analyze.parse_ticker_dataframe(json.load(data_file))
|
||||
|
||||
|
||||
def test_default_strategy_class_name():
|
||||
assert class_name == DefaultStrategy.__name__
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
# pragma pylint: disable=missing-docstring,C0103,protected-access
|
||||
|
||||
import freqtrade.tests.conftest as tt # test tools
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
# whitelist, blacklist, filtering, all of that will
|
||||
# eventually become some rules to run on a generic ACL engine
|
||||
@ -26,81 +27,12 @@ def whitelist_conf():
|
||||
return config
|
||||
|
||||
|
||||
def get_market_summaries():
|
||||
return {
|
||||
'TKN/BTC': {
|
||||
'symbol': 'TKN/BTC',
|
||||
'info': {
|
||||
'High': 0.00000919,
|
||||
'Low': 0.00000820,
|
||||
'Volume': 74339.61396015,
|
||||
'Last': 0.00000820,
|
||||
'BaseVolume': 1664,
|
||||
'TimeStamp': '2014-07-09T07:19:30.15',
|
||||
'Bid': 0.00000820,
|
||||
'Ask': 0.00000831,
|
||||
'OpenBuyOrders': 15,
|
||||
'OpenSellOrders': 15,
|
||||
'PrevDay': 0.00000821,
|
||||
'Created': '2014-03-20T06:00:00',
|
||||
'DisplayMarketName': ''
|
||||
}
|
||||
},
|
||||
'ETH/BTC': {
|
||||
'symbol': 'ETH/BTC',
|
||||
'info': {
|
||||
'High': 0.00000072,
|
||||
'Low': 0.00000001,
|
||||
'Volume': 166340678.42280999,
|
||||
'Last': 0.00000005,
|
||||
'BaseVolume': 42,
|
||||
'TimeStamp': '2014-07-09T07:21:40.51',
|
||||
'Bid': 0.00000004,
|
||||
'Ask': 0.00000005,
|
||||
'OpenBuyOrders': 18,
|
||||
'OpenSellOrders': 18,
|
||||
'PrevDay': 0.00000002,
|
||||
'Created': '2014-05-30T07:57:49.637',
|
||||
'DisplayMarketName': ''
|
||||
}
|
||||
},
|
||||
'BLK/BTC': {
|
||||
'symbol': 'BLK/BTC',
|
||||
'info': {
|
||||
'High': 0.00000072,
|
||||
'Low': 0.00000001,
|
||||
'Volume': 166340678.42280999,
|
||||
'Last': 0.00000005,
|
||||
'BaseVolume': 3,
|
||||
'TimeStamp': '2014-07-09T07:21:40.51',
|
||||
'Bid': 0.00000004,
|
||||
'Ask': 0.00000005,
|
||||
'OpenBuyOrders': 18,
|
||||
'OpenSellOrders': 18,
|
||||
'PrevDay': 0.00000002,
|
||||
'Created': '2014-05-30T07:57:49.637',
|
||||
'DisplayMarketName': ''
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
def get_health():
|
||||
return {
|
||||
'ETH/BTC': {'base': 'ETH', 'active': True},
|
||||
'TKN/BTC': {'base': 'TKN', 'active': True},
|
||||
'BLK/BTC': {'base': 'BLK', 'active': True}}
|
||||
|
||||
|
||||
def get_health_empty():
|
||||
return {}
|
||||
|
||||
|
||||
def test_refresh_market_pair_not_in_whitelist(mocker):
|
||||
def test_refresh_market_pair_not_in_whitelist(mocker, markets):
|
||||
conf = whitelist_conf()
|
||||
|
||||
freqtradebot = tt.get_patched_freqtradebot(mocker, conf)
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_wallet_health', get_health)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_markets', markets)
|
||||
refreshedwhitelist = freqtradebot._refresh_whitelist(
|
||||
conf['exchange']['pair_whitelist'] + ['XXX/BTC']
|
||||
)
|
||||
@ -110,11 +42,11 @@ def test_refresh_market_pair_not_in_whitelist(mocker):
|
||||
assert whitelist == refreshedwhitelist
|
||||
|
||||
|
||||
def test_refresh_whitelist(mocker):
|
||||
def test_refresh_whitelist(mocker, markets):
|
||||
conf = whitelist_conf()
|
||||
freqtradebot = tt.get_patched_freqtradebot(mocker, conf)
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_wallet_health', get_health)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_markets', markets)
|
||||
refreshedwhitelist = freqtradebot._refresh_whitelist(conf['exchange']['pair_whitelist'])
|
||||
|
||||
# List ordered by BaseVolume
|
||||
@ -123,17 +55,18 @@ def test_refresh_whitelist(mocker):
|
||||
assert whitelist == refreshedwhitelist
|
||||
|
||||
|
||||
def test_refresh_whitelist_dynamic(mocker):
|
||||
def test_refresh_whitelist_dynamic(mocker, markets, tickers):
|
||||
conf = whitelist_conf()
|
||||
freqtradebot = tt.get_patched_freqtradebot(mocker, conf)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
get_wallet_health=get_health,
|
||||
get_market_summaries=get_market_summaries
|
||||
get_markets=markets,
|
||||
get_tickers=tickers,
|
||||
exchange_has=MagicMock(return_value=True)
|
||||
)
|
||||
|
||||
# argument: use the whitelist dynamically by exchange-volume
|
||||
whitelist = ['TKN/BTC', 'ETH/BTC']
|
||||
whitelist = ['ETH/BTC', 'TKN/BTC']
|
||||
|
||||
refreshedwhitelist = freqtradebot._refresh_whitelist(
|
||||
freqtradebot._gen_pair_whitelist(conf['stake_currency'])
|
||||
@ -142,10 +75,10 @@ def test_refresh_whitelist_dynamic(mocker):
|
||||
assert whitelist == refreshedwhitelist
|
||||
|
||||
|
||||
def test_refresh_whitelist_dynamic_empty(mocker):
|
||||
def test_refresh_whitelist_dynamic_empty(mocker, markets_empty):
|
||||
conf = whitelist_conf()
|
||||
freqtradebot = tt.get_patched_freqtradebot(mocker, conf)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_wallet_health', get_health_empty)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_markets', markets_empty)
|
||||
|
||||
# argument: use the whitelist dynamically by exchange-volume
|
||||
whitelist = []
|
||||
|
@ -50,7 +50,7 @@ def test_dataframe_correct_length(result):
|
||||
|
||||
def test_dataframe_correct_columns(result):
|
||||
assert result.columns.tolist() == \
|
||||
['close', 'high', 'low', 'open', 'date', 'volume']
|
||||
['date', 'open', 'high', 'low', 'close', 'volume']
|
||||
|
||||
|
||||
def test_populates_buy_trend(result):
|
||||
@ -169,17 +169,13 @@ def test_get_signal_handles_exceptions(mocker):
|
||||
assert _ANALYZE.get_signal('ETH/BTC', '5m') == (False, False)
|
||||
|
||||
|
||||
def test_parse_ticker_dataframe(ticker_history, ticker_history_without_bv):
|
||||
columns = ['close', 'high', 'low', 'open', 'date', 'volume']
|
||||
def test_parse_ticker_dataframe(ticker_history):
|
||||
columns = ['date', 'open', 'high', 'low', 'close', 'volume']
|
||||
|
||||
# Test file with BV data
|
||||
dataframe = Analyze.parse_ticker_dataframe(ticker_history)
|
||||
assert dataframe.columns.tolist() == columns
|
||||
|
||||
# Test file without BV data
|
||||
dataframe = Analyze.parse_ticker_dataframe(ticker_history_without_bv)
|
||||
assert dataframe.columns.tolist() == columns
|
||||
|
||||
|
||||
def test_tickerdata_to_dataframe(default_conf) -> None:
|
||||
"""
|
||||
|
@ -201,29 +201,28 @@ def test_throttle_with_assets(mocker, default_conf) -> None:
|
||||
assert result == -1
|
||||
|
||||
|
||||
def test_gen_pair_whitelist(mocker, default_conf, get_market_summaries_data) -> None:
|
||||
def test_gen_pair_whitelist(mocker, default_conf, tickers) -> None:
|
||||
"""
|
||||
Test _gen_pair_whitelist() method
|
||||
"""
|
||||
freqtrade = get_patched_freqtradebot(mocker, default_conf)
|
||||
mocker.patch(
|
||||
'freqtrade.freqtradebot.exchange.get_market_summaries',
|
||||
return_value=get_market_summaries_data
|
||||
)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.get_tickers', tickers)
|
||||
mocker.patch('freqtrade.freqtradebot.exchange.exchange_has', MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.exchange.validate_pairs', MagicMock(return_value=True))
|
||||
|
||||
# Test to retrieved BTC sorted on BaseVolume
|
||||
# Test to retrieved BTC sorted on quoteVolume (default)
|
||||
whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC')
|
||||
assert whitelist == ['ZCL/BTC', 'ZEC/BTC', 'XZC/BTC', 'XWC/BTC']
|
||||
assert whitelist == ['ETH/BTC', 'TKN/BTC', 'BLK/BTC', 'LTC/BTC']
|
||||
|
||||
# Test to retrieved BTC sorted on OpenBuyOrders
|
||||
whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC', key='OpenBuyOrders')
|
||||
assert whitelist == ['XWC/BTC', 'ZCL/BTC', 'ZEC/BTC', 'XZC/BTC']
|
||||
# Test to retrieve BTC sorted on bidVolume
|
||||
whitelist = freqtrade._gen_pair_whitelist(base_currency='BTC', key='bidVolume')
|
||||
assert whitelist == ['LTC/BTC', 'TKN/BTC', 'ETH/BTC', 'BLK/BTC']
|
||||
|
||||
# Test with USDT sorted on BaseVolume
|
||||
# Test with USDT sorted on quoteVolume (default)
|
||||
whitelist = freqtrade._gen_pair_whitelist(base_currency='USDT')
|
||||
assert whitelist == ['XRP/USDT', 'XVG/USDT', 'XMR/USDT', 'ZEC/USDT']
|
||||
assert whitelist == ['TKN/USDT', 'ETH/USDT', 'LTC/USDT', 'BLK/USDT']
|
||||
|
||||
# Test with ETH (our fixture does not have ETH, but Bittrex returns them)
|
||||
# Test with ETH (our fixture does not have ETH, so result should be empty)
|
||||
whitelist = freqtrade._gen_pair_whitelist(base_currency='ETH')
|
||||
assert whitelist == []
|
||||
|
||||
@ -247,7 +246,7 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
)
|
||||
|
||||
# Save state of current whitelist
|
||||
@ -271,14 +270,14 @@ def test_create_trade(default_conf, ticker, limit_buy_order, mocker) -> None:
|
||||
assert whitelist == default_conf['exchange']['pair_whitelist']
|
||||
|
||||
|
||||
def test_create_trade_minimal_amount(default_conf, ticker, mocker) -> None:
|
||||
def test_create_trade_minimal_amount(default_conf, ticker, limit_buy_order, mocker) -> None:
|
||||
"""
|
||||
Test create_trade() method
|
||||
"""
|
||||
patch_get_signal(mocker)
|
||||
patch_RPCManager(mocker)
|
||||
patch_coinmarketcap(mocker)
|
||||
buy_mock = MagicMock(return_value='mocked_limit_buy')
|
||||
buy_mock = MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
@ -295,7 +294,7 @@ def test_create_trade_minimal_amount(default_conf, ticker, mocker) -> None:
|
||||
assert rate * amount >= conf['stake_amount']
|
||||
|
||||
|
||||
def test_create_trade_no_stake_amount(default_conf, ticker, mocker) -> None:
|
||||
def test_create_trade_no_stake_amount(default_conf, ticker, limit_buy_order, mocker) -> None:
|
||||
"""
|
||||
Test create_trade() method
|
||||
"""
|
||||
@ -306,7 +305,7 @@ def test_create_trade_no_stake_amount(default_conf, ticker, mocker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_limit_buy'),
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_balance=MagicMock(return_value=default_conf['stake_amount'] * 0.5)
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@ -315,7 +314,7 @@ def test_create_trade_no_stake_amount(default_conf, ticker, mocker) -> None:
|
||||
freqtrade.create_trade()
|
||||
|
||||
|
||||
def test_create_trade_no_pairs(default_conf, ticker, mocker) -> None:
|
||||
def test_create_trade_no_pairs(default_conf, ticker, limit_buy_order, mocker) -> None:
|
||||
"""
|
||||
Test create_trade() method
|
||||
"""
|
||||
@ -326,7 +325,7 @@ def test_create_trade_no_pairs(default_conf, ticker, mocker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
@ -340,7 +339,8 @@ def test_create_trade_no_pairs(default_conf, ticker, mocker) -> None:
|
||||
freqtrade.create_trade()
|
||||
|
||||
|
||||
def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker) -> None:
|
||||
def test_create_trade_no_pairs_after_blacklist(default_conf, ticker,
|
||||
limit_buy_order, mocker) -> None:
|
||||
"""
|
||||
Test create_trade() method
|
||||
"""
|
||||
@ -351,7 +351,7 @@ def test_create_trade_no_pairs_after_blacklist(default_conf, ticker, mocker) ->
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
@ -392,7 +392,7 @@ def test_create_trade_no_signal(default_conf, mocker) -> None:
|
||||
|
||||
|
||||
def test_process_trade_creation(default_conf, ticker, limit_buy_order,
|
||||
health, mocker, caplog) -> None:
|
||||
markets, mocker, caplog) -> None:
|
||||
"""
|
||||
Test the trade creation in _process() method
|
||||
"""
|
||||
@ -403,8 +403,8 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order,
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
get_wallet_health=health,
|
||||
buy=MagicMock(return_value='mocked_limit_buy'),
|
||||
get_markets=markets,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_order=MagicMock(return_value=limit_buy_order)
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@ -432,7 +432,7 @@ def test_process_trade_creation(default_conf, ticker, limit_buy_order,
|
||||
)
|
||||
|
||||
|
||||
def test_process_exchange_failures(default_conf, ticker, health, mocker) -> None:
|
||||
def test_process_exchange_failures(default_conf, ticker, markets, mocker) -> None:
|
||||
"""
|
||||
Test _process() method when a RequestException happens
|
||||
"""
|
||||
@ -443,7 +443,7 @@ def test_process_exchange_failures(default_conf, ticker, health, mocker) -> None
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
get_wallet_health=health,
|
||||
get_markets=markets,
|
||||
buy=MagicMock(side_effect=requests.exceptions.RequestException)
|
||||
)
|
||||
sleep_mock = mocker.patch('time.sleep', side_effect=lambda _: None)
|
||||
@ -454,7 +454,7 @@ def test_process_exchange_failures(default_conf, ticker, health, mocker) -> None
|
||||
assert sleep_mock.has_calls()
|
||||
|
||||
|
||||
def test_process_operational_exception(default_conf, ticker, health, mocker) -> None:
|
||||
def test_process_operational_exception(default_conf, ticker, markets, mocker) -> None:
|
||||
"""
|
||||
Test _process() method when an OperationalException happens
|
||||
"""
|
||||
@ -465,7 +465,7 @@ def test_process_operational_exception(default_conf, ticker, health, mocker) ->
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
get_wallet_health=health,
|
||||
get_markets=markets,
|
||||
buy=MagicMock(side_effect=OperationalException)
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@ -477,7 +477,7 @@ def test_process_operational_exception(default_conf, ticker, health, mocker) ->
|
||||
assert 'OperationalException' in msg_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
def test_process_trade_handling(default_conf, ticker, limit_buy_order, health, mocker) -> None:
|
||||
def test_process_trade_handling(default_conf, ticker, limit_buy_order, markets, mocker) -> None:
|
||||
"""
|
||||
Test _process()
|
||||
"""
|
||||
@ -488,8 +488,8 @@ def test_process_trade_handling(default_conf, ticker, limit_buy_order, health, m
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
get_wallet_health=health,
|
||||
buy=MagicMock(return_value='mocked_limit_buy'),
|
||||
get_markets=markets,
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
get_order=MagicMock(return_value=limit_buy_order)
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@ -577,7 +577,7 @@ def test_process_maybe_execute_sell(mocker, default_conf) -> None:
|
||||
assert freqtrade.process_maybe_execute_sell(trade)
|
||||
|
||||
|
||||
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker) -> None:
|
||||
def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, fee, mocker) -> None:
|
||||
"""
|
||||
Test check_handle() method
|
||||
"""
|
||||
@ -591,8 +591,9 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker) -
|
||||
'ask': 0.00001173,
|
||||
'last': 0.00001172
|
||||
}),
|
||||
buy=MagicMock(return_value='mocked_limit_buy'),
|
||||
sell=MagicMock(return_value='mocked_limit_sell')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']}),
|
||||
sell=MagicMock(return_value={'id': limit_sell_order['id']}),
|
||||
get_fee=fee
|
||||
)
|
||||
patch_coinmarketcap(mocker, value={'price_usd': 15000.0})
|
||||
|
||||
@ -603,12 +604,13 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker) -
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
|
||||
time.sleep(0.01) # Race condition fix
|
||||
trade.update(limit_buy_order)
|
||||
assert trade.is_open is True
|
||||
|
||||
patch_get_signal(mocker, value=(False, True))
|
||||
assert freqtrade.handle_trade(trade) is True
|
||||
assert trade.open_order_id == 'mocked_limit_sell'
|
||||
assert trade.open_order_id == limit_sell_order['id']
|
||||
|
||||
# Simulate fulfilled LIMIT_SELL order for trade
|
||||
trade.update(limit_sell_order)
|
||||
@ -619,7 +621,7 @@ def test_handle_trade(default_conf, limit_buy_order, limit_sell_order, mocker) -
|
||||
assert trade.close_date is not None
|
||||
|
||||
|
||||
def test_handle_overlpapping_signals(default_conf, ticker, mocker) -> None:
|
||||
def test_handle_overlpapping_signals(default_conf, ticker, limit_buy_order, mocker) -> None:
|
||||
"""
|
||||
Test check_handle() method
|
||||
"""
|
||||
@ -634,7 +636,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, mocker) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
)
|
||||
|
||||
freqtrade = FreqtradeBot(conf, create_engine('sqlite://'))
|
||||
@ -676,7 +678,7 @@ def test_handle_overlpapping_signals(default_conf, ticker, mocker) -> None:
|
||||
assert freqtrade.handle_trade(trades[0]) is True
|
||||
|
||||
|
||||
def test_handle_trade_roi(default_conf, ticker, mocker, caplog) -> None:
|
||||
def test_handle_trade_roi(default_conf, ticker, limit_buy_order, mocker, caplog) -> None:
|
||||
"""
|
||||
Test check_handle() method
|
||||
"""
|
||||
@ -691,7 +693,7 @@ def test_handle_trade_roi(default_conf, ticker, mocker, caplog) -> None:
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
)
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=True)
|
||||
@ -711,7 +713,7 @@ def test_handle_trade_roi(default_conf, ticker, mocker, caplog) -> None:
|
||||
assert log_has('Required profit reached. Selling..', caplog.record_tuples)
|
||||
|
||||
|
||||
def test_handle_trade_experimental(default_conf, ticker, mocker, caplog) -> None:
|
||||
def test_handle_trade_experimental(default_conf, ticker, limit_buy_order, mocker, caplog) -> None:
|
||||
"""
|
||||
Test check_handle() method
|
||||
"""
|
||||
@ -726,7 +728,7 @@ def test_handle_trade_experimental(default_conf, ticker, mocker, caplog) -> None
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
)
|
||||
mocker.patch('freqtrade.freqtradebot.Analyze.min_roi_reached', return_value=False)
|
||||
|
||||
@ -755,7 +757,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mo
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
@ -773,7 +775,7 @@ def test_close_trade(default_conf, ticker, limit_buy_order, limit_sell_order, mo
|
||||
freqtrade.handle_trade(trade)
|
||||
|
||||
|
||||
def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mocker) -> None:
|
||||
def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, fee, mocker) -> None:
|
||||
"""
|
||||
Test check_handle_timedout() method
|
||||
"""
|
||||
@ -785,14 +787,15 @@ def test_check_handle_timedout_buy(default_conf, ticker, limit_buy_order_old, mo
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker,
|
||||
get_order=MagicMock(return_value=limit_buy_order_old),
|
||||
cancel_order=cancel_order_mock
|
||||
cancel_order=cancel_order_mock,
|
||||
get_fee=fee
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
trade_buy = Trade(
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='BITTREX',
|
||||
exchange='bittrex',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee=0.0,
|
||||
@ -831,7 +834,7 @@ def test_check_handle_timedout_sell(default_conf, ticker, limit_sell_order_old,
|
||||
trade_sell = Trade(
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='BITTREX',
|
||||
exchange='bittrex',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee=0.0,
|
||||
@ -870,7 +873,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old
|
||||
trade_buy = Trade(
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='BITTREX',
|
||||
exchange='bittrex',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee=0.0,
|
||||
@ -917,7 +920,7 @@ def test_check_handle_timedout_exception(default_conf, ticker, mocker, caplog) -
|
||||
trade_buy = Trade(
|
||||
pair='ETH/BTC',
|
||||
open_rate=0.00001099,
|
||||
exchange='BITTREX',
|
||||
exchange='bittrex',
|
||||
open_order_id='123456789',
|
||||
amount=90.99181073,
|
||||
fee=0.0,
|
||||
@ -989,7 +992,7 @@ def test_handle_timedout_limit_sell(mocker, default_conf) -> None:
|
||||
assert cancel_order_mock.call_count == 1
|
||||
|
||||
|
||||
def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker) -> None:
|
||||
def test_execute_sell_up(default_conf, ticker, fee, ticker_sell_up, mocker) -> None:
|
||||
"""
|
||||
Test execute_sell() method with a ticker going UP
|
||||
"""
|
||||
@ -999,7 +1002,8 @@ def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker) -> None:
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
mocker.patch('freqtrade.fiat_convert.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
@ -1029,7 +1033,7 @@ def test_execute_sell_up(default_conf, ticker, ticker_sell_up, mocker) -> None:
|
||||
assert '0.919 USD' in rpc_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker) -> None:
|
||||
def test_execute_sell_down(default_conf, ticker, fee, ticker_sell_down, mocker) -> None:
|
||||
"""
|
||||
Test execute_sell() method with a ticker going DOWN
|
||||
"""
|
||||
@ -1040,7 +1044,8 @@ def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker) -> No
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
@ -1068,7 +1073,8 @@ def test_execute_sell_down(default_conf, ticker, ticker_sell_down, mocker) -> No
|
||||
assert '-0.824 USD' in rpc_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
def test_execute_sell_without_conf_sell_up(default_conf, ticker, ticker_sell_up, mocker) -> None:
|
||||
def test_execute_sell_without_conf_sell_up(default_conf, ticker, fee,
|
||||
ticker_sell_up, mocker) -> None:
|
||||
"""
|
||||
Test execute_sell() method with a ticker going DOWN and with a bot config empty
|
||||
"""
|
||||
@ -1078,7 +1084,8 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, ticker_sell_up,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
@ -1107,7 +1114,7 @@ def test_execute_sell_without_conf_sell_up(default_conf, ticker, ticker_sell_up,
|
||||
assert 'USD' not in rpc_mock.call_args_list[-1][0][0]
|
||||
|
||||
|
||||
def test_execute_sell_without_conf_sell_down(default_conf, ticker,
|
||||
def test_execute_sell_without_conf_sell_down(default_conf, ticker, fee,
|
||||
ticker_sell_down, mocker) -> None:
|
||||
"""
|
||||
Test execute_sell() method with a ticker going DOWN and with a bot config empty
|
||||
@ -1118,7 +1125,8 @@ def test_execute_sell_without_conf_sell_down(default_conf, ticker,
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.freqtradebot.exchange',
|
||||
validate_pairs=MagicMock(),
|
||||
get_ticker=ticker
|
||||
get_ticker=ticker,
|
||||
get_fee=fee
|
||||
)
|
||||
freqtrade = FreqtradeBot(default_conf, create_engine('sqlite://'))
|
||||
|
||||
@ -1161,7 +1169,7 @@ def test_sell_profit_only_enable_profit(default_conf, limit_buy_order, mocker) -
|
||||
'ask': 0.00002173,
|
||||
'last': 0.00002172
|
||||
}),
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
)
|
||||
conf = deepcopy(default_conf)
|
||||
conf['experimental'] = {
|
||||
@ -1193,7 +1201,7 @@ def test_sell_profit_only_disable_profit(default_conf, limit_buy_order, mocker)
|
||||
'ask': 0.00002173,
|
||||
'last': 0.00002172
|
||||
}),
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
)
|
||||
conf = deepcopy(default_conf)
|
||||
conf['experimental'] = {
|
||||
@ -1225,7 +1233,7 @@ def test_sell_profit_only_enable_loss(default_conf, limit_buy_order, mocker) ->
|
||||
'ask': 0.00000173,
|
||||
'last': 0.00000172
|
||||
}),
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
)
|
||||
conf = deepcopy(default_conf)
|
||||
conf['experimental'] = {
|
||||
@ -1257,7 +1265,7 @@ def test_sell_profit_only_disable_loss(default_conf, limit_buy_order, mocker) ->
|
||||
'ask': 0.00000173,
|
||||
'last': 0.00000172
|
||||
}),
|
||||
buy=MagicMock(return_value='mocked_limit_buy')
|
||||
buy=MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
)
|
||||
|
||||
conf = deepcopy(default_conf)
|
||||
|
@ -89,7 +89,7 @@ def test_init_prod_db(default_conf, mocker):
|
||||
os.rename(prod_db_swp, prod_db)
|
||||
|
||||
|
||||
def test_update_with_bittrex(limit_buy_order, limit_sell_order):
|
||||
def test_update_with_bittrex(limit_buy_order, limit_sell_order, fee):
|
||||
"""
|
||||
On this test we will buy and sell a crypto currency.
|
||||
|
||||
@ -120,7 +120,7 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
fee=0.0025,
|
||||
fee=fee.return_value,
|
||||
exchange='bittrex',
|
||||
)
|
||||
assert trade.open_order_id is None
|
||||
@ -143,11 +143,11 @@ def test_update_with_bittrex(limit_buy_order, limit_sell_order):
|
||||
assert trade.close_date is not None
|
||||
|
||||
|
||||
def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order):
|
||||
def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order, fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
fee=0.0025,
|
||||
fee=fee.return_value,
|
||||
exchange='bittrex',
|
||||
)
|
||||
|
||||
@ -165,11 +165,11 @@ def test_calc_open_close_trade_price(limit_buy_order, limit_sell_order):
|
||||
assert trade.calc_profit_percent() == 0.06201057
|
||||
|
||||
|
||||
def test_calc_close_trade_price_exception(limit_buy_order):
|
||||
def test_calc_close_trade_price_exception(limit_buy_order, fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
fee=0.0025,
|
||||
fee=fee.return_value,
|
||||
exchange='bittrex',
|
||||
)
|
||||
|
||||
@ -191,7 +191,7 @@ def test_update_open_order(limit_buy_order):
|
||||
assert trade.close_profit is None
|
||||
assert trade.close_date is None
|
||||
|
||||
limit_buy_order['closed'] = False
|
||||
limit_buy_order['status'] = 'open'
|
||||
trade.update(limit_buy_order)
|
||||
|
||||
assert trade.open_order_id is None
|
||||
@ -212,11 +212,11 @@ def test_update_invalid_order(limit_buy_order):
|
||||
trade.update(limit_buy_order)
|
||||
|
||||
|
||||
def test_calc_open_trade_price(limit_buy_order):
|
||||
def test_calc_open_trade_price(limit_buy_order, fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
fee=0.0025,
|
||||
fee=fee.return_value,
|
||||
exchange='bittrex',
|
||||
)
|
||||
trade.open_order_id = 'open_trade'
|
||||
@ -229,11 +229,11 @@ def test_calc_open_trade_price(limit_buy_order):
|
||||
assert trade.calc_open_trade_price(fee=0.003) == 0.001003000
|
||||
|
||||
|
||||
def test_calc_close_trade_price(limit_buy_order, limit_sell_order):
|
||||
def test_calc_close_trade_price(limit_buy_order, limit_sell_order, fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
fee=0.0025,
|
||||
fee=fee.return_value,
|
||||
exchange='bittrex',
|
||||
)
|
||||
trade.open_order_id = 'close_trade'
|
||||
@ -250,11 +250,11 @@ def test_calc_close_trade_price(limit_buy_order, limit_sell_order):
|
||||
assert trade.calc_close_trade_price(fee=0.005) == 0.0010619972
|
||||
|
||||
|
||||
def test_calc_profit(limit_buy_order, limit_sell_order):
|
||||
def test_calc_profit(limit_buy_order, limit_sell_order, fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
fee=0.0025,
|
||||
fee=fee.return_value,
|
||||
exchange='bittrex',
|
||||
)
|
||||
trade.open_order_id = 'profit_percent'
|
||||
@ -280,11 +280,11 @@ def test_calc_profit(limit_buy_order, limit_sell_order):
|
||||
assert trade.calc_profit(fee=0.003) == 0.00006163
|
||||
|
||||
|
||||
def test_calc_profit_percent(limit_buy_order, limit_sell_order):
|
||||
def test_calc_profit_percent(limit_buy_order, limit_sell_order, fee):
|
||||
trade = Trade(
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
fee=0.0025,
|
||||
fee=fee.return_value,
|
||||
exchange='bittrex',
|
||||
)
|
||||
trade.open_order_id = 'profit_percent'
|
||||
@ -304,7 +304,7 @@ def test_calc_profit_percent(limit_buy_order, limit_sell_order):
|
||||
assert trade.calc_profit_percent(fee=0.003) == 0.0614782
|
||||
|
||||
|
||||
def test_clean_dry_run_db(default_conf):
|
||||
def test_clean_dry_run_db(default_conf, fee):
|
||||
init(default_conf, create_engine('sqlite://'))
|
||||
|
||||
# Simulate dry_run entries
|
||||
@ -312,7 +312,7 @@ def test_clean_dry_run_db(default_conf):
|
||||
pair='ETH/BTC',
|
||||
stake_amount=0.001,
|
||||
amount=123.0,
|
||||
fee=0.0025,
|
||||
fee=fee.return_value,
|
||||
open_rate=0.123,
|
||||
exchange='bittrex',
|
||||
open_order_id='dry_run_buy_12345'
|
||||
@ -323,7 +323,7 @@ def test_clean_dry_run_db(default_conf):
|
||||
pair='ETC/BTC',
|
||||
stake_amount=0.001,
|
||||
amount=123.0,
|
||||
fee=0.0025,
|
||||
fee=fee.return_value,
|
||||
open_rate=0.123,
|
||||
exchange='bittrex',
|
||||
open_order_id='dry_run_sell_12345'
|
||||
@ -335,7 +335,7 @@ def test_clean_dry_run_db(default_conf):
|
||||
pair='ETC/BTC',
|
||||
stake_amount=0.001,
|
||||
amount=123.0,
|
||||
fee=0.0025,
|
||||
fee=fee.return_value,
|
||||
open_rate=0.123,
|
||||
exchange='bittrex',
|
||||
open_order_id='prod_buy_12345'
|
||||
|
@ -25,6 +25,7 @@ def hyperopt_optimize_conf() -> dict:
|
||||
"ask_last_balance": 0.0
|
||||
},
|
||||
"exchange": {
|
||||
"name": "bittrex",
|
||||
"pair_whitelist": [
|
||||
"ETH/BTC",
|
||||
"LTC/BTC",
|
||||
|
Loading…
Reference in New Issue
Block a user