Move tests out of freqtrade module
This commit is contained in:
0
tests/rpc/__init__.py
Normal file
0
tests/rpc/__init__.py
Normal file
212
tests/rpc/test_fiat_convert.py
Normal file
212
tests/rpc/test_fiat_convert.py
Normal file
@@ -0,0 +1,212 @@
|
||||
# pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors,
|
||||
# pragma pylint: disable=protected-access, C0103
|
||||
|
||||
import time
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from freqtrade.rpc.fiat_convert import CryptoFiat, CryptoToFiatConverter
|
||||
from freqtrade.tests.conftest import log_has
|
||||
|
||||
|
||||
def test_pair_convertion_object():
|
||||
pair_convertion = CryptoFiat(
|
||||
crypto_symbol='btc',
|
||||
fiat_symbol='usd',
|
||||
price=12345.0
|
||||
)
|
||||
|
||||
# Check the cache duration is 6 hours
|
||||
assert pair_convertion.CACHE_DURATION == 6 * 60 * 60
|
||||
|
||||
# Check a regular usage
|
||||
assert pair_convertion.crypto_symbol == 'BTC'
|
||||
assert pair_convertion.fiat_symbol == 'USD'
|
||||
assert pair_convertion.price == 12345.0
|
||||
assert pair_convertion.is_expired() is False
|
||||
|
||||
# Update the expiration time (- 2 hours) and check the behavior
|
||||
pair_convertion._expiration = time.time() - 2 * 60 * 60
|
||||
assert pair_convertion.is_expired() is True
|
||||
|
||||
# Check set price behaviour
|
||||
time_reference = time.time() + pair_convertion.CACHE_DURATION
|
||||
pair_convertion.set_price(price=30000.123)
|
||||
assert pair_convertion.is_expired() is False
|
||||
assert pair_convertion._expiration >= time_reference
|
||||
assert pair_convertion.price == 30000.123
|
||||
|
||||
|
||||
def test_fiat_convert_is_supported(mocker):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
assert fiat_convert._is_supported_fiat(fiat='USD') is True
|
||||
assert fiat_convert._is_supported_fiat(fiat='usd') is True
|
||||
assert fiat_convert._is_supported_fiat(fiat='abc') is False
|
||||
assert fiat_convert._is_supported_fiat(fiat='ABC') is False
|
||||
|
||||
|
||||
def test_fiat_convert_add_pair(mocker):
|
||||
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
|
||||
pair_len = len(fiat_convert._pairs)
|
||||
assert pair_len == 0
|
||||
|
||||
fiat_convert._add_pair(crypto_symbol='btc', fiat_symbol='usd', price=12345.0)
|
||||
pair_len = len(fiat_convert._pairs)
|
||||
assert pair_len == 1
|
||||
assert fiat_convert._pairs[0].crypto_symbol == 'BTC'
|
||||
assert fiat_convert._pairs[0].fiat_symbol == 'USD'
|
||||
assert fiat_convert._pairs[0].price == 12345.0
|
||||
|
||||
fiat_convert._add_pair(crypto_symbol='btc', fiat_symbol='Eur', price=13000.2)
|
||||
pair_len = len(fiat_convert._pairs)
|
||||
assert pair_len == 2
|
||||
assert fiat_convert._pairs[1].crypto_symbol == 'BTC'
|
||||
assert fiat_convert._pairs[1].fiat_symbol == 'EUR'
|
||||
assert fiat_convert._pairs[1].price == 13000.2
|
||||
|
||||
|
||||
def test_fiat_convert_find_price(mocker):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
|
||||
with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'):
|
||||
fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='ABC')
|
||||
|
||||
assert fiat_convert.get_price(crypto_symbol='XRP', fiat_symbol='USD') == 0.0
|
||||
|
||||
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
|
||||
return_value=12345.0)
|
||||
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 12345.0
|
||||
assert fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='usd') == 12345.0
|
||||
|
||||
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
|
||||
return_value=13000.2)
|
||||
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='EUR') == 13000.2
|
||||
|
||||
|
||||
def test_fiat_convert_unsupported_crypto(mocker, caplog):
|
||||
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._cryptomap', return_value=[])
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
assert fiat_convert._find_price(crypto_symbol='CRYPTO_123', fiat_symbol='EUR') == 0.0
|
||||
assert log_has('unsupported crypto-symbol CRYPTO_123 - returning 0.0', caplog)
|
||||
|
||||
|
||||
def test_fiat_convert_get_price(mocker):
|
||||
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
|
||||
return_value=28000.0)
|
||||
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
|
||||
with pytest.raises(ValueError, match=r'The fiat US DOLLAR is not supported.'):
|
||||
fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='US Dollar')
|
||||
|
||||
# Check the value return by the method
|
||||
pair_len = len(fiat_convert._pairs)
|
||||
assert pair_len == 0
|
||||
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 28000.0
|
||||
assert fiat_convert._pairs[0].crypto_symbol == 'BTC'
|
||||
assert fiat_convert._pairs[0].fiat_symbol == 'USD'
|
||||
assert fiat_convert._pairs[0].price == 28000.0
|
||||
assert fiat_convert._pairs[0]._expiration != 0
|
||||
assert len(fiat_convert._pairs) == 1
|
||||
|
||||
# Verify the cached is used
|
||||
fiat_convert._pairs[0].price = 9867.543
|
||||
expiration = fiat_convert._pairs[0]._expiration
|
||||
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 9867.543
|
||||
assert fiat_convert._pairs[0]._expiration == expiration
|
||||
|
||||
# Verify the cache expiration
|
||||
expiration = time.time() - 2 * 60 * 60
|
||||
fiat_convert._pairs[0]._expiration = expiration
|
||||
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='USD') == 28000.0
|
||||
assert fiat_convert._pairs[0]._expiration is not expiration
|
||||
|
||||
|
||||
def test_fiat_convert_same_currencies(mocker):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
|
||||
assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='USD') == 1.0
|
||||
|
||||
|
||||
def test_fiat_convert_two_FIAT(mocker):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
|
||||
assert fiat_convert.get_price(crypto_symbol='USD', fiat_symbol='EUR') == 0.0
|
||||
|
||||
|
||||
def test_loadcryptomap(mocker):
|
||||
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
assert len(fiat_convert._cryptomap) == 2
|
||||
|
||||
assert fiat_convert._cryptomap["BTC"] == "1"
|
||||
|
||||
|
||||
def test_fiat_init_network_exception(mocker):
|
||||
# Because CryptoToFiatConverter is a Singleton we reset the listings
|
||||
listmock = MagicMock(side_effect=RequestException)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.rpc.fiat_convert.Market',
|
||||
listings=listmock,
|
||||
)
|
||||
# with pytest.raises(RequestEsxception):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
fiat_convert._cryptomap = {}
|
||||
fiat_convert._load_cryptomap()
|
||||
|
||||
length_cryptomap = len(fiat_convert._cryptomap)
|
||||
assert length_cryptomap == 0
|
||||
|
||||
|
||||
def test_fiat_convert_without_network(mocker):
|
||||
# Because CryptoToFiatConverter is a Singleton we reset the value of _coinmarketcap
|
||||
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
|
||||
cmc_temp = CryptoToFiatConverter._coinmarketcap
|
||||
CryptoToFiatConverter._coinmarketcap = None
|
||||
|
||||
assert fiat_convert._coinmarketcap is None
|
||||
assert fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='USD') == 0.0
|
||||
CryptoToFiatConverter._coinmarketcap = cmc_temp
|
||||
|
||||
|
||||
def test_fiat_invalid_response(mocker, caplog):
|
||||
# Because CryptoToFiatConverter is a Singleton we reset the listings
|
||||
listmock = MagicMock(return_value="{'novalidjson':DEADBEEFf}")
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.rpc.fiat_convert.Market',
|
||||
listings=listmock,
|
||||
)
|
||||
# with pytest.raises(RequestEsxception):
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
fiat_convert._cryptomap = {}
|
||||
fiat_convert._load_cryptomap()
|
||||
|
||||
length_cryptomap = len(fiat_convert._cryptomap)
|
||||
assert length_cryptomap == 0
|
||||
assert log_has('Could not load FIAT Cryptocurrency map for the following problem: TypeError',
|
||||
caplog)
|
||||
|
||||
|
||||
def test_convert_amount(mocker):
|
||||
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter.get_price', return_value=12345.0)
|
||||
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
result = fiat_convert.convert_amount(
|
||||
crypto_amount=1.23,
|
||||
crypto_symbol="BTC",
|
||||
fiat_symbol="USD"
|
||||
)
|
||||
assert result == 15184.35
|
||||
|
||||
result = fiat_convert.convert_amount(
|
||||
crypto_amount=1.23,
|
||||
crypto_symbol="BTC",
|
||||
fiat_symbol="BTC"
|
||||
)
|
||||
assert result == 1.23
|
813
tests/rpc/test_rpc.py
Normal file
813
tests/rpc/test_rpc.py
Normal file
@@ -0,0 +1,813 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
# pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments
|
||||
|
||||
from datetime import datetime
|
||||
from unittest.mock import ANY, MagicMock, PropertyMock
|
||||
|
||||
import pytest
|
||||
from numpy import isnan
|
||||
|
||||
from freqtrade import DependencyException, TemporaryError
|
||||
from freqtrade.edge import PairInfo
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.rpc import RPC, RPCException
|
||||
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
||||
from freqtrade.state import State
|
||||
from freqtrade.tests.conftest import patch_exchange, patch_get_signal
|
||||
|
||||
|
||||
# Functions for recurrent object patching
|
||||
def prec_satoshi(a, b) -> float:
|
||||
"""
|
||||
:return: True if A and B differs less than one satoshi.
|
||||
"""
|
||||
return abs(a - b) < 0.00000001
|
||||
|
||||
|
||||
# Unit tests
|
||||
def test_rpc_trade_status(default_conf, ticker, fee, markets, mocker) -> None:
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
_load_markets=MagicMock(return_value={}),
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
rpc = RPC(freqtradebot)
|
||||
|
||||
freqtradebot.state = State.RUNNING
|
||||
with pytest.raises(RPCException, match=r'.*no active trade*'):
|
||||
rpc._rpc_trade_status()
|
||||
|
||||
freqtradebot.create_trades()
|
||||
results = rpc._rpc_trade_status()
|
||||
assert {
|
||||
'trade_id': 1,
|
||||
'pair': 'ETH/BTC',
|
||||
'base_currency': 'BTC',
|
||||
'open_date': ANY,
|
||||
'open_date_hum': ANY,
|
||||
'close_date': None,
|
||||
'close_date_hum': None,
|
||||
'open_rate': 1.099e-05,
|
||||
'close_rate': None,
|
||||
'current_rate': 1.098e-05,
|
||||
'amount': 90.99181074,
|
||||
'stake_amount': 0.001,
|
||||
'close_profit': None,
|
||||
'current_profit': -0.59,
|
||||
'stop_loss': 0.0,
|
||||
'initial_stop_loss': 0.0,
|
||||
'initial_stop_loss_pct': None,
|
||||
'stop_loss_pct': None,
|
||||
'open_order': '(limit buy rem=0.00000000)'
|
||||
} == results[0]
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
|
||||
MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available")))
|
||||
# invalidate ticker cache
|
||||
rpc._freqtrade.exchange._cached_ticker = {}
|
||||
results = rpc._rpc_trade_status()
|
||||
assert isnan(results[0]['current_profit'])
|
||||
assert isnan(results[0]['current_rate'])
|
||||
assert {
|
||||
'trade_id': 1,
|
||||
'pair': 'ETH/BTC',
|
||||
'base_currency': 'BTC',
|
||||
'open_date': ANY,
|
||||
'open_date_hum': ANY,
|
||||
'close_date': None,
|
||||
'close_date_hum': None,
|
||||
'open_rate': 1.099e-05,
|
||||
'close_rate': None,
|
||||
'current_rate': ANY,
|
||||
'amount': 90.99181074,
|
||||
'stake_amount': 0.001,
|
||||
'close_profit': None,
|
||||
'current_profit': ANY,
|
||||
'stop_loss': 0.0,
|
||||
'initial_stop_loss': 0.0,
|
||||
'initial_stop_loss_pct': None,
|
||||
'stop_loss_pct': None,
|
||||
'open_order': '(limit buy rem=0.00000000)'
|
||||
} == results[0]
|
||||
|
||||
|
||||
def test_rpc_status_table(default_conf, ticker, fee, markets, mocker) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
rpc = RPC(freqtradebot)
|
||||
|
||||
freqtradebot.state = State.RUNNING
|
||||
with pytest.raises(RPCException, match=r'.*no active order*'):
|
||||
rpc._rpc_status_table()
|
||||
|
||||
freqtradebot.create_trades()
|
||||
result = rpc._rpc_status_table()
|
||||
assert 'instantly' in result['Since'].all()
|
||||
assert 'ETH/BTC' in result['Pair'].all()
|
||||
assert '-0.59%' in result['Profit'].all()
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
|
||||
MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available")))
|
||||
# invalidate ticker cache
|
||||
rpc._freqtrade.exchange._cached_ticker = {}
|
||||
result = rpc._rpc_status_table()
|
||||
assert 'instantly' in result['Since'].all()
|
||||
assert 'ETH/BTC' in result['Pair'].all()
|
||||
assert 'nan%' in result['Profit'].all()
|
||||
|
||||
|
||||
def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
||||
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
stake_currency = default_conf['stake_currency']
|
||||
fiat_display_currency = default_conf['fiat_display_currency']
|
||||
|
||||
rpc = RPC(freqtradebot)
|
||||
rpc._fiat_converter = CryptoToFiatConverter()
|
||||
# Create some test data
|
||||
freqtradebot.create_trades()
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
|
||||
# Simulate buy & sell
|
||||
trade.update(limit_buy_order)
|
||||
trade.update(limit_sell_order)
|
||||
trade.close_date = datetime.utcnow()
|
||||
trade.is_open = False
|
||||
|
||||
# Try valid data
|
||||
update.message.text = '/daily 2'
|
||||
days = rpc._rpc_daily_profit(7, stake_currency, fiat_display_currency)
|
||||
assert len(days) == 7
|
||||
for day in days:
|
||||
# [datetime.date(2018, 1, 11), '0.00000000 BTC', '0.000 USD']
|
||||
assert (day[1] == '0.00000000 BTC' or
|
||||
day[1] == '0.00006217 BTC')
|
||||
|
||||
assert (day[2] == '0.000 USD' or
|
||||
day[2] == '0.933 USD')
|
||||
# ensure first day is current date
|
||||
assert str(days[0][0]) == str(datetime.utcnow().date())
|
||||
|
||||
# Try invalid data
|
||||
with pytest.raises(RPCException, match=r'.*must be an integer greater than 0*'):
|
||||
rpc._rpc_daily_profit(0, stake_currency, fiat_display_currency)
|
||||
|
||||
|
||||
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
||||
limit_buy_order, limit_sell_order, markets, mocker) -> None:
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.rpc.fiat_convert.Market',
|
||||
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
||||
)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
stake_currency = default_conf['stake_currency']
|
||||
fiat_display_currency = default_conf['fiat_display_currency']
|
||||
|
||||
rpc = RPC(freqtradebot)
|
||||
rpc._fiat_converter = CryptoToFiatConverter()
|
||||
|
||||
with pytest.raises(RPCException, match=r'.*no closed trade*'):
|
||||
rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
|
||||
|
||||
# Create some test data
|
||||
freqtradebot.create_trades()
|
||||
trade = Trade.query.first()
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
trade.update(limit_buy_order)
|
||||
|
||||
# Update the ticker with a market going up
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=ticker_sell_up
|
||||
)
|
||||
trade.update(limit_sell_order)
|
||||
trade.close_date = datetime.utcnow()
|
||||
trade.is_open = False
|
||||
|
||||
freqtradebot.create_trades()
|
||||
trade = Trade.query.first()
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
trade.update(limit_buy_order)
|
||||
|
||||
# Update the ticker with a market going up
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=ticker_sell_up
|
||||
)
|
||||
trade.update(limit_sell_order)
|
||||
trade.close_date = datetime.utcnow()
|
||||
trade.is_open = False
|
||||
|
||||
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
|
||||
assert prec_satoshi(stats['profit_closed_coin'], 6.217e-05)
|
||||
assert prec_satoshi(stats['profit_closed_percent'], 6.2)
|
||||
assert prec_satoshi(stats['profit_closed_fiat'], 0.93255)
|
||||
assert prec_satoshi(stats['profit_all_coin'], 5.632e-05)
|
||||
assert prec_satoshi(stats['profit_all_percent'], 2.81)
|
||||
assert prec_satoshi(stats['profit_all_fiat'], 0.8448)
|
||||
assert stats['trade_count'] == 2
|
||||
assert stats['first_trade_date'] == 'just now'
|
||||
assert stats['latest_trade_date'] == 'just now'
|
||||
assert stats['avg_duration'] == '0:00:00'
|
||||
assert stats['best_pair'] == 'ETH/BTC'
|
||||
assert prec_satoshi(stats['best_rate'], 6.2)
|
||||
|
||||
# Test non-available pair
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_ticker',
|
||||
MagicMock(side_effect=DependencyException(f"Pair 'ETH/BTC' not available")))
|
||||
# invalidate ticker cache
|
||||
rpc._freqtrade.exchange._cached_ticker = {}
|
||||
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
|
||||
assert stats['trade_count'] == 2
|
||||
assert stats['first_trade_date'] == 'just now'
|
||||
assert stats['latest_trade_date'] == 'just now'
|
||||
assert stats['avg_duration'] == '0:00:00'
|
||||
assert stats['best_pair'] == 'ETH/BTC'
|
||||
assert prec_satoshi(stats['best_rate'], 6.2)
|
||||
assert isnan(stats['profit_all_coin'])
|
||||
|
||||
|
||||
# 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, fee, markets,
|
||||
ticker_sell_up, limit_buy_order, limit_sell_order):
|
||||
patch_exchange(mocker)
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.rpc.fiat_convert.Market',
|
||||
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
||||
)
|
||||
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
|
||||
return_value=15000.0)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
stake_currency = default_conf['stake_currency']
|
||||
fiat_display_currency = default_conf['fiat_display_currency']
|
||||
|
||||
rpc = RPC(freqtradebot)
|
||||
|
||||
# Create some test data
|
||||
freqtradebot.create_trades()
|
||||
trade = Trade.query.first()
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
trade.update(limit_buy_order)
|
||||
# Update the ticker with a market going up
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=ticker_sell_up,
|
||||
get_fee=fee
|
||||
)
|
||||
trade.update(limit_sell_order)
|
||||
trade.close_date = datetime.utcnow()
|
||||
trade.is_open = False
|
||||
|
||||
for trade in Trade.query.order_by(Trade.id).all():
|
||||
trade.open_rate = None
|
||||
|
||||
stats = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
|
||||
assert prec_satoshi(stats['profit_closed_coin'], 0)
|
||||
assert prec_satoshi(stats['profit_closed_percent'], 0)
|
||||
assert prec_satoshi(stats['profit_closed_fiat'], 0)
|
||||
assert prec_satoshi(stats['profit_all_coin'], 0)
|
||||
assert prec_satoshi(stats['profit_all_percent'], 0)
|
||||
assert prec_satoshi(stats['profit_all_fiat'], 0)
|
||||
assert stats['trade_count'] == 1
|
||||
assert stats['first_trade_date'] == 'just now'
|
||||
assert stats['latest_trade_date'] == 'just now'
|
||||
assert stats['avg_duration'] == '0:00:00'
|
||||
assert stats['best_pair'] == 'ETH/BTC'
|
||||
assert prec_satoshi(stats['best_rate'], 6.2)
|
||||
|
||||
|
||||
def test_rpc_balance_handle_error(default_conf, mocker):
|
||||
mock_balance = {
|
||||
'BTC': {
|
||||
'free': 10.0,
|
||||
'total': 12.0,
|
||||
'used': 2.0,
|
||||
},
|
||||
'ETH': {
|
||||
'free': 1.0,
|
||||
'total': 5.0,
|
||||
'used': 4.0,
|
||||
}
|
||||
}
|
||||
# ETH will be skipped due to mocked Error below
|
||||
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.rpc.fiat_convert.Market',
|
||||
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
||||
)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value=mock_balance),
|
||||
get_ticker=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx'))
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
rpc = RPC(freqtradebot)
|
||||
rpc._fiat_converter = CryptoToFiatConverter()
|
||||
|
||||
result = rpc._rpc_balance(default_conf['fiat_display_currency'])
|
||||
assert prec_satoshi(result['total'], 12)
|
||||
assert prec_satoshi(result['value'], 180000)
|
||||
assert 'USD' == result['symbol']
|
||||
assert result['currencies'] == [{
|
||||
'currency': 'BTC',
|
||||
'free': 10.0,
|
||||
'balance': 12.0,
|
||||
'used': 2.0,
|
||||
'est_btc': 12.0,
|
||||
}]
|
||||
assert result['total'] == 12.0
|
||||
|
||||
|
||||
def test_rpc_balance_handle(default_conf, mocker):
|
||||
mock_balance = {
|
||||
'BTC': {
|
||||
'free': 10.0,
|
||||
'total': 12.0,
|
||||
'used': 2.0,
|
||||
},
|
||||
'ETH': {
|
||||
'free': 1.0,
|
||||
'total': 5.0,
|
||||
'used': 4.0,
|
||||
},
|
||||
'PAX': {
|
||||
'free': 5.0,
|
||||
'total': 10.0,
|
||||
'used': 5.0,
|
||||
}
|
||||
}
|
||||
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.rpc.fiat_convert.Market',
|
||||
ticker=MagicMock(return_value={'price_usd': 15000.0}),
|
||||
)
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value=mock_balance),
|
||||
get_ticker=MagicMock(
|
||||
side_effect=lambda p, r: {'bid': 100} if p == "BTC/PAX" else {'bid': 0.01}),
|
||||
get_valid_pair_combination=MagicMock(
|
||||
side_effect=lambda a, b: f"{b}/{a}" if a == "PAX" else f"{a}/{b}")
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
rpc = RPC(freqtradebot)
|
||||
rpc._fiat_converter = CryptoToFiatConverter()
|
||||
|
||||
result = rpc._rpc_balance(default_conf['fiat_display_currency'])
|
||||
assert prec_satoshi(result['total'], 12.15)
|
||||
assert prec_satoshi(result['value'], 182250)
|
||||
assert 'USD' == result['symbol']
|
||||
assert result['currencies'] == [
|
||||
{'currency': 'BTC',
|
||||
'free': 10.0,
|
||||
'balance': 12.0,
|
||||
'used': 2.0,
|
||||
'est_btc': 12.0,
|
||||
},
|
||||
{'free': 1.0,
|
||||
'balance': 5.0,
|
||||
'currency': 'ETH',
|
||||
'est_btc': 0.05,
|
||||
'used': 4.0
|
||||
},
|
||||
{'free': 5.0,
|
||||
'balance': 10.0,
|
||||
'currency': 'PAX',
|
||||
'est_btc': 0.1,
|
||||
'used': 5.0}
|
||||
]
|
||||
assert result['total'] == 12.15
|
||||
|
||||
|
||||
def test_rpc_start(mocker, default_conf) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=MagicMock()
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
rpc = RPC(freqtradebot)
|
||||
freqtradebot.state = State.STOPPED
|
||||
|
||||
result = rpc._rpc_start()
|
||||
assert {'status': 'starting trader ...'} == result
|
||||
assert freqtradebot.state == State.RUNNING
|
||||
|
||||
result = rpc._rpc_start()
|
||||
assert {'status': 'already running'} == result
|
||||
assert freqtradebot.state == State.RUNNING
|
||||
|
||||
|
||||
def test_rpc_stop(mocker, default_conf) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=MagicMock()
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
rpc = RPC(freqtradebot)
|
||||
freqtradebot.state = State.RUNNING
|
||||
|
||||
result = rpc._rpc_stop()
|
||||
assert {'status': 'stopping trader ...'} == result
|
||||
assert freqtradebot.state == State.STOPPED
|
||||
|
||||
result = rpc._rpc_stop()
|
||||
|
||||
assert {'status': 'already stopped'} == result
|
||||
assert freqtradebot.state == State.STOPPED
|
||||
|
||||
|
||||
def test_rpc_stopbuy(mocker, default_conf) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=MagicMock()
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
rpc = RPC(freqtradebot)
|
||||
freqtradebot.state = State.RUNNING
|
||||
|
||||
assert freqtradebot.config['max_open_trades'] != 0
|
||||
result = rpc._rpc_stopbuy()
|
||||
assert {'status': 'No more buy will occur from now. Run /reload_conf to reset.'} == result
|
||||
assert freqtradebot.config['max_open_trades'] == 0
|
||||
|
||||
|
||||
def test_rpc_forcesell(default_conf, ticker, fee, mocker, markets) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
|
||||
cancel_order_mock = MagicMock()
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_ticker=ticker,
|
||||
cancel_order=cancel_order_mock,
|
||||
get_order=MagicMock(
|
||||
return_value={
|
||||
'status': 'closed',
|
||||
'type': 'limit',
|
||||
'side': 'buy'
|
||||
}
|
||||
),
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
rpc = RPC(freqtradebot)
|
||||
|
||||
freqtradebot.state = State.STOPPED
|
||||
with pytest.raises(RPCException, match=r'.*trader is not running*'):
|
||||
rpc._rpc_forcesell(None)
|
||||
|
||||
freqtradebot.state = State.RUNNING
|
||||
with pytest.raises(RPCException, match=r'.*invalid argument*'):
|
||||
rpc._rpc_forcesell(None)
|
||||
|
||||
msg = rpc._rpc_forcesell('all')
|
||||
assert msg == {'result': 'Created sell orders for all open trades.'}
|
||||
|
||||
freqtradebot.create_trades()
|
||||
msg = rpc._rpc_forcesell('all')
|
||||
assert msg == {'result': 'Created sell orders for all open trades.'}
|
||||
|
||||
msg = rpc._rpc_forcesell('1')
|
||||
assert msg == {'result': 'Created sell order for trade 1.'}
|
||||
|
||||
freqtradebot.state = State.STOPPED
|
||||
with pytest.raises(RPCException, match=r'.*trader is not running*'):
|
||||
rpc._rpc_forcesell(None)
|
||||
|
||||
with pytest.raises(RPCException, match=r'.*trader is not running*'):
|
||||
rpc._rpc_forcesell('all')
|
||||
|
||||
freqtradebot.state = State.RUNNING
|
||||
assert cancel_order_mock.call_count == 0
|
||||
# make an limit-buy open trade
|
||||
trade = Trade.query.filter(Trade.id == '1').first()
|
||||
filled_amount = trade.amount / 2
|
||||
mocker.patch(
|
||||
'freqtrade.exchange.Exchange.get_order',
|
||||
return_value={
|
||||
'status': 'open',
|
||||
'type': 'limit',
|
||||
'side': 'buy',
|
||||
'filled': filled_amount
|
||||
}
|
||||
)
|
||||
# check that the trade is called, which is done by ensuring exchange.cancel_order is called
|
||||
# and trade amount is updated
|
||||
rpc._rpc_forcesell('1')
|
||||
assert cancel_order_mock.call_count == 1
|
||||
assert trade.amount == filled_amount
|
||||
|
||||
freqtradebot.create_trades()
|
||||
trade = Trade.query.filter(Trade.id == '2').first()
|
||||
amount = trade.amount
|
||||
# make an limit-buy open trade, if there is no 'filled', don't sell it
|
||||
mocker.patch(
|
||||
'freqtrade.exchange.Exchange.get_order',
|
||||
return_value={
|
||||
'status': 'open',
|
||||
'type': 'limit',
|
||||
'side': 'buy',
|
||||
'filled': None
|
||||
}
|
||||
)
|
||||
# check that the trade is called, which is done by ensuring exchange.cancel_order is called
|
||||
msg = rpc._rpc_forcesell('2')
|
||||
assert msg == {'result': 'Created sell order for trade 2.'}
|
||||
assert cancel_order_mock.call_count == 2
|
||||
assert trade.amount == amount
|
||||
|
||||
freqtradebot.create_trades()
|
||||
# make an limit-sell open trade
|
||||
mocker.patch(
|
||||
'freqtrade.exchange.Exchange.get_order',
|
||||
return_value={
|
||||
'status': 'open',
|
||||
'type': 'limit',
|
||||
'side': 'sell'
|
||||
}
|
||||
)
|
||||
msg = rpc._rpc_forcesell('3')
|
||||
assert msg == {'result': 'Created sell order for trade 3.'}
|
||||
# status quo, no exchange calls
|
||||
assert cancel_order_mock.call_count == 2
|
||||
|
||||
|
||||
def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
|
||||
limit_sell_order, markets, mocker) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
rpc = RPC(freqtradebot)
|
||||
|
||||
# Create some test data
|
||||
freqtradebot.create_trades()
|
||||
trade = Trade.query.first()
|
||||
assert trade
|
||||
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
trade.update(limit_buy_order)
|
||||
|
||||
# Simulate fulfilled LIMIT_SELL order for trade
|
||||
trade.update(limit_sell_order)
|
||||
|
||||
trade.close_date = datetime.utcnow()
|
||||
trade.is_open = False
|
||||
res = rpc._rpc_performance()
|
||||
assert len(res) == 1
|
||||
assert res[0]['pair'] == 'ETH/BTC'
|
||||
assert res[0]['count'] == 1
|
||||
assert prec_satoshi(res[0]['profit'], 6.2)
|
||||
|
||||
|
||||
def test_rpc_count(mocker, default_conf, ticker, fee, markets) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
rpc = RPC(freqtradebot)
|
||||
|
||||
counts = rpc._rpc_count()
|
||||
assert counts["current"] == 0
|
||||
|
||||
# Create some test data
|
||||
freqtradebot.create_trades()
|
||||
counts = rpc._rpc_count()
|
||||
assert counts["current"] == 1
|
||||
|
||||
|
||||
def test_rpcforcebuy(mocker, default_conf, ticker, fee, markets, limit_buy_order) -> None:
|
||||
default_conf['forcebuy_enable'] = True
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
buy_mm = MagicMock(return_value={'id': limit_buy_order['id']})
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets),
|
||||
buy=buy_mm
|
||||
)
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
rpc = RPC(freqtradebot)
|
||||
pair = 'ETH/BTC'
|
||||
trade = rpc._rpc_forcebuy(pair, None)
|
||||
assert isinstance(trade, Trade)
|
||||
assert trade.pair == pair
|
||||
assert trade.open_rate == ticker()['ask']
|
||||
|
||||
# Test buy duplicate
|
||||
with pytest.raises(RPCException, match=r'position for ETH/BTC already open - id: 1'):
|
||||
rpc._rpc_forcebuy(pair, 0.0001)
|
||||
pair = 'XRP/BTC'
|
||||
trade = rpc._rpc_forcebuy(pair, 0.0001)
|
||||
assert isinstance(trade, Trade)
|
||||
assert trade.pair == pair
|
||||
assert trade.open_rate == 0.0001
|
||||
|
||||
# Test buy pair not with stakes
|
||||
with pytest.raises(RPCException, match=r'Wrong pair selected. Please pairs with stake.*'):
|
||||
rpc._rpc_forcebuy('XRP/ETH', 0.0001)
|
||||
pair = 'XRP/BTC'
|
||||
|
||||
# Test not buying
|
||||
default_conf['stake_amount'] = 0.0000001
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
rpc = RPC(freqtradebot)
|
||||
pair = 'TKN/BTC'
|
||||
trade = rpc._rpc_forcebuy(pair, None)
|
||||
assert trade is None
|
||||
|
||||
|
||||
def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
|
||||
default_conf['forcebuy_enable'] = True
|
||||
default_conf['initial_state'] = 'stopped'
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
rpc = RPC(freqtradebot)
|
||||
pair = 'ETH/BTC'
|
||||
with pytest.raises(RPCException, match=r'trader is not running'):
|
||||
rpc._rpc_forcebuy(pair, None)
|
||||
|
||||
|
||||
def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
rpc = RPC(freqtradebot)
|
||||
pair = 'ETH/BTC'
|
||||
with pytest.raises(RPCException, match=r'Forcebuy not enabled.'):
|
||||
rpc._rpc_forcebuy(pair, None)
|
||||
|
||||
|
||||
def test_rpc_whitelist(mocker, default_conf) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
rpc = RPC(freqtradebot)
|
||||
ret = rpc._rpc_whitelist()
|
||||
assert ret['method'] == 'StaticPairList'
|
||||
assert ret['whitelist'] == default_conf['exchange']['pair_whitelist']
|
||||
|
||||
|
||||
def test_rpc_whitelist_dynamic(mocker, default_conf) -> None:
|
||||
patch_exchange(mocker)
|
||||
default_conf['pairlist'] = {'method': 'VolumePairList',
|
||||
'config': {'number_assets': 4}
|
||||
}
|
||||
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
rpc = RPC(freqtradebot)
|
||||
ret = rpc._rpc_whitelist()
|
||||
assert ret['method'] == 'VolumePairList'
|
||||
assert ret['length'] == 4
|
||||
assert ret['whitelist'] == default_conf['exchange']['pair_whitelist']
|
||||
|
||||
|
||||
def test_rpc_blacklist(mocker, default_conf) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
rpc = RPC(freqtradebot)
|
||||
ret = rpc._rpc_blacklist(None)
|
||||
assert ret['method'] == 'StaticPairList'
|
||||
assert len(ret['blacklist']) == 2
|
||||
assert ret['blacklist'] == default_conf['exchange']['pair_blacklist']
|
||||
assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC']
|
||||
|
||||
ret = rpc._rpc_blacklist(["ETH/BTC"])
|
||||
assert ret['method'] == 'StaticPairList'
|
||||
assert len(ret['blacklist']) == 3
|
||||
assert ret['blacklist'] == default_conf['exchange']['pair_blacklist']
|
||||
assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC']
|
||||
|
||||
|
||||
def test_rpc_edge_disabled(mocker, default_conf) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
freqtradebot = FreqtradeBot(default_conf)
|
||||
rpc = RPC(freqtradebot)
|
||||
with pytest.raises(RPCException, match=r'Edge is not enabled.'):
|
||||
rpc._rpc_edge()
|
||||
|
||||
|
||||
def test_rpc_edge_enabled(mocker, edge_conf) -> None:
|
||||
patch_exchange(mocker)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||
return_value={
|
||||
'E/F': PairInfo(-0.02, 0.66, 3.71, 0.50, 1.71, 10, 60),
|
||||
}
|
||||
))
|
||||
freqtradebot = FreqtradeBot(edge_conf)
|
||||
|
||||
rpc = RPC(freqtradebot)
|
||||
ret = rpc._rpc_edge()
|
||||
|
||||
assert len(ret) == 1
|
||||
assert ret[0]['Pair'] == 'E/F'
|
||||
assert ret[0]['Winrate'] == 0.66
|
||||
assert ret[0]['Expectancy'] == 1.71
|
||||
assert ret[0]['Stoploss'] == -0.02
|
556
tests/rpc/test_rpc_apiserver.py
Normal file
556
tests/rpc/test_rpc_apiserver.py
Normal file
@@ -0,0 +1,556 @@
|
||||
"""
|
||||
Unit test file for rpc/api_server.py
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
from unittest.mock import ANY, MagicMock, PropertyMock
|
||||
|
||||
import pytest
|
||||
from flask import Flask
|
||||
from requests.auth import _basic_auth_str
|
||||
|
||||
from freqtrade.__init__ import __version__
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.rpc.api_server import BASE_URI, ApiServer
|
||||
from freqtrade.state import State
|
||||
from freqtrade.tests.conftest import (get_patched_freqtradebot, log_has,
|
||||
patch_get_signal)
|
||||
|
||||
|
||||
_TEST_USER = "FreqTrader"
|
||||
_TEST_PASS = "SuperSecurePassword1!"
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def botclient(default_conf, mocker):
|
||||
default_conf.update({"api_server": {"enabled": True,
|
||||
"listen_ip_address": "127.0.0.1",
|
||||
"listen_port": "8080",
|
||||
"username": _TEST_USER,
|
||||
"password": _TEST_PASS,
|
||||
}})
|
||||
|
||||
ftbot = get_patched_freqtradebot(mocker, default_conf)
|
||||
mocker.patch('freqtrade.rpc.api_server.ApiServer.run', MagicMock())
|
||||
apiserver = ApiServer(ftbot)
|
||||
yield ftbot, apiserver.app.test_client()
|
||||
# Cleanup ... ?
|
||||
|
||||
|
||||
def client_post(client, url, data={}):
|
||||
return client.post(url,
|
||||
content_type="application/json",
|
||||
data=data,
|
||||
headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS)})
|
||||
|
||||
|
||||
def client_get(client, url):
|
||||
return client.get(url, headers={'Authorization': _basic_auth_str(_TEST_USER, _TEST_PASS)})
|
||||
|
||||
|
||||
def assert_response(response, expected_code=200):
|
||||
assert response.status_code == expected_code
|
||||
assert response.content_type == "application/json"
|
||||
|
||||
|
||||
def test_api_not_found(botclient):
|
||||
ftbot, client = botclient
|
||||
|
||||
rc = client_post(client, f"{BASE_URI}/invalid_url")
|
||||
assert_response(rc, 404)
|
||||
assert rc.json == {"status": "error",
|
||||
"reason": f"There's no API call for http://localhost{BASE_URI}/invalid_url.",
|
||||
"code": 404
|
||||
}
|
||||
|
||||
|
||||
def test_api_unauthorized(botclient):
|
||||
ftbot, client = botclient
|
||||
# Don't send user/pass information
|
||||
rc = client.get(f"{BASE_URI}/version")
|
||||
assert_response(rc, 401)
|
||||
assert rc.json == {'error': 'Unauthorized'}
|
||||
|
||||
# Change only username
|
||||
ftbot.config['api_server']['username'] = "Ftrader"
|
||||
rc = client_get(client, f"{BASE_URI}/version")
|
||||
assert_response(rc, 401)
|
||||
assert rc.json == {'error': 'Unauthorized'}
|
||||
|
||||
# Change only password
|
||||
ftbot.config['api_server']['username'] = _TEST_USER
|
||||
ftbot.config['api_server']['password'] = "WrongPassword"
|
||||
rc = client_get(client, f"{BASE_URI}/version")
|
||||
assert_response(rc, 401)
|
||||
assert rc.json == {'error': 'Unauthorized'}
|
||||
|
||||
ftbot.config['api_server']['username'] = "Ftrader"
|
||||
ftbot.config['api_server']['password'] = "WrongPassword"
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/version")
|
||||
assert_response(rc, 401)
|
||||
assert rc.json == {'error': 'Unauthorized'}
|
||||
|
||||
|
||||
def test_api_stop_workflow(botclient):
|
||||
ftbot, client = botclient
|
||||
assert ftbot.state == State.RUNNING
|
||||
rc = client_post(client, f"{BASE_URI}/stop")
|
||||
assert_response(rc)
|
||||
assert rc.json == {'status': 'stopping trader ...'}
|
||||
assert ftbot.state == State.STOPPED
|
||||
|
||||
# Stop bot again
|
||||
rc = client_post(client, f"{BASE_URI}/stop")
|
||||
assert_response(rc)
|
||||
assert rc.json == {'status': 'already stopped'}
|
||||
|
||||
# Start bot
|
||||
rc = client_post(client, f"{BASE_URI}/start")
|
||||
assert_response(rc)
|
||||
assert rc.json == {'status': 'starting trader ...'}
|
||||
assert ftbot.state == State.RUNNING
|
||||
|
||||
# Call start again
|
||||
rc = client_post(client, f"{BASE_URI}/start")
|
||||
assert_response(rc)
|
||||
assert rc.json == {'status': 'already running'}
|
||||
|
||||
|
||||
def test_api__init__(default_conf, mocker):
|
||||
"""
|
||||
Test __init__() method
|
||||
"""
|
||||
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
|
||||
mocker.patch('freqtrade.rpc.api_server.ApiServer.run', MagicMock())
|
||||
|
||||
apiserver = ApiServer(get_patched_freqtradebot(mocker, default_conf))
|
||||
assert apiserver._config == default_conf
|
||||
|
||||
|
||||
def test_api_run(default_conf, mocker, caplog):
|
||||
default_conf.update({"api_server": {"enabled": True,
|
||||
"listen_ip_address": "127.0.0.1",
|
||||
"listen_port": "8080"}})
|
||||
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
|
||||
mocker.patch('freqtrade.rpc.api_server.threading.Thread', MagicMock())
|
||||
|
||||
server_mock = MagicMock()
|
||||
mocker.patch('freqtrade.rpc.api_server.make_server', server_mock)
|
||||
|
||||
apiserver = ApiServer(get_patched_freqtradebot(mocker, default_conf))
|
||||
|
||||
assert apiserver._config == default_conf
|
||||
apiserver.run()
|
||||
assert server_mock.call_count == 1
|
||||
assert server_mock.call_args_list[0][0][0] == "127.0.0.1"
|
||||
assert server_mock.call_args_list[0][0][1] == "8080"
|
||||
assert isinstance(server_mock.call_args_list[0][0][2], Flask)
|
||||
assert hasattr(apiserver, "srv")
|
||||
|
||||
assert log_has("Starting HTTP Server at 127.0.0.1:8080", caplog)
|
||||
assert log_has("Starting Local Rest Server.", caplog)
|
||||
|
||||
# Test binding to public
|
||||
caplog.clear()
|
||||
server_mock.reset_mock()
|
||||
apiserver._config.update({"api_server": {"enabled": True,
|
||||
"listen_ip_address": "0.0.0.0",
|
||||
"listen_port": "8089",
|
||||
"password": "",
|
||||
}})
|
||||
apiserver.run()
|
||||
|
||||
assert server_mock.call_count == 1
|
||||
assert server_mock.call_args_list[0][0][0] == "0.0.0.0"
|
||||
assert server_mock.call_args_list[0][0][1] == "8089"
|
||||
assert isinstance(server_mock.call_args_list[0][0][2], Flask)
|
||||
assert log_has("Starting HTTP Server at 0.0.0.0:8089", caplog)
|
||||
assert log_has("Starting Local Rest Server.", caplog)
|
||||
assert log_has("SECURITY WARNING - Local Rest Server listening to external connections",
|
||||
caplog)
|
||||
assert log_has("SECURITY WARNING - This is insecure please set to your loopback,"
|
||||
"e.g 127.0.0.1 in config.json", caplog)
|
||||
assert log_has("SECURITY WARNING - No password for local REST Server defined. "
|
||||
"Please make sure that this is intentional!", caplog)
|
||||
|
||||
# Test crashing flask
|
||||
caplog.clear()
|
||||
mocker.patch('freqtrade.rpc.api_server.make_server', MagicMock(side_effect=Exception))
|
||||
apiserver.run()
|
||||
assert log_has("Api server failed to start.", caplog)
|
||||
|
||||
|
||||
def test_api_cleanup(default_conf, mocker, caplog):
|
||||
default_conf.update({"api_server": {"enabled": True,
|
||||
"listen_ip_address": "127.0.0.1",
|
||||
"listen_port": "8080"}})
|
||||
mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
|
||||
mocker.patch('freqtrade.rpc.api_server.threading.Thread', MagicMock())
|
||||
mocker.patch('freqtrade.rpc.api_server.make_server', MagicMock())
|
||||
|
||||
apiserver = ApiServer(get_patched_freqtradebot(mocker, default_conf))
|
||||
apiserver.run()
|
||||
stop_mock = MagicMock()
|
||||
stop_mock.shutdown = MagicMock()
|
||||
apiserver.srv = stop_mock
|
||||
|
||||
apiserver.cleanup()
|
||||
assert stop_mock.shutdown.call_count == 1
|
||||
assert log_has("Stopping API Server", caplog)
|
||||
|
||||
|
||||
def test_api_reloadconf(botclient):
|
||||
ftbot, client = botclient
|
||||
|
||||
rc = client_post(client, f"{BASE_URI}/reload_conf")
|
||||
assert_response(rc)
|
||||
assert rc.json == {'status': 'reloading config ...'}
|
||||
assert ftbot.state == State.RELOAD_CONF
|
||||
|
||||
|
||||
def test_api_stopbuy(botclient):
|
||||
ftbot, client = botclient
|
||||
assert ftbot.config['max_open_trades'] != 0
|
||||
|
||||
rc = client_post(client, f"{BASE_URI}/stopbuy")
|
||||
assert_response(rc)
|
||||
assert rc.json == {'status': 'No more buy will occur from now. Run /reload_conf to reset.'}
|
||||
assert ftbot.config['max_open_trades'] == 0
|
||||
|
||||
|
||||
def test_api_balance(botclient, mocker, rpc_balance):
|
||||
ftbot, client = botclient
|
||||
|
||||
def mock_ticker(symbol, refresh):
|
||||
if symbol == 'BTC/USDT':
|
||||
return {
|
||||
'bid': 10000.00,
|
||||
'ask': 10000.00,
|
||||
'last': 10000.00,
|
||||
}
|
||||
elif symbol == 'XRP/BTC':
|
||||
return {
|
||||
'bid': 0.00001,
|
||||
'ask': 0.00001,
|
||||
'last': 0.00001,
|
||||
}
|
||||
return {
|
||||
'bid': 0.1,
|
||||
'ask': 0.1,
|
||||
'last': 0.1,
|
||||
}
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_balances', return_value=rpc_balance)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_ticker', side_effect=mock_ticker)
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_valid_pair_combination',
|
||||
side_effect=lambda a, b: f"{a}/{b}")
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/balance")
|
||||
assert_response(rc)
|
||||
assert "currencies" in rc.json
|
||||
assert len(rc.json["currencies"]) == 5
|
||||
assert rc.json['currencies'][0] == {
|
||||
'currency': 'BTC',
|
||||
'free': 12.0,
|
||||
'balance': 12.0,
|
||||
'used': 0.0,
|
||||
'est_btc': 12.0,
|
||||
}
|
||||
|
||||
|
||||
def test_api_count(botclient, mocker, ticker, fee, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
rc = client_get(client, f"{BASE_URI}/count")
|
||||
assert_response(rc)
|
||||
|
||||
assert rc.json["current"] == 0
|
||||
assert rc.json["max"] == 1.0
|
||||
|
||||
# Create some test data
|
||||
ftbot.create_trades()
|
||||
rc = client_get(client, f"{BASE_URI}/count")
|
||||
assert_response(rc)
|
||||
assert rc.json["current"] == 1.0
|
||||
assert rc.json["max"] == 1.0
|
||||
|
||||
|
||||
def test_api_daily(botclient, mocker, ticker, fee, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
rc = client_get(client, f"{BASE_URI}/daily")
|
||||
assert_response(rc)
|
||||
assert len(rc.json) == 7
|
||||
assert rc.json[0][0] == str(datetime.utcnow().date())
|
||||
|
||||
|
||||
def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
rc = client_get(client, f"{BASE_URI}/edge")
|
||||
assert_response(rc, 502)
|
||||
assert rc.json == {"error": "Error querying _edge: Edge is not enabled."}
|
||||
|
||||
|
||||
def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, limit_sell_order):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/profit")
|
||||
assert_response(rc, 502)
|
||||
assert len(rc.json) == 1
|
||||
assert rc.json == {"error": "Error querying _profit: no closed trade"}
|
||||
|
||||
ftbot.create_trades()
|
||||
trade = Trade.query.first()
|
||||
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
trade.update(limit_buy_order)
|
||||
rc = client_get(client, f"{BASE_URI}/profit")
|
||||
assert_response(rc, 502)
|
||||
assert rc.json == {"error": "Error querying _profit: no closed trade"}
|
||||
|
||||
trade.update(limit_sell_order)
|
||||
|
||||
trade.close_date = datetime.utcnow()
|
||||
trade.is_open = False
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/profit")
|
||||
assert_response(rc)
|
||||
assert rc.json == {'avg_duration': '0:00:00',
|
||||
'best_pair': 'ETH/BTC',
|
||||
'best_rate': 6.2,
|
||||
'first_trade_date': 'just now',
|
||||
'latest_trade_date': 'just now',
|
||||
'profit_all_coin': 6.217e-05,
|
||||
'profit_all_fiat': 0,
|
||||
'profit_all_percent': 6.2,
|
||||
'profit_closed_coin': 6.217e-05,
|
||||
'profit_closed_fiat': 0,
|
||||
'profit_closed_percent': 6.2,
|
||||
'trade_count': 1
|
||||
}
|
||||
|
||||
|
||||
def test_api_performance(botclient, mocker, ticker, fee):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
|
||||
trade = Trade(
|
||||
pair='LTC/ETH',
|
||||
amount=1,
|
||||
exchange='binance',
|
||||
stake_amount=1,
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456",
|
||||
is_open=False,
|
||||
fee_close=fee.return_value,
|
||||
fee_open=fee.return_value,
|
||||
close_rate=0.265441,
|
||||
|
||||
)
|
||||
trade.close_profit = trade.calc_profit_percent()
|
||||
Trade.session.add(trade)
|
||||
|
||||
trade = Trade(
|
||||
pair='XRP/ETH',
|
||||
amount=5,
|
||||
stake_amount=1,
|
||||
exchange='binance',
|
||||
open_rate=0.412,
|
||||
open_order_id="123456",
|
||||
is_open=False,
|
||||
fee_close=fee.return_value,
|
||||
fee_open=fee.return_value,
|
||||
close_rate=0.391
|
||||
)
|
||||
trade.close_profit = trade.calc_profit_percent()
|
||||
Trade.session.add(trade)
|
||||
Trade.session.flush()
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/performance")
|
||||
assert_response(rc)
|
||||
assert len(rc.json) == 2
|
||||
assert rc.json == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61},
|
||||
{'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57}]
|
||||
|
||||
|
||||
def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/status")
|
||||
assert_response(rc, 502)
|
||||
assert rc.json == {'error': 'Error querying _status: no active trade'}
|
||||
|
||||
ftbot.create_trades()
|
||||
rc = client_get(client, f"{BASE_URI}/status")
|
||||
assert_response(rc)
|
||||
assert len(rc.json) == 1
|
||||
assert rc.json == [{'amount': 90.99181074,
|
||||
'base_currency': 'BTC',
|
||||
'close_date': None,
|
||||
'close_date_hum': None,
|
||||
'close_profit': None,
|
||||
'close_rate': None,
|
||||
'current_profit': -0.59,
|
||||
'current_rate': 1.098e-05,
|
||||
'initial_stop_loss': 0.0,
|
||||
'initial_stop_loss_pct': None,
|
||||
'open_date': ANY,
|
||||
'open_date_hum': 'just now',
|
||||
'open_order': '(limit buy rem=0.00000000)',
|
||||
'open_rate': 1.099e-05,
|
||||
'pair': 'ETH/BTC',
|
||||
'stake_amount': 0.001,
|
||||
'stop_loss': 0.0,
|
||||
'stop_loss_pct': None,
|
||||
'trade_id': 1}]
|
||||
|
||||
|
||||
def test_api_version(botclient):
|
||||
ftbot, client = botclient
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/version")
|
||||
assert_response(rc)
|
||||
assert rc.json == {"version": __version__}
|
||||
|
||||
|
||||
def test_api_blacklist(botclient, mocker):
|
||||
ftbot, client = botclient
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/blacklist")
|
||||
assert_response(rc)
|
||||
assert rc.json == {"blacklist": ["DOGE/BTC", "HOT/BTC"],
|
||||
"length": 2,
|
||||
"method": "StaticPairList"}
|
||||
|
||||
# Add ETH/BTC to blacklist
|
||||
rc = client_post(client, f"{BASE_URI}/blacklist",
|
||||
data='{"blacklist": ["ETH/BTC"]}')
|
||||
assert_response(rc)
|
||||
assert rc.json == {"blacklist": ["DOGE/BTC", "HOT/BTC", "ETH/BTC"],
|
||||
"length": 3,
|
||||
"method": "StaticPairList"}
|
||||
|
||||
|
||||
def test_api_whitelist(botclient):
|
||||
ftbot, client = botclient
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/whitelist")
|
||||
assert_response(rc)
|
||||
assert rc.json == {"whitelist": ['ETH/BTC', 'LTC/BTC', 'XRP/BTC', 'NEO/BTC'],
|
||||
"length": 4,
|
||||
"method": "StaticPairList"}
|
||||
|
||||
|
||||
def test_api_forcebuy(botclient, mocker, fee):
|
||||
ftbot, client = botclient
|
||||
|
||||
rc = client_post(client, f"{BASE_URI}/forcebuy",
|
||||
data='{"pair": "ETH/BTC"}')
|
||||
assert_response(rc, 502)
|
||||
assert rc.json == {"error": "Error querying _forcebuy: Forcebuy not enabled."}
|
||||
|
||||
# enable forcebuy
|
||||
ftbot.config["forcebuy_enable"] = True
|
||||
|
||||
fbuy_mock = MagicMock(return_value=None)
|
||||
mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock)
|
||||
rc = client_post(client, f"{BASE_URI}/forcebuy",
|
||||
data='{"pair": "ETH/BTC"}')
|
||||
assert_response(rc)
|
||||
assert rc.json == {"status": "Error buying pair ETH/BTC."}
|
||||
|
||||
# Test creating trae
|
||||
fbuy_mock = MagicMock(return_value=Trade(
|
||||
pair='ETH/ETH',
|
||||
amount=1,
|
||||
exchange='bittrex',
|
||||
stake_amount=1,
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456",
|
||||
open_date=datetime.utcnow(),
|
||||
is_open=False,
|
||||
fee_close=fee.return_value,
|
||||
fee_open=fee.return_value,
|
||||
close_rate=0.265441,
|
||||
))
|
||||
mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock)
|
||||
|
||||
rc = client_post(client, f"{BASE_URI}/forcebuy",
|
||||
data='{"pair": "ETH/BTC"}')
|
||||
assert_response(rc)
|
||||
assert rc.json == {'amount': 1,
|
||||
'close_date': None,
|
||||
'close_date_hum': None,
|
||||
'close_rate': 0.265441,
|
||||
'initial_stop_loss': None,
|
||||
'initial_stop_loss_pct': None,
|
||||
'open_date': ANY,
|
||||
'open_date_hum': 'just now',
|
||||
'open_rate': 0.245441,
|
||||
'pair': 'ETH/ETH',
|
||||
'stake_amount': 1,
|
||||
'stop_loss': None,
|
||||
'stop_loss_pct': None,
|
||||
'trade_id': None}
|
||||
|
||||
|
||||
def test_api_forcesell(botclient, mocker, ticker, fee, markets):
|
||||
ftbot, client = botclient
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
get_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
)
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
|
||||
rc = client_post(client, f"{BASE_URI}/forcesell",
|
||||
data='{"tradeid": "1"}')
|
||||
assert_response(rc, 502)
|
||||
assert rc.json == {"error": "Error querying _forcesell: invalid argument"}
|
||||
|
||||
ftbot.create_trades()
|
||||
|
||||
rc = client_post(client, f"{BASE_URI}/forcesell",
|
||||
data='{"tradeid": "1"}')
|
||||
assert_response(rc)
|
||||
assert rc.json == {'result': 'Created sell order for trade 1.'}
|
182
tests/rpc/test_rpc_manager.py
Normal file
182
tests/rpc/test_rpc_manager.py
Normal file
@@ -0,0 +1,182 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
|
||||
import logging
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from freqtrade.rpc import RPCMessageType, RPCManager
|
||||
from freqtrade.tests.conftest import log_has, get_patched_freqtradebot
|
||||
|
||||
|
||||
def test__init__(mocker, default_conf) -> None:
|
||||
default_conf['telegram']['enabled'] = False
|
||||
|
||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
||||
assert rpc_manager.registered_modules == []
|
||||
|
||||
|
||||
def test_init_telegram_disabled(mocker, default_conf, caplog) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
default_conf['telegram']['enabled'] = False
|
||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
||||
|
||||
assert not log_has('Enabling rpc.telegram ...', caplog)
|
||||
assert rpc_manager.registered_modules == []
|
||||
|
||||
|
||||
def test_init_telegram_enabled(mocker, default_conf, caplog) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
||||
|
||||
assert log_has('Enabling rpc.telegram ...', caplog)
|
||||
len_modules = len(rpc_manager.registered_modules)
|
||||
assert len_modules == 1
|
||||
assert 'telegram' in [mod.name for mod in rpc_manager.registered_modules]
|
||||
|
||||
|
||||
def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock())
|
||||
default_conf['telegram']['enabled'] = False
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc_manager = RPCManager(freqtradebot)
|
||||
rpc_manager.cleanup()
|
||||
|
||||
assert not log_has('Cleaning up rpc.telegram ...', caplog)
|
||||
assert telegram_mock.call_count == 0
|
||||
|
||||
|
||||
def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock())
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc_manager = RPCManager(freqtradebot)
|
||||
|
||||
# Check we have Telegram as a registered modules
|
||||
assert 'telegram' in [mod.name for mod in rpc_manager.registered_modules]
|
||||
|
||||
rpc_manager.cleanup()
|
||||
assert log_has('Cleaning up rpc.telegram ...', caplog)
|
||||
assert 'telegram' not in [mod.name for mod in rpc_manager.registered_modules]
|
||||
assert telegram_mock.call_count == 1
|
||||
|
||||
|
||||
def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
|
||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
||||
default_conf['telegram']['enabled'] = False
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc_manager = RPCManager(freqtradebot)
|
||||
rpc_manager.send_msg({
|
||||
'type': RPCMessageType.STATUS_NOTIFICATION,
|
||||
'status': 'test'
|
||||
})
|
||||
|
||||
assert log_has("Sending rpc message: {'type': status, 'status': 'test'}", caplog)
|
||||
assert telegram_mock.call_count == 0
|
||||
|
||||
|
||||
def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None:
|
||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc_manager = RPCManager(freqtradebot)
|
||||
rpc_manager.send_msg({
|
||||
'type': RPCMessageType.STATUS_NOTIFICATION,
|
||||
'status': 'test'
|
||||
})
|
||||
|
||||
assert log_has("Sending rpc message: {'type': status, 'status': 'test'}", caplog)
|
||||
assert telegram_mock.call_count == 1
|
||||
|
||||
|
||||
def test_init_webhook_disabled(mocker, default_conf, caplog) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
default_conf['telegram']['enabled'] = False
|
||||
default_conf['webhook'] = {'enabled': False}
|
||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
||||
|
||||
assert not log_has('Enabling rpc.webhook ...', caplog)
|
||||
assert rpc_manager.registered_modules == []
|
||||
|
||||
|
||||
def test_init_webhook_enabled(mocker, default_conf, caplog) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
default_conf['telegram']['enabled'] = False
|
||||
default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"}
|
||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
||||
|
||||
assert log_has('Enabling rpc.webhook ...', caplog)
|
||||
assert len(rpc_manager.registered_modules) == 1
|
||||
assert 'webhook' in [mod.name for mod in rpc_manager.registered_modules]
|
||||
|
||||
|
||||
def test_send_msg_webhook_CustomMessagetype(mocker, default_conf, caplog) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
default_conf['telegram']['enabled'] = False
|
||||
default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"}
|
||||
mocker.patch('freqtrade.rpc.webhook.Webhook.send_msg',
|
||||
MagicMock(side_effect=NotImplementedError))
|
||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
||||
|
||||
assert 'webhook' in [mod.name for mod in rpc_manager.registered_modules]
|
||||
rpc_manager.send_msg({'type': RPCMessageType.CUSTOM_NOTIFICATION,
|
||||
'status': 'TestMessage'})
|
||||
assert log_has(
|
||||
"Message type RPCMessageType.CUSTOM_NOTIFICATION not implemented by handler webhook.",
|
||||
caplog)
|
||||
|
||||
|
||||
def test_startupmessages_telegram_enabled(mocker, default_conf, caplog) -> None:
|
||||
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
|
||||
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc_manager = RPCManager(freqtradebot)
|
||||
rpc_manager.startup_messages(default_conf, freqtradebot.pairlists)
|
||||
|
||||
assert telegram_mock.call_count == 3
|
||||
assert "*Exchange:* `bittrex`" in telegram_mock.call_args_list[1][0][0]['status']
|
||||
|
||||
telegram_mock.reset_mock()
|
||||
default_conf['dry_run'] = True
|
||||
default_conf['whitelist'] = {'method': 'VolumePairList',
|
||||
'config': {'number_assets': 20}
|
||||
}
|
||||
|
||||
rpc_manager.startup_messages(default_conf, freqtradebot.pairlists)
|
||||
assert telegram_mock.call_count == 3
|
||||
assert "Dry run is enabled." in telegram_mock.call_args_list[0][0][0]['status']
|
||||
|
||||
|
||||
def test_init_apiserver_disabled(mocker, default_conf, caplog) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
run_mock = MagicMock()
|
||||
mocker.patch('freqtrade.rpc.api_server.ApiServer.run', run_mock)
|
||||
default_conf['telegram']['enabled'] = False
|
||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
||||
|
||||
assert not log_has('Enabling rpc.api_server', caplog)
|
||||
assert rpc_manager.registered_modules == []
|
||||
assert run_mock.call_count == 0
|
||||
|
||||
|
||||
def test_init_apiserver_enabled(mocker, default_conf, caplog) -> None:
|
||||
caplog.set_level(logging.DEBUG)
|
||||
run_mock = MagicMock()
|
||||
mocker.patch('freqtrade.rpc.api_server.ApiServer.run', run_mock)
|
||||
|
||||
default_conf["telegram"]["enabled"] = False
|
||||
default_conf["api_server"] = {"enabled": True,
|
||||
"listen_ip_address": "127.0.0.1",
|
||||
"listen_port": "8080"}
|
||||
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
|
||||
|
||||
assert log_has('Enabling rpc.api_server', caplog)
|
||||
assert len(rpc_manager.registered_modules) == 1
|
||||
assert 'apiserver' in [mod.name for mod in rpc_manager.registered_modules]
|
||||
assert run_mock.call_count == 1
|
1447
tests/rpc/test_rpc_telegram.py
Normal file
1447
tests/rpc/test_rpc_telegram.py
Normal file
File diff suppressed because it is too large
Load Diff
170
tests/rpc/test_rpc_webhook.py
Normal file
170
tests/rpc/test_rpc_webhook.py
Normal file
@@ -0,0 +1,170 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103, protected-access
|
||||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from requests import RequestException
|
||||
|
||||
from freqtrade.rpc import RPCMessageType
|
||||
from freqtrade.rpc.webhook import Webhook
|
||||
from freqtrade.strategy.interface import SellType
|
||||
from freqtrade.tests.conftest import get_patched_freqtradebot, log_has
|
||||
|
||||
|
||||
def get_webhook_dict() -> dict:
|
||||
return {
|
||||
"enabled": True,
|
||||
"url": "https://maker.ifttt.com/trigger/freqtrade_test/with/key/c764udvJ5jfSlswVRukZZ2/",
|
||||
"webhookbuy": {
|
||||
"value1": "Buying {pair}",
|
||||
"value2": "limit {limit:8f}",
|
||||
"value3": "{stake_amount:8f} {stake_currency}"
|
||||
},
|
||||
"webhooksell": {
|
||||
"value1": "Selling {pair}",
|
||||
"value2": "limit {limit:8f}",
|
||||
"value3": "profit: {profit_amount:8f} {stake_currency}"
|
||||
},
|
||||
"webhookstatus": {
|
||||
"value1": "Status: {status}",
|
||||
"value2": "",
|
||||
"value3": ""
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
def test__init__(mocker, default_conf):
|
||||
default_conf['webhook'] = {'enabled': True, 'url': "https://DEADBEEF.com"}
|
||||
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
|
||||
assert webhook._config == default_conf
|
||||
|
||||
|
||||
def test_send_msg(default_conf, mocker):
|
||||
default_conf["webhook"] = get_webhook_dict()
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
|
||||
msg = {
|
||||
'type': RPCMessageType.BUY_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'pair': 'ETH/BTC',
|
||||
'limit': 0.005,
|
||||
'stake_amount': 0.8,
|
||||
'stake_amount_fiat': 500,
|
||||
'stake_currency': 'BTC',
|
||||
'fiat_currency': 'EUR'
|
||||
}
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
webhook.send_msg(msg=msg)
|
||||
assert msg_mock.call_count == 1
|
||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||
default_conf["webhook"]["webhookbuy"]["value1"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||
default_conf["webhook"]["webhookbuy"]["value2"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||
default_conf["webhook"]["webhookbuy"]["value3"].format(**msg))
|
||||
# Test sell
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
msg = {
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'pair': 'ETH/BTC',
|
||||
'gain': "profit",
|
||||
'limit': 0.005,
|
||||
'amount': 0.8,
|
||||
'order_type': 'limit',
|
||||
'open_rate': 0.004,
|
||||
'current_rate': 0.005,
|
||||
'profit_amount': 0.001,
|
||||
'profit_percent': 0.20,
|
||||
'stake_currency': 'BTC',
|
||||
'sell_reason': SellType.STOP_LOSS.value
|
||||
}
|
||||
webhook.send_msg(msg=msg)
|
||||
assert msg_mock.call_count == 1
|
||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||
default_conf["webhook"]["webhooksell"]["value1"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||
default_conf["webhook"]["webhooksell"]["value2"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||
default_conf["webhook"]["webhooksell"]["value3"].format(**msg))
|
||||
|
||||
for msgtype in [RPCMessageType.STATUS_NOTIFICATION,
|
||||
RPCMessageType.WARNING_NOTIFICATION,
|
||||
RPCMessageType.CUSTOM_NOTIFICATION]:
|
||||
# Test notification
|
||||
msg = {
|
||||
'type': msgtype,
|
||||
'status': 'Unfilled sell order for BTC cancelled due to timeout'
|
||||
}
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
webhook.send_msg(msg)
|
||||
assert msg_mock.call_count == 1
|
||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||
default_conf["webhook"]["webhookstatus"]["value1"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||
default_conf["webhook"]["webhookstatus"]["value2"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||
default_conf["webhook"]["webhookstatus"]["value3"].format(**msg))
|
||||
|
||||
|
||||
def test_exception_send_msg(default_conf, mocker, caplog):
|
||||
default_conf["webhook"] = get_webhook_dict()
|
||||
default_conf["webhook"]["webhookbuy"] = None
|
||||
|
||||
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
|
||||
webhook.send_msg({'type': RPCMessageType.BUY_NOTIFICATION})
|
||||
assert log_has(f"Message type {RPCMessageType.BUY_NOTIFICATION} not configured for webhooks",
|
||||
caplog)
|
||||
|
||||
default_conf["webhook"] = get_webhook_dict()
|
||||
default_conf["webhook"]["webhookbuy"]["value1"] = "{DEADBEEF:8f}"
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
|
||||
msg = {
|
||||
'type': RPCMessageType.BUY_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'pair': 'ETH/BTC',
|
||||
'limit': 0.005,
|
||||
'order_type': 'limit',
|
||||
'stake_amount': 0.8,
|
||||
'stake_amount_fiat': 500,
|
||||
'stake_currency': 'BTC',
|
||||
'fiat_currency': 'EUR'
|
||||
}
|
||||
webhook.send_msg(msg)
|
||||
assert log_has("Problem calling Webhook. Please check your webhook configuration. "
|
||||
"Exception: 'DEADBEEF'", caplog)
|
||||
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
msg = {
|
||||
'type': 'DEADBEEF',
|
||||
'status': 'whatever'
|
||||
}
|
||||
with pytest.raises(NotImplementedError):
|
||||
webhook.send_msg(msg)
|
||||
|
||||
|
||||
def test__send_msg(default_conf, mocker, caplog):
|
||||
default_conf["webhook"] = get_webhook_dict()
|
||||
webhook = Webhook(get_patched_freqtradebot(mocker, default_conf))
|
||||
msg = {'value1': 'DEADBEEF',
|
||||
'value2': 'ALIVEBEEF',
|
||||
'value3': 'FREQTRADE'}
|
||||
post = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.post", post)
|
||||
webhook._send_msg(msg)
|
||||
|
||||
assert post.call_count == 1
|
||||
assert post.call_args[1] == {'data': msg}
|
||||
assert post.call_args[0] == (default_conf['webhook']['url'], )
|
||||
|
||||
post = MagicMock(side_effect=RequestException)
|
||||
mocker.patch("freqtrade.rpc.webhook.post", post)
|
||||
webhook._send_msg(msg)
|
||||
assert log_has('Could not call webhook url. Exception: ', caplog)
|
Reference in New Issue
Block a user