stable/tests/rpc/test_rpc.py

971 lines
33 KiB
Python
Raw Normal View History

2018-07-29 14:09:44 +00:00
# pragma pylint: disable=missing-docstring, C0103
2018-02-13 03:45:59 +00:00
# pragma pylint: disable=invalid-sequence-index, invalid-name, too-many-arguments
from datetime import datetime
from unittest.mock import ANY, MagicMock, PropertyMock
2018-02-13 03:45:59 +00:00
2018-06-08 02:52:50 +00:00
import pytest
2018-10-10 20:03:54 +00:00
from numpy import isnan
2018-06-08 02:52:50 +00:00
from freqtrade.edge import PairInfo
from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError
from freqtrade.persistence import Trade
from freqtrade.rpc import RPC, RPCException
from freqtrade.rpc.fiat_convert import CryptoToFiatConverter
2018-02-13 03:45:59 +00:00
from freqtrade.state import State
from tests.conftest import (create_mock_trades, get_patched_freqtradebot,
patch_get_signal)
2018-02-13 03:45:59 +00:00
# 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
2018-02-13 03:45:59 +00:00
# Unit tests
2019-10-26 08:08:23 +00:00
def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2018-06-17 19:24:51 +00:00
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2018-06-16 23:23:12 +00:00
get_fee=fee,
2018-02-13 03:45:59 +00:00
)
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
freqtradebot.state = State.RUNNING
2018-06-08 02:52:50 +00:00
with pytest.raises(RPCException, match=r'.*no active trade*'):
rpc._rpc_trade_status()
freqtradebot.enter_positions()
2020-06-04 05:20:50 +00:00
trades = Trade.get_open_trades()
trades[0].open_order_id = None
freqtradebot.exit_positions(trades)
results = rpc._rpc_trade_status()
2020-06-04 05:20:50 +00:00
assert results[0] == {
'trade_id': 1,
'pair': 'ETH/BTC',
'base_currency': 'BTC',
2019-05-06 04:56:07 +00:00
'open_date': ANY,
'open_date_hum': ANY,
2020-05-24 06:47:10 +00:00
'open_timestamp': ANY,
2020-04-06 09:32:00 +00:00
'is_open': ANY,
'fee_open': ANY,
2020-04-30 04:58:43 +00:00
'fee_open_cost': ANY,
'fee_open_currency': ANY,
2020-06-04 05:20:50 +00:00
'fee_close': fee.return_value,
2020-04-30 04:58:43 +00:00
'fee_close_cost': ANY,
'fee_close_currency': ANY,
2020-04-06 09:32:00 +00:00
'open_rate_requested': ANY,
2020-06-04 05:20:50 +00:00
'open_trade_price': 0.0010025,
2020-04-06 09:32:00 +00:00
'close_rate_requested': ANY,
'sell_reason': ANY,
'sell_order_status': ANY,
2020-04-06 09:32:00 +00:00
'min_rate': ANY,
'max_rate': ANY,
'strategy': ANY,
'ticker_interval': ANY,
'timeframe': ANY,
2020-04-06 09:32:00 +00:00
'open_order_id': ANY,
2019-05-06 04:56:07 +00:00
'close_date': None,
'close_date_hum': None,
2020-05-24 06:47:10 +00:00
'close_timestamp': None,
'open_rate': 1.098e-05,
'close_rate': None,
'current_rate': 1.099e-05,
'amount': 91.07468123,
'amount_requested': 91.07468123,
2019-04-04 10:06:45 +00:00
'stake_amount': 0.001,
'close_profit': None,
2020-05-26 17:25:03 +00:00
'close_profit_pct': None,
'close_profit_abs': None,
2020-05-24 06:47:10 +00:00
'current_profit': -0.00408133,
2020-05-26 17:25:03 +00:00
'current_profit_pct': -0.41,
2020-06-04 05:20:50 +00:00
'current_profit_abs': -4.09e-06,
'stop_loss': 9.882e-06,
'stop_loss_abs': 9.882e-06,
'stop_loss_pct': -10.0,
'stop_loss_ratio': -0.1,
'stoploss_order_id': None,
2020-06-04 05:20:50 +00:00
'stoploss_last_update': ANY,
'stoploss_last_update_timestamp': ANY,
'initial_stop_loss': 9.882e-06,
'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_entry_dist': -0.00010475,
'stoploss_entry_dist_ratio': -0.10448878,
'open_order': None,
'exchange': 'bittrex',
2020-06-04 05:20:50 +00:00
}
2020-02-22 10:12:33 +00:00
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
results = rpc._rpc_trade_status()
assert isnan(results[0]['current_profit'])
assert isnan(results[0]['current_rate'])
2020-06-04 05:20:50 +00:00
assert results[0] == {
'trade_id': 1,
'pair': 'ETH/BTC',
'base_currency': 'BTC',
2019-05-06 04:56:07 +00:00
'open_date': ANY,
'open_date_hum': ANY,
2020-05-24 06:47:10 +00:00
'open_timestamp': ANY,
2020-04-06 09:32:00 +00:00
'is_open': ANY,
'fee_open': ANY,
2020-04-30 04:58:43 +00:00
'fee_open_cost': ANY,
'fee_open_currency': ANY,
2020-06-04 05:20:50 +00:00
'fee_close': fee.return_value,
2020-04-30 04:58:43 +00:00
'fee_close_cost': ANY,
'fee_close_currency': ANY,
2020-04-06 09:32:00 +00:00
'open_rate_requested': ANY,
'open_trade_price': ANY,
'close_rate_requested': ANY,
'sell_reason': ANY,
'sell_order_status': ANY,
2020-04-06 09:32:00 +00:00
'min_rate': ANY,
'max_rate': ANY,
'strategy': ANY,
'ticker_interval': ANY,
'timeframe': ANY,
2020-04-06 09:32:00 +00:00
'open_order_id': ANY,
2019-05-06 04:56:07 +00:00
'close_date': None,
'close_date_hum': None,
2020-05-24 06:47:10 +00:00
'close_timestamp': None,
'open_rate': 1.098e-05,
'close_rate': None,
'current_rate': ANY,
'amount': 91.07468123,
'amount_requested': 91.07468123,
2019-04-04 10:06:45 +00:00
'stake_amount': 0.001,
'close_profit': None,
2020-05-26 17:25:03 +00:00
'close_profit_pct': None,
'close_profit_abs': None,
'current_profit': ANY,
2020-05-26 17:25:03 +00:00
'current_profit_pct': ANY,
2020-06-04 05:20:50 +00:00
'current_profit_abs': ANY,
'stop_loss': 9.882e-06,
'stop_loss_abs': 9.882e-06,
'stop_loss_pct': -10.0,
'stop_loss_ratio': -0.1,
'stoploss_order_id': None,
2020-06-04 05:20:50 +00:00
'stoploss_last_update': ANY,
'stoploss_last_update_timestamp': ANY,
'initial_stop_loss': 9.882e-06,
'initial_stop_loss_abs': 9.882e-06,
'initial_stop_loss_pct': -10.0,
'initial_stop_loss_ratio': -0.1,
'stoploss_current_dist': ANY,
'stoploss_current_dist_ratio': ANY,
'stoploss_entry_dist': -0.00010475,
'stoploss_entry_dist_ratio': -0.10448878,
'open_order': None,
'exchange': 'bittrex',
2020-06-04 05:20:50 +00:00
}
2019-10-26 08:08:23 +00:00
def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
2019-11-12 13:58:41 +00:00
mocker.patch.multiple(
2020-03-07 12:01:26 +00:00
'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}),
2019-11-12 13:58:41 +00:00
)
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2018-06-17 19:24:51 +00:00
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2018-06-16 23:23:12 +00:00
get_fee=fee,
2018-02-13 03:45:59 +00:00
)
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
freqtradebot.state = State.RUNNING
2019-12-29 18:51:47 +00:00
with pytest.raises(RPCException, match=r'.*no active trade*'):
2019-11-12 13:58:41 +00:00
rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
freqtradebot.enter_positions()
2019-11-12 13:58:41 +00:00
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
assert "Since" in headers
assert "Pair" in headers
assert 'instantly' == result[0][2]
2020-02-08 20:02:52 +00:00
assert 'ETH/BTC' in result[0][1]
assert '-0.41%' == result[0][3]
2019-11-12 13:58:41 +00:00
# Test with fiatconvert
rpc._fiat_converter = CryptoToFiatConverter()
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
2019-11-12 12:54:26 +00:00
assert "Since" in headers
assert "Pair" in headers
2019-11-12 13:58:41 +00:00
assert 'instantly' == result[0][2]
2020-02-08 20:02:52 +00:00
assert 'ETH/BTC' in result[0][1]
assert '-0.41% (-0.06)' == result[0][3]
2020-02-22 10:12:33 +00:00
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.get_sell_rate',
MagicMock(side_effect=ExchangeError("Pair 'ETH/BTC' not available")))
2019-11-12 13:58:41 +00:00
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
assert 'instantly' == result[0][2]
2020-02-08 20:02:52 +00:00
assert 'ETH/BTC' in result[0][1]
2019-11-12 13:58:41 +00:00
assert 'nan%' == result[0][3]
def test_rpc_daily_profit(default_conf, update, ticker, fee,
2018-06-16 23:23:12 +00:00
limit_buy_order, limit_sell_order, markets, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2018-06-17 19:24:51 +00:00
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2018-06-16 23:23:12 +00:00
get_fee=fee,
2019-03-05 18:46:03 +00:00
markets=PropertyMock(return_value=markets)
2018-02-13 03:45:59 +00:00
)
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))
stake_currency = default_conf['stake_currency']
fiat_display_currency = default_conf['fiat_display_currency']
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
2018-07-21 18:44:38 +00:00
rpc._fiat_converter = CryptoToFiatConverter()
# Create some test data
freqtradebot.enter_positions()
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'
2018-06-08 02:52:50 +00:00
days = rpc._rpc_daily_profit(7, stake_currency, fiat_display_currency)
2020-05-17 18:12:01 +00:00
assert len(days['data']) == 7
assert days['stake_currency'] == default_conf['stake_currency']
assert days['fiat_display_currency'] == default_conf['fiat_display_currency']
for day in days['data']:
# [datetime.date(2018, 1, 11), '0.00000000 BTC', '0.000 USD']
2020-08-18 18:15:41 +00:00
assert (day['abs_profit'] == 0.0 or
day['abs_profit'] == 0.00006217)
2020-08-18 18:15:41 +00:00
assert (day['fiat_value'] == 0.0 or
day['fiat_value'] == 0.76748865)
# ensure first day is current date
2020-05-17 18:12:01 +00:00
assert str(days['data'][0]['date']) == str(datetime.utcnow().date())
# Try invalid data
2018-06-08 02:52:50 +00:00
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_history(mocker, default_conf, markets, fee):
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets)
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
create_mock_trades(fee)
rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter()
trades = rpc._rpc_trade_history(2)
assert len(trades['trades']) == 2
assert trades['trades_count'] == 2
assert isinstance(trades['trades'][0], dict)
assert isinstance(trades['trades'][1], dict)
trades = rpc._rpc_trade_history(0)
assert len(trades['trades']) == 2
assert trades['trades_count'] == 2
# The first closed trade is for ETC ... sorting is descending
assert trades['trades'][-1]['pair'] == 'ETC/BTC'
assert trades['trades'][0]['pair'] == 'XRP/BTC'
def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog):
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
stoploss_mock = MagicMock()
cancel_mock = MagicMock()
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets),
cancel_order=cancel_mock,
cancel_stoploss_order=stoploss_mock,
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
freqtradebot.strategy.order_types['stoploss_on_exchange'] = True
create_mock_trades(fee)
rpc = RPC(freqtradebot)
with pytest.raises(RPCException, match='invalid argument'):
rpc._rpc_delete('200')
create_mock_trades(fee)
trades = Trade.query.all()
trades[1].stoploss_order_id = '1234'
trades[2].stoploss_order_id = '1234'
assert len(trades) > 2
res = rpc._rpc_delete('1')
assert isinstance(res, dict)
assert res['result'] == 'success'
assert res['trade_id'] == '1'
assert res['cancel_order_count'] == 1
assert cancel_mock.call_count == 1
assert stoploss_mock.call_count == 0
cancel_mock.reset_mock()
stoploss_mock.reset_mock()
res = rpc._rpc_delete('2')
assert isinstance(res, dict)
assert cancel_mock.call_count == 1
assert stoploss_mock.call_count == 1
assert res['cancel_order_count'] == 2
stoploss_mock = mocker.patch('freqtrade.exchange.Exchange.cancel_stoploss_order',
side_effect=InvalidOrderException)
res = rpc._rpc_delete('3')
assert stoploss_mock.call_count == 1
stoploss_mock.reset_mock()
cancel_mock = mocker.patch('freqtrade.exchange.Exchange.cancel_order',
side_effect=InvalidOrderException)
res = rpc._rpc_delete('4')
assert cancel_mock.call_count == 1
assert stoploss_mock.call_count == 0
def test_rpc_trade_statistics(default_conf, ticker, ticker_sell_up, fee,
2019-10-26 08:08:23 +00:00
limit_buy_order, limit_sell_order, mocker) -> None:
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2020-03-07 12:01:26 +00:00
'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}),
2018-02-13 03:45:59 +00:00
)
2018-07-21 18:44:38 +00:00
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2018-06-17 19:24:51 +00:00
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2018-06-16 23:23:12 +00:00
get_fee=fee,
2018-02-13 03:45:59 +00:00
)
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))
stake_currency = default_conf['stake_currency']
fiat_display_currency = default_conf['fiat_display_currency']
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
2018-07-21 18:44:38 +00:00
rpc._fiat_converter = CryptoToFiatConverter()
2018-02-13 03:45:59 +00:00
res = rpc._rpc_trade_statistics(stake_currency, fiat_display_currency)
assert res['trade_count'] == 0
assert res['first_trade_date'] == ''
assert res['first_trade_timestamp'] == 0
assert res['latest_trade_date'] == ''
assert res['latest_trade_timestamp'] == 0
# Create some test data
freqtradebot.enter_positions()
trade = Trade.query.first()
# Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order)
2018-02-13 03:45:59 +00:00
# Update the ticker with a market going up
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2018-06-17 19:24:51 +00:00
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker_sell_up
2018-02-13 03:45:59 +00:00
)
trade.update(limit_sell_order)
trade.close_date = datetime.utcnow()
trade.is_open = False
freqtradebot.enter_positions()
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(
2018-06-17 19:24:51 +00:00
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker_sell_up
)
trade.update(limit_sell_order)
trade.close_date = datetime.utcnow()
trade.is_open = False
2018-06-08 02:52:50 +00:00
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.802e-05)
assert prec_satoshi(stats['profit_all_percent'], 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'
2018-03-24 19:59:09 +00:00
assert stats['best_pair'] == 'ETH/BTC'
assert prec_satoshi(stats['best_rate'], 6.2)
# Test non-available pair
2020-02-22 10:12:33 +00:00
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.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['best_pair'] == 'ETH/BTC'
assert prec_satoshi(stats['best_rate'], 6.2)
assert isnan(stats['profit_all_coin'])
2018-10-10 20:03:54 +00:00
# Test that rpc_trade_statistics can handle trades that lacks
# trade.open_rate (it is set to None)
2019-10-26 08:08:23 +00:00
def test_rpc_trade_statistics_closed(mocker, default_conf, ticker, fee,
ticker_sell_up, limit_buy_order, limit_sell_order):
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2020-03-07 12:01:26 +00:00
'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}),
2018-02-13 03:45:59 +00:00
)
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
return_value=15000.0)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2018-06-17 19:24:51 +00:00
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2018-06-16 23:23:12 +00:00
get_fee=fee,
2018-02-13 03:45:59 +00:00
)
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))
stake_currency = default_conf['stake_currency']
fiat_display_currency = default_conf['fiat_display_currency']
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
# Create some test data
freqtradebot.enter_positions()
trade = Trade.query.first()
# Simulate fulfilled LIMIT_BUY order for trade
trade.update(limit_buy_order)
# Update the ticker with a market going up
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2018-06-17 19:24:51 +00:00
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker_sell_up,
get_fee=fee
2018-02-13 03:45:59 +00:00
)
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
2018-06-08 02:52:50 +00:00
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'
2018-03-24 19:59:09 +00:00
assert stats['best_pair'] == 'ETH/BTC'
assert prec_satoshi(stats['best_rate'], 6.2)
2019-07-03 18:07:26 +00:00
def test_rpc_balance_handle_error(default_conf, mocker):
2018-05-14 21:31:56 +00:00
mock_balance = {
'BTC': {
'free': 10.0,
'total': 12.0,
'used': 2.0,
2018-02-13 03:45:59 +00:00
},
2018-05-14 21:31:56 +00:00
'ETH': {
2018-08-08 19:55:48 +00:00
'free': 1.0,
'total': 5.0,
'used': 4.0,
2018-02-13 03:45:59 +00:00
}
2018-05-14 21:31:56 +00:00
}
2018-08-08 19:55:48 +00:00
# ETH will be skipped due to mocked Error below
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2020-03-07 12:01:26 +00:00
'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}),
2018-02-13 03:45:59 +00:00
)
2018-07-21 18:44:38 +00:00
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2018-06-17 19:24:51 +00:00
'freqtrade.exchange.Exchange',
2018-08-08 19:55:48 +00:00
get_balances=MagicMock(return_value=mock_balance),
2019-11-14 19:12:41 +00:00
get_tickers=MagicMock(side_effect=TemporaryError('Could not load ticker due to xxx'))
2018-02-13 03:45:59 +00:00
)
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
2018-07-21 18:44:38 +00:00
rpc._fiat_converter = CryptoToFiatConverter()
2019-11-14 19:12:41 +00:00
with pytest.raises(RPCException, match="Error getting current tickers."):
2019-11-15 05:33:07 +00:00
rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency'])
2018-08-08 19:54:52 +00:00
2019-11-14 19:12:41 +00:00
def test_rpc_balance_handle(default_conf, mocker, tickers):
2019-07-03 18:07:26 +00:00
mock_balance = {
'BTC': {
'free': 10.0,
'total': 12.0,
'used': 2.0,
},
'ETH': {
'free': 1.0,
'total': 5.0,
'used': 4.0,
},
2019-11-14 19:12:41 +00:00
'USDT': {
2019-07-03 18:07:26 +00:00
'free': 5.0,
'total': 10.0,
'used': 5.0,
}
}
mocker.patch.multiple(
2020-03-07 12:01:26 +00:00
'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
get_price=MagicMock(return_value={'bitcoin': {'usd': 15000.0}}),
2019-07-03 18:07:26 +00:00
)
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),
2019-11-14 19:12:41 +00:00
get_tickers=tickers,
2019-07-03 18:07:26 +00:00
get_valid_pair_combination=MagicMock(
2019-11-14 19:12:41 +00:00
side_effect=lambda a, b: f"{b}/{a}" if a == "USDT" else f"{a}/{b}")
2019-07-03 18:07:26 +00:00
)
default_conf['dry_run'] = False
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
2019-07-03 18:07:26 +00:00
patch_get_signal(freqtradebot, (True, False))
rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter()
2019-11-15 05:33:07 +00:00
result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency'])
2019-11-14 19:12:41 +00:00
assert prec_satoshi(result['total'], 12.309096315)
assert prec_satoshi(result['value'], 184636.44472997)
2019-07-03 18:07:26 +00:00
assert 'USD' == result['symbol']
assert result['currencies'] == [
{'currency': 'BTC',
2019-11-15 05:33:07 +00:00
'free': 10.0,
'balance': 12.0,
'used': 2.0,
'est_stake': 12.0,
'stake': 'BTC',
2019-07-03 18:07:26 +00:00
},
{'free': 1.0,
2019-07-03 18:07:26 +00:00
'balance': 5.0,
'currency': 'ETH',
2019-11-15 05:33:07 +00:00
'est_stake': 0.30794,
'used': 4.0,
'stake': 'BTC',
2019-07-03 18:07:26 +00:00
},
{'free': 5.0,
2019-07-03 18:07:26 +00:00
'balance': 10.0,
2019-11-14 19:12:41 +00:00
'currency': 'USDT',
2019-11-15 05:33:07 +00:00
'est_stake': 0.0011563153318162476,
'used': 5.0,
'stake': 'BTC',
}
2019-07-03 18:07:26 +00:00
]
2019-11-14 19:12:41 +00:00
assert result['total'] == 12.309096315331816
2019-07-03 18:07:26 +00:00
2018-02-13 03:45:59 +00:00
def test_rpc_start(mocker, default_conf) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2018-06-17 19:24:51 +00:00
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock()
2018-02-13 03:45:59 +00:00
)
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED
2018-02-13 03:45:59 +00:00
2018-06-08 02:52:50 +00:00
result = rpc._rpc_start()
assert {'status': 'starting trader ...'} == result
assert freqtradebot.state == State.RUNNING
2018-02-13 03:45:59 +00:00
2018-06-08 02:52:50 +00:00
result = rpc._rpc_start()
assert {'status': 'already running'} == result
assert freqtradebot.state == State.RUNNING
2018-02-13 03:45:59 +00:00
def test_rpc_stop(mocker, default_conf) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2018-06-17 19:24:51 +00:00
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock()
2018-02-13 03:45:59 +00:00
)
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
freqtradebot.state = State.RUNNING
2018-02-13 03:45:59 +00:00
2018-06-08 02:52:50 +00:00
result = rpc._rpc_stop()
assert {'status': 'stopping trader ...'} == result
assert freqtradebot.state == State.STOPPED
2018-02-13 03:45:59 +00:00
2018-06-08 02:52:50 +00:00
result = rpc._rpc_stop()
assert {'status': 'already stopped'} == result
assert freqtradebot.state == State.STOPPED
2018-02-13 03:45:59 +00:00
2019-03-17 18:36:02 +00:00
def test_rpc_stopbuy(mocker, default_conf) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=MagicMock()
2019-03-17 18:36:02 +00:00
)
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
2019-03-17 18:36:02 +00:00
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_config to reset.'} == result
2019-03-17 18:36:02 +00:00
assert freqtradebot.config['max_open_trades'] == 0
2019-10-26 08:08:23 +00:00
def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
2018-02-13 03:45:59 +00:00
cancel_order_mock = MagicMock()
mocker.patch.multiple(
2018-06-17 19:24:51 +00:00
'freqtrade.exchange.Exchange',
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2018-02-13 03:45:59 +00:00
cancel_order=cancel_order_mock,
fetch_order=MagicMock(
2018-02-13 03:45:59 +00:00
return_value={
'status': 'closed',
'type': 'limit',
'side': 'buy'
2018-02-13 03:45:59 +00:00
}
2018-04-21 17:39:18 +00:00
),
get_fee=fee,
2018-02-13 03:45:59 +00:00
)
mocker.patch('freqtrade.wallets.Wallets.get_free', return_value=1000)
2018-02-13 03:45:59 +00:00
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
freqtradebot.state = State.STOPPED
with pytest.raises(RPCException, match=r'.*trader is not running*'):
2018-06-08 02:52:50 +00:00
rpc._rpc_forcesell(None)
2018-02-13 03:45:59 +00:00
freqtradebot.state = State.RUNNING
with pytest.raises(RPCException, match=r'.*invalid argument*'):
2018-06-08 02:52:50 +00:00
rpc._rpc_forcesell(None)
2018-02-13 03:45:59 +00:00
2019-04-30 04:25:02 +00:00
msg = rpc._rpc_forcesell('all')
assert msg == {'result': 'Created sell orders for all open trades.'}
2018-02-13 03:45:59 +00:00
freqtradebot.enter_positions()
2019-04-30 04:25:02 +00:00
msg = rpc._rpc_forcesell('all')
assert msg == {'result': 'Created sell orders for all open trades.'}
2018-02-13 03:45:59 +00:00
2019-04-30 04:25:02 +00:00
msg = rpc._rpc_forcesell('1')
2019-05-01 14:21:14 +00:00
assert msg == {'result': 'Created sell order for trade 1.'}
2018-02-13 03:45:59 +00:00
freqtradebot.state = State.STOPPED
with pytest.raises(RPCException, match=r'.*trader is not running*'):
2018-06-08 02:52:50 +00:00
rpc._rpc_forcesell(None)
2018-02-13 03:45:59 +00:00
with pytest.raises(RPCException, match=r'.*trader is not running*'):
2018-06-08 02:52:50 +00:00
rpc._rpc_forcesell('all')
2018-02-13 03:45:59 +00:00
freqtradebot.state = State.RUNNING
2018-02-13 03:45:59 +00:00
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
2018-02-13 03:45:59 +00:00
mocker.patch(
'freqtrade.exchange.Exchange.fetch_order',
2018-02-13 03:45:59 +00:00
return_value={
'status': 'open',
'type': 'limit',
'side': 'buy',
'filled': filled_amount
2018-02-13 03:45:59 +00:00
}
)
# check that the trade is called, which is done by ensuring exchange.cancel_order is called
# and trade amount is updated
2018-06-08 02:52:50 +00:00
rpc._rpc_forcesell('1')
2018-02-13 03:45:59 +00:00
assert cancel_order_mock.call_count == 1
assert trade.amount == filled_amount
freqtradebot.enter_positions()
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.fetch_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
2019-04-30 04:25:02 +00:00
msg = rpc._rpc_forcesell('2')
2019-05-01 14:21:14 +00:00
assert msg == {'result': 'Created sell order for trade 2.'}
assert cancel_order_mock.call_count == 2
assert trade.amount == amount
2018-02-13 03:45:59 +00:00
freqtradebot.enter_positions()
2018-02-13 03:45:59 +00:00
# make an limit-sell open trade
mocker.patch(
'freqtrade.exchange.Exchange.fetch_order',
2018-02-13 03:45:59 +00:00
return_value={
'status': 'open',
'type': 'limit',
'side': 'sell'
2018-02-13 03:45:59 +00:00
}
)
2019-04-30 04:25:02 +00:00
msg = rpc._rpc_forcesell('3')
2019-05-01 14:21:14 +00:00
assert msg == {'result': 'Created sell order for trade 3.'}
2018-02-13 03:45:59 +00:00
# status quo, no exchange calls
assert cancel_order_mock.call_count == 2
2018-02-13 03:45:59 +00:00
def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
2019-10-26 08:08:23 +00:00
limit_sell_order, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2018-06-17 19:24:51 +00:00
'freqtrade.exchange.Exchange',
2018-02-13 03:45:59 +00:00
get_balances=MagicMock(return_value=ticker),
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2018-06-16 23:23:12 +00:00
get_fee=fee,
2018-02-13 03:45:59 +00:00
)
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
# Create some test data
freqtradebot.enter_positions()
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
2018-06-08 02:52:50 +00:00
res = rpc._rpc_performance()
assert len(res) == 1
2018-03-24 19:59:09 +00:00
assert res[0]['pair'] == 'ETH/BTC'
assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit'], 6.2)
2018-02-13 03:45:59 +00:00
2019-10-26 08:08:23 +00:00
def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
2018-02-13 03:45:59 +00:00
mocker.patch.multiple(
2018-06-17 19:24:51 +00:00
'freqtrade.exchange.Exchange',
2018-02-13 03:45:59 +00:00
get_balances=MagicMock(return_value=ticker),
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2018-04-21 17:39:18 +00:00
get_fee=fee,
2018-02-13 03:45:59 +00:00
)
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot, (True, False))
2018-02-13 03:45:59 +00:00
rpc = RPC(freqtradebot)
2019-04-06 18:01:29 +00:00
counts = rpc._rpc_count()
assert counts["current"] == 0
2018-02-13 03:45:59 +00:00
# Create some test data
freqtradebot.enter_positions()
2019-04-06 18:01:29 +00:00
counts = rpc._rpc_count()
assert counts["current"] == 1
2018-10-09 18:04:53 +00:00
def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> None:
2018-10-09 18:04:53 +00:00
default_conf['forcebuy_enable'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
buy_mm = MagicMock(return_value=limit_buy_order_open)
2018-10-09 18:04:53 +00:00
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
2019-12-18 15:34:30 +00:00
fetch_ticker=ticker,
2018-10-09 18:04:53 +00:00
get_fee=fee,
buy=buy_mm
)
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
2018-10-09 18:04:53 +00:00
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()['bid']
2018-10-09 18:04:53 +00:00
# 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.*'):
2020-02-25 06:16:37 +00:00
rpc._rpc_forcebuy('LTC/ETH', 0.0001)
2018-10-09 18:04:53 +00:00
pair = 'XRP/BTC'
# Test not buying
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
2019-11-23 14:20:53 +00:00
freqtradebot.config['stake_amount'] = 0.0000001
2018-10-09 18:04:53 +00:00
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'
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
2018-10-09 18:04:53 +00:00
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:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
2018-10-09 18:04:53 +00:00
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)
2018-11-10 19:07:09 +00:00
def test_rpc_whitelist(mocker, default_conf) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
2018-11-10 19:07:09 +00:00
rpc = RPC(freqtradebot)
ret = rpc._rpc_whitelist()
2019-11-09 13:00:32 +00:00
assert len(ret['method']) == 1
assert 'StaticPairList' in ret['method']
2018-11-10 19:07:09 +00:00
assert ret['whitelist'] == default_conf['exchange']['pair_whitelist']
def test_rpc_whitelist_dynamic(mocker, default_conf) -> None:
2019-11-09 13:00:32 +00:00
default_conf['pairlists'] = [{'method': 'VolumePairList',
2019-11-19 05:34:54 +00:00
'number_assets': 4,
2019-11-09 13:00:32 +00:00
}]
2018-12-02 21:12:12 +00:00
mocker.patch('freqtrade.exchange.Exchange.exchange_has', MagicMock(return_value=True))
2018-11-10 19:07:09 +00:00
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
2018-11-10 19:07:09 +00:00
rpc = RPC(freqtradebot)
ret = rpc._rpc_whitelist()
2019-11-09 13:00:32 +00:00
assert len(ret['method']) == 1
assert 'VolumePairList' in ret['method']
2018-12-03 19:31:25 +00:00
assert ret['length'] == 4
2018-11-10 19:07:09 +00:00
assert ret['whitelist'] == default_conf['exchange']['pair_whitelist']
2019-03-24 15:09:20 +00:00
def test_rpc_blacklist(mocker, default_conf) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
2019-03-24 15:09:20 +00:00
rpc = RPC(freqtradebot)
ret = rpc._rpc_blacklist(None)
2019-11-09 13:00:32 +00:00
assert len(ret['method']) == 1
assert 'StaticPairList' in ret['method']
assert len(ret['blacklist']) == 2
2019-03-24 15:09:20 +00:00
assert ret['blacklist'] == default_conf['exchange']['pair_blacklist']
assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC']
ret = rpc._rpc_blacklist(["ETH/BTC"])
2019-11-09 13:00:32 +00:00
assert 'StaticPairList' in ret['method']
assert len(ret['blacklist']) == 3
assert ret['blacklist'] == default_conf['exchange']['pair_blacklist']
assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC']
ret = rpc._rpc_blacklist(["ETH/BTC"])
assert 'errors' in ret
assert isinstance(ret['errors'], dict)
assert ret['errors']['ETH/BTC']['error_msg'] == 'Pair ETH/BTC already in pairlist.'
ret = rpc._rpc_blacklist(["ETH/ETH"])
assert 'StaticPairList' in ret['method']
assert len(ret['blacklist']) == 3
assert ret['blacklist'] == default_conf['exchange']['pair_blacklist']
assert ret['blacklist'] == ['DOGE/BTC', 'HOT/BTC', 'ETH/BTC']
assert 'errors' in ret
assert isinstance(ret['errors'], dict)
assert ret['errors']['ETH/ETH']['error_msg'] == 'Pair ETH/ETH does not match stake currency.'
2019-03-27 13:04:33 +00:00
def test_rpc_edge_disabled(mocker, default_conf) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc = RPC(freqtradebot)
with pytest.raises(RPCException, match=r'Edge is not enabled.'):
2019-03-27 13:04:33 +00:00
rpc._rpc_edge()
def test_rpc_edge_enabled(mocker, edge_conf) -> None:
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),
}
))
2019-10-26 08:08:23 +00:00
freqtradebot = get_patched_freqtradebot(mocker, 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
2019-03-27 13:04:33 +00:00
assert ret[0]['Stoploss'] == -0.02