Merge branch 'develop' into pr/eatrisno/4308
This commit is contained in:
@@ -1,44 +1,16 @@
|
||||
# pragma pylint: disable=missing-docstring, too-many-arguments, too-many-ancestors,
|
||||
# pragma pylint: disable=protected-access, C0103
|
||||
|
||||
import time
|
||||
import datetime
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
from requests.exceptions import RequestException
|
||||
|
||||
from freqtrade.rpc.fiat_convert import CryptoFiat, CryptoToFiatConverter
|
||||
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
||||
from tests.conftest import log_has, log_has_re
|
||||
|
||||
|
||||
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
|
||||
@@ -47,31 +19,15 @@ def test_fiat_convert_is_supported(mocker):
|
||||
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()
|
||||
|
||||
fiat_convert._cryptomap = {}
|
||||
fiat_convert._backoff = 0
|
||||
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._load_cryptomap',
|
||||
return_value=None)
|
||||
assert fiat_convert.get_price(crypto_symbol='BTC', fiat_symbol='EUR') == 0.0
|
||||
|
||||
with pytest.raises(ValueError, match=r'The fiat ABC is not supported.'):
|
||||
fiat_convert._find_price(crypto_symbol='BTC', fiat_symbol='ABC')
|
||||
|
||||
@@ -95,8 +51,8 @@ def test_fiat_convert_unsupported_crypto(mocker, caplog):
|
||||
|
||||
|
||||
def test_fiat_convert_get_price(mocker):
|
||||
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
|
||||
return_value=28000.0)
|
||||
find_price = mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
|
||||
return_value=28000.0)
|
||||
|
||||
fiat_convert = CryptoToFiatConverter()
|
||||
|
||||
@@ -104,26 +60,17 @@ def test_fiat_convert_get_price(mocker):
|
||||
fiat_convert.get_price(crypto_symbol='btc', fiat_symbol='US Dollar')
|
||||
|
||||
# Check the value return by the method
|
||||
pair_len = len(fiat_convert._pairs)
|
||||
pair_len = len(fiat_convert._pair_price)
|
||||
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
|
||||
assert fiat_convert._pair_price['btc/usd'] == 28000.0
|
||||
assert len(fiat_convert._pair_price) == 1
|
||||
assert find_price.call_count == 1
|
||||
|
||||
# Verify the cached is used
|
||||
fiat_convert._pairs[0].price = 9867.543
|
||||
expiration = fiat_convert._pairs[0]._expiration
|
||||
fiat_convert._pair_price['btc/usd'] = 9867.543
|
||||
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
|
||||
assert find_price.call_count == 1
|
||||
|
||||
|
||||
def test_fiat_convert_same_currencies(mocker):
|
||||
@@ -175,6 +122,28 @@ def test_fiat_convert_without_network(mocker):
|
||||
CryptoToFiatConverter._coingekko = cmc_temp
|
||||
|
||||
|
||||
def test_fiat_too_many_requests_response(mocker, caplog):
|
||||
# Because CryptoToFiatConverter is a Singleton we reset the listings
|
||||
req_exception = "429 Too Many Requests"
|
||||
listmock = MagicMock(return_value="{}", side_effect=RequestException(req_exception))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
|
||||
get_coins_list=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 fiat_convert._backoff > datetime.datetime.now().timestamp()
|
||||
assert log_has(
|
||||
'Too many requests for Coingecko API, backing off and trying again later.',
|
||||
caplog
|
||||
)
|
||||
|
||||
|
||||
def test_fiat_invalid_response(mocker, caplog):
|
||||
# Because CryptoToFiatConverter is a Singleton we reset the listings
|
||||
listmock = MagicMock(return_value="{'novalidjson':DEADBEEFf}")
|
||||
|
@@ -1,18 +1,19 @@
|
||||
# pragma pylint: disable=missing-docstring, C0103
|
||||
# pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from unittest.mock import ANY, MagicMock, PropertyMock
|
||||
|
||||
import pytest
|
||||
from numpy import isnan
|
||||
|
||||
from freqtrade.edge import PairInfo
|
||||
from freqtrade.enums import State
|
||||
from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError
|
||||
from freqtrade.persistence import Trade
|
||||
from freqtrade.persistence.pairlock_middleware import PairLocks
|
||||
from freqtrade.rpc import RPC, RPCException
|
||||
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
|
||||
from freqtrade.state import State
|
||||
from tests.conftest import create_mock_trades, get_patched_freqtradebot, patch_get_signal
|
||||
|
||||
|
||||
@@ -52,7 +53,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'pair': 'ETH/BTC',
|
||||
'base_currency': 'BTC',
|
||||
'open_date': ANY,
|
||||
'open_date_hum': ANY,
|
||||
'open_timestamp': ANY,
|
||||
'is_open': ANY,
|
||||
'fee_open': ANY,
|
||||
@@ -72,7 +72,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'timeframe': 5,
|
||||
'open_order_id': ANY,
|
||||
'close_date': None,
|
||||
'close_date_hum': None,
|
||||
'close_timestamp': None,
|
||||
'open_rate': 1.098e-05,
|
||||
'close_rate': None,
|
||||
@@ -91,6 +90,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'profit_ratio': -0.00408133,
|
||||
'profit_pct': -0.41,
|
||||
'profit_abs': -4.09e-06,
|
||||
'profit_fiat': ANY,
|
||||
'stop_loss_abs': 9.882e-06,
|
||||
'stop_loss_pct': -10.0,
|
||||
'stop_loss_ratio': -0.1,
|
||||
@@ -106,10 +106,10 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'stoploss_entry_dist': -0.00010475,
|
||||
'stoploss_entry_dist_ratio': -0.10448878,
|
||||
'open_order': None,
|
||||
'exchange': 'bittrex',
|
||||
'exchange': 'binance',
|
||||
}
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate',
|
||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||
results = rpc._rpc_trade_status()
|
||||
assert isnan(results[0]['current_profit'])
|
||||
@@ -119,7 +119,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'pair': 'ETH/BTC',
|
||||
'base_currency': 'BTC',
|
||||
'open_date': ANY,
|
||||
'open_date_hum': ANY,
|
||||
'open_timestamp': ANY,
|
||||
'is_open': ANY,
|
||||
'fee_open': ANY,
|
||||
@@ -139,7 +138,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'timeframe': ANY,
|
||||
'open_order_id': ANY,
|
||||
'close_date': None,
|
||||
'close_date_hum': None,
|
||||
'close_timestamp': None,
|
||||
'open_rate': 1.098e-05,
|
||||
'close_rate': None,
|
||||
@@ -158,6 +156,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'profit_ratio': ANY,
|
||||
'profit_pct': ANY,
|
||||
'profit_abs': ANY,
|
||||
'profit_fiat': ANY,
|
||||
'stop_loss_abs': 9.882e-06,
|
||||
'stop_loss_pct': -10.0,
|
||||
'stop_loss_ratio': -0.1,
|
||||
@@ -173,7 +172,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
|
||||
'stoploss_entry_dist': -0.00010475,
|
||||
'stoploss_entry_dist_ratio': -0.10448878,
|
||||
'open_order': None,
|
||||
'exchange': 'bittrex',
|
||||
'exchange': 'binance',
|
||||
}
|
||||
|
||||
|
||||
@@ -200,28 +199,31 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
|
||||
|
||||
freqtradebot.enter_positions()
|
||||
|
||||
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||
assert "Since" in headers
|
||||
assert "Pair" in headers
|
||||
assert 'instantly' == result[0][2]
|
||||
assert 'ETH/BTC' in result[0][1]
|
||||
assert '-0.41%' == result[0][3]
|
||||
assert isnan(fiat_profit_sum)
|
||||
# Test with fiatconvert
|
||||
|
||||
rpc._fiat_converter = CryptoToFiatConverter()
|
||||
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||
assert "Since" in headers
|
||||
assert "Pair" in headers
|
||||
assert 'instantly' == result[0][2]
|
||||
assert 'ETH/BTC' in result[0][1]
|
||||
assert '-0.41% (-0.06)' == result[0][3]
|
||||
assert '-0.06' == f'{fiat_profit_sum:.2f}'
|
||||
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate',
|
||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||
result, headers, fiat_profit_sum = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
|
||||
assert 'instantly' == result[0][2]
|
||||
assert 'ETH/BTC' in result[0][1]
|
||||
assert 'nan%' == result[0][3]
|
||||
assert isnan(fiat_profit_sum)
|
||||
|
||||
|
||||
def test_rpc_daily_profit(default_conf, update, ticker, fee,
|
||||
@@ -412,26 +414,26 @@ def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
|
||||
|
||||
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_percent_mean'], 6.2)
|
||||
assert prec_satoshi(stats['profit_closed_fiat'], 0.93255)
|
||||
assert prec_satoshi(stats['profit_all_coin'], 5.802e-05)
|
||||
assert prec_satoshi(stats['profit_all_percent'], 2.89)
|
||||
assert prec_satoshi(stats['profit_all_percent_mean'], 2.89)
|
||||
assert prec_satoshi(stats['profit_all_fiat'], 0.8703)
|
||||
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['avg_duration'] in ('0:00:00', '0:00:01')
|
||||
assert stats['best_pair'] == 'ETH/BTC'
|
||||
assert prec_satoshi(stats['best_rate'], 6.2)
|
||||
|
||||
# Test non-available pair
|
||||
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate',
|
||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||
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['avg_duration'] in ('0:00:00', '0:00:01')
|
||||
assert stats['best_pair'] == 'ETH/BTC'
|
||||
assert prec_satoshi(stats['best_rate'], 6.2)
|
||||
assert isnan(stats['profit_all_coin'])
|
||||
@@ -481,10 +483,10 @@ def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
|
||||
|
||||
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_percent_mean'], 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_percent_mean'], 0)
|
||||
assert prec_satoshi(stats['profit_all_fiat'], 0)
|
||||
assert stats['trade_count'] == 1
|
||||
assert stats['first_trade_date'] == 'just now'
|
||||
@@ -570,6 +572,8 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
|
||||
result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency'])
|
||||
assert prec_satoshi(result['total'], 12.309096315)
|
||||
assert prec_satoshi(result['value'], 184636.44472997)
|
||||
assert tickers.call_count == 1
|
||||
assert tickers.call_args_list[0][1]['cached'] is True
|
||||
assert 'USD' == result['symbol']
|
||||
assert result['currencies'] == [
|
||||
{'currency': 'BTC',
|
||||
@@ -911,6 +915,24 @@ def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
|
||||
rpc._rpc_forcebuy(pair, None)
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_rpc_delete_lock(mocker, default_conf):
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc = RPC(freqtradebot)
|
||||
pair = 'ETH/BTC'
|
||||
|
||||
PairLocks.lock_pair(pair, datetime.now(timezone.utc) + timedelta(minutes=4))
|
||||
PairLocks.lock_pair(pair, datetime.now(timezone.utc) + timedelta(minutes=5))
|
||||
PairLocks.lock_pair(pair, datetime.now(timezone.utc) + timedelta(minutes=10))
|
||||
locks = rpc._rpc_locks()
|
||||
assert locks['lock_count'] == 3
|
||||
locks1 = rpc._rpc_delete_lock(lockid=locks['locks'][0]['id'])
|
||||
assert locks1['lock_count'] == 2
|
||||
|
||||
locks2 = rpc._rpc_delete_lock(pair=pair)
|
||||
assert locks2['lock_count'] == 0
|
||||
|
||||
|
||||
def test_rpc_whitelist(mocker, default_conf) -> None:
|
||||
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
|
||||
|
||||
|
@@ -11,18 +11,20 @@ import uvicorn
|
||||
from fastapi import FastAPI
|
||||
from fastapi.exceptions import HTTPException
|
||||
from fastapi.testclient import TestClient
|
||||
from numpy import isnan
|
||||
from requests.auth import _basic_auth_str
|
||||
|
||||
from freqtrade.__init__ import __version__
|
||||
from freqtrade.enums import RunMode, State
|
||||
from freqtrade.exceptions import ExchangeError
|
||||
from freqtrade.loggers import setup_logging, setup_logging_pre
|
||||
from freqtrade.persistence import PairLocks, Trade
|
||||
from freqtrade.rpc import RPC
|
||||
from freqtrade.rpc.api_server import ApiServer
|
||||
from freqtrade.rpc.api_server.api_auth import create_token, get_user_from_token
|
||||
from freqtrade.rpc.api_server.uvicorn_threaded import UvicornServer
|
||||
from freqtrade.state import RunMode, State
|
||||
from tests.conftest import (create_mock_trades, get_patched_freqtradebot, log_has, log_has_re,
|
||||
patch_get_signal)
|
||||
from tests.conftest import (create_mock_trades, get_mock_coro, get_patched_freqtradebot, log_has,
|
||||
log_has_re, patch_get_signal)
|
||||
|
||||
|
||||
BASE_URI = "/api/v1"
|
||||
@@ -228,7 +230,7 @@ def test_api__init__(default_conf, mocker):
|
||||
assert apiserver._config == default_conf
|
||||
|
||||
|
||||
def test_api_UvicornServer(default_conf, mocker):
|
||||
def test_api_UvicornServer(mocker):
|
||||
thread_mock = mocker.patch('freqtrade.rpc.api_server.uvicorn_threaded.threading.Thread')
|
||||
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1'))
|
||||
assert thread_mock.call_count == 0
|
||||
@@ -246,6 +248,38 @@ def test_api_UvicornServer(default_conf, mocker):
|
||||
assert s.should_exit is True
|
||||
|
||||
|
||||
def test_api_UvicornServer_run(mocker):
|
||||
serve_mock = mocker.patch('freqtrade.rpc.api_server.uvicorn_threaded.UvicornServer.serve',
|
||||
get_mock_coro(None))
|
||||
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1'))
|
||||
assert serve_mock.call_count == 0
|
||||
|
||||
s.install_signal_handlers()
|
||||
# Original implementation starts a thread - make sure that's not the case
|
||||
assert serve_mock.call_count == 0
|
||||
|
||||
# Fake started to avoid sleeping forever
|
||||
s.started = True
|
||||
s.run()
|
||||
assert serve_mock.call_count == 1
|
||||
|
||||
|
||||
def test_api_UvicornServer_run_no_uvloop(mocker, import_fails):
|
||||
serve_mock = mocker.patch('freqtrade.rpc.api_server.uvicorn_threaded.UvicornServer.serve',
|
||||
get_mock_coro(None))
|
||||
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1'))
|
||||
assert serve_mock.call_count == 0
|
||||
|
||||
s.install_signal_handlers()
|
||||
# Original implementation starts a thread - make sure that's not the case
|
||||
assert serve_mock.call_count == 0
|
||||
|
||||
# Fake started to avoid sleeping forever
|
||||
s.started = True
|
||||
s.run()
|
||||
assert serve_mock.call_count == 1
|
||||
|
||||
|
||||
def test_api_run(default_conf, mocker, caplog):
|
||||
default_conf.update({"api_server": {"enabled": True,
|
||||
"listen_ip_address": "127.0.0.1",
|
||||
@@ -295,7 +329,7 @@ def test_api_run(default_conf, mocker, caplog):
|
||||
"Please make sure that this is intentional!", caplog)
|
||||
assert log_has_re("SECURITY WARNING - `jwt_secret_key` seems to be default.*", caplog)
|
||||
|
||||
# Test crashing flask
|
||||
# Test crashing API server
|
||||
caplog.clear()
|
||||
mocker.patch('freqtrade.rpc.api_server.webserver.UvicornServer',
|
||||
MagicMock(side_effect=Exception))
|
||||
@@ -382,10 +416,10 @@ def test_api_count(botclient, mocker, ticker, fee, markets):
|
||||
assert rc.json()["max"] == 1
|
||||
|
||||
# Create some test data
|
||||
ftbot.enter_positions()
|
||||
create_mock_trades(fee)
|
||||
rc = client_get(client, f"{BASE_URI}/count")
|
||||
assert_response(rc)
|
||||
assert rc.json()["current"] == 1
|
||||
assert rc.json()["current"] == 4
|
||||
assert rc.json()["max"] == 1
|
||||
|
||||
ftbot.config['max_open_trades'] = float('inf')
|
||||
@@ -416,6 +450,16 @@ def test_api_locks(botclient):
|
||||
assert 'randreason' in (rc.json()['locks'][0]['reason'], rc.json()['locks'][1]['reason'])
|
||||
assert 'deadbeef' in (rc.json()['locks'][0]['reason'], rc.json()['locks'][1]['reason'])
|
||||
|
||||
# Test deletions
|
||||
rc = client_delete(client, f"{BASE_URI}/locks/1")
|
||||
assert_response(rc)
|
||||
assert rc.json()['lock_count'] == 1
|
||||
|
||||
rc = client_post(client, f"{BASE_URI}/locks/delete",
|
||||
data='{"pair": "XRP/BTC"}')
|
||||
assert_response(rc)
|
||||
assert rc.json()['lock_count'] == 0
|
||||
|
||||
|
||||
def test_api_show_config(botclient, mocker):
|
||||
ftbot, client = botclient
|
||||
@@ -424,7 +468,7 @@ def test_api_show_config(botclient, mocker):
|
||||
rc = client_get(client, f"{BASE_URI}/show_config")
|
||||
assert_response(rc)
|
||||
assert 'dry_run' in rc.json()
|
||||
assert rc.json()['exchange'] == 'bittrex'
|
||||
assert rc.json()['exchange'] == 'binance'
|
||||
assert rc.json()['timeframe'] == '5m'
|
||||
assert rc.json()['timeframe_ms'] == 300000
|
||||
assert rc.json()['timeframe_min'] == 5
|
||||
@@ -462,20 +506,43 @@ def test_api_trades(botclient, mocker, fee, markets):
|
||||
)
|
||||
rc = client_get(client, f"{BASE_URI}/trades")
|
||||
assert_response(rc)
|
||||
assert len(rc.json()) == 2
|
||||
assert len(rc.json()) == 3
|
||||
assert rc.json()['trades_count'] == 0
|
||||
assert rc.json()['total_trades'] == 0
|
||||
|
||||
create_mock_trades(fee)
|
||||
Trade.session.flush()
|
||||
Trade.query.session.flush()
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/trades")
|
||||
assert_response(rc)
|
||||
assert len(rc.json()['trades']) == 2
|
||||
assert rc.json()['trades_count'] == 2
|
||||
assert rc.json()['total_trades'] == 2
|
||||
rc = client_get(client, f"{BASE_URI}/trades?limit=1")
|
||||
assert_response(rc)
|
||||
assert len(rc.json()['trades']) == 1
|
||||
assert rc.json()['trades_count'] == 1
|
||||
assert rc.json()['total_trades'] == 2
|
||||
|
||||
|
||||
def test_api_trade_single(botclient, mocker, fee, ticker, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
mocker.patch.multiple(
|
||||
'freqtrade.exchange.Exchange',
|
||||
markets=PropertyMock(return_value=markets),
|
||||
fetch_ticker=ticker,
|
||||
)
|
||||
rc = client_get(client, f"{BASE_URI}/trade/3")
|
||||
assert_response(rc, 404)
|
||||
assert rc.json()['detail'] == 'Trade not found.'
|
||||
|
||||
create_mock_trades(fee)
|
||||
Trade.query.session.flush()
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/trade/3")
|
||||
assert_response(rc)
|
||||
assert rc.json()['trade_id'] == 3
|
||||
|
||||
|
||||
def test_api_delete_trade(botclient, mocker, fee, markets):
|
||||
@@ -494,7 +561,7 @@ def test_api_delete_trade(botclient, mocker, fee, markets):
|
||||
assert_response(rc, 502)
|
||||
|
||||
create_mock_trades(fee)
|
||||
Trade.session.flush()
|
||||
Trade.query.session.flush()
|
||||
ftbot.strategy.order_types['stoploss_on_exchange'] = True
|
||||
trades = Trade.query.all()
|
||||
trades[1].stoploss_order_id = '1234'
|
||||
@@ -568,7 +635,7 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("init_persistence")
|
||||
def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, limit_sell_order):
|
||||
def test_api_profit(botclient, mocker, ticker, fee, markets):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
mocker.patch.multiple(
|
||||
@@ -583,50 +650,33 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, limit_buy_order, li
|
||||
assert_response(rc, 200)
|
||||
assert rc.json()['trade_count'] == 0
|
||||
|
||||
ftbot.enter_positions()
|
||||
trade = Trade.query.first()
|
||||
|
||||
create_mock_trades(fee)
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
trade.update(limit_buy_order)
|
||||
rc = client_get(client, f"{BASE_URI}/profit")
|
||||
assert_response(rc, 200)
|
||||
# One open trade
|
||||
assert rc.json()['trade_count'] == 1
|
||||
assert rc.json()['best_pair'] == ''
|
||||
assert rc.json()['best_rate'] == 0
|
||||
|
||||
trade = Trade.query.first()
|
||||
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': ANY,
|
||||
'best_pair': 'ETH/BTC',
|
||||
'best_rate': 6.2,
|
||||
'first_trade_date': 'just now',
|
||||
'best_pair': 'XRP/BTC',
|
||||
'best_rate': 1.0,
|
||||
'first_trade_date': ANY,
|
||||
'first_trade_timestamp': ANY,
|
||||
'latest_trade_date': 'just now',
|
||||
'latest_trade_date': '5 minutes ago',
|
||||
'latest_trade_timestamp': ANY,
|
||||
'profit_all_coin': 6.217e-05,
|
||||
'profit_all_fiat': 0.76748865,
|
||||
'profit_all_percent': 6.2,
|
||||
'profit_all_percent_mean': 6.2,
|
||||
'profit_all_ratio_mean': 0.06201058,
|
||||
'profit_all_percent_sum': 6.2,
|
||||
'profit_all_ratio_sum': 0.06201058,
|
||||
'profit_closed_coin': 6.217e-05,
|
||||
'profit_closed_fiat': 0.76748865,
|
||||
'profit_closed_percent': 6.2,
|
||||
'profit_closed_ratio_mean': 0.06201058,
|
||||
'profit_closed_percent_mean': 6.2,
|
||||
'profit_closed_ratio_sum': 0.06201058,
|
||||
'profit_closed_percent_sum': 6.2,
|
||||
'trade_count': 1,
|
||||
'closed_trade_count': 1,
|
||||
'winning_trades': 1,
|
||||
'profit_all_coin': -44.0631579,
|
||||
'profit_all_fiat': -543959.6842755,
|
||||
'profit_all_percent_mean': -66.41,
|
||||
'profit_all_ratio_mean': -0.6641100666666667,
|
||||
'profit_all_percent_sum': -398.47,
|
||||
'profit_all_ratio_sum': -3.9846604,
|
||||
'profit_closed_coin': 0.00073913,
|
||||
'profit_closed_fiat': 9.124559849999999,
|
||||
'profit_closed_ratio_mean': 0.0075,
|
||||
'profit_closed_percent_mean': 0.75,
|
||||
'profit_closed_ratio_sum': 0.015,
|
||||
'profit_closed_percent_sum': 1.5,
|
||||
'trade_count': 6,
|
||||
'closed_trade_count': 2,
|
||||
'winning_trades': 2,
|
||||
'losing_trades': 0,
|
||||
}
|
||||
|
||||
@@ -660,7 +710,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,):
|
||||
assert 'draws' in rc.json()['durations']
|
||||
|
||||
|
||||
def test_api_performance(botclient, mocker, ticker, fee):
|
||||
def test_api_performance(botclient, fee):
|
||||
ftbot, client = botclient
|
||||
patch_get_signal(ftbot, (True, False))
|
||||
|
||||
@@ -678,7 +728,8 @@ def test_api_performance(botclient, mocker, ticker, fee):
|
||||
|
||||
)
|
||||
trade.close_profit = trade.calc_profit_ratio()
|
||||
Trade.session.add(trade)
|
||||
trade.close_profit_abs = trade.calc_profit()
|
||||
Trade.query.session.add(trade)
|
||||
|
||||
trade = Trade(
|
||||
pair='XRP/ETH',
|
||||
@@ -693,14 +744,16 @@ def test_api_performance(botclient, mocker, ticker, fee):
|
||||
close_rate=0.391
|
||||
)
|
||||
trade.close_profit = trade.calc_profit_ratio()
|
||||
Trade.session.add(trade)
|
||||
Trade.session.flush()
|
||||
trade.close_profit_abs = trade.calc_profit()
|
||||
|
||||
Trade.query.session.add(trade)
|
||||
Trade.query.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}]
|
||||
assert rc.json() == [{'count': 1, 'pair': 'LTC/ETH', 'profit': 7.61, 'profit_abs': 0.01872279},
|
||||
{'count': 1, 'pair': 'XRP/ETH', 'profit': -5.57, 'profit_abs': -0.1150375}]
|
||||
|
||||
|
||||
def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||
@@ -711,83 +764,84 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
|
||||
get_balances=MagicMock(return_value=ticker),
|
||||
fetch_ticker=ticker,
|
||||
get_fee=fee,
|
||||
markets=PropertyMock(return_value=markets)
|
||||
markets=PropertyMock(return_value=markets),
|
||||
fetch_order=MagicMock(return_value={}),
|
||||
)
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/status")
|
||||
assert_response(rc, 200)
|
||||
assert rc.json() == []
|
||||
|
||||
ftbot.enter_positions()
|
||||
trades = Trade.get_open_trades()
|
||||
trades[0].open_order_id = None
|
||||
ftbot.exit_positions(trades)
|
||||
Trade.session.flush()
|
||||
create_mock_trades(fee)
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/status")
|
||||
assert_response(rc)
|
||||
assert len(rc.json()) == 1
|
||||
assert rc.json() == [{
|
||||
'amount': 91.07468123,
|
||||
'amount_requested': 91.07468123,
|
||||
'base_currency': 'BTC',
|
||||
assert len(rc.json()) == 4
|
||||
assert rc.json()[0] == {
|
||||
'amount': 123.0,
|
||||
'amount_requested': 123.0,
|
||||
'close_date': None,
|
||||
'close_date_hum': None,
|
||||
'close_timestamp': None,
|
||||
'close_profit': None,
|
||||
'close_profit_pct': None,
|
||||
'close_profit_abs': None,
|
||||
'close_rate': None,
|
||||
'current_profit': -0.00408133,
|
||||
'current_profit_pct': -0.41,
|
||||
'current_profit_abs': -4.09e-06,
|
||||
'profit_ratio': -0.00408133,
|
||||
'profit_pct': -0.41,
|
||||
'profit_abs': -4.09e-06,
|
||||
'current_profit': ANY,
|
||||
'current_profit_pct': ANY,
|
||||
'current_profit_abs': ANY,
|
||||
'profit_ratio': ANY,
|
||||
'profit_pct': ANY,
|
||||
'profit_abs': ANY,
|
||||
'profit_fiat': ANY,
|
||||
'current_rate': 1.099e-05,
|
||||
'open_date': ANY,
|
||||
'open_date_hum': 'just now',
|
||||
'open_timestamp': ANY,
|
||||
'open_order': None,
|
||||
'open_rate': 1.098e-05,
|
||||
'open_rate': 0.123,
|
||||
'pair': 'ETH/BTC',
|
||||
'stake_amount': 0.001,
|
||||
'stop_loss_abs': 9.882e-06,
|
||||
'stop_loss_pct': -10.0,
|
||||
'stop_loss_ratio': -0.1,
|
||||
'stop_loss_abs': ANY,
|
||||
'stop_loss_pct': ANY,
|
||||
'stop_loss_ratio': ANY,
|
||||
'stoploss_order_id': None,
|
||||
'stoploss_last_update': ANY,
|
||||
'stoploss_last_update_timestamp': ANY,
|
||||
'initial_stop_loss_abs': 9.882e-06,
|
||||
'initial_stop_loss_pct': -10.0,
|
||||
'initial_stop_loss_ratio': -0.1,
|
||||
'stoploss_current_dist': -1.1080000000000002e-06,
|
||||
'stoploss_current_dist_ratio': -0.10081893,
|
||||
'stoploss_current_dist_pct': -10.08,
|
||||
'stoploss_entry_dist': -0.00010475,
|
||||
'stoploss_entry_dist_ratio': -0.10448878,
|
||||
'initial_stop_loss_abs': 0.0,
|
||||
'initial_stop_loss_pct': ANY,
|
||||
'initial_stop_loss_ratio': ANY,
|
||||
'stoploss_current_dist': ANY,
|
||||
'stoploss_current_dist_ratio': ANY,
|
||||
'stoploss_current_dist_pct': ANY,
|
||||
'stoploss_entry_dist': ANY,
|
||||
'stoploss_entry_dist_ratio': ANY,
|
||||
'trade_id': 1,
|
||||
'close_rate_requested': None,
|
||||
'current_rate': 1.099e-05,
|
||||
'close_rate_requested': ANY,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
'fee_open': 0.0025,
|
||||
'fee_open_cost': None,
|
||||
'fee_open_currency': None,
|
||||
'open_date': ANY,
|
||||
'is_open': True,
|
||||
'max_rate': 1.099e-05,
|
||||
'min_rate': 1.098e-05,
|
||||
'open_order_id': None,
|
||||
'open_rate_requested': 1.098e-05,
|
||||
'open_trade_value': 0.0010025,
|
||||
'max_rate': ANY,
|
||||
'min_rate': ANY,
|
||||
'open_order_id': 'dry_run_buy_12345',
|
||||
'open_rate_requested': ANY,
|
||||
'open_trade_value': 15.1668225,
|
||||
'sell_reason': None,
|
||||
'sell_order_status': None,
|
||||
'strategy': 'DefaultStrategy',
|
||||
'timeframe': 5,
|
||||
'exchange': 'bittrex',
|
||||
}]
|
||||
'exchange': 'binance',
|
||||
}
|
||||
|
||||
mocker.patch('freqtrade.exchange.Exchange.get_sell_rate',
|
||||
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
|
||||
|
||||
rc = client_get(client, f"{BASE_URI}/status")
|
||||
assert_response(rc)
|
||||
resp_values = rc.json()
|
||||
assert len(resp_values) == 4
|
||||
assert isnan(resp_values[0]['profit_abs'])
|
||||
|
||||
|
||||
def test_api_version(botclient):
|
||||
@@ -868,7 +922,7 @@ def test_api_forcebuy(botclient, mocker, fee):
|
||||
pair='ETH/ETH',
|
||||
amount=1,
|
||||
amount_requested=1,
|
||||
exchange='bittrex',
|
||||
exchange='binance',
|
||||
stake_amount=1,
|
||||
open_rate=0.245441,
|
||||
open_order_id="123456",
|
||||
@@ -891,11 +945,9 @@ def test_api_forcebuy(botclient, mocker, fee):
|
||||
'amount_requested': 1,
|
||||
'trade_id': 22,
|
||||
'close_date': None,
|
||||
'close_date_hum': None,
|
||||
'close_timestamp': None,
|
||||
'close_rate': 0.265441,
|
||||
'open_date': ANY,
|
||||
'open_date_hum': 'just now',
|
||||
'open_timestamp': ANY,
|
||||
'open_rate': 0.245441,
|
||||
'pair': 'ETH/ETH',
|
||||
@@ -916,6 +968,7 @@ def test_api_forcebuy(botclient, mocker, fee):
|
||||
'profit_ratio': None,
|
||||
'profit_pct': None,
|
||||
'profit_abs': None,
|
||||
'profit_fiat': None,
|
||||
'fee_close': 0.0025,
|
||||
'fee_close_cost': None,
|
||||
'fee_close_currency': None,
|
||||
@@ -932,7 +985,7 @@ def test_api_forcebuy(botclient, mocker, fee):
|
||||
'sell_order_status': None,
|
||||
'strategy': 'DefaultStrategy',
|
||||
'timeframe': 5,
|
||||
'exchange': 'bittrex',
|
||||
'exchange': 'binance',
|
||||
}
|
||||
|
||||
|
||||
@@ -1092,6 +1145,14 @@ def test_api_plot_config(botclient):
|
||||
assert_response(rc)
|
||||
assert rc.json() == ftbot.strategy.plot_config
|
||||
assert isinstance(rc.json()['main_plot'], dict)
|
||||
assert isinstance(rc.json()['subplots'], dict)
|
||||
|
||||
ftbot.strategy.plot_config = {'main_plot': {'sma': {}}}
|
||||
rc = client_get(client, f"{BASE_URI}/plot_config")
|
||||
assert_response(rc)
|
||||
|
||||
assert isinstance(rc.json()['main_plot'], dict)
|
||||
assert isinstance(rc.json()['subplots'], dict)
|
||||
|
||||
|
||||
def test_api_strategies(botclient):
|
||||
@@ -1100,7 +1161,11 @@ def test_api_strategies(botclient):
|
||||
rc = client_get(client, f"{BASE_URI}/strategies")
|
||||
|
||||
assert_response(rc)
|
||||
assert rc.json() == {'strategies': ['DefaultStrategy', 'TestStrategyLegacy']}
|
||||
assert rc.json() == {'strategies': [
|
||||
'DefaultStrategy',
|
||||
'HyperoptableStrategy',
|
||||
'TestStrategyLegacy'
|
||||
]}
|
||||
|
||||
|
||||
def test_api_strategy(botclient):
|
||||
|
@@ -3,7 +3,8 @@ import logging
|
||||
import time
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from freqtrade.rpc import RPCManager, RPCMessageType
|
||||
from freqtrade.enums import RPCMessageType
|
||||
from freqtrade.rpc import RPCManager
|
||||
from tests.conftest import get_patched_freqtradebot, log_has
|
||||
|
||||
|
||||
@@ -71,7 +72,7 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc_manager = RPCManager(freqtradebot)
|
||||
rpc_manager.send_msg({
|
||||
'type': RPCMessageType.STATUS_NOTIFICATION,
|
||||
'type': RPCMessageType.STATUS,
|
||||
'status': 'test'
|
||||
})
|
||||
|
||||
@@ -86,7 +87,7 @@ def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None:
|
||||
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
|
||||
rpc_manager = RPCManager(freqtradebot)
|
||||
rpc_manager.send_msg({
|
||||
'type': RPCMessageType.STATUS_NOTIFICATION,
|
||||
'type': RPCMessageType.STATUS,
|
||||
'status': 'test'
|
||||
})
|
||||
|
||||
@@ -124,7 +125,7 @@ def test_send_msg_webhook_CustomMessagetype(mocker, default_conf, caplog) -> Non
|
||||
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.STARTUP_NOTIFICATION,
|
||||
rpc_manager.send_msg({'type': RPCMessageType.STARTUP,
|
||||
'status': 'TestMessage'})
|
||||
assert log_has(
|
||||
"Message type 'startup' not implemented by handler webhook.",
|
||||
@@ -140,7 +141,7 @@ def test_startupmessages_telegram_enabled(mocker, default_conf, caplog) -> None:
|
||||
rpc_manager.startup_messages(default_conf, freqtradebot.pairlists, freqtradebot.protections)
|
||||
|
||||
assert telegram_mock.call_count == 3
|
||||
assert "*Exchange:* `bittrex`" in telegram_mock.call_args_list[1][0][0]['status']
|
||||
assert "*Exchange:* `binance`" in telegram_mock.call_args_list[1][0][0]['status']
|
||||
|
||||
telegram_mock.reset_mock()
|
||||
default_conf['dry_run'] = True
|
||||
|
@@ -4,6 +4,7 @@
|
||||
|
||||
import re
|
||||
from datetime import datetime
|
||||
from functools import reduce
|
||||
from random import choice, randint
|
||||
from string import ascii_uppercase
|
||||
from unittest.mock import ANY, MagicMock
|
||||
@@ -16,14 +17,13 @@ from telegram.error import NetworkError
|
||||
from freqtrade import __version__
|
||||
from freqtrade.constants import CANCEL_REASON
|
||||
from freqtrade.edge import PairInfo
|
||||
from freqtrade.enums import RPCMessageType, RunMode, SellType, State
|
||||
from freqtrade.exceptions import OperationalException
|
||||
from freqtrade.freqtradebot import FreqtradeBot
|
||||
from freqtrade.loggers import setup_logging
|
||||
from freqtrade.persistence import PairLocks, Trade
|
||||
from freqtrade.rpc import RPC, RPCMessageType
|
||||
from freqtrade.rpc import RPC
|
||||
from freqtrade.rpc.telegram import Telegram, authorized_only
|
||||
from freqtrade.state import RunMode, State
|
||||
from freqtrade.strategy.interface import SellType
|
||||
from tests.conftest import (create_mock_trades, get_patched_freqtradebot, log_has, patch_exchange,
|
||||
patch_get_signal, patch_whitelist)
|
||||
|
||||
@@ -92,7 +92,8 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
|
||||
message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], "
|
||||
"['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], "
|
||||
"['delete'], ['performance'], ['stats'], ['daily'], ['count'], ['locks'], "
|
||||
"['reload_config', 'reload_conf'], ['show_config', 'show_conf'], ['stopbuy'], "
|
||||
"['unlock', 'delete_locks'], ['reload_config', 'reload_conf'], "
|
||||
"['show_config', 'show_conf'], ['stopbuy'], "
|
||||
"['whitelist'], ['blacklist'], ['logs'], ['edge'], ['help'], ['version']"
|
||||
"]")
|
||||
|
||||
@@ -176,9 +177,7 @@ def test_telegram_status(default_conf, update, mocker) -> None:
|
||||
'pair': 'ETH/BTC',
|
||||
'base_currency': 'BTC',
|
||||
'open_date': arrow.utcnow(),
|
||||
'open_date_hum': arrow.utcnow().humanize,
|
||||
'close_date': None,
|
||||
'close_date_hum': None,
|
||||
'open_rate': 1.099e-05,
|
||||
'close_rate': None,
|
||||
'current_rate': 1.098e-05,
|
||||
@@ -443,8 +442,10 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
|
||||
|
||||
# Simulate fulfilled LIMIT_BUY order for trade
|
||||
trade.update(limit_buy_order)
|
||||
|
||||
telegram._profit(update=update, context=MagicMock())
|
||||
context = MagicMock()
|
||||
# Test with invalid 2nd argument (should silently pass)
|
||||
context.args = ["aaa"]
|
||||
telegram._profit(update=update, context=context)
|
||||
assert msg_mock.call_count == 1
|
||||
assert 'No closed trade' in msg_mock.call_args_list[-1][0][0]
|
||||
assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0]
|
||||
@@ -520,7 +521,7 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick
|
||||
assert 'Balance:' in result
|
||||
assert 'Est. BTC:' in result
|
||||
assert 'BTC: 12.00000000' in result
|
||||
assert '*XRP:* not showing <1$ amount' in result
|
||||
assert '*XRP:* not showing <0.0001 BTC amount' in result
|
||||
|
||||
|
||||
def test_balance_handle_empty_response(default_conf, update, mocker) -> None:
|
||||
@@ -684,12 +685,12 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
|
||||
context.args = ["1"]
|
||||
telegram._forcesell(update=update, context=context)
|
||||
|
||||
assert msg_mock.call_count == 3
|
||||
assert msg_mock.call_count == 4
|
||||
last_msg = msg_mock.call_args_list[-1][0][0]
|
||||
assert {
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'type': RPCMessageType.SELL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Bittrex',
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'gain': 'profit',
|
||||
'limit': 1.173e-05,
|
||||
@@ -704,6 +705,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
|
||||
'sell_reason': SellType.FORCE_SELL.value,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
'close_rate': ANY,
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -744,13 +746,13 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
|
||||
context.args = ["1"]
|
||||
telegram._forcesell(update=update, context=context)
|
||||
|
||||
assert msg_mock.call_count == 3
|
||||
assert msg_mock.call_count == 4
|
||||
|
||||
last_msg = msg_mock.call_args_list[-1][0][0]
|
||||
assert {
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'type': RPCMessageType.SELL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Bittrex',
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'gain': 'loss',
|
||||
'limit': 1.043e-05,
|
||||
@@ -765,6 +767,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
|
||||
'sell_reason': SellType.FORCE_SELL.value,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
'close_rate': ANY,
|
||||
} == last_msg
|
||||
|
||||
|
||||
@@ -795,13 +798,13 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
|
||||
context.args = ["all"]
|
||||
telegram._forcesell(update=update, context=context)
|
||||
|
||||
# Called for each trade 3 times
|
||||
assert msg_mock.call_count == 8
|
||||
msg = msg_mock.call_args_list[1][0][0]
|
||||
# Called for each trade 4 times
|
||||
assert msg_mock.call_count == 12
|
||||
msg = msg_mock.call_args_list[2][0][0]
|
||||
assert {
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'type': RPCMessageType.SELL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Bittrex',
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'gain': 'loss',
|
||||
'limit': 1.099e-05,
|
||||
@@ -816,6 +819,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
|
||||
'sell_reason': SellType.FORCE_SELL.value,
|
||||
'open_date': ANY,
|
||||
'close_date': ANY,
|
||||
'close_rate': ANY,
|
||||
} == msg
|
||||
|
||||
|
||||
@@ -900,6 +904,33 @@ def test_forcebuy_handle_exception(default_conf, update, mocker) -> None:
|
||||
assert msg_mock.call_args_list[0][0][0] == 'Forcebuy not enabled.'
|
||||
|
||||
|
||||
def test_forcebuy_no_pair(default_conf, update, mocker) -> None:
|
||||
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
|
||||
|
||||
fbuy_mock = MagicMock(return_value=None)
|
||||
mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock)
|
||||
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
|
||||
context = MagicMock()
|
||||
context.args = []
|
||||
telegram._forcebuy(update=update, context=context)
|
||||
|
||||
assert fbuy_mock.call_count == 0
|
||||
assert msg_mock.call_count == 1
|
||||
assert msg_mock.call_args_list[0][1]['msg'] == 'Which pair?'
|
||||
# assert msg_mock.call_args_list[0][1]['callback_query_handler'] == 'forcebuy'
|
||||
keyboard = msg_mock.call_args_list[0][1]['keyboard']
|
||||
assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 4
|
||||
update = MagicMock()
|
||||
update.callback_query = MagicMock()
|
||||
update.callback_query.data = 'XRP/USDT'
|
||||
telegram._forcebuy_inline(update, None)
|
||||
assert fbuy_mock.call_count == 1
|
||||
|
||||
|
||||
def test_performance_handle(default_conf, update, ticker, fee,
|
||||
limit_buy_order, limit_sell_order, mocker) -> None:
|
||||
|
||||
@@ -927,7 +958,7 @@ def test_performance_handle(default_conf, update, ticker, fee,
|
||||
telegram._performance(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
assert 'Performance' in msg_mock.call_args_list[0][0][0]
|
||||
assert '<code>ETH/BTC\t6.20% (1)</code>' in msg_mock.call_args_list[0][0][0]
|
||||
assert '<code>ETH/BTC\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
|
||||
@@ -967,6 +998,11 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None
|
||||
)
|
||||
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
patch_get_signal(freqtradebot, (True, False))
|
||||
telegram._locks(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
assert 'No active locks.' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
msg_mock.reset_mock()
|
||||
|
||||
PairLocks.lock_pair('ETH/BTC', arrow.utcnow().shift(minutes=4).datetime, 'randreason')
|
||||
PairLocks.lock_pair('XRP/BTC', arrow.utcnow().shift(minutes=20).datetime, 'deadbeef')
|
||||
@@ -981,6 +1017,16 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None
|
||||
assert 'deadbeef' in msg_mock.call_args_list[0][0][0]
|
||||
assert 'randreason' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
context = MagicMock()
|
||||
context.args = ['XRP/BTC']
|
||||
msg_mock.reset_mock()
|
||||
telegram._delete_locks(update=update, context=context)
|
||||
|
||||
assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0]
|
||||
assert 'randreason' in msg_mock.call_args_list[0][0][0]
|
||||
assert 'XRP/BTC' not in msg_mock.call_args_list[0][0][0]
|
||||
assert 'deadbeef' not in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_whitelist_static(default_conf, update, mocker) -> None:
|
||||
|
||||
@@ -1090,6 +1136,15 @@ def test_edge_enabled(edge_conf, update, mocker) -> None:
|
||||
assert '<b>Edge only validated following pairs:</b>\n<pre>' in msg_mock.call_args_list[0][0][0]
|
||||
assert 'Pair Winrate Expectancy Stoploss' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
msg_mock.reset_mock()
|
||||
|
||||
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
|
||||
return_value={}))
|
||||
telegram._edge(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
assert '<b>Edge only validated following pairs:</b>' in msg_mock.call_args_list[0][0][0]
|
||||
assert 'Winrate' not in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
|
||||
def test_telegram_trades(mocker, update, default_conf, fee):
|
||||
|
||||
@@ -1117,8 +1172,10 @@ def test_telegram_trades(mocker, update, default_conf, fee):
|
||||
msg_mock.call_count == 1
|
||||
assert "2 recent trades</b>:" in msg_mock.call_args_list[0][0][0]
|
||||
assert "Profit (" in msg_mock.call_args_list[0][0][0]
|
||||
assert "Open Date" in msg_mock.call_args_list[0][0][0]
|
||||
assert "Close Date" in msg_mock.call_args_list[0][0][0]
|
||||
assert "<pre>" in msg_mock.call_args_list[0][0][0]
|
||||
assert bool(re.search(r"just now[ ]*XRP\/BTC \(#3\) 1.00% \(",
|
||||
msg_mock.call_args_list[0][0][0]))
|
||||
|
||||
|
||||
def test_telegram_delete_trade(mocker, update, default_conf, fee):
|
||||
@@ -1167,7 +1224,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
|
||||
telegram._show_config(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
|
||||
assert '*Exchange:* `bittrex`' in msg_mock.call_args_list[0][0][0]
|
||||
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
|
||||
assert '*Strategy:* `DefaultStrategy`' in msg_mock.call_args_list[0][0][0]
|
||||
assert '*Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
@@ -1176,7 +1233,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
|
||||
telegram._show_config(update=update, context=MagicMock())
|
||||
assert msg_mock.call_count == 1
|
||||
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
|
||||
assert '*Exchange:* `bittrex`' in msg_mock.call_args_list[0][0][0]
|
||||
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
|
||||
assert '*Strategy:* `DefaultStrategy`' in msg_mock.call_args_list[0][0][0]
|
||||
assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
|
||||
|
||||
@@ -1184,8 +1241,9 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
|
||||
def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
|
||||
|
||||
msg = {
|
||||
'type': RPCMessageType.BUY_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'type': RPCMessageType.BUY,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'limit': 1.099e-05,
|
||||
'order_type': 'limit',
|
||||
@@ -1201,7 +1259,7 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
|
||||
|
||||
telegram.send_msg(msg)
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== '\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC\n' \
|
||||
== '\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' \
|
||||
'*Amount:* `1333.33333333`\n' \
|
||||
'*Open Rate:* `0.00001099`\n' \
|
||||
'*Current Rate:* `0.00001099`\n' \
|
||||
@@ -1228,13 +1286,34 @@ def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None:
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.BUY_CANCEL_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'type': RPCMessageType.BUY_CANCEL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'reason': CANCEL_REASON['TIMEOUT']
|
||||
})
|
||||
assert (msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Bittrex:* '
|
||||
'Cancelling open buy Order for ETH/BTC. Reason: cancelled due to timeout.')
|
||||
assert (msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Binance:* '
|
||||
'Cancelling open buy Order for ETH/BTC (#1). '
|
||||
'Reason: cancelled due to timeout.')
|
||||
|
||||
|
||||
def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
|
||||
|
||||
default_conf['telegram']['notification_settings']['buy_fill'] = 'on'
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.BUY_FILL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/USDT',
|
||||
'open_rate': 200,
|
||||
'stake_amount': 100,
|
||||
'amount': 0.5,
|
||||
'open_date': arrow.utcnow().datetime
|
||||
})
|
||||
assert (msg_mock.call_args[0][0] == '\N{LARGE CIRCLE} *Binance:* '
|
||||
'Buy order for ETH/USDT (#1) filled for 200.')
|
||||
|
||||
|
||||
def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
@@ -1244,7 +1323,8 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
old_convamount = telegram._rpc._fiat_converter.convert_amount
|
||||
telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'type': RPCMessageType.SELL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'KEY/ETH',
|
||||
'gain': 'loss',
|
||||
@@ -1262,18 +1342,20 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
'close_date': arrow.utcnow(),
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n'
|
||||
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
|
||||
'*Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
|
||||
'*Sell Reason:* `stop_loss`\n'
|
||||
'*Duration:* `1:00:00 (60.0 min)`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Current Rate:* `0.00003201`\n'
|
||||
'*Close Rate:* `0.00003201`\n'
|
||||
'*Sell Reason:* `stop_loss`\n'
|
||||
'*Duration:* `1:00:00 (60.0 min)`\n'
|
||||
'*Profit:* `-57.41%` `(loss: -0.05746268 ETH / -24.812 USD)`')
|
||||
'*Close Rate:* `0.00003201`'
|
||||
)
|
||||
|
||||
msg_mock.reset_mock()
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'type': RPCMessageType.SELL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'KEY/ETH',
|
||||
'gain': 'loss',
|
||||
@@ -1290,14 +1372,15 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
|
||||
'close_date': arrow.utcnow(),
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n'
|
||||
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
|
||||
'*Profit:* `-57.41%`\n'
|
||||
'*Sell Reason:* `stop_loss`\n'
|
||||
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Current Rate:* `0.00003201`\n'
|
||||
'*Close Rate:* `0.00003201`\n'
|
||||
'*Sell Reason:* `stop_loss`\n'
|
||||
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
|
||||
'*Profit:* `-57.41%`')
|
||||
'*Close Rate:* `0.00003201`'
|
||||
)
|
||||
# Reset singleton function to avoid random breaks
|
||||
telegram._rpc._fiat_converter.convert_amount = old_convamount
|
||||
|
||||
@@ -1309,33 +1392,65 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
|
||||
old_convamount = telegram._rpc._fiat_converter.convert_amount
|
||||
telegram._rpc._fiat_converter.convert_amount = lambda a, b, c: -24.812
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
|
||||
'type': RPCMessageType.SELL_CANCEL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'KEY/ETH',
|
||||
'reason': 'Cancelled on exchange'
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH. '
|
||||
'Reason: Cancelled on exchange')
|
||||
== ('\N{WARNING SIGN} *Binance:* Cancelling open sell Order for KEY/ETH (#1).'
|
||||
' Reason: Cancelled on exchange.')
|
||||
|
||||
msg_mock.reset_mock()
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
|
||||
'type': RPCMessageType.SELL_CANCEL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'KEY/ETH',
|
||||
'reason': 'timeout'
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== ('\N{WARNING SIGN} *Binance:* Cancelling Open Sell Order for KEY/ETH. Reason: timeout')
|
||||
== ('\N{WARNING SIGN} *Binance:* Cancelling open sell Order for KEY/ETH (#1).'
|
||||
' Reason: timeout.')
|
||||
# Reset singleton function to avoid random breaks
|
||||
telegram._rpc._fiat_converter.convert_amount = old_convamount
|
||||
|
||||
|
||||
def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
|
||||
|
||||
default_conf['telegram']['notification_settings']['sell_fill'] = 'on'
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.SELL_FILL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/USDT',
|
||||
'gain': 'loss',
|
||||
'limit': 3.201e-05,
|
||||
'amount': 0.1,
|
||||
'order_type': 'market',
|
||||
'open_rate': 500,
|
||||
'close_rate': 550,
|
||||
'current_rate': 3.201e-05,
|
||||
'profit_amount': -0.05746268,
|
||||
'profit_ratio': -0.57405275,
|
||||
'stake_currency': 'ETH',
|
||||
'fiat_currency': 'USD',
|
||||
'sell_reason': SellType.STOP_LOSS.value,
|
||||
'open_date': arrow.utcnow().shift(hours=-1),
|
||||
'close_date': arrow.utcnow(),
|
||||
})
|
||||
assert msg_mock.call_args[0][0] \
|
||||
== ('\N{LARGE CIRCLE} *Binance:* Sell order for ETH/USDT (#1) filled for 550.')
|
||||
|
||||
|
||||
def test_send_msg_status_notification(default_conf, mocker) -> None:
|
||||
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.STATUS_NOTIFICATION,
|
||||
'type': RPCMessageType.STATUS,
|
||||
'status': 'running'
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == '*Status:* `running`'
|
||||
@@ -1344,7 +1459,7 @@ def test_send_msg_status_notification(default_conf, mocker) -> None:
|
||||
def test_warning_notification(default_conf, mocker) -> None:
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.WARNING_NOTIFICATION,
|
||||
'type': RPCMessageType.WARNING,
|
||||
'status': 'message'
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Warning:* `message`'
|
||||
@@ -1353,7 +1468,7 @@ def test_warning_notification(default_conf, mocker) -> None:
|
||||
def test_startup_notification(default_conf, mocker) -> None:
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.STARTUP_NOTIFICATION,
|
||||
'type': RPCMessageType.STARTUP,
|
||||
'status': '*Custom:* `Hello World`'
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == '*Custom:* `Hello World`'
|
||||
@@ -1372,8 +1487,9 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.BUY_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'type': RPCMessageType.BUY,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'limit': 1.099e-05,
|
||||
'order_type': 'limit',
|
||||
@@ -1385,7 +1501,7 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
|
||||
'amount': 1333.3333333333335,
|
||||
'open_date': arrow.utcnow().shift(hours=-1)
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == ('\N{LARGE BLUE CIRCLE} *Bittrex:* Buying ETH/BTC\n'
|
||||
assert msg_mock.call_args[0][0] == ('\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00001099`\n'
|
||||
'*Current Rate:* `0.00001099`\n'
|
||||
@@ -1397,7 +1513,8 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
|
||||
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
|
||||
|
||||
telegram.send_msg({
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'type': RPCMessageType.SELL,
|
||||
'trade_id': 1,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'KEY/ETH',
|
||||
'gain': 'loss',
|
||||
@@ -1414,14 +1531,15 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
|
||||
'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3),
|
||||
'close_date': arrow.utcnow(),
|
||||
})
|
||||
assert msg_mock.call_args[0][0] == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH\n'
|
||||
assert msg_mock.call_args[0][0] == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
|
||||
'*Profit:* `-57.41%`\n'
|
||||
'*Sell Reason:* `stop_loss`\n'
|
||||
'*Duration:* `2:35:03 (155.1 min)`\n'
|
||||
'*Amount:* `1333.33333333`\n'
|
||||
'*Open Rate:* `0.00007500`\n'
|
||||
'*Current Rate:* `0.00003201`\n'
|
||||
'*Close Rate:* `0.00003201`\n'
|
||||
'*Sell Reason:* `stop_loss`\n'
|
||||
'*Duration:* `2:35:03 (155.1 min)`\n'
|
||||
'*Profit:* `-57.41%`')
|
||||
'*Close Rate:* `0.00003201`'
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize('msg,expected', [
|
||||
@@ -1482,7 +1600,7 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
|
||||
['/count', '/start', '/stop', '/help']]
|
||||
default_keyboard = ReplyKeyboardMarkup(default_keys_list)
|
||||
|
||||
custom_keys_list = [['/daily', '/stats', '/balance', '/profit'],
|
||||
custom_keys_list = [['/daily', '/stats', '/balance', '/profit', '/profit 5'],
|
||||
['/count', '/start', '/reload_config', '/help']]
|
||||
custom_keyboard = ReplyKeyboardMarkup(custom_keys_list)
|
||||
|
||||
@@ -1516,5 +1634,5 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
|
||||
used_keyboard = bot.send_message.call_args[1]['reply_markup']
|
||||
assert used_keyboard == custom_keyboard
|
||||
assert log_has("using custom keyboard from config.json: "
|
||||
"[['/daily', '/stats', '/balance', '/profit'], ['/count', "
|
||||
"[['/daily', '/stats', '/balance', '/profit', '/profit 5'], ['/count', "
|
||||
"'/start', '/reload_config', '/help']]", caplog)
|
||||
|
@@ -5,9 +5,9 @@ from unittest.mock import MagicMock
|
||||
import pytest
|
||||
from requests import RequestException
|
||||
|
||||
from freqtrade.rpc import RPC, RPCMessageType
|
||||
from freqtrade.enums import RPCMessageType, SellType
|
||||
from freqtrade.rpc import RPC
|
||||
from freqtrade.rpc.webhook import Webhook
|
||||
from freqtrade.strategy.interface import SellType
|
||||
from tests.conftest import get_patched_freqtradebot, log_has
|
||||
|
||||
|
||||
@@ -25,6 +25,11 @@ def get_webhook_dict() -> dict:
|
||||
"value2": "limit {limit:8f}",
|
||||
"value3": "{stake_amount:8f} {stake_currency}"
|
||||
},
|
||||
"webhookbuyfill": {
|
||||
"value1": "Buy Order for {pair} filled",
|
||||
"value2": "at {open_rate:8f}",
|
||||
"value3": "{stake_amount:8f} {stake_currency}"
|
||||
},
|
||||
"webhooksell": {
|
||||
"value1": "Selling {pair}",
|
||||
"value2": "limit {limit:8f}",
|
||||
@@ -35,6 +40,11 @@ def get_webhook_dict() -> dict:
|
||||
"value2": "limit {limit:8f}",
|
||||
"value3": "profit: {profit_amount:8f} {stake_currency} ({profit_ratio})"
|
||||
},
|
||||
"webhooksellfill": {
|
||||
"value1": "Sell Order for {pair} filled",
|
||||
"value2": "at {close_rate:8f}",
|
||||
"value3": ""
|
||||
},
|
||||
"webhookstatus": {
|
||||
"value1": "Status: {status}",
|
||||
"value2": "",
|
||||
@@ -49,7 +59,7 @@ def test__init__(mocker, default_conf):
|
||||
assert webhook._config == default_conf
|
||||
|
||||
|
||||
def test_send_msg(default_conf, mocker):
|
||||
def test_send_msg_webhook(default_conf, mocker):
|
||||
default_conf["webhook"] = get_webhook_dict()
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
@@ -58,8 +68,8 @@ def test_send_msg(default_conf, mocker):
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
msg = {
|
||||
'type': RPCMessageType.BUY_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'type': RPCMessageType.BUY,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'limit': 0.005,
|
||||
'stake_amount': 0.8,
|
||||
@@ -76,11 +86,11 @@ def test_send_msg(default_conf, mocker):
|
||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||
default_conf["webhook"]["webhookbuy"]["value3"].format(**msg))
|
||||
# Test buy cancel
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
msg_mock.reset_mock()
|
||||
|
||||
msg = {
|
||||
'type': RPCMessageType.BUY_CANCEL_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'type': RPCMessageType.BUY_CANCEL,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'limit': 0.005,
|
||||
'stake_amount': 0.8,
|
||||
@@ -96,12 +106,32 @@ def test_send_msg(default_conf, mocker):
|
||||
default_conf["webhook"]["webhookbuycancel"]["value2"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||
default_conf["webhook"]["webhookbuycancel"]["value3"].format(**msg))
|
||||
# Test sell
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
# Test buy fill
|
||||
msg_mock.reset_mock()
|
||||
|
||||
msg = {
|
||||
'type': RPCMessageType.SELL_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'type': RPCMessageType.BUY_FILL,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'open_rate': 0.005,
|
||||
'stake_amount': 0.8,
|
||||
'stake_amount_fiat': 500,
|
||||
'stake_currency': 'BTC',
|
||||
'fiat_currency': 'EUR'
|
||||
}
|
||||
webhook.send_msg(msg=msg)
|
||||
assert msg_mock.call_count == 1
|
||||
assert (msg_mock.call_args[0][0]["value1"] ==
|
||||
default_conf["webhook"]["webhookbuyfill"]["value1"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||
default_conf["webhook"]["webhookbuyfill"]["value2"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||
default_conf["webhook"]["webhookbuyfill"]["value3"].format(**msg))
|
||||
# Test sell
|
||||
msg_mock.reset_mock()
|
||||
msg = {
|
||||
'type': RPCMessageType.SELL,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'gain': "profit",
|
||||
'limit': 0.005,
|
||||
@@ -123,11 +153,10 @@ def test_send_msg(default_conf, mocker):
|
||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||
default_conf["webhook"]["webhooksell"]["value3"].format(**msg))
|
||||
# Test sell cancel
|
||||
msg_mock = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
msg_mock.reset_mock()
|
||||
msg = {
|
||||
'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'type': RPCMessageType.SELL_CANCEL,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'gain': "profit",
|
||||
'limit': 0.005,
|
||||
@@ -148,9 +177,35 @@ def test_send_msg(default_conf, mocker):
|
||||
default_conf["webhook"]["webhooksellcancel"]["value2"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||
default_conf["webhook"]["webhooksellcancel"]["value3"].format(**msg))
|
||||
for msgtype in [RPCMessageType.STATUS_NOTIFICATION,
|
||||
RPCMessageType.WARNING_NOTIFICATION,
|
||||
RPCMessageType.STARTUP_NOTIFICATION]:
|
||||
# Test Sell fill
|
||||
msg_mock.reset_mock()
|
||||
msg = {
|
||||
'type': RPCMessageType.SELL_FILL,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'gain': "profit",
|
||||
'close_rate': 0.005,
|
||||
'amount': 0.8,
|
||||
'order_type': 'limit',
|
||||
'open_rate': 0.004,
|
||||
'current_rate': 0.005,
|
||||
'profit_amount': 0.001,
|
||||
'profit_ratio': 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"]["webhooksellfill"]["value1"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value2"] ==
|
||||
default_conf["webhook"]["webhooksellfill"]["value2"].format(**msg))
|
||||
assert (msg_mock.call_args[0][0]["value3"] ==
|
||||
default_conf["webhook"]["webhooksellfill"]["value3"].format(**msg))
|
||||
|
||||
for msgtype in [RPCMessageType.STATUS,
|
||||
RPCMessageType.WARNING,
|
||||
RPCMessageType.STARTUP]:
|
||||
# Test notification
|
||||
msg = {
|
||||
'type': msgtype,
|
||||
@@ -173,8 +228,8 @@ def test_exception_send_msg(default_conf, mocker, caplog):
|
||||
del default_conf["webhook"]["webhookbuy"]
|
||||
|
||||
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
||||
webhook.send_msg({'type': RPCMessageType.BUY_NOTIFICATION})
|
||||
assert log_has(f"Message type '{RPCMessageType.BUY_NOTIFICATION}' not configured for webhooks",
|
||||
webhook.send_msg({'type': RPCMessageType.BUY})
|
||||
assert log_has(f"Message type '{RPCMessageType.BUY}' not configured for webhooks",
|
||||
caplog)
|
||||
|
||||
default_conf["webhook"] = get_webhook_dict()
|
||||
@@ -183,8 +238,8 @@ def test_exception_send_msg(default_conf, mocker, caplog):
|
||||
mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock)
|
||||
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
||||
msg = {
|
||||
'type': RPCMessageType.BUY_NOTIFICATION,
|
||||
'exchange': 'Bittrex',
|
||||
'type': RPCMessageType.BUY,
|
||||
'exchange': 'Binance',
|
||||
'pair': 'ETH/BTC',
|
||||
'limit': 0.005,
|
||||
'order_type': 'limit',
|
||||
@@ -225,3 +280,15 @@ def test__send_msg(default_conf, mocker, caplog):
|
||||
mocker.patch("freqtrade.rpc.webhook.post", post)
|
||||
webhook._send_msg(msg)
|
||||
assert log_has('Could not call webhook url. Exception: ', caplog)
|
||||
|
||||
|
||||
def test__send_msg_with_json_format(default_conf, mocker, caplog):
|
||||
default_conf["webhook"] = get_webhook_dict()
|
||||
default_conf["webhook"]["format"] = "json"
|
||||
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
|
||||
msg = {'text': 'Hello'}
|
||||
post = MagicMock()
|
||||
mocker.patch("freqtrade.rpc.webhook.post", post)
|
||||
webhook._send_msg(msg)
|
||||
|
||||
assert post.call_args[1] == {'json': msg}
|
||||
|
Reference in New Issue
Block a user