Merge branch 'develop' into cyber-forcesell-tg

This commit is contained in:
Ron Klinkien
2022-04-02 20:02:42 +02:00
committed by GitHub
173 changed files with 32904 additions and 4381 deletions

View File

@@ -137,7 +137,7 @@ def test_fiat_too_many_requests_response(mocker, caplog):
assert len(fiat_convert._coinlistings) == 0
assert fiat_convert._backoff > datetime.datetime.now().timestamp()
assert log_has(
'Too many requests for Coingecko API, backing off and trying again later.',
'Too many requests for CoinGecko API, backing off and trying again later.',
caplog
)
@@ -156,7 +156,7 @@ def test_fiat_multiple_coins(mocker, caplog):
assert fiat_convert._get_gekko_id('hnt') is None
assert fiat_convert._get_gekko_id('eth') == 'ethereum'
assert log_has('Found multiple mappings in goingekko for hnt.', caplog)
assert log_has('Found multiple mappings in CoinGecko for hnt.', caplog)
def test_fiat_invalid_response(mocker, caplog):

View File

@@ -8,7 +8,7 @@ import pytest
from numpy import isnan
from freqtrade.edge import PairInfo
from freqtrade.enums import State
from freqtrade.enums import SignalDirection, State, TradingMode
from freqtrade.exceptions import ExchangeError, InvalidOrderException, TemporaryError
from freqtrade.persistence import Trade
from freqtrade.persistence.models import Order
@@ -71,6 +71,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'max_rate': ANY,
'strategy': ANY,
'buy_tag': ANY,
'enter_tag': ANY,
'timeframe': 5,
'open_order_id': ANY,
'close_date': None,
@@ -109,13 +110,20 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'stoploss_entry_dist_ratio': -0.10448878,
'open_order': None,
'exchange': 'binance',
'leverage': 1.0,
'interest_rate': 0.0,
'liquidation_price': None,
'is_short': False,
'funding_fees': 0.0,
'trading_mode': TradingMode.SPOT,
'orders': [{
'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05,
'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy',
'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY,
'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05,
'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY,
'remaining': ANY, 'status': ANY}],
'remaining': ANY, 'status': ANY, 'ft_is_entry': True,
}],
}
mocker.patch('freqtrade.exchange.Exchange.get_rate',
@@ -145,6 +153,7 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'max_rate': ANY,
'strategy': ANY,
'buy_tag': ANY,
'enter_tag': ANY,
'timeframe': ANY,
'open_order_id': ANY,
'close_date': None,
@@ -183,13 +192,20 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'stoploss_entry_dist_ratio': -0.10448878,
'open_order': None,
'exchange': 'binance',
'leverage': 1.0,
'interest_rate': 0.0,
'liquidation_price': None,
'is_short': False,
'funding_fees': 0.0,
'trading_mode': TradingMode.SPOT,
'orders': [{
'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05,
'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy',
'order_date': ANY, 'order_timestamp': ANY, 'order_filled_date': ANY,
'order_filled_timestamp': ANY, 'order_type': 'limit', 'price': 1.098e-05,
'is_open': False, 'pair': 'ETH/BTC', 'order_id': ANY,
'remaining': ANY, 'status': ANY}],
'remaining': ANY, 'status': ANY, 'ft_is_entry': True,
}],
}
@@ -304,7 +320,8 @@ def test_rpc_daily_profit(default_conf, update, ticker, fee,
rpc._rpc_daily_profit(0, stake_currency, fiat_display_currency)
def test_rpc_trade_history(mocker, default_conf, markets, fee):
@pytest.mark.parametrize('is_short', [True, False])
def test_rpc_trade_history(mocker, default_conf, markets, fee, is_short):
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -312,7 +329,7 @@ def test_rpc_trade_history(mocker, default_conf, markets, fee):
)
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
create_mock_trades(fee)
create_mock_trades(fee, is_short)
rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter()
trades = rpc._rpc_trade_history(2)
@@ -329,7 +346,8 @@ def test_rpc_trade_history(mocker, default_conf, markets, fee):
assert trades['trades'][0]['pair'] == 'XRP/BTC'
def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog):
@pytest.mark.parametrize('is_short', [True, False])
def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog, is_short):
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
stoploss_mock = MagicMock()
cancel_mock = MagicMock()
@@ -342,7 +360,7 @@ def test_rpc_delete_trade(mocker, default_conf, fee, markets, caplog):
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
freqtradebot.strategy.order_types['stoploss_on_exchange'] = True
create_mock_trades(fee)
create_mock_trades(fee, is_short)
rpc = RPC(freqtradebot)
with pytest.raises(RPCException, match='invalid argument'):
rpc._rpc_delete('200')
@@ -584,6 +602,30 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
'used': 5.0,
}
}
mock_pos = [
{
"symbol": "ETH/USDT:USDT",
"timestamp": None,
"datetime": None,
"initialMargin": 0.0,
"initialMarginPercentage": None,
"maintenanceMargin": 0.0,
"maintenanceMarginPercentage": 0.005,
"entryPrice": 0.0,
"notional": 100.0,
"leverage": 5.0,
"unrealizedPnl": 0.0,
"contracts": 100.0,
"contractSize": 1,
"marginRatio": None,
"liquidationPrice": 0.0,
"markPrice": 2896.41,
"collateral": 20,
"marginType": "isolated",
"side": 'short',
"percentage": None
}
]
mocker.patch.multiple(
'freqtrade.rpc.fiat_convert.CoinGeckoAPI',
@@ -593,47 +635,77 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
validate_trading_mode_and_margin_mode=MagicMock(),
get_balances=MagicMock(return_value=mock_balance),
fetch_positions=MagicMock(return_value=mock_pos),
get_tickers=tickers,
get_valid_pair_combination=MagicMock(
side_effect=lambda a, b: f"{b}/{a}" if a == "USDT" else f"{a}/{b}")
)
default_conf['dry_run'] = False
default_conf['trading_mode'] = 'futures'
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot)
rpc._fiat_converter = CryptoToFiatConverter()
result = rpc._rpc_balance(default_conf['stake_currency'], default_conf['fiat_display_currency'])
assert prec_satoshi(result['total'], 12.30909624)
assert prec_satoshi(result['value'], 184636.443606915)
assert prec_satoshi(result['total'], 30.30909624)
assert prec_satoshi(result['value'], 454636.44360691)
assert tickers.call_count == 1
assert tickers.call_args_list[0][1]['cached'] is True
assert 'USD' == result['symbol']
assert result['currencies'] == [
{'currency': 'BTC',
'free': 10.0,
'balance': 12.0,
'used': 2.0,
'est_stake': 12.0,
'stake': 'BTC',
},
{'free': 1.0,
'balance': 5.0,
'currency': 'ETH',
'est_stake': 0.30794,
'used': 4.0,
'stake': 'BTC',
},
{'free': 5.0,
'balance': 10.0,
'currency': 'USDT',
'est_stake': 0.0011562404610161968,
'used': 5.0,
'stake': 'BTC',
}
{
'currency': 'BTC',
'free': 10.0,
'balance': 12.0,
'used': 2.0,
'est_stake': 10.0, # In futures mode, "free" is used here.
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
},
{
'free': 1.0,
'balance': 5.0,
'currency': 'ETH',
'est_stake': 0.30794,
'used': 4.0,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
},
{
'free': 5.0,
'balance': 10.0,
'currency': 'USDT',
'est_stake': 0.0011562404610161968,
'used': 5.0,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
},
{
'free': 0.0,
'balance': 0.0,
'currency': 'ETH/USDT:USDT',
'est_stake': 20,
'used': 0,
'stake': 'BTC',
'is_position': True,
'leverage': 5.0,
'position': 1000.0,
'side': 'short',
}
]
assert result['total'] == 12.309096240461017
def test_rpc_start(mocker, default_conf) -> None:
@@ -697,7 +769,7 @@ def test_rpc_stopbuy(mocker, default_conf) -> None:
assert freqtradebot.config['max_open_trades'] == 0
def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
def test_rpc_forceexit(default_conf, ticker, fee, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
cancel_order_mock = MagicMock()
@@ -724,29 +796,29 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
freqtradebot.state = State.STOPPED
with pytest.raises(RPCException, match=r'.*trader is not running*'):
rpc._rpc_forcesell(None)
rpc._rpc_forceexit(None)
freqtradebot.state = State.RUNNING
with pytest.raises(RPCException, match=r'.*invalid argument*'):
rpc._rpc_forcesell(None)
rpc._rpc_forceexit(None)
msg = rpc._rpc_forcesell('all')
msg = rpc._rpc_forceexit('all')
assert msg == {'result': 'Created sell orders for all open trades.'}
freqtradebot.enter_positions()
msg = rpc._rpc_forcesell('all')
msg = rpc._rpc_forceexit('all')
assert msg == {'result': 'Created sell orders for all open trades.'}
freqtradebot.enter_positions()
msg = rpc._rpc_forcesell('2')
msg = rpc._rpc_forceexit('2')
assert msg == {'result': 'Created sell order for trade 2.'}
freqtradebot.state = State.STOPPED
with pytest.raises(RPCException, match=r'.*trader is not running*'):
rpc._rpc_forcesell(None)
rpc._rpc_forceexit(None)
with pytest.raises(RPCException, match=r'.*trader is not running*'):
rpc._rpc_forcesell('all')
rpc._rpc_forceexit('all')
freqtradebot.state = State.RUNNING
assert cancel_order_mock.call_count == 0
@@ -775,7 +847,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
)
# check that the trade is called, which is done by ensuring exchange.cancel_order is called
# and trade amount is updated
rpc._rpc_forcesell('3')
rpc._rpc_forceexit('3')
assert cancel_order_mock.call_count == 1
assert trade.amount == filled_amount
@@ -803,7 +875,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
}
)
# check that the trade is called, which is done by ensuring exchange.cancel_order is called
msg = rpc._rpc_forcesell('4')
msg = rpc._rpc_forceexit('4')
assert msg == {'result': 'Created sell order for trade 4.'}
assert cancel_order_mock.call_count == 2
assert trade.amount == amount
@@ -820,7 +892,7 @@ def test_rpc_forcesell(default_conf, ticker, fee, mocker) -> None:
'filled': 0.0
}
)
msg = rpc._rpc_forcesell('3')
msg = rpc._rpc_forceexit('3')
assert msg == {'result': 'Created sell order for trade 3.'}
# status quo, no exchange calls
assert cancel_order_mock.call_count == 3
@@ -862,8 +934,8 @@ def test_performance_handle(default_conf, ticker, limit_buy_order, fee,
assert prec_satoshi(res[0]['profit_pct'], 6.2)
def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
limit_sell_order, mocker) -> None:
def test_enter_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
limit_sell_order, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -891,23 +963,23 @@ def test_buy_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
trade.close_date = datetime.utcnow()
trade.is_open = False
res = rpc._rpc_buy_tag_performance(None)
res = rpc._rpc_enter_tag_performance(None)
assert len(res) == 1
assert res[0]['buy_tag'] == 'Other'
assert res[0]['enter_tag'] == 'Other'
assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2)
trade.buy_tag = "TEST_TAG"
res = rpc._rpc_buy_tag_performance(None)
trade.enter_tag = "TEST_TAG"
res = rpc._rpc_enter_tag_performance(None)
assert len(res) == 1
assert res[0]['buy_tag'] == 'TEST_TAG'
assert res[0]['enter_tag'] == 'TEST_TAG'
assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2)
def test_buy_tag_performance_handle_2(mocker, default_conf, markets, fee):
def test_enter_tag_performance_handle_2(mocker, default_conf, markets, fee):
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -918,21 +990,21 @@ def test_buy_tag_performance_handle_2(mocker, default_conf, markets, fee):
create_mock_trades(fee)
rpc = RPC(freqtradebot)
res = rpc._rpc_buy_tag_performance(None)
res = rpc._rpc_enter_tag_performance(None)
assert len(res) == 2
assert res[0]['buy_tag'] == 'TEST1'
assert res[0]['enter_tag'] == 'TEST1'
assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 0.5)
assert res[1]['buy_tag'] == 'Other'
assert res[1]['enter_tag'] == 'Other'
assert res[1]['count'] == 1
assert prec_satoshi(res[1]['profit_pct'], 1.0)
# Test for a specific pair
res = rpc._rpc_buy_tag_performance('ETC/BTC')
res = rpc._rpc_enter_tag_performance('ETC/BTC')
assert len(res) == 1
assert res[0]['count'] == 1
assert res[0]['buy_tag'] == 'TEST1'
assert res[0]['enter_tag'] == 'TEST1'
assert prec_satoshi(res[0]['profit_pct'], 0.5)
@@ -1046,7 +1118,7 @@ def test_mix_tag_performance_handle(default_conf, ticker, limit_buy_order, fee,
assert res[0]['count'] == 1
assert prec_satoshi(res[0]['profit_pct'], 6.2)
trade.buy_tag = "TESTBUY"
trade.enter_tag = "TESTBUY"
trade.sell_reason = "TESTSELL"
res = rpc._rpc_mix_tag_performance(None)
@@ -1108,7 +1180,7 @@ def test_rpc_count(mocker, default_conf, ticker, fee) -> None:
assert counts["current"] == 1
def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) -> None:
def test_rpc_forceentry(mocker, default_conf, ticker, fee, limit_buy_order_open) -> None:
default_conf['forcebuy_enable'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
buy_mm = MagicMock(return_value=limit_buy_order_open)
@@ -1124,16 +1196,16 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot)
pair = 'ETH/BTC'
trade = rpc._rpc_forcebuy(pair, None)
trade = rpc._rpc_force_entry(pair, None)
assert isinstance(trade, Trade)
assert trade.pair == pair
assert trade.open_rate == ticker()['bid']
# Test buy duplicate
with pytest.raises(RPCException, match=r'position for ETH/BTC already open - id: 1'):
rpc._rpc_forcebuy(pair, 0.0001)
rpc._rpc_force_entry(pair, 0.0001)
pair = 'XRP/BTC'
trade = rpc._rpc_forcebuy(pair, 0.0001, order_type='limit')
trade = rpc._rpc_force_entry(pair, 0.0001, order_type='limit')
assert isinstance(trade, Trade)
assert trade.pair == pair
assert trade.open_rate == 0.0001
@@ -1141,11 +1213,11 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
# Test buy pair not with stakes
with pytest.raises(RPCException,
match=r'Wrong pair selected. Only pairs with stake-currency.*'):
rpc._rpc_forcebuy('LTC/ETH', 0.0001)
rpc._rpc_force_entry('LTC/ETH', 0.0001)
# Test with defined stake_amount
pair = 'LTC/BTC'
trade = rpc._rpc_forcebuy(pair, 0.0001, order_type='limit', stake_amount=0.05)
trade = rpc._rpc_force_entry(pair, 0.0001, order_type='limit', stake_amount=0.05)
assert trade.stake_amount == 0.05
assert trade.buy_tag == 'forceentry'
@@ -1156,11 +1228,11 @@ def test_rpcforcebuy(mocker, default_conf, ticker, fee, limit_buy_order_open) ->
patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot)
pair = 'TKN/BTC'
trade = rpc._rpc_forcebuy(pair, None)
trade = rpc._rpc_force_entry(pair, None)
assert trade is None
def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
def test_rpc_forceentry_stopped(mocker, default_conf) -> None:
default_conf['forcebuy_enable'] = True
default_conf['initial_state'] = 'stopped'
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
@@ -1170,18 +1242,30 @@ def test_rpcforcebuy_stopped(mocker, default_conf) -> None:
rpc = RPC(freqtradebot)
pair = 'ETH/BTC'
with pytest.raises(RPCException, match=r'trader is not running'):
rpc._rpc_forcebuy(pair, None)
rpc._rpc_force_entry(pair, None)
def test_rpcforcebuy_disabled(mocker, default_conf) -> None:
def test_rpc_forceentry_disabled(mocker, default_conf) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot)
pair = 'ETH/BTC'
with pytest.raises(RPCException, match=r'Forcebuy not enabled.'):
rpc._rpc_forcebuy(pair, None)
with pytest.raises(RPCException, match=r'Forceentry not enabled.'):
rpc._rpc_force_entry(pair, None)
def test_rpc_forceentry_wrong_mode(mocker, default_conf) -> None:
default_conf['forcebuy_enable'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram', MagicMock())
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
patch_get_signal(freqtradebot)
rpc = RPC(freqtradebot)
pair = 'ETH/BTC'
with pytest.raises(RPCException, match="Can't go short on Spot markets."):
rpc._rpc_force_entry(pair, None, order_side=SignalDirection.SHORT)
@pytest.mark.usefixtures("init_persistence")

View File

@@ -17,7 +17,7 @@ from numpy import isnan
from requests.auth import _basic_auth_str
from freqtrade.__init__ import __version__
from freqtrade.enums import RunMode, State
from freqtrade.enums import CandleType, RunMode, State, TradingMode
from freqtrade.exceptions import DependencyException, ExchangeError, OperationalException
from freqtrade.loggers import setup_logging, setup_logging_pre
from freqtrade.persistence import PairLocks, Trade
@@ -25,8 +25,8 @@ 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 tests.conftest import (create_mock_trades, get_mock_coro, get_patched_freqtradebot, log_has,
log_has_re, patch_get_signal)
from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_mock_coro,
get_patched_freqtradebot, log_has, log_has_re, patch_get_signal)
BASE_URI = "/api/v1"
@@ -452,6 +452,10 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers):
'used': 0.0,
'est_stake': 12.0,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
}
assert 'starting_capital' in response
assert 'starting_capital_fiat' in response
@@ -459,7 +463,8 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers):
assert 'starting_capital_ratio' in response
def test_api_count(botclient, mocker, ticker, fee, markets):
@pytest.mark.parametrize('is_short', [True, False])
def test_api_count(botclient, mocker, ticker, fee, markets, is_short):
ftbot, client = botclient
patch_get_signal(ftbot)
mocker.patch.multiple(
@@ -476,7 +481,7 @@ def test_api_count(botclient, mocker, ticker, fee, markets):
assert rc.json()["max"] == 1
# Create some test data
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
rc = client_get(client, f"{BASE_URI}/count")
assert_response(rc)
assert rc.json()["current"] == 4
@@ -527,21 +532,23 @@ def test_api_show_config(botclient):
rc = client_get(client, f"{BASE_URI}/show_config")
assert_response(rc)
assert 'dry_run' in rc.json()
assert rc.json()['exchange'] == 'binance'
assert rc.json()['timeframe'] == '5m'
assert rc.json()['timeframe_ms'] == 300000
assert rc.json()['timeframe_min'] == 5
assert rc.json()['state'] == 'running'
assert rc.json()['bot_name'] == 'freqtrade'
assert rc.json()['strategy_version'] is None
assert not rc.json()['trailing_stop']
assert 'bid_strategy' in rc.json()
assert 'ask_strategy' in rc.json()
assert 'unfilledtimeout' in rc.json()
assert 'version' in rc.json()
assert 'api_version' in rc.json()
assert 1.1 <= rc.json()['api_version'] <= 1.2
response = rc.json()
assert 'dry_run' in response
assert response['exchange'] == 'binance'
assert response['timeframe'] == '5m'
assert response['timeframe_ms'] == 300000
assert response['timeframe_min'] == 5
assert response['state'] == 'running'
assert response['bot_name'] == 'freqtrade'
assert response['trading_mode'] == 'spot'
assert response['strategy_version'] is None
assert not response['trailing_stop']
assert 'entry_pricing' in response
assert 'exit_pricing' in response
assert 'unfilledtimeout' in response
assert 'version' in response
assert 'api_version' in response
assert 2.1 <= response['api_version'] <= 2.2
def test_api_daily(botclient, mocker, ticker, fee, markets):
@@ -562,7 +569,8 @@ def test_api_daily(botclient, mocker, ticker, fee, markets):
assert rc.json()['data'][0]['date'] == str(datetime.utcnow().date())
def test_api_trades(botclient, mocker, fee, markets):
@pytest.mark.parametrize('is_short', [True, False])
def test_api_trades(botclient, mocker, fee, markets, is_short):
ftbot, client = botclient
patch_get_signal(ftbot)
mocker.patch.multiple(
@@ -575,13 +583,15 @@ def test_api_trades(botclient, mocker, fee, markets):
assert rc.json()['trades_count'] == 0
assert rc.json()['total_trades'] == 0
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
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
assert rc.json()['trades'][0]['is_short'] == is_short
rc = client_get(client, f"{BASE_URI}/trades?limit=1")
assert_response(rc)
assert len(rc.json()['trades']) == 1
@@ -589,9 +599,10 @@ def test_api_trades(botclient, mocker, fee, markets):
assert rc.json()['total_trades'] == 2
def test_api_trade_single(botclient, mocker, fee, ticker, markets):
@pytest.mark.parametrize('is_short', [True, False])
def test_api_trade_single(botclient, mocker, fee, ticker, markets, is_short):
ftbot, client = botclient
patch_get_signal(ftbot)
patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
markets=PropertyMock(return_value=markets),
@@ -601,16 +612,19 @@ def test_api_trade_single(botclient, mocker, fee, ticker, markets):
assert_response(rc, 404)
assert rc.json()['detail'] == 'Trade not found.'
create_mock_trades(fee)
Trade.query.session.rollback()
create_mock_trades(fee, is_short=is_short)
rc = client_get(client, f"{BASE_URI}/trade/3")
assert_response(rc)
assert rc.json()['trade_id'] == 3
assert rc.json()['is_short'] == is_short
def test_api_delete_trade(botclient, mocker, fee, markets):
@pytest.mark.parametrize('is_short', [True, False])
def test_api_delete_trade(botclient, mocker, fee, markets, is_short):
ftbot, client = botclient
patch_get_signal(ftbot)
patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
stoploss_mock = MagicMock()
cancel_mock = MagicMock()
mocker.patch.multiple(
@@ -619,11 +633,8 @@ def test_api_delete_trade(botclient, mocker, fee, markets):
cancel_order=cancel_mock,
cancel_stoploss_order=stoploss_mock,
)
rc = client_delete(client, f"{BASE_URI}/trades/1")
# Error - trade won't exist yet.
assert_response(rc, 502)
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
ftbot.strategy.order_types['stoploss_on_exchange'] = True
trades = Trade.query.all()
@@ -650,6 +661,10 @@ def test_api_delete_trade(botclient, mocker, fee, markets):
assert len(trades) - 2 == len(Trade.query.all())
assert stoploss_mock.call_count == 1
rc = client_delete(client, f"{BASE_URI}/trades/502")
# Error - trade won't exist.
assert_response(rc, 502)
def test_api_logs(botclient):
ftbot, client = botclient
@@ -698,7 +713,48 @@ def test_api_edge_disabled(botclient, mocker, ticker, fee, markets):
assert rc.json() == {"error": "Error querying /api/v1/edge: Edge is not enabled."}
def test_api_profit(botclient, mocker, ticker, fee, markets):
@pytest.mark.parametrize('is_short,expected', [
(
True,
{'best_pair': 'ETC/BTC', 'best_rate': -0.5, 'best_pair_profit_ratio': -0.005,
'profit_all_coin': 43.61269123,
'profit_all_fiat': 538398.67323435, 'profit_all_percent_mean': 66.41,
'profit_all_ratio_mean': 0.664109545, 'profit_all_percent_sum': 398.47,
'profit_all_ratio_sum': 3.98465727, 'profit_all_percent': 4.36,
'profit_all_ratio': 0.043612222872799825, 'profit_closed_coin': -0.00673913,
'profit_closed_fiat': -83.19455985, 'profit_closed_ratio_mean': -0.0075,
'profit_closed_percent_mean': -0.75, 'profit_closed_ratio_sum': -0.015,
'profit_closed_percent_sum': -1.5, 'profit_closed_ratio': -6.739057628404269e-06,
'profit_closed_percent': -0.0, 'winning_trades': 0, 'losing_trades': 2}
),
(
False,
{'best_pair': 'XRP/BTC', 'best_rate': 1.0, 'best_pair_profit_ratio': 0.01,
'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_all_percent': -4.41,
'profit_all_ratio': -0.044063014216106644, '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, 'profit_closed_ratio': 7.391275897987988e-07,
'profit_closed_percent': 0.0, 'winning_trades': 2, 'losing_trades': 0}
),
(
None,
{'best_pair': 'XRP/BTC', 'best_rate': 1.0, 'best_pair_profit_ratio': 0.01,
'profit_all_coin': -14.43790415,
'profit_all_fiat': -178235.92673175, 'profit_all_percent_mean': 0.08,
'profit_all_ratio_mean': 0.000835751666666662, 'profit_all_percent_sum': 0.5,
'profit_all_ratio_sum': 0.005014509999999972, 'profit_all_percent': -1.44,
'profit_all_ratio': -0.014437768014451796, 'profit_closed_coin': -0.00542913,
'profit_closed_fiat': -67.02260985, 'profit_closed_ratio_mean': 0.0025,
'profit_closed_percent_mean': 0.25, 'profit_closed_ratio_sum': 0.005,
'profit_closed_percent_sum': 0.5, 'profit_closed_ratio': -5.429078808526421e-06,
'profit_closed_percent': -0.0, 'winning_trades': 1, 'losing_trades': 1}
)
])
def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected):
ftbot, client = botclient
patch_get_signal(ftbot)
mocker.patch.multiple(
@@ -713,45 +769,48 @@ def test_api_profit(botclient, mocker, ticker, fee, markets):
assert_response(rc, 200)
assert rc.json()['trade_count'] == 0
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
# Simulate fulfilled LIMIT_BUY order for trade
rc = client_get(client, f"{BASE_URI}/profit")
assert_response(rc)
assert rc.json() == {'avg_duration': ANY,
'best_pair': 'XRP/BTC',
'best_rate': 1.0,
'best_pair_profit_ratio': 0.01,
'first_trade_date': ANY,
'first_trade_timestamp': ANY,
'latest_trade_date': '5 minutes ago',
'latest_trade_timestamp': ANY,
'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_all_percent': -4.41,
'profit_all_ratio': -0.044063014216106644,
'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,
'profit_closed_ratio': 7.391275897987988e-07,
'profit_closed_percent': 0.0,
'trade_count': 6,
'closed_trade_count': 2,
'winning_trades': 2,
'losing_trades': 0,
}
# raise ValueError(rc.json())
assert rc.json() == {
'avg_duration': ANY,
'best_pair': expected['best_pair'],
'best_pair_profit_ratio': expected['best_pair_profit_ratio'],
'best_rate': expected['best_rate'],
'first_trade_date': ANY,
'first_trade_timestamp': ANY,
'latest_trade_date': '5 minutes ago',
'latest_trade_timestamp': ANY,
'profit_all_coin': expected['profit_all_coin'],
'profit_all_fiat': expected['profit_all_fiat'],
'profit_all_percent_mean': expected['profit_all_percent_mean'],
'profit_all_ratio_mean': expected['profit_all_ratio_mean'],
'profit_all_percent_sum': expected['profit_all_percent_sum'],
'profit_all_ratio_sum': expected['profit_all_ratio_sum'],
'profit_all_percent': expected['profit_all_percent'],
'profit_all_ratio': expected['profit_all_ratio'],
'profit_closed_coin': expected['profit_closed_coin'],
'profit_closed_fiat': expected['profit_closed_fiat'],
'profit_closed_ratio_mean': expected['profit_closed_ratio_mean'],
'profit_closed_percent_mean': expected['profit_closed_percent_mean'],
'profit_closed_ratio_sum': expected['profit_closed_ratio_sum'],
'profit_closed_percent_sum': expected['profit_closed_percent_sum'],
'profit_closed_ratio': expected['profit_closed_ratio'],
'profit_closed_percent': expected['profit_closed_percent'],
'trade_count': 6,
'closed_trade_count': 2,
'winning_trades': expected['winning_trades'],
'losing_trades': expected['losing_trades'],
}
def test_api_stats(botclient, mocker, ticker, fee, markets,):
@pytest.mark.parametrize('is_short', [True, False])
def test_api_stats(botclient, mocker, ticker, fee, markets, is_short):
ftbot, client = botclient
patch_get_signal(ftbot)
patch_get_signal(ftbot, enter_long=not is_short, enter_short=is_short)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
get_balances=MagicMock(return_value=ticker),
@@ -765,7 +824,7 @@ def test_api_stats(botclient, mocker, ticker, fee, markets,):
assert 'durations' in rc.json()
assert 'sell_reasons' in rc.json()
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
rc = client_get(client, f"{BASE_URI}/stats")
assert_response(rc, 200)
@@ -825,7 +884,12 @@ def test_api_performance(botclient, fee):
'profit_ratio': -0.05570419, 'profit_abs': -0.1150375}]
def test_api_status(botclient, mocker, ticker, fee, markets):
@pytest.mark.parametrize(
'is_short,current_rate,open_order_id,open_trade_value',
[(True, 1.098e-05, 'dry_run_buy_short_12345', 15.0911775),
(False, 1.099e-05, 'dry_run_buy_long_12345', 15.1668225)])
def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
current_rate, open_order_id, open_trade_value):
ftbot, client = botclient
patch_get_signal(ftbot)
mocker.patch.multiple(
@@ -840,7 +904,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
rc = client_get(client, f"{BASE_URI}/status")
assert_response(rc, 200)
assert rc.json() == []
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
rc = client_get(client, f"{BASE_URI}/status")
assert_response(rc)
@@ -861,7 +925,7 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
'profit_pct': ANY,
'profit_abs': ANY,
'profit_fiat': ANY,
'current_rate': 1.099e-05,
'current_rate': current_rate,
'open_date': ANY,
'open_timestamp': ANY,
'open_order': None,
@@ -891,19 +955,24 @@ def test_api_status(botclient, mocker, ticker, fee, markets):
'fee_open_cost': None,
'fee_open_currency': None,
'is_open': True,
"is_short": is_short,
'max_rate': ANY,
'min_rate': ANY,
'open_order_id': 'dry_run_buy_12345',
'open_order_id': open_order_id,
'open_rate_requested': ANY,
'open_trade_value': 15.1668225,
'open_trade_value': open_trade_value,
'sell_reason': None,
'sell_order_status': None,
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
'buy_tag': None,
'enter_tag': None,
'timeframe': 5,
'exchange': 'binance',
'leverage': 1.0,
'interest_rate': 0.0,
'funding_fees': None,
'trading_mode': ANY,
'orders': [ANY],
}
mocker.patch('freqtrade.exchange.Exchange.get_rate',
@@ -977,8 +1046,8 @@ def test_api_blacklist(botclient, mocker):
"NOTHING/BTC": {
"error_msg": "Pair NOTHING/BTC is not in the current blacklist."
}
},
}
},
}
rc = client_delete(
client,
f"{BASE_URI}/blacklist?pairs_to_delete=HOT/BTC&pairs_to_delete=ETH/BTC")
@@ -1003,23 +1072,27 @@ def test_api_whitelist(botclient):
}
def test_api_forcebuy(botclient, mocker, fee):
@pytest.mark.parametrize('endpoint', [
'forcebuy',
'forceenter',
])
def test_api_forceentry(botclient, mocker, fee, endpoint):
ftbot, client = botclient
rc = client_post(client, f"{BASE_URI}/forcebuy",
rc = client_post(client, f"{BASE_URI}/{endpoint}",
data='{"pair": "ETH/BTC"}')
assert_response(rc, 502)
assert rc.json() == {"error": "Error querying /api/v1/forcebuy: Forcebuy not enabled."}
assert rc.json() == {"error": f"Error querying /api/v1/{endpoint}: Forceentry not enabled."}
# enable forcebuy
ftbot.config['forcebuy_enable'] = True
fbuy_mock = MagicMock(return_value=None)
mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock)
rc = client_post(client, f"{BASE_URI}/forcebuy",
mocker.patch("freqtrade.rpc.RPC._rpc_force_entry", fbuy_mock)
rc = client_post(client, f"{BASE_URI}/{endpoint}",
data='{"pair": "ETH/BTC"}')
assert_response(rc)
assert rc.json() == {"status": "Error buying pair ETH/BTC."}
assert rc.json() == {"status": "Error entering long trade for pair ETH/BTC."}
# Test creating trade
fbuy_mock = MagicMock(return_value=Trade(
@@ -1032,21 +1105,23 @@ def test_api_forcebuy(botclient, mocker, fee):
open_order_id="123456",
open_date=datetime.utcnow(),
is_open=False,
is_short=False,
fee_close=fee.return_value,
fee_open=fee.return_value,
close_rate=0.265441,
id=22,
timeframe=5,
strategy="StrategyTestV2"
strategy=CURRENT_TEST_STRATEGY,
trading_mode=TradingMode.SPOT
))
mocker.patch("freqtrade.rpc.RPC._rpc_forcebuy", fbuy_mock)
mocker.patch("freqtrade.rpc.RPC._rpc_force_entry", fbuy_mock)
rc = client_post(client, f"{BASE_URI}/forcebuy",
rc = client_post(client, f"{BASE_URI}/{endpoint}",
data='{"pair": "ETH/BTC"}')
assert_response(rc)
assert rc.json() == {
'amount': 1,
'amount_requested': 1,
'amount': 1.0,
'amount_requested': 1.0,
'trade_id': 22,
'close_date': None,
'close_timestamp': None,
@@ -1080,6 +1155,7 @@ def test_api_forcebuy(botclient, mocker, fee):
'fee_open_cost': None,
'fee_open_currency': None,
'is_open': False,
'is_short': False,
'max_rate': None,
'min_rate': None,
'open_order_id': '123456',
@@ -1087,10 +1163,15 @@ def test_api_forcebuy(botclient, mocker, fee):
'open_trade_value': 0.24605460,
'sell_reason': None,
'sell_order_status': None,
'strategy': 'StrategyTestV2',
'strategy': CURRENT_TEST_STRATEGY,
'buy_tag': None,
'enter_tag': None,
'timeframe': 5,
'exchange': 'binance',
'leverage': None,
'interest_rate': None,
'funding_fees': None,
'trading_mode': 'spot',
'orders': [],
}
@@ -1146,17 +1227,19 @@ def test_api_pair_candles(botclient, ohlcv_history):
assert 'data_stop_ts' in rc.json()
assert len(rc.json()['data']) == 0
ohlcv_history['sma'] = ohlcv_history['close'].rolling(2).mean()
ohlcv_history['buy'] = 0
ohlcv_history.loc[1, 'buy'] = 1
ohlcv_history['sell'] = 0
ohlcv_history['enter_long'] = 0
ohlcv_history.loc[1, 'enter_long'] = 1
ohlcv_history['exit_long'] = 0
ohlcv_history['enter_short'] = 0
ohlcv_history['exit_short'] = 0
ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history)
ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history, CandleType.SPOT)
rc = client_get(client,
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
assert_response(rc)
assert 'strategy' in rc.json()
assert rc.json()['strategy'] == 'StrategyTestV2'
assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
assert 'columns' in rc.json()
assert 'data_start_ts' in rc.json()
assert 'data_start' in rc.json()
@@ -1167,9 +1250,12 @@ def test_api_pair_candles(botclient, ohlcv_history):
assert rc.json()['data_stop'] == '2017-11-26 09:00:00+00:00'
assert rc.json()['data_stop_ts'] == 1511686800000
assert isinstance(rc.json()['columns'], list)
assert rc.json()['columns'] == ['date', 'open', 'high',
'low', 'close', 'volume', 'sma', 'buy', 'sell',
'__date_ts', '_buy_signal_close', '_sell_signal_close']
assert set(rc.json()['columns']) == {
'date', 'open', 'high', 'low', 'close', 'volume',
'sma', 'enter_long', 'exit_long', 'enter_short', 'exit_short', '__date_ts',
'_enter_long_signal_close', '_exit_long_signal_close',
'_enter_short_signal_close', '_exit_short_signal_close'
}
assert 'pair' in rc.json()
assert rc.json()['pair'] == 'XRP/BTC'
@@ -1178,31 +1264,32 @@ def test_api_pair_candles(botclient, ohlcv_history):
assert (rc.json()['data'] ==
[['2017-11-26 08:50:00', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, 0.0877869,
None, 0, 0, 1511686200000, None, None],
None, 0, 0, 0, 0, 1511686200000, None, None, None, None],
['2017-11-26 08:55:00', 8.88e-05, 8.942e-05, 8.88e-05,
8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0, 1511686500000, 8.893e-05,
None],
8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0, 0, 0, 1511686500000, 8.893e-05,
None, None, None],
['2017-11-26 09:00:00', 8.891e-05, 8.893e-05, 8.875e-05, 8.877e-05,
0.7039405, 8.885e-05, 0, 0, 1511686800000, None, None]
0.7039405, 8.885e-05, 0, 0, 0, 0, 1511686800000, None, None, None, None]
])
ohlcv_history['sell'] = ohlcv_history['sell'].astype('float64')
ohlcv_history.at[0, 'sell'] = float('inf')
ohlcv_history['exit_long'] = ohlcv_history['exit_long'].astype('float64')
ohlcv_history.at[0, 'exit_long'] = float('inf')
ohlcv_history['date1'] = ohlcv_history['date']
ohlcv_history.at[0, 'date1'] = pd.NaT
ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history)
ftbot.dataprovider._set_cached_df("XRP/BTC", timeframe, ohlcv_history, CandleType.SPOT)
rc = client_get(client,
f"{BASE_URI}/pair_candles?limit={amount}&pair=XRP%2FBTC&timeframe={timeframe}")
assert_response(rc)
assert (rc.json()['data'] ==
[['2017-11-26 08:50:00', 8.794e-05, 8.948e-05, 8.794e-05, 8.88e-05, 0.0877869,
None, 0, None, None, 1511686200000, None, None],
None, 0, None, 0, 0, None, 1511686200000, None, None, None, None],
['2017-11-26 08:55:00', 8.88e-05, 8.942e-05, 8.88e-05,
8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0.0, '2017-11-26 08:55:00',
1511686500000, 8.893e-05, None],
8.893e-05, 0.05874751, 8.886500000000001e-05, 1, 0.0, 0, 0, '2017-11-26 08:55:00',
1511686500000, 8.893e-05, None, None, None],
['2017-11-26 09:00:00', 8.891e-05, 8.893e-05, 8.875e-05, 8.877e-05,
0.7039405, 8.885e-05, 0, 0.0, '2017-11-26 09:00:00', 1511686800000, None, None]
0.7039405, 8.885e-05, 0, 0.0, 0, 0, '2017-11-26 09:00:00', 1511686800000,
None, None, None, None]
])
@@ -1213,19 +1300,19 @@ def test_api_pair_history(botclient, ohlcv_history):
# No pair
rc = client_get(client,
f"{BASE_URI}/pair_history?timeframe={timeframe}"
"&timerange=20180111-20180112&strategy=StrategyTestV2")
f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 422)
# No Timeframe
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC"
"&timerange=20180111-20180112&strategy=StrategyTestV2")
f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 422)
# No timerange
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&strategy=StrategyTestV2")
f"&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 422)
# No strategy
@@ -1237,14 +1324,14 @@ def test_api_pair_history(botclient, ohlcv_history):
# Working
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&timerange=20180111-20180112&strategy=StrategyTestV2")
f"&timerange=20180111-20180112&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 200)
assert rc.json()['length'] == 289
assert len(rc.json()['data']) == rc.json()['length']
assert 'columns' in rc.json()
assert 'data' in rc.json()
assert rc.json()['pair'] == 'UNITTEST/BTC'
assert rc.json()['strategy'] == 'StrategyTestV2'
assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00'
assert rc.json()['data_start_ts'] == 1515628800000
assert rc.json()['data_stop'] == '2018-01-12 00:00:00+00:00'
@@ -1253,7 +1340,7 @@ def test_api_pair_history(botclient, ohlcv_history):
# No data found
rc = client_get(client,
f"{BASE_URI}/pair_history?pair=UNITTEST%2FBTC&timeframe={timeframe}"
"&timerange=20200111-20200112&strategy=StrategyTestV2")
f"&timerange=20200111-20200112&strategy={CURRENT_TEST_STRATEGY}")
assert_response(rc, 502)
assert rc.json()['error'] == ("Error querying /api/v1/pair_history: "
"No data for UNITTEST/BTC, 5m in 20200111-20200112 found.")
@@ -1294,19 +1381,21 @@ def test_api_strategies(botclient):
'HyperoptableStrategy',
'InformativeDecoratorTest',
'StrategyTestV2',
'TestStrategyLegacyV1'
'StrategyTestV3',
'StrategyTestV3Futures',
'TestStrategyLegacyV1',
]}
def test_api_strategy(botclient):
ftbot, client = botclient
rc = client_get(client, f"{BASE_URI}/strategy/StrategyTestV2")
rc = client_get(client, f"{BASE_URI}/strategy/{CURRENT_TEST_STRATEGY}")
assert_response(rc)
assert rc.json()['strategy'] == 'StrategyTestV2'
assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
data = (Path(__file__).parents[1] / "strategy/strats/strategy_test_v2.py").read_text()
data = (Path(__file__).parents[1] / "strategy/strats/strategy_test_v3.py").read_text()
assert rc.json()['code'] == data
rc = client_get(client, f"{BASE_URI}/strategy/NoStrat")
@@ -1338,6 +1427,20 @@ def test_list_available_pairs(botclient):
assert rc.json()['pairs'] == ['XRP/ETH']
assert len(rc.json()['pair_interval']) == 1
ftbot.config['trading_mode'] = 'futures'
rc = client_get(
client, f"{BASE_URI}/available_pairs?timeframe=1h")
assert_response(rc)
assert rc.json()['length'] == 1
assert rc.json()['pairs'] == ['XRP/USDT']
rc = client_get(
client, f"{BASE_URI}/available_pairs?timeframe=1h&candletype=mark")
assert_response(rc)
assert rc.json()['length'] == 2
assert rc.json()['pairs'] == ['UNITTEST/USDT', 'XRP/USDT']
assert len(rc.json()['pair_interval']) == 2
def test_sysinfo(botclient):
ftbot, client = botclient
@@ -1383,7 +1486,7 @@ def test_api_backtesting(botclient, mocker, fee, caplog, tmpdir):
# start backtesting
data = {
"strategy": "StrategyTestV2",
"strategy": CURRENT_TEST_STRATEGY,
"timeframe": "5m",
"timerange": "20180110-20180111",
"max_open_trades": 3,

View File

@@ -18,7 +18,7 @@ from telegram.error import BadRequest, NetworkError, TelegramError
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.enums import ExitType, RPCMessageType, RunMode, SignalDirection, State
from freqtrade.exceptions import OperationalException
from freqtrade.freqtradebot import FreqtradeBot
from freqtrade.loggers import setup_logging
@@ -27,8 +27,8 @@ from freqtrade.persistence.models import Order
from freqtrade.rpc import RPC
from freqtrade.rpc.rpc import RPCException
from freqtrade.rpc.telegram import Telegram, authorized_only
from tests.conftest import (create_mock_trades, get_patched_freqtradebot, log_has, log_has_re,
patch_exchange, patch_get_signal, patch_whitelist)
from tests.conftest import (CURRENT_TEST_STRATEGY, create_mock_trades, get_patched_freqtradebot,
log_has, log_has_re, patch_exchange, patch_get_signal, patch_whitelist)
class DummyCls(Telegram):
@@ -94,8 +94,10 @@ def test_telegram_init(default_conf, mocker, caplog) -> None:
assert start_polling.start_polling.call_count == 1
message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], "
"['balance'], ['start'], ['stop'], ['forcesell'], ['forcebuy'], ['trades'], "
"['delete'], ['performance'], ['buys'], ['sells'], ['mix_tags'], "
"['balance'], ['start'], ['stop'], "
"['forcesell', 'forceexit'], ['forcebuy', 'forcelong'], ['forceshort'], "
"['trades'], ['delete'], ['performance'], "
"['buys', 'entries'], ['sells'], ['mix_tags'], "
"['stats'], ['daily'], ['weekly'], ['monthly'], "
"['count'], ['locks'], ['unlock', 'delete_locks'], "
"['reload_config', 'reload_conf'], ['show_config', 'show_conf'], "
@@ -191,6 +193,7 @@ def test_telegram_status(default_conf, update, mocker) -> None:
'amount': 90.99181074,
'stake_amount': 90.99181074,
'buy_tag': None,
'enter_tag': None,
'close_profit_ratio': None,
'profit': -0.0059,
'profit_ratio': -0.0059,
@@ -203,6 +206,8 @@ def test_telegram_status(default_conf, update, mocker) -> None:
'stop_loss_ratio': -0.0001,
'open_order': '(limit buy rem=0.00000000)',
'is_open': True,
'is_short': False,
'filled_entry_orders': [],
'orders': []
}]),
)
@@ -393,7 +398,8 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ')
assert int(fields[0]) == 1
assert 'ETH/BTC' in fields[1]
assert 'L' in fields[1]
assert 'ETH/BTC' in fields[2]
assert msg_mock.call_count == 1
@@ -623,7 +629,7 @@ def test_weekly_wrong_input(default_conf, update, ticker, mocker) -> None:
context.args = ["this week"]
telegram._weekly(update=update, context=context)
assert str('Weekly Profit over the last 8 weeks (starting from Monday)</b>:') \
in msg_mock.call_args_list[0][0][0]
in msg_mock.call_args_list[0][0][0]
def test_monthly_handle(default_conf, update, ticker, limit_buy_order, fee,
@@ -809,8 +815,9 @@ def test_profit_handle(default_conf, update, ticker, ticker_sell_up, fee,
assert '*Best Performing:* `ETH/BTC: 6.20%`' in msg_mock.call_args_list[-1][0][0]
@pytest.mark.parametrize('is_short', [True, False])
def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee,
limit_buy_order, limit_sell_order, mocker) -> None:
limit_buy_order, limit_sell_order, mocker, is_short) -> None:
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch.multiple(
'freqtrade.exchange.Exchange',
@@ -826,7 +833,7 @@ def test_telegram_stats(default_conf, update, ticker, ticker_sell_up, fee,
msg_mock.reset_mock()
# Create some test data
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
telegram._stats(update=update, context=MagicMock())
assert msg_mock.call_count == 1
@@ -900,6 +907,10 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None
'balance': i,
'est_stake': 1,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
})
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_balance', return_value={
'currencies': balances,
@@ -1024,7 +1035,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
# /forcesell 1
context = MagicMock()
context.args = ["1"]
telegram._forcesell(update=update, context=context)
telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 4
last_msg = msg_mock.call_args_list[-2][0][0]
@@ -1034,18 +1045,21 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'gain': 'profit',
'leverage': 1.0,
'limit': 1.173e-05,
'amount': 91.07468123,
'order_type': 'limit',
'open_rate': 1.098e-05,
'current_rate': 1.173e-05,
'direction': 'Long',
'profit_amount': 6.314e-05,
'profit_ratio': 0.0629778,
'stake_currency': 'BTC',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'buy_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value,
'enter_tag': ANY,
'sell_reason': ExitType.FORCE_SELL.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
@@ -1088,7 +1102,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
# /forcesell 1
context = MagicMock()
context.args = ["1"]
telegram._forcesell(update=update, context=context)
telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 4
@@ -1099,18 +1113,21 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'gain': 'loss',
'leverage': 1.0,
'limit': 1.043e-05,
'amount': 91.07468123,
'order_type': 'limit',
'open_rate': 1.098e-05,
'current_rate': 1.043e-05,
'direction': 'Long',
'profit_amount': -5.497e-05,
'profit_ratio': -0.05482878,
'stake_currency': 'BTC',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'buy_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value,
'enter_tag': ANY,
'sell_reason': ExitType.FORCE_SELL.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
@@ -1143,7 +1160,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
# /forcesell all
context = MagicMock()
context.args = ["all"]
telegram._forcesell(update=update, context=context)
telegram._forceexit(update=update, context=context)
# Called for each trade 2 times
assert msg_mock.call_count == 8
@@ -1154,18 +1171,21 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
'exchange': 'Binance',
'pair': 'ETH/BTC',
'gain': 'loss',
'leverage': 1.0,
'limit': 1.099e-05,
'amount': 91.07468123,
'order_type': 'limit',
'open_rate': 1.098e-05,
'current_rate': 1.099e-05,
'direction': 'Long',
'profit_amount': -4.09e-06,
'profit_ratio': -0.00408133,
'stake_currency': 'BTC',
'base_currency': 'ETH',
'fiat_currency': 'USD',
'buy_tag': ANY,
'sell_reason': SellType.FORCE_SELL.value,
'enter_tag': ANY,
'sell_reason': ExitType.FORCE_SELL.value,
'open_date': ANY,
'close_date': ANY,
'close_rate': ANY,
@@ -1184,7 +1204,7 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
# /forcesell 1
context = MagicMock()
context.args = ["1"]
telegram._forcesell(update=update, context=context)
telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 1
assert 'not running' in msg_mock.call_args_list[0][0][0]
@@ -1194,36 +1214,37 @@ def test_forcesell_handle_invalid(default_conf, update, mocker) -> None:
# /forcesell 123456
context = MagicMock()
context.args = ["123456"]
telegram._forcesell(update=update, context=context)
telegram._forceexit(update=update, context=context)
assert msg_mock.call_count == 1
assert 'invalid argument' in msg_mock.call_args_list[0][0][0]
def test_forcebuy_handle(default_conf, update, mocker) -> None:
def test_forceenter_handle(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)
mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock)
telegram, freqtradebot, _ = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot)
# /forcebuy ETH/BTC
# /forcelong ETH/BTC
context = MagicMock()
context.args = ["ETH/BTC"]
telegram._forcebuy(update=update, context=context)
telegram._forceenter(update=update, context=context, order_side=SignalDirection.LONG)
assert fbuy_mock.call_count == 1
assert fbuy_mock.call_args_list[0][0][0] == 'ETH/BTC'
assert fbuy_mock.call_args_list[0][0][1] is None
assert fbuy_mock.call_args_list[0][1]['order_side'] == SignalDirection.LONG
# Reset and retry with specified price
fbuy_mock = MagicMock(return_value=None)
mocker.patch('freqtrade.rpc.RPC._rpc_forcebuy', fbuy_mock)
# /forcebuy ETH/BTC 0.055
mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock)
# /forcelong ETH/BTC 0.055
context = MagicMock()
context.args = ["ETH/BTC", "0.055"]
telegram._forcebuy(update=update, context=context)
telegram._forceenter(update=update, context=context, order_side=SignalDirection.LONG)
assert fbuy_mock.call_count == 1
assert fbuy_mock.call_args_list[0][0][0] == 'ETH/BTC'
@@ -1231,24 +1252,24 @@ def test_forcebuy_handle(default_conf, update, mocker) -> None:
assert fbuy_mock.call_args_list[0][0][1] == 0.055
def test_forcebuy_handle_exception(default_conf, update, mocker) -> None:
def test_forceenter_handle_exception(default_conf, update, mocker) -> None:
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot)
update.message.text = '/forcebuy ETH/Nonepair'
telegram._forcebuy(update=update, context=MagicMock())
telegram._forceenter(update=update, context=MagicMock(), order_side=SignalDirection.LONG)
assert msg_mock.call_count == 1
assert msg_mock.call_args_list[0][0][0] == 'Forcebuy not enabled.'
assert msg_mock.call_args_list[0][0][0] == 'Forceentry not enabled.'
def test_forcebuy_no_pair(default_conf, update, mocker) -> None:
def test_forceenter_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)
mocker.patch('freqtrade.rpc.RPC._rpc_force_entry', fbuy_mock)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
@@ -1256,7 +1277,7 @@ def test_forcebuy_no_pair(default_conf, update, mocker) -> None:
context = MagicMock()
context.args = []
telegram._forcebuy(update=update, context=context)
telegram._forceenter(update=update, context=context, order_side=SignalDirection.LONG)
assert fbuy_mock.call_count == 0
assert msg_mock.call_count == 1
@@ -1267,8 +1288,8 @@ def test_forcebuy_no_pair(default_conf, update, mocker) -> None:
assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 5
update = MagicMock()
update.callback_query = MagicMock()
update.callback_query.data = 'XRP/USDT'
telegram._forcebuy_inline(update, None)
update.callback_query.data = 'XRP/USDT_||_long'
telegram._forceenter_inline(update, None)
assert fbuy_mock.call_count == 1
@@ -1318,12 +1339,12 @@ def test_telegram_buy_tag_performance_handle(default_conf, update, ticker, fee,
freqtradebot.enter_positions()
trade = Trade.query.first()
assert trade
trade.buy_tag = "TESTBUY"
# Simulate fulfilled LIMIT_BUY order for trade
oobj = Order.parse_from_ccxt_object(limit_buy_order, limit_buy_order['symbol'], 'buy')
trade.update_trade(oobj)
trade.enter_tag = "TESTBUY"
# Simulate fulfilled LIMIT_SELL order for trade
oobj = Order.parse_from_ccxt_object(limit_sell_order, limit_sell_order['symbol'], 'sell')
trade.update_trade(oobj)
@@ -1331,19 +1352,19 @@ def test_telegram_buy_tag_performance_handle(default_conf, update, ticker, fee,
trade.close_date = datetime.utcnow()
trade.is_open = False
context = MagicMock()
telegram._buy_tag_performance(update=update, context=context)
telegram._enter_tag_performance(update=update, context=context)
assert msg_mock.call_count == 1
assert 'Buy Tag Performance' in msg_mock.call_args_list[0][0][0]
assert '<code>TESTBUY\t0.00006217 BTC (6.20%) (1)</code>' in msg_mock.call_args_list[0][0][0]
context.args = [trade.pair]
telegram._buy_tag_performance(update=update, context=context)
telegram._enter_tag_performance(update=update, context=context)
assert msg_mock.call_count == 2
msg_mock.reset_mock()
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_buy_tag_performance',
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_enter_tag_performance',
side_effect=RPCException('Error'))
telegram._buy_tag_performance(update=update, context=MagicMock())
telegram._enter_tag_performance(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert "Error" in msg_mock.call_args_list[0][0][0]
@@ -1407,7 +1428,8 @@ def test_telegram_mix_tag_performance_handle(default_conf, update, ticker, fee,
freqtradebot.enter_positions()
trade = Trade.query.first()
assert trade
trade.buy_tag = "TESTBUY"
trade.enter_tag = "TESTBUY"
trade.sell_reason = "TESTSELL"
# Simulate fulfilled LIMIT_BUY order for trade
@@ -1633,7 +1655,10 @@ def test_edge_enabled(edge_conf, update, mocker) -> None:
assert 'Winrate' not in msg_mock.call_args_list[0][0][0]
def test_telegram_trades(mocker, update, default_conf, fee):
@pytest.mark.parametrize('is_short,regex_pattern',
[(True, r"just now[ ]*XRP\/BTC \(#3\) -1.00% \("),
(False, r"just now[ ]*XRP\/BTC \(#3\) 1.00% \(")])
def test_telegram_trades(mocker, update, default_conf, fee, is_short, regex_pattern):
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
@@ -1651,7 +1676,7 @@ def test_telegram_trades(mocker, update, default_conf, fee):
assert "<pre>" not in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
context = MagicMock()
context.args = [5]
@@ -1661,11 +1686,11 @@ def test_telegram_trades(mocker, update, default_conf, fee):
assert "Profit (" 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]))
assert bool(re.search(regex_pattern, msg_mock.call_args_list[0][0][0]))
def test_telegram_delete_trade(mocker, update, default_conf, fee):
@pytest.mark.parametrize('is_short', [True, False])
def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short):
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
context = MagicMock()
@@ -1675,7 +1700,7 @@ def test_telegram_delete_trade(mocker, update, default_conf, fee):
assert "Trade-id not set." in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
create_mock_trades(fee)
create_mock_trades(fee, is_short=is_short)
context = MagicMock()
context.args = [1]
@@ -1720,7 +1745,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
assert msg_mock.call_count == 1
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
assert '*Strategy:* `StrategyTestV2`' in msg_mock.call_args_list[0][0][0]
assert f'*Strategy:* `{CURRENT_TEST_STRATEGY}`' in msg_mock.call_args_list[0][0][0]
assert '*Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
@@ -1729,18 +1754,25 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
assert msg_mock.call_count == 1
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
assert '*Strategy:* `StrategyTestV2`' in msg_mock.call_args_list[0][0][0]
assert f'*Strategy:* `{CURRENT_TEST_STRATEGY}`' in msg_mock.call_args_list[0][0][0]
assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
@pytest.mark.parametrize('message_type,enter,enter_signal,leverage', [
(RPCMessageType.BUY, 'Long', 'long_signal_01', None),
(RPCMessageType.BUY, 'Long', 'long_signal_01', 1.0),
(RPCMessageType.BUY, 'Long', 'long_signal_01', 5.0),
(RPCMessageType.SHORT, 'Short', 'short_signal_01', 2.0)])
def test_send_msg_buy_notification(default_conf, mocker, caplog, message_type,
enter, enter_signal, leverage) -> None:
msg = {
'type': RPCMessageType.BUY,
'type': message_type,
'trade_id': 1,
'buy_tag': 'buy_signal_01',
'enter_tag': enter_signal,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': leverage,
'limit': 1.099e-05,
'order_type': 'limit',
'stake_amount': 0.01465333,
@@ -1754,13 +1786,17 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg(msg)
assert msg_mock.call_args[0][0] \
== '\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' \
'*Buy Tag:* `buy_signal_01`\n' \
'*Amount:* `1333.33333333`\n' \
'*Open Rate:* `0.00001099`\n' \
'*Current Rate:* `0.00001099`\n' \
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
assert msg_mock.call_args[0][0] == (
f'\N{LARGE BLUE CIRCLE} *Binance:* {enter} ETH/BTC (#1)\n'
f'*Enter Tag:* `{enter_signal}`\n'
'*Amount:* `1333.33333333`\n'
f'{leverage_text}'
'*Open Rate:* `0.00001099`\n'
'*Current Rate:* `0.00001099`\n'
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
)
freqtradebot.config['telegram']['notification_settings'] = {'buy': 'off'}
caplog.clear()
@@ -1778,20 +1814,23 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
msg_mock.call_args_list[0][1]['disable_notification'] is True
def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None:
@pytest.mark.parametrize('message_type,enter_signal', [
(RPCMessageType.BUY_CANCEL, 'long_signal_01'),
(RPCMessageType.SHORT_CANCEL, 'short_signal_01')])
def test_send_msg_buy_cancel_notification(default_conf, mocker, message_type, enter_signal) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg({
'type': RPCMessageType.BUY_CANCEL,
'buy_tag': 'buy_signal_01',
'type': message_type,
'enter_tag': enter_signal,
'trade_id': 1,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'reason': CANCEL_REASON['TIMEOUT']
})
assert (msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Binance:* '
'Cancelling open buy Order for ETH/BTC (#1). '
'Cancelling enter Order for ETH/BTC (#1). '
'Reason: cancelled due to timeout.')
@@ -1823,17 +1862,24 @@ def test_send_msg_protection_notification(default_conf, mocker, time_machine) ->
"*All pairs* will be locked until `2021-09-01 06:45:00`.")
def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
@pytest.mark.parametrize('message_type,entered,enter_signal,leverage', [
(RPCMessageType.BUY_FILL, 'Longed', 'long_signal_01', 1.0),
(RPCMessageType.BUY_FILL, 'Longed', 'long_signal_02', 2.0),
(RPCMessageType.SHORT_FILL, 'Shorted', 'short_signal_01', 2.0),
])
def test_send_msg_buy_fill_notification(default_conf, mocker, message_type, entered,
enter_signal, leverage) -> 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,
'type': message_type,
'trade_id': 1,
'buy_tag': 'buy_signal_01',
'enter_tag': enter_signal,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': leverage,
'stake_amount': 0.01465333,
# 'stake_amount_fiat': 0.0,
'stake_currency': 'BTC',
@@ -1842,13 +1888,15 @@ def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
'amount': 1333.3333333333335,
'open_date': arrow.utcnow().shift(hours=-1)
})
assert msg_mock.call_args[0][0] \
== '\N{CHECK MARK} *Binance:* Bought ETH/BTC (#1)\n' \
'*Buy Tag:* `buy_signal_01`\n' \
'*Amount:* `1333.33333333`\n' \
'*Open Rate:* `0.00001099`\n' \
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage != 1.0 else ''
assert msg_mock.call_args[0][0] == (
f'\N{CHECK MARK} *Binance:* {entered} ETH/BTC (#1)\n'
f'*Enter Tag:* `{enter_signal}`\n'
'*Amount:* `1333.33333333`\n'
f"{leverage_text}"
'*Open Rate:* `0.00001099`\n'
'*Total:* `(0.01465333 BTC, 180.895 USD)`'
)
def test_send_msg_sell_notification(default_conf, mocker) -> None:
@@ -1862,6 +1910,8 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'trade_id': 1,
'exchange': 'Binance',
'pair': 'KEY/ETH',
'leverage': 1.0,
'direction': 'Long',
'gain': 'loss',
'limit': 3.201e-05,
'amount': 1333.3333333333335,
@@ -1872,22 +1922,23 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'fiat_currency': 'USD',
'buy_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value,
'enter_tag': 'buy_signal1',
'sell_reason': ExitType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(hours=-1),
'close_date': arrow.utcnow(),
})
assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
'*Buy Tag:* `buy_signal1`\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`'
)
assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance:* Exiting KEY/ETH (#1)\n'
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
'*Enter Tag:* `buy_signal1`\n'
'*Exit Reason:* `stop_loss`\n'
'*Duration:* `1:00:00 (60.0 min)`\n'
'*Direction:* `Long`\n'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Current Rate:* `0.00003201`\n'
'*Close Rate:* `0.00003201`'
)
msg_mock.reset_mock()
telegram.send_msg({
@@ -1895,6 +1946,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'trade_id': 1,
'exchange': 'Binance',
'pair': 'KEY/ETH',
'direction': 'Long',
'gain': 'loss',
'limit': 3.201e-05,
'amount': 1333.3333333333335,
@@ -1904,22 +1956,23 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'profit_amount': -0.05746268,
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'buy_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value,
'enter_tag': 'buy_signal1',
'sell_reason': ExitType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
'close_date': arrow.utcnow(),
})
assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
'*Unrealized Profit:* `-57.41%`\n'
'*Buy Tag:* `buy_signal1`\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`'
)
assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance:* Exiting KEY/ETH (#1)\n'
'*Unrealized Profit:* `-57.41%`\n'
'*Enter Tag:* `buy_signal1`\n'
'*Exit Reason:* `stop_loss`\n'
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
'*Direction:* `Long`\n'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Current Rate:* `0.00003201`\n'
'*Close Rate:* `0.00003201`'
)
# Reset singleton function to avoid random breaks
telegram._rpc._fiat_converter.convert_amount = old_convamount
@@ -1937,9 +1990,9 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
'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 (#1).'
' Reason: Cancelled on exchange.')
assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance:* Cancelling exit Order for KEY/ETH (#1).'
' Reason: Cancelled on exchange.')
msg_mock.reset_mock()
telegram.send_msg({
@@ -1949,14 +2002,19 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
'pair': 'KEY/ETH',
'reason': 'timeout'
})
assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Cancelling open sell Order for KEY/ETH (#1).'
' Reason: timeout.')
assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance:* Cancelling exit 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:
@pytest.mark.parametrize('direction,enter_signal,leverage', [
('Long', 'long_signal_01', None),
('Long', 'long_signal_01', 1.0),
('Long', 'long_signal_01', 5.0),
('Short', 'short_signal_01', 2.0)])
def test_send_msg_sell_fill_notification(default_conf, mocker, direction,
enter_signal, leverage) -> None:
default_conf['telegram']['notification_settings']['sell_fill'] = 'on'
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
@@ -1966,6 +2024,8 @@ def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
'trade_id': 1,
'exchange': 'Binance',
'pair': 'KEY/ETH',
'leverage': leverage,
'direction': direction,
'gain': 'loss',
'limit': 3.201e-05,
'amount': 1333.3333333333335,
@@ -1975,21 +2035,25 @@ def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
'profit_amount': -0.05746268,
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'buy_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value,
'enter_tag': enter_signal,
'sell_reason': ExitType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
'close_date': arrow.utcnow(),
})
assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Sold KEY/ETH (#1)\n'
'*Profit:* `-57.41%`\n'
'*Buy Tag:* `buy_signal1`\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'
'*Close Rate:* `0.00003201`'
)
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance:* Exited KEY/ETH (#1)\n'
'*Profit:* `-57.41%`\n'
f'*Enter Tag:* `{enter_signal}`\n'
'*Exit Reason:* `stop_loss`\n'
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
f"*Direction:* `{direction}`\n"
f"{leverage_text}"
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Close Rate:* `0.00003201`'
)
def test_send_msg_status_notification(default_conf, mocker) -> None:
@@ -2028,16 +2092,22 @@ def test_send_msg_unknown_type(default_conf, mocker) -> None:
})
def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
@pytest.mark.parametrize('message_type,enter,enter_signal,leverage', [
(RPCMessageType.BUY, 'Long', 'long_signal_01', None),
(RPCMessageType.BUY, 'Long', 'long_signal_01', 2.0),
(RPCMessageType.SHORT, 'Short', 'short_signal_01', 2.0)])
def test_send_msg_buy_notification_no_fiat(
default_conf, mocker, message_type, enter, enter_signal, leverage) -> None:
del default_conf['fiat_display_currency']
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg({
'type': RPCMessageType.BUY,
'buy_tag': 'buy_signal_01',
'type': message_type,
'enter_tag': enter_signal,
'trade_id': 1,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': leverage,
'limit': 1.099e-05,
'order_type': 'limit',
'stake_amount': 0.01465333,
@@ -2048,15 +2118,27 @@ 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} *Binance:* Buying ETH/BTC (#1)\n'
'*Buy Tag:* `buy_signal_01`\n'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00001099`\n'
'*Current Rate:* `0.00001099`\n'
'*Total:* `(0.01465333 BTC)`')
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
assert msg_mock.call_args[0][0] == (
f'\N{LARGE BLUE CIRCLE} *Binance:* {enter} ETH/BTC (#1)\n'
f'*Enter Tag:* `{enter_signal}`\n'
'*Amount:* `1333.33333333`\n'
f'{leverage_text}'
'*Open Rate:* `0.00001099`\n'
'*Current Rate:* `0.00001099`\n'
'*Total:* `(0.01465333 BTC)`'
)
def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
@pytest.mark.parametrize('direction,enter_signal,leverage', [
('Long', 'long_signal_01', None),
('Long', 'long_signal_01', 1.0),
('Long', 'long_signal_01', 5.0),
('Short', 'short_signal_01', 2.0),
])
def test_send_msg_sell_notification_no_fiat(
default_conf, mocker, direction, enter_signal, leverage) -> None:
del default_conf['fiat_display_currency']
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
@@ -2066,6 +2148,8 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
'exchange': 'Binance',
'pair': 'KEY/ETH',
'gain': 'loss',
'leverage': leverage,
'direction': direction,
'limit': 3.201e-05,
'amount': 1333.3333333333335,
'order_type': 'limit',
@@ -2075,21 +2159,26 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
'profit_ratio': -0.57405275,
'stake_currency': 'ETH',
'fiat_currency': 'USD',
'buy_tag': 'buy_signal1',
'sell_reason': SellType.STOP_LOSS.value,
'enter_tag': enter_signal,
'sell_reason': ExitType.STOP_LOSS.value,
'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 (#1)\n'
'*Unrealized Profit:* `-57.41%`\n'
'*Buy Tag:* `buy_signal1`\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`'
)
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
assert msg_mock.call_args[0][0] == (
'\N{WARNING SIGN} *Binance:* Exiting KEY/ETH (#1)\n'
'*Unrealized Profit:* `-57.41%`\n'
f'*Enter Tag:* `{enter_signal}`\n'
'*Exit Reason:* `stop_loss`\n'
'*Duration:* `2:35:03 (155.1 min)`\n'
f'*Direction:* `{direction}`\n'
f'{leverage_text}'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Current Rate:* `0.00003201`\n'
'*Close Rate:* `0.00003201`'
)
@pytest.mark.parametrize('msg,expected', [

View File

@@ -5,7 +5,7 @@ from unittest.mock import MagicMock
import pytest
from requests import RequestException
from freqtrade.enums import RPCMessageType, SellType
from freqtrade.enums import ExitType, RPCMessageType
from freqtrade.rpc import RPC
from freqtrade.rpc.webhook import Webhook
from tests.conftest import get_patched_freqtradebot, log_has
@@ -18,17 +18,23 @@ def get_webhook_dict() -> dict:
"webhookbuy": {
"value1": "Buying {pair}",
"value2": "limit {limit:8f}",
"value3": "{stake_amount:8f} {stake_currency}"
"value3": "{stake_amount:8f} {stake_currency}",
"value4": "leverage {leverage:.1f}",
"value5": "direction {direction}"
},
"webhookbuycancel": {
"value1": "Cancelling Open Buy Order for {pair}",
"value2": "limit {limit:8f}",
"value3": "{stake_amount:8f} {stake_currency}"
"value3": "{stake_amount:8f} {stake_currency}",
"value4": "leverage {leverage:.1f}",
"value5": "direction {direction}"
},
"webhookbuyfill": {
"value1": "Buy Order for {pair} filled",
"value2": "at {open_rate:8f}",
"value3": "{stake_amount:8f} {stake_currency}"
"value3": "{stake_amount:8f} {stake_currency}",
"value4": "leverage {leverage:.1f}",
"value5": "direction {direction}"
},
"webhooksell": {
"value1": "Selling {pair}",
@@ -71,6 +77,8 @@ def test_send_msg_webhook(default_conf, mocker):
'type': RPCMessageType.BUY,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': 1.0,
'direction': 'Long',
'limit': 0.005,
'stake_amount': 0.8,
'stake_amount_fiat': 500,
@@ -85,6 +93,37 @@ def test_send_msg_webhook(default_conf, mocker):
default_conf["webhook"]["webhookbuy"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuy"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
default_conf["webhook"]["webhookbuy"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
default_conf["webhook"]["webhookbuy"]["value5"].format(**msg))
# Test short
msg_mock.reset_mock()
msg = {
'type': RPCMessageType.SHORT,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': 2.0,
'direction': 'Short',
'limit': 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"]["webhookbuy"]["value1"].format(**msg))
assert (msg_mock.call_args[0][0]["value2"] ==
default_conf["webhook"]["webhookbuy"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuy"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
default_conf["webhook"]["webhookbuy"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
default_conf["webhook"]["webhookbuy"]["value5"].format(**msg))
# Test buy cancel
msg_mock.reset_mock()
@@ -92,6 +131,8 @@ def test_send_msg_webhook(default_conf, mocker):
'type': RPCMessageType.BUY_CANCEL,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': 1.0,
'direction': 'Long',
'limit': 0.005,
'stake_amount': 0.8,
'stake_amount_fiat': 500,
@@ -106,6 +147,33 @@ def test_send_msg_webhook(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 short cancel
msg_mock.reset_mock()
msg = {
'type': RPCMessageType.SHORT_CANCEL,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': 2.0,
'direction': 'Short',
'limit': 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"]["webhookbuycancel"]["value1"].format(**msg))
assert (msg_mock.call_args[0][0]["value2"] ==
default_conf["webhook"]["webhookbuycancel"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuycancel"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
default_conf["webhook"]["webhookbuycancel"]["value5"].format(**msg))
# Test buy fill
msg_mock.reset_mock()
@@ -113,6 +181,8 @@ def test_send_msg_webhook(default_conf, mocker):
'type': RPCMessageType.BUY_FILL,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': 1.0,
'direction': 'Long',
'open_rate': 0.005,
'stake_amount': 0.8,
'stake_amount_fiat': 500,
@@ -127,8 +197,40 @@ def test_send_msg_webhook(default_conf, mocker):
default_conf["webhook"]["webhookbuyfill"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuyfill"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
default_conf["webhook"]["webhookbuycancel"]["value5"].format(**msg))
# Test short fill
msg_mock.reset_mock()
msg = {
'type': RPCMessageType.SHORT_FILL,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': 2.0,
'direction': 'Short',
'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))
assert (msg_mock.call_args[0][0]["value4"] ==
default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
default_conf["webhook"]["webhookbuycancel"]["value5"].format(**msg))
# Test sell
msg_mock.reset_mock()
msg = {
'type': RPCMessageType.SELL,
'exchange': 'Binance',
@@ -142,7 +244,7 @@ def test_send_msg_webhook(default_conf, mocker):
'profit_amount': 0.001,
'profit_ratio': 0.20,
'stake_currency': 'BTC',
'sell_reason': SellType.STOP_LOSS.value
'sell_reason': ExitType.STOP_LOSS.value
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
@@ -167,7 +269,7 @@ def test_send_msg_webhook(default_conf, mocker):
'profit_amount': 0.001,
'profit_ratio': 0.20,
'stake_currency': 'BTC',
'sell_reason': SellType.STOP_LOSS.value
'sell_reason': ExitType.STOP_LOSS.value
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
@@ -192,7 +294,7 @@ def test_send_msg_webhook(default_conf, mocker):
'profit_amount': 0.001,
'profit_ratio': 0.20,
'stake_currency': 'BTC',
'sell_reason': SellType.STOP_LOSS.value
'sell_reason': ExitType.STOP_LOSS.value
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1