_signals.pkl
- assert str(dump_mock.call_args_list[0][0][0]).endswith(str('_signals.pkl'))
+ assert str(dump_mock.call_args_list[0][0][0]).endswith('_signals.pkl')
dump_mock.reset_mock()
@@ -466,11 +466,14 @@ def test_generate_periodic_breakdown_stats(testdatadir):
def test__get_resample_from_period():
assert _get_resample_from_period('day') == '1d'
- assert _get_resample_from_period('week') == '1w'
+ assert _get_resample_from_period('week') == '1W-MON'
assert _get_resample_from_period('month') == '1M'
with pytest.raises(ValueError, match=r"Period noooo is not supported."):
_get_resample_from_period('noooo')
+ for period in BACKTEST_BREAKDOWNS:
+ assert isinstance(_get_resample_from_period(period), str)
+
def test_show_sorted_pairlist(testdatadir, default_conf, capsys):
filename = testdatadir / "backtest_results/backtest-result.json"
diff --git a/tests/persistence/test_key_value_store.py b/tests/persistence/test_key_value_store.py
new file mode 100644
index 000000000..1dab8764a
--- /dev/null
+++ b/tests/persistence/test_key_value_store.py
@@ -0,0 +1,69 @@
+from datetime import datetime, timedelta, timezone
+
+import pytest
+
+from freqtrade.persistence.key_value_store import KeyValueStore, set_startup_time
+from tests.conftest import create_mock_trades_usdt
+
+
+@pytest.mark.usefixtures("init_persistence")
+def test_key_value_store(time_machine):
+ start = datetime(2023, 1, 1, 4, tzinfo=timezone.utc)
+ time_machine.move_to(start, tick=False)
+
+ KeyValueStore.store_value("test", "testStringValue")
+ KeyValueStore.store_value("test_dt", datetime.now(timezone.utc))
+ KeyValueStore.store_value("test_float", 22.51)
+ KeyValueStore.store_value("test_int", 15)
+
+ assert KeyValueStore.get_value("test") == "testStringValue"
+ assert KeyValueStore.get_value("test") == "testStringValue"
+ assert KeyValueStore.get_string_value("test") == "testStringValue"
+ assert KeyValueStore.get_value("test_dt") == datetime.now(timezone.utc)
+ assert KeyValueStore.get_datetime_value("test_dt") == datetime.now(timezone.utc)
+ assert KeyValueStore.get_string_value("test_dt") is None
+ assert KeyValueStore.get_float_value("test_dt") is None
+ assert KeyValueStore.get_int_value("test_dt") is None
+ assert KeyValueStore.get_value("test_float") == 22.51
+ assert KeyValueStore.get_float_value("test_float") == 22.51
+ assert KeyValueStore.get_value("test_int") == 15
+ assert KeyValueStore.get_int_value("test_int") == 15
+ assert KeyValueStore.get_datetime_value("test_int") is None
+
+ time_machine.move_to(start + timedelta(days=20, hours=5), tick=False)
+ assert KeyValueStore.get_value("test_dt") != datetime.now(timezone.utc)
+ assert KeyValueStore.get_value("test_dt") == start
+ # Test update works
+ KeyValueStore.store_value("test_dt", datetime.now(timezone.utc))
+ assert KeyValueStore.get_value("test_dt") == datetime.now(timezone.utc)
+
+ KeyValueStore.store_value("test_float", 23.51)
+ assert KeyValueStore.get_value("test_float") == 23.51
+ # test deleting
+ KeyValueStore.delete_value("test_float")
+ assert KeyValueStore.get_value("test_float") is None
+ # Delete same value again (should not fail)
+ KeyValueStore.delete_value("test_float")
+
+ with pytest.raises(ValueError, match=r"Unknown value type"):
+ KeyValueStore.store_value("test_float", {'some': 'dict'})
+
+
+@pytest.mark.usefixtures("init_persistence")
+def test_set_startup_time(fee, time_machine):
+ create_mock_trades_usdt(fee)
+ start = datetime.now(timezone.utc)
+ time_machine.move_to(start, tick=False)
+ set_startup_time()
+
+ assert KeyValueStore.get_value("startup_time") == start
+ initial_time = KeyValueStore.get_value("bot_start_time")
+ assert initial_time <= start
+
+ # Simulate bot restart
+ new_start = start + timedelta(days=5)
+ time_machine.move_to(new_start, tick=False)
+ set_startup_time()
+
+ assert KeyValueStore.get_value("startup_time") == new_start
+ assert KeyValueStore.get_value("bot_start_time") == initial_time
diff --git a/tests/persistence/test_migrations.py b/tests/persistence/test_migrations.py
index 854d39994..13b3f89bf 100644
--- a/tests/persistence/test_migrations.py
+++ b/tests/persistence/test_migrations.py
@@ -1,15 +1,18 @@
# pragma pylint: disable=missing-docstring, C0103
import logging
+from importlib import import_module
from pathlib import Path
from unittest.mock import MagicMock
import pytest
from sqlalchemy import create_engine, select, text
+from sqlalchemy.schema import CreateTable
from freqtrade.constants import DEFAULT_DB_PROD_URL
from freqtrade.enums import TradingMode
from freqtrade.exceptions import OperationalException
from freqtrade.persistence import Trade, init_db
+from freqtrade.persistence.base import ModelBase
from freqtrade.persistence.migrations import get_last_sequence_ids, set_sequence_ids
from freqtrade.persistence.models import PairLock
from tests.conftest import log_has
@@ -411,3 +414,14 @@ def test_migrate_pairlocks(mocker, default_conf, fee, caplog):
assert len(pairlocks) == 1
pairlocks[0].pair == 'ETH/BTC'
pairlocks[0].side == '*'
+
+
+@pytest.mark.parametrize('dialect', [
+ 'sqlite', 'postgresql', 'mysql', 'oracle', 'mssql',
+ ])
+def test_create_table_compiles(dialect):
+
+ dialect_mod = import_module(f"sqlalchemy.dialects.{dialect}")
+ for table in ModelBase.metadata.tables.values():
+ create_sql = str(CreateTable(table).compile(dialect=dialect_mod.dialect()))
+ assert 'CREATE TABLE' in create_sql
diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py
index db882d56d..1a7d84eca 100644
--- a/tests/persistence/test_persistence.py
+++ b/tests/persistence/test_persistence.py
@@ -6,7 +6,7 @@ import arrow
import pytest
from sqlalchemy import select
-from freqtrade.constants import DATETIME_PRINT_FORMAT
+from freqtrade.constants import CUSTOM_TAG_MAX_LENGTH, DATETIME_PRINT_FORMAT
from freqtrade.enums import TradingMode
from freqtrade.exceptions import DependencyException
from freqtrade.persistence import LocalTrade, Order, Trade, init_db
@@ -1330,71 +1330,78 @@ def test_to_json(fee):
open_rate=0.123,
exchange='binance',
enter_tag=None,
- open_order_id='dry_run_buy_12345'
+ open_order_id='dry_run_buy_12345',
+ precision_mode=1,
+ amount_precision=8.0,
+ price_precision=7.0,
)
result = trade.to_json()
assert isinstance(result, dict)
- assert result == {'trade_id': None,
- 'pair': 'ADA/USDT',
- 'base_currency': 'ADA',
- 'quote_currency': 'USDT',
- 'is_open': None,
- 'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
- 'open_timestamp': int(trade.open_date.timestamp() * 1000),
- 'open_order_id': 'dry_run_buy_12345',
- 'close_date': None,
- 'close_timestamp': None,
- 'open_rate': 0.123,
- 'open_rate_requested': None,
- 'open_trade_value': 15.1668225,
- 'fee_close': 0.0025,
- 'fee_close_cost': None,
- 'fee_close_currency': None,
- 'fee_open': 0.0025,
- 'fee_open_cost': None,
- 'fee_open_currency': None,
- 'close_rate': None,
- 'close_rate_requested': None,
- 'amount': 123.0,
- 'amount_requested': 123.0,
- 'stake_amount': 0.001,
- 'max_stake_amount': None,
- 'trade_duration': None,
- 'trade_duration_s': None,
- 'realized_profit': 0.0,
- 'realized_profit_ratio': None,
- 'close_profit': None,
- 'close_profit_pct': None,
- 'close_profit_abs': None,
- 'profit_ratio': None,
- 'profit_pct': None,
- 'profit_abs': None,
- 'exit_reason': None,
- 'exit_order_status': None,
- 'stop_loss_abs': None,
- 'stop_loss_ratio': None,
- 'stop_loss_pct': None,
- 'stoploss_order_id': None,
- 'stoploss_last_update': None,
- 'stoploss_last_update_timestamp': None,
- 'initial_stop_loss_abs': None,
- 'initial_stop_loss_pct': None,
- 'initial_stop_loss_ratio': None,
- 'min_rate': None,
- 'max_rate': None,
- 'strategy': None,
- 'enter_tag': None,
- 'timeframe': None,
- 'exchange': 'binance',
- 'leverage': None,
- 'interest_rate': None,
- 'liquidation_price': None,
- 'is_short': None,
- 'trading_mode': None,
- 'funding_fees': None,
- 'orders': [],
- }
+ assert result == {
+ 'trade_id': None,
+ 'pair': 'ADA/USDT',
+ 'base_currency': 'ADA',
+ 'quote_currency': 'USDT',
+ 'is_open': None,
+ 'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
+ 'open_timestamp': int(trade.open_date.timestamp() * 1000),
+ 'open_order_id': 'dry_run_buy_12345',
+ 'close_date': None,
+ 'close_timestamp': None,
+ 'open_rate': 0.123,
+ 'open_rate_requested': None,
+ 'open_trade_value': 15.1668225,
+ 'fee_close': 0.0025,
+ 'fee_close_cost': None,
+ 'fee_close_currency': None,
+ 'fee_open': 0.0025,
+ 'fee_open_cost': None,
+ 'fee_open_currency': None,
+ 'close_rate': None,
+ 'close_rate_requested': None,
+ 'amount': 123.0,
+ 'amount_requested': 123.0,
+ 'stake_amount': 0.001,
+ 'max_stake_amount': None,
+ 'trade_duration': None,
+ 'trade_duration_s': None,
+ 'realized_profit': 0.0,
+ 'realized_profit_ratio': None,
+ 'close_profit': None,
+ 'close_profit_pct': None,
+ 'close_profit_abs': None,
+ 'profit_ratio': None,
+ 'profit_pct': None,
+ 'profit_abs': None,
+ 'exit_reason': None,
+ 'exit_order_status': None,
+ 'stop_loss_abs': None,
+ 'stop_loss_ratio': None,
+ 'stop_loss_pct': None,
+ 'stoploss_order_id': None,
+ 'stoploss_last_update': None,
+ 'stoploss_last_update_timestamp': None,
+ 'initial_stop_loss_abs': None,
+ 'initial_stop_loss_pct': None,
+ 'initial_stop_loss_ratio': None,
+ 'min_rate': None,
+ 'max_rate': None,
+ 'strategy': None,
+ 'enter_tag': None,
+ 'timeframe': None,
+ 'exchange': 'binance',
+ 'leverage': None,
+ 'interest_rate': None,
+ 'liquidation_price': None,
+ 'is_short': None,
+ 'trading_mode': None,
+ 'funding_fees': None,
+ 'amount_precision': 8.0,
+ 'price_precision': 7.0,
+ 'precision_mode': 1,
+ 'orders': [],
+ }
# Simulate dry_run entries
trade = Trade(
@@ -1410,70 +1417,77 @@ def test_to_json(fee):
close_rate=0.125,
enter_tag='buys_signal_001',
exchange='binance',
+ precision_mode=2,
+ amount_precision=7.0,
+ price_precision=8.0,
)
result = trade.to_json()
assert isinstance(result, dict)
- assert result == {'trade_id': None,
- 'pair': 'XRP/BTC',
- 'base_currency': 'XRP',
- 'quote_currency': 'BTC',
- 'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
- 'open_timestamp': int(trade.open_date.timestamp() * 1000),
- 'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT),
- 'close_timestamp': int(trade.close_date.timestamp() * 1000),
- 'open_rate': 0.123,
- 'close_rate': 0.125,
- 'amount': 100.0,
- 'amount_requested': 101.0,
- 'stake_amount': 0.001,
- 'max_stake_amount': None,
- 'trade_duration': 60,
- 'trade_duration_s': 3600,
- 'stop_loss_abs': None,
- 'stop_loss_pct': None,
- 'stop_loss_ratio': None,
- 'stoploss_order_id': None,
- 'stoploss_last_update': None,
- 'stoploss_last_update_timestamp': None,
- 'initial_stop_loss_abs': None,
- 'initial_stop_loss_pct': None,
- 'initial_stop_loss_ratio': None,
- 'realized_profit': 0.0,
- 'realized_profit_ratio': None,
- 'close_profit': None,
- 'close_profit_pct': None,
- 'close_profit_abs': None,
- 'profit_ratio': None,
- 'profit_pct': None,
- 'profit_abs': None,
- 'close_rate_requested': None,
- 'fee_close': 0.0025,
- 'fee_close_cost': None,
- 'fee_close_currency': None,
- 'fee_open': 0.0025,
- 'fee_open_cost': None,
- 'fee_open_currency': None,
- 'is_open': None,
- 'max_rate': None,
- 'min_rate': None,
- 'open_order_id': None,
- 'open_rate_requested': None,
- 'open_trade_value': 12.33075,
- 'exit_reason': None,
- 'exit_order_status': None,
- 'strategy': None,
- 'enter_tag': 'buys_signal_001',
- 'timeframe': None,
- 'exchange': 'binance',
- 'leverage': None,
- 'interest_rate': None,
- 'liquidation_price': None,
- 'is_short': None,
- 'trading_mode': None,
- 'funding_fees': None,
- 'orders': [],
- }
+ assert result == {
+ 'trade_id': None,
+ 'pair': 'XRP/BTC',
+ 'base_currency': 'XRP',
+ 'quote_currency': 'BTC',
+ 'open_date': trade.open_date.strftime(DATETIME_PRINT_FORMAT),
+ 'open_timestamp': int(trade.open_date.timestamp() * 1000),
+ 'close_date': trade.close_date.strftime(DATETIME_PRINT_FORMAT),
+ 'close_timestamp': int(trade.close_date.timestamp() * 1000),
+ 'open_rate': 0.123,
+ 'close_rate': 0.125,
+ 'amount': 100.0,
+ 'amount_requested': 101.0,
+ 'stake_amount': 0.001,
+ 'max_stake_amount': None,
+ 'trade_duration': 60,
+ 'trade_duration_s': 3600,
+ 'stop_loss_abs': None,
+ 'stop_loss_pct': None,
+ 'stop_loss_ratio': None,
+ 'stoploss_order_id': None,
+ 'stoploss_last_update': None,
+ 'stoploss_last_update_timestamp': None,
+ 'initial_stop_loss_abs': None,
+ 'initial_stop_loss_pct': None,
+ 'initial_stop_loss_ratio': None,
+ 'realized_profit': 0.0,
+ 'realized_profit_ratio': None,
+ 'close_profit': None,
+ 'close_profit_pct': None,
+ 'close_profit_abs': None,
+ 'profit_ratio': None,
+ 'profit_pct': None,
+ 'profit_abs': None,
+ 'close_rate_requested': None,
+ 'fee_close': 0.0025,
+ 'fee_close_cost': None,
+ 'fee_close_currency': None,
+ 'fee_open': 0.0025,
+ 'fee_open_cost': None,
+ 'fee_open_currency': None,
+ 'is_open': None,
+ 'max_rate': None,
+ 'min_rate': None,
+ 'open_order_id': None,
+ 'open_rate_requested': None,
+ 'open_trade_value': 12.33075,
+ 'exit_reason': None,
+ 'exit_order_status': None,
+ 'strategy': None,
+ 'enter_tag': 'buys_signal_001',
+ 'timeframe': None,
+ 'exchange': 'binance',
+ 'leverage': None,
+ 'interest_rate': None,
+ 'liquidation_price': None,
+ 'is_short': None,
+ 'trading_mode': None,
+ 'funding_fees': None,
+ 'amount_precision': 7.0,
+ 'price_precision': 8.0,
+ 'precision_mode': 2,
+ 'orders': [],
+ }
def test_stoploss_reinitialization(default_conf, fee):
@@ -2023,6 +2037,7 @@ def test_Trade_object_idem():
'get_mix_tag_performance',
'get_trading_volume',
'from_json',
+ 'validate_string_len',
)
EXCLUDES2 = ('trades', 'trades_open', 'bt_trades_open_pp', 'bt_open_open_trade_count',
'total_profit')
@@ -2041,6 +2056,31 @@ def test_Trade_object_idem():
assert item in trade
+@pytest.mark.usefixtures("init_persistence")
+def test_trade_truncates_string_fields():
+ trade = Trade(
+ pair='ADA/USDT',
+ stake_amount=20.0,
+ amount=30.0,
+ open_rate=2.0,
+ open_date=datetime.utcnow() - timedelta(minutes=20),
+ fee_open=0.001,
+ fee_close=0.001,
+ exchange='binance',
+ leverage=1.0,
+ trading_mode='futures',
+ enter_tag='a' * CUSTOM_TAG_MAX_LENGTH * 2,
+ exit_reason='b' * CUSTOM_TAG_MAX_LENGTH * 2,
+ )
+ Trade.session.add(trade)
+ Trade.commit()
+
+ trade1 = Trade.session.scalars(select(Trade)).first()
+
+ assert trade1.enter_tag == 'a' * CUSTOM_TAG_MAX_LENGTH
+ assert trade1.exit_reason == 'b' * CUSTOM_TAG_MAX_LENGTH
+
+
def test_recalc_trade_from_orders(fee):
o1_amount = 100
@@ -2441,7 +2481,7 @@ def test_select_filled_orders(fee):
@pytest.mark.usefixtures("init_persistence")
-def test_order_to_ccxt(limit_buy_order_open):
+def test_order_to_ccxt(limit_buy_order_open, limit_sell_order_usdt_open):
order = Order.parse_from_ccxt_object(limit_buy_order_open, 'mocked', 'buy')
order.ft_trade_id = 1
@@ -2455,11 +2495,23 @@ def test_order_to_ccxt(limit_buy_order_open):
del raw_order['fee']
del raw_order['datetime']
del raw_order['info']
- assert raw_order['stopPrice'] is None
- del raw_order['stopPrice']
+ assert raw_order.get('stopPrice') is None
+ raw_order.pop('stopPrice', None)
del limit_buy_order_open['datetime']
assert raw_order == limit_buy_order_open
+ order1 = Order.parse_from_ccxt_object(limit_sell_order_usdt_open, 'mocked', 'sell')
+ order1.ft_order_side = 'stoploss'
+ order1.stop_price = order1.price * 0.9
+ order1.ft_trade_id = 1
+ order1.session.add(order1)
+ Order.session.commit()
+
+ order_resp1 = Order.order_by_id(limit_sell_order_usdt_open['id'])
+ raw_order1 = order_resp1.to_ccxt_object()
+
+ assert raw_order1.get('stopPrice') is not None
+
@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize('data', [
diff --git a/tests/persistence/test_trade_fromjson.py b/tests/persistence/test_trade_fromjson.py
index 529008e02..22053463d 100644
--- a/tests/persistence/test_trade_fromjson.py
+++ b/tests/persistence/test_trade_fromjson.py
@@ -50,8 +50,8 @@ def test_trade_fromjson():
"stop_loss_ratio": -0.216,
"stop_loss_pct": -21.6,
"stoploss_order_id": null,
- "stoploss_last_update": null,
- "stoploss_last_update_timestamp": null,
+ "stoploss_last_update": "2022-10-18 09:13:42",
+ "stoploss_last_update_timestamp": 1666077222000,
"initial_stop_loss_abs": 0.1981,
"initial_stop_loss_ratio": -0.216,
"initial_stop_loss_pct": -21.6,
diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py
index 7d829bdb6..bb84ff8e9 100644
--- a/tests/rpc/test_rpc.py
+++ b/tests/rpc/test_rpc.py
@@ -88,6 +88,9 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'is_short': False,
'funding_fees': 0.0,
'trading_mode': TradingMode.SPOT,
+ 'amount_precision': 8.0,
+ 'price_precision': 8.0,
+ 'precision_mode': 2,
'orders': [{
'amount': 91.07468123, 'average': 1.098e-05, 'safe_price': 1.098e-05,
'cost': 0.0009999999999054, 'filled': 91.07468123, 'ft_order_side': 'buy',
@@ -125,17 +128,6 @@ def test_rpc_trade_status(default_conf, ticker, fee, mocker) -> None:
'profit_pct': 0.0,
'profit_abs': 0.0,
'total_profit_abs': 0.0,
- 'stop_loss_abs': 0.0,
- 'stop_loss_pct': None,
- 'stop_loss_ratio': None,
- 'stoploss_current_dist': -1.099e-05,
- 'stoploss_current_dist_ratio': -1.0,
- 'stoploss_current_dist_pct': pytest.approx(-100.0),
- 'stoploss_entry_dist': -0.0010025,
- 'stoploss_entry_dist_ratio': -1.0,
- 'initial_stop_loss_abs': 0.0,
- 'initial_stop_loss_pct': None,
- 'initial_stop_loss_ratio': None,
'open_order': '(limit buy rem=91.07468123)',
})
response_unfilled['orders'][0].update({
@@ -554,51 +546,67 @@ def test_rpc_balance_handle(default_conf, mocker, tickers):
'free': 10.0,
'balance': 12.0,
'used': 2.0,
+ 'bot_owned': 9.9, # available stake - reducing by reserved amount
'est_stake': 10.0, # In futures mode, "free" is used here.
+ 'est_stake_bot': 9.9,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
+ 'is_bot_managed': True,
},
{
'free': 1.0,
'balance': 5.0,
'currency': 'ETH',
+ 'bot_owned': 0,
'est_stake': 0.30794,
+ 'est_stake_bot': 0,
'used': 4.0,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
-
+ 'is_bot_managed': False,
},
{
'free': 5.0,
'balance': 10.0,
'currency': 'USDT',
+ 'bot_owned': 0,
'est_stake': 0.0011562404610161968,
+ 'est_stake_bot': 0,
'used': 5.0,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
+ 'is_bot_managed': False,
},
{
'free': 0.0,
'balance': 0.0,
'currency': 'ETH/USDT:USDT',
'est_stake': 20,
+ 'est_stake_bot': 20,
'used': 0,
'stake': 'BTC',
'is_position': True,
'leverage': 5.0,
'position': 1000.0,
'side': 'short',
+ 'is_bot_managed': True,
}
]
+ assert pytest.approx(result['total_bot']) == 29.9
+ assert pytest.approx(result['total']) == 30.309096
+ assert result['starting_capital'] == 10
+ # Very high starting capital ratio, because the futures position really has the wrong unit.
+ # TODO: improve this test (see comment above)
+ assert result['starting_capital_ratio'] == pytest.approx(1.98999999)
def test_rpc_start(mocker, default_conf) -> None:
diff --git a/tests/rpc/test_rpc_apiserver.py b/tests/rpc/test_rpc_apiserver.py
index 97319b78b..8123e4689 100644
--- a/tests/rpc/test_rpc_apiserver.py
+++ b/tests/rpc/test_rpc_apiserver.py
@@ -1,6 +1,7 @@
"""
Unit test file for rpc/api_server.py
"""
+import asyncio
import logging
import time
from datetime import datetime, timedelta, timezone
@@ -282,7 +283,7 @@ def test_api__init__(default_conf, mocker):
"username": "TestUser",
"password": "testPass",
}})
- mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
+ mocker.patch('freqtrade.rpc.telegram.Telegram._init')
mocker.patch('freqtrade.rpc.api_server.webserver.ApiServer.start_api', MagicMock())
apiserver = ApiServer(default_conf)
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
@@ -299,10 +300,6 @@ def test_api_UvicornServer(mocker):
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1'))
assert thread_mock.call_count == 0
- s.install_signal_handlers()
- # Original implementation starts a thread - make sure that's not the case
- assert thread_mock.call_count == 0
-
# Fake started to avoid sleeping forever
s.started = True
s.run_in_thread()
@@ -318,10 +315,6 @@ def test_api_UvicornServer_run(mocker):
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1'))
assert serve_mock.call_count == 0
- s.install_signal_handlers()
- # Original implementation starts a thread - make sure that's not the case
- assert serve_mock.call_count == 0
-
# Fake started to avoid sleeping forever
s.started = True
s.run()
@@ -331,13 +324,10 @@ def test_api_UvicornServer_run(mocker):
def test_api_UvicornServer_run_no_uvloop(mocker, import_fails):
serve_mock = mocker.patch('freqtrade.rpc.api_server.uvicorn_threaded.UvicornServer.serve',
get_mock_coro(None))
+ asyncio.set_event_loop(asyncio.new_event_loop())
s = UvicornServer(uvicorn.Config(MagicMock(), port=8080, host='127.0.0.1'))
assert serve_mock.call_count == 0
- s.install_signal_handlers()
- # Original implementation starts a thread - make sure that's not the case
- assert serve_mock.call_count == 0
-
# Fake started to avoid sleeping forever
s.started = True
s.run()
@@ -351,7 +341,7 @@ def test_api_run(default_conf, mocker, caplog):
"username": "TestUser",
"password": "testPass",
}})
- mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
+ mocker.patch('freqtrade.rpc.telegram.Telegram._init')
server_inst_mock = MagicMock()
server_inst_mock.run_in_thread = MagicMock()
@@ -429,7 +419,7 @@ def test_api_cleanup(default_conf, mocker, caplog):
"username": "TestUser",
"password": "testPass",
}})
- mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
+ mocker.patch('freqtrade.rpc.telegram.Telegram._init')
server_mock = MagicMock()
server_mock.cleanup = MagicMock()
@@ -490,13 +480,18 @@ def test_api_balance(botclient, mocker, rpc_balance, tickers):
'free': 12.0,
'balance': 12.0,
'used': 0.0,
+ 'bot_owned': pytest.approx(11.879999),
'est_stake': 12.0,
+ 'est_stake_bot': pytest.approx(11.879999),
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
+ 'is_bot_managed': True,
}
+ assert response['total'] == 12.159513094
+ assert response['total_bot'] == pytest.approx(11.879999)
assert 'starting_capital' in response
assert 'starting_capital_fiat' in response
assert 'starting_capital_pct' in response
@@ -893,6 +888,8 @@ def test_api_profit(botclient, mocker, ticker, fee, markets, is_short, expected)
'max_drawdown': ANY,
'max_drawdown_abs': ANY,
'trading_volume': expected['trading_volume'],
+ 'bot_start_timestamp': 0,
+ 'bot_start_date': '',
}
@@ -1066,6 +1063,9 @@ def test_api_status(botclient, mocker, ticker, fee, markets, is_short,
'liquidation_price': None,
'funding_fees': None,
'trading_mode': ANY,
+ 'amount_precision': None,
+ 'price_precision': None,
+ 'precision_mode': None,
'orders': [ANY],
}
@@ -1271,6 +1271,9 @@ def test_api_force_entry(botclient, mocker, fee, endpoint):
'liquidation_price': None,
'funding_fees': None,
'trading_mode': 'spot',
+ 'amount_precision': None,
+ 'price_precision': None,
+ 'precision_mode': None,
'orders': [],
}
@@ -1407,10 +1410,10 @@ def test_api_pair_candles(botclient, ohlcv_history):
])
-def test_api_pair_history(botclient, ohlcv_history):
+def test_api_pair_history(botclient, mocker):
ftbot, client = botclient
timeframe = '5m'
-
+ lfm = mocker.patch('freqtrade.strategy.interface.IStrategy.load_freqAI_model')
# No pair
rc = client_get(client,
f"{BASE_URI}/pair_history?timeframe={timeframe}"
@@ -1444,6 +1447,7 @@ def test_api_pair_history(botclient, ohlcv_history):
assert len(rc.json()['data']) == rc.json()['length']
assert 'columns' in rc.json()
assert 'data' in rc.json()
+ assert lfm.call_count == 1
assert rc.json()['pair'] == 'UNITTEST/BTC'
assert rc.json()['strategy'] == CURRENT_TEST_STRATEGY
assert rc.json()['data_start'] == '2018-01-11 00:00:00+00:00'
@@ -1873,7 +1877,7 @@ def test_api_ws_send_msg(default_conf, mocker, caplog):
"password": _TEST_PASS,
"ws_token": _TEST_WS_TOKEN
}})
- mocker.patch('freqtrade.rpc.telegram.Updater')
+ mocker.patch('freqtrade.rpc.telegram.Telegram._init')
mocker.patch('freqtrade.rpc.api_server.ApiServer.start_api')
apiserver = ApiServer(default_conf)
apiserver.add_rpc_handler(RPC(get_patched_freqtradebot(mocker, default_conf)))
diff --git a/tests/rpc/test_rpc_manager.py b/tests/rpc/test_rpc_manager.py
index 21c8b0813..f0bb72fc9 100644
--- a/tests/rpc/test_rpc_manager.py
+++ b/tests/rpc/test_rpc_manager.py
@@ -28,6 +28,7 @@ def test_init_telegram_disabled(mocker, default_conf, caplog) -> None:
def test_init_telegram_enabled(mocker, default_conf, caplog) -> None:
caplog.set_level(logging.DEBUG)
+ default_conf['telegram']['enabled'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
rpc_manager = RPCManager(get_patched_freqtradebot(mocker, default_conf))
@@ -52,6 +53,7 @@ def test_cleanup_telegram_disabled(mocker, default_conf, caplog) -> None:
def test_cleanup_telegram_enabled(mocker, default_conf, caplog) -> None:
caplog.set_level(logging.DEBUG)
+ default_conf['telegram']['enabled'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.cleanup', MagicMock())
@@ -85,7 +87,7 @@ def test_send_msg_telegram_disabled(mocker, default_conf, caplog) -> None:
def test_send_msg_telegram_error(mocker, default_conf, caplog) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', side_effect=ValueError())
-
+ default_conf['telegram']['enabled'] = True
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc_manager = RPCManager(freqtradebot)
rpc_manager.send_msg({
@@ -99,6 +101,7 @@ def test_send_msg_telegram_error(mocker, default_conf, caplog) -> None:
def test_process_msg_queue(mocker, default_conf, caplog) -> None:
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg')
+ default_conf['telegram']['enabled'] = True
default_conf['telegram']['allow_custom_messages'] = True
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
@@ -115,9 +118,9 @@ def test_process_msg_queue(mocker, default_conf, caplog) -> None:
def test_send_msg_telegram_enabled(mocker, default_conf, caplog) -> None:
+ default_conf['telegram']['enabled'] = True
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg')
mocker.patch('freqtrade.rpc.telegram.Telegram._init')
-
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc_manager = RPCManager(freqtradebot)
rpc_manager.send_msg({
@@ -166,7 +169,8 @@ def test_send_msg_webhook_CustomMessagetype(mocker, default_conf, caplog) -> Non
caplog)
-def test_startupmessages_telegram_enabled(mocker, default_conf, caplog) -> None:
+def test_startupmessages_telegram_enabled(mocker, default_conf) -> None:
+ default_conf['telegram']['enabled'] = True
telegram_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index b1859f581..4b4c2b028 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -2,13 +2,15 @@
# pragma pylint: disable=protected-access, unused-argument, invalid-name
# pragma pylint: disable=too-many-lines, too-many-arguments
+import asyncio
import logging
import re
+import threading
from datetime import datetime, timedelta, timezone
from functools import reduce
from random import choice, randint
from string import ascii_uppercase
-from unittest.mock import ANY, MagicMock
+from unittest.mock import ANY, AsyncMock, MagicMock
import arrow
import pytest
@@ -36,6 +38,40 @@ from tests.conftest import (CURRENT_TEST_STRATEGY, EXMS, create_mock_trades,
patch_exchange, patch_get_signal, patch_whitelist)
+@pytest.fixture(autouse=True)
+def mock_exchange_loop(mocker):
+ mocker.patch('freqtrade.exchange.exchange.Exchange._init_async_loop')
+
+
+@pytest.fixture
+def default_conf(default_conf) -> dict:
+ # Telegram is enabled by default
+ default_conf['telegram']['enabled'] = True
+ return default_conf
+
+
+@pytest.fixture
+def update():
+ message = Message(0, datetime.utcnow(), Chat(0, 0))
+ _update = Update(0, message=message)
+
+ return _update
+
+
+def patch_eventloop_threading(telegrambot):
+ is_init = False
+
+ def thread_fuck():
+ nonlocal is_init
+ telegrambot._loop = asyncio.new_event_loop()
+ is_init = True
+ telegrambot._loop.run_forever()
+ x = threading.Thread(target=thread_fuck, daemon=True)
+ x.start()
+ while not is_init:
+ pass
+
+
class DummyCls(Telegram):
"""
Dummy class for testing the Telegram @authorized_only decorator
@@ -49,14 +85,14 @@ class DummyCls(Telegram):
pass
@authorized_only
- def dummy_handler(self, *args, **kwargs) -> None:
+ async def dummy_handler(self, *args, **kwargs) -> None:
"""
Fake method that only change the state of the object
"""
self.state['called'] = True
@authorized_only
- def dummy_exception(self, *args, **kwargs) -> None:
+ async def dummy_exception(self, *args, **kwargs) -> None:
"""
Fake method that throw an exception
"""
@@ -64,23 +100,26 @@ class DummyCls(Telegram):
def get_telegram_testobject(mocker, default_conf, mock=True, ftbot=None):
- msg_mock = MagicMock()
+ msg_mock = AsyncMock()
if mock:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
- _send_msg=msg_mock
+ _send_msg=msg_mock,
+ _start_thread=MagicMock(),
)
if not ftbot:
+ mocker.patch('freqtrade.exchange.exchange.Exchange._init_async_loop')
ftbot = get_patched_freqtradebot(mocker, default_conf)
rpc = RPC(ftbot)
telegram = Telegram(rpc, default_conf)
+ telegram._loop = MagicMock()
+ patch_eventloop_threading(telegram)
return telegram, ftbot, msg_mock
def test_telegram__init__(default_conf, mocker) -> None:
- mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
telegram, _, _ = get_telegram_testobject(mocker, default_conf)
@@ -88,43 +127,73 @@ def test_telegram__init__(default_conf, mocker) -> None:
def test_telegram_init(default_conf, mocker, caplog) -> None:
- start_polling = MagicMock()
- mocker.patch('freqtrade.rpc.telegram.Updater', MagicMock(return_value=start_polling))
+ app_mock = MagicMock()
+ mocker.patch('freqtrade.rpc.telegram.Telegram._start_thread', MagicMock())
+ mocker.patch('freqtrade.rpc.telegram.Telegram._init_telegram_app', return_value=app_mock)
+ mocker.patch('freqtrade.rpc.telegram.Telegram._startup_telegram', AsyncMock())
- get_telegram_testobject(mocker, default_conf, mock=False)
- assert start_polling.call_count == 0
+ telegram, _, _ = get_telegram_testobject(mocker, default_conf, mock=False)
+ telegram._init()
+ assert app_mock.call_count == 0
# number of handles registered
- assert start_polling.dispatcher.add_handler.call_count > 0
- assert start_polling.start_polling.call_count == 1
+ assert app_mock.add_handler.call_count > 0
+ # assert start_polling.start_polling.call_count == 1
message_str = ("rpc.telegram is listening for following commands: [['status'], ['profit'], "
"['balance'], ['start'], ['stop'], "
- "['forcesell', 'forceexit', 'fx'], ['forcebuy', 'forcelong'], ['forceshort'], "
- "['trades'], ['delete'], ['coo', 'cancel_open_order'], ['performance'], "
- "['buys', 'entries'], ['sells', 'exits'], ['mix_tags'], "
+ "['forceexit', 'forcesell', 'fx'], ['forcebuy', 'forcelong'], ['forceshort'], "
+ "['trades'], ['delete'], ['cancel_open_order', 'coo'], ['performance'], "
+ "['buys', 'entries'], ['exits', 'sells'], ['mix_tags'], "
"['stats'], ['daily'], ['weekly'], ['monthly'], "
- "['count'], ['locks'], ['unlock', 'delete_locks'], "
- "['reload_config', 'reload_conf'], ['show_config', 'show_conf'], "
+ "['count'], ['locks'], ['delete_locks', 'unlock'], "
+ "['reload_conf', 'reload_config'], ['show_conf', 'show_config'], "
"['stopbuy', 'stopentry'], ['whitelist'], ['blacklist'], "
- "['blacklist_delete', 'bl_delete'], "
+ "['bl_delete', 'blacklist_delete'], "
"['logs'], ['edge'], ['health'], ['help'], ['version'], ['marketdir']"
"]")
assert log_has(message_str, caplog)
-def test_cleanup(default_conf, mocker, ) -> None:
+async def test_telegram_startup(default_conf, mocker) -> None:
+ app_mock = MagicMock()
+ app_mock.initialize = AsyncMock()
+ app_mock.start = AsyncMock()
+ app_mock.updater.start_polling = AsyncMock()
+ app_mock.updater.running = False
+ sleep_mock = mocker.patch('freqtrade.rpc.telegram.asyncio.sleep', AsyncMock())
+
+ telegram, _, _ = get_telegram_testobject(mocker, default_conf)
+ telegram._app = app_mock
+ await telegram._startup_telegram()
+ assert app_mock.initialize.call_count == 1
+ assert app_mock.start.call_count == 1
+ assert app_mock.updater.start_polling.call_count == 1
+ assert sleep_mock.call_count == 1
+
+
+async def test_telegram_cleanup(default_conf, mocker, ) -> None:
+ app_mock = MagicMock()
+ app_mock.stop = AsyncMock()
+ app_mock.initialize = AsyncMock()
+
updater_mock = MagicMock()
- updater_mock.stop = MagicMock()
- mocker.patch('freqtrade.rpc.telegram.Updater', updater_mock)
+ updater_mock.stop = AsyncMock()
+ app_mock.updater = updater_mock
+ # mocker.patch('freqtrade.rpc.telegram.Application', app_mock)
- telegram, _, _ = get_telegram_testobject(mocker, default_conf, mock=False)
+ telegram, _, _ = get_telegram_testobject(mocker, default_conf)
+ telegram._app = app_mock
+ telegram._loop = asyncio.get_running_loop()
+ telegram._thread = MagicMock()
telegram.cleanup()
- assert telegram._updater.stop.call_count == 1
+ await asyncio.sleep(0.1)
+ assert app_mock.stop.call_count == 1
+ assert telegram._thread.join.call_count == 1
-def test_authorized_only(default_conf, mocker, caplog, update) -> None:
+async def test_authorized_only(default_conf, mocker, caplog, update) -> None:
patch_exchange(mocker)
caplog.set_level(logging.DEBUG)
default_conf['telegram']['enabled'] = False
@@ -133,19 +202,19 @@ def test_authorized_only(default_conf, mocker, caplog, update) -> None:
dummy = DummyCls(rpc, default_conf)
patch_get_signal(bot)
- dummy.dummy_handler(update=update, context=MagicMock())
+ await dummy.dummy_handler(update=update, context=MagicMock())
assert dummy.state['called'] is True
assert log_has('Executing handler: dummy_handler for chat_id: 0', caplog)
assert not log_has('Rejected unauthorized message from: 0', caplog)
assert not log_has('Exception occurred within Telegram module', caplog)
-def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
+async def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
patch_exchange(mocker)
caplog.set_level(logging.DEBUG)
chat = Chat(0xdeadbeef, 0)
- update = Update(randint(1, 100))
- update.message = Message(randint(1, 100), datetime.utcnow(), chat)
+ message = Message(randint(1, 100), datetime.utcnow(), chat)
+ update = Update(randint(1, 100), message=message)
default_conf['telegram']['enabled'] = False
bot = FreqtradeBot(default_conf)
@@ -153,14 +222,14 @@ def test_authorized_only_unauthorized(default_conf, mocker, caplog) -> None:
dummy = DummyCls(rpc, default_conf)
patch_get_signal(bot)
- dummy.dummy_handler(update=update, context=MagicMock())
+ await dummy.dummy_handler(update=update, context=MagicMock())
assert dummy.state['called'] is False
assert not log_has('Executing handler: dummy_handler for chat_id: 3735928559', caplog)
assert log_has('Rejected unauthorized message from: 3735928559', caplog)
assert not log_has('Exception occurred within Telegram module', caplog)
-def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None:
+async def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None:
patch_exchange(mocker)
default_conf['telegram']['enabled'] = False
@@ -170,17 +239,15 @@ def test_authorized_only_exception(default_conf, mocker, caplog, update) -> None
dummy = DummyCls(rpc, default_conf)
patch_get_signal(bot)
- dummy.dummy_exception(update=update, context=MagicMock())
+ await dummy.dummy_exception(update=update, context=MagicMock())
assert dummy.state['called'] is False
assert not log_has('Executing handler: dummy_handler for chat_id: 0', caplog)
assert not log_has('Rejected unauthorized message from: 0', caplog)
assert log_has('Exception occurred within Telegram module', caplog)
-def test_telegram_status(default_conf, update, mocker) -> None:
- update.message.chat.id = "123"
+async def test_telegram_status(default_conf, update, mocker) -> None:
default_conf['telegram']['enabled'] = False
- default_conf['telegram']['chat_id'] = "123"
status_table = MagicMock()
mocker.patch('freqtrade.rpc.telegram.Telegram._status_table', status_table)
@@ -225,21 +292,19 @@ def test_telegram_status(default_conf, update, mocker) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
- telegram._status(update=update, context=MagicMock())
+ await telegram._status(update=update, context=MagicMock())
assert msg_mock.call_count == 1
context = MagicMock()
# /status table
context.args = ["table"]
- telegram._status(update=update, context=context)
+ await telegram._status(update=update, context=context)
assert status_table.call_count == 1
@pytest.mark.usefixtures("init_persistence")
-def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None:
- update.message.chat.id = "123"
+async def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None:
default_conf['telegram']['enabled'] = False
- default_conf['telegram']['chat_id'] = "123"
default_conf['position_adjustment_enable'] = True
mocker.patch.multiple(
EXMS,
@@ -277,7 +342,7 @@ def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None:
trade.recalc_trade_from_orders()
Trade.commit()
- telegram._status(update=update, context=MagicMock())
+ await telegram._status(update=update, context=MagicMock())
assert msg_mock.call_count == 4
msg = msg_mock.call_args_list[0][0][0]
assert re.search(r'Number of Entries.*2', msg)
@@ -289,10 +354,7 @@ def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None:
@pytest.mark.usefixtures("init_persistence")
-def test_telegram_status_closed_trade(default_conf, update, mocker, fee) -> None:
- update.message.chat.id = "123"
- default_conf['telegram']['enabled'] = False
- default_conf['telegram']['chat_id'] = "123"
+async def test_telegram_status_closed_trade(default_conf, update, mocker, fee) -> None:
default_conf['position_adjustment_enable'] = True
mocker.patch.multiple(
EXMS,
@@ -306,14 +368,14 @@ def test_telegram_status_closed_trade(default_conf, update, mocker, fee) -> None
trade = Trade.get_trades([Trade.is_open.is_(False)]).first()
context = MagicMock()
context.args = [str(trade.id)]
- telegram._status(update=update, context=context)
+ await telegram._status(update=update, context=context)
assert msg_mock.call_count == 1
msg = msg_mock.call_args_list[0][0][0]
assert re.search(r'Close Date:', msg)
assert re.search(r'Close Profit:', msg)
-def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
+async def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
default_conf['max_open_trades'] = 3
mocker.patch.multiple(
EXMS,
@@ -333,13 +395,13 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
freqtradebot.state = State.STOPPED
# Status is also enabled when stopped
- telegram._status(update=update, context=MagicMock())
+ await telegram._status(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert 'no active trade' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
freqtradebot.state = State.RUNNING
- telegram._status(update=update, context=MagicMock())
+ await telegram._status(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert 'no active trade' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
@@ -347,7 +409,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
# Create some test data
freqtradebot.enter_positions()
# Trigger status while we have a fulfilled order for the open trade
- telegram._status(update=update, context=MagicMock())
+ await telegram._status(update=update, context=MagicMock())
# close_rate should not be included in the message as the trade is not closed
# and no line should be empty
@@ -364,7 +426,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
context = MagicMock()
context.args = ["2", "3"]
- telegram._status(update=update, context=context)
+ await telegram._status(update=update, context=context)
lines = msg_mock.call_args_list[0][0][0].split('\n')
assert '' not in lines[:-1]
@@ -379,7 +441,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
msg_mock.reset_mock()
context = MagicMock()
context.args = ["2"]
- telegram._status(update=update, context=context)
+ await telegram._status(update=update, context=context)
assert msg_mock.call_count == 2
@@ -391,7 +453,7 @@ def test_status_handle(default_conf, update, ticker, fee, mocker) -> None:
assert 'Trade ID:* `2` - continued' in msg2
-def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
+async def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
mocker.patch.multiple(
EXMS,
fetch_ticker=ticker,
@@ -406,13 +468,13 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
freqtradebot.state = State.STOPPED
# Status table is also enabled when stopped
- telegram._status_table(update=update, context=MagicMock())
+ await telegram._status_table(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert 'no active trade' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
freqtradebot.state = State.RUNNING
- telegram._status_table(update=update, context=MagicMock())
+ await telegram._status_table(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert 'no active trade' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
@@ -420,7 +482,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
# Create some test data
freqtradebot.enter_positions()
- telegram._status_table(update=update, context=MagicMock())
+ await telegram._status_table(update=update, context=MagicMock())
text = re.sub('?pre>', '', msg_mock.call_args_list[-1][0][0])
line = text.split("\n")
@@ -432,7 +494,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
assert msg_mock.call_count == 1
-def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None:
+async def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None:
mocker.patch(
'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price',
return_value=1.1
@@ -454,7 +516,7 @@ def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machi
# /daily 2
context = MagicMock()
context.args = ["2"]
- telegram._daily(update=update, context=context)
+ await telegram._daily(update=update, context=context)
assert msg_mock.call_count == 1
assert "Daily Profit over the last 2 days:" in msg_mock.call_args_list[0][0][0]
assert 'Day ' in msg_mock.call_args_list[0][0][0]
@@ -468,7 +530,7 @@ def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machi
# Reset msg_mock
msg_mock.reset_mock()
context.args = []
- telegram._daily(update=update, context=context)
+ await telegram._daily(update=update, context=context)
assert msg_mock.call_count == 1
assert "Daily Profit over the last 7 days:" in msg_mock.call_args_list[0][0][0]
assert str(datetime.utcnow().date()) in msg_mock.call_args_list[0][0][0]
@@ -485,13 +547,13 @@ def test_daily_handle(default_conf_usdt, update, ticker, fee, mocker, time_machi
# /daily 1
context = MagicMock()
context.args = ["1"]
- telegram._daily(update=update, context=context)
+ await telegram._daily(update=update, context=context)
assert ' 6.83 USDT' in msg_mock.call_args_list[0][0][0]
assert ' 7.51 USD' in msg_mock.call_args_list[0][0][0]
assert '(2)' in msg_mock.call_args_list[0][0][0]
-def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
+async def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
mocker.patch.multiple(
EXMS,
fetch_ticker=ticker
@@ -506,7 +568,7 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
# /daily -2
context = MagicMock()
context.args = ["-2"]
- telegram._daily(update=update, context=context)
+ await telegram._daily(update=update, context=context)
assert msg_mock.call_count == 1
assert 'must be an integer greater than 0' in msg_mock.call_args_list[0][0][0]
@@ -516,11 +578,11 @@ def test_daily_wrong_input(default_conf, update, ticker, mocker) -> None:
# /daily today
context = MagicMock()
context.args = ["today"]
- telegram._daily(update=update, context=context)
+ await telegram._daily(update=update, context=context)
assert 'Daily Profit over the last 7 days:' in msg_mock.call_args_list[0][0][0]
-def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None:
+async def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None:
default_conf_usdt['max_open_trades'] = 1
mocker.patch(
'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price',
@@ -541,7 +603,7 @@ def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mach
# /weekly 2
context = MagicMock()
context.args = ["2"]
- telegram._weekly(update=update, context=context)
+ await telegram._weekly(update=update, context=context)
assert msg_mock.call_count == 1
assert "Weekly Profit over the last 2 weeks (starting from Monday):" \
in msg_mock.call_args_list[0][0][0]
@@ -557,7 +619,7 @@ def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mach
# Reset msg_mock
msg_mock.reset_mock()
context.args = []
- telegram._weekly(update=update, context=context)
+ await telegram._weekly(update=update, context=context)
assert msg_mock.call_count == 1
assert "Weekly Profit over the last 8 weeks (starting from Monday):" \
in msg_mock.call_args_list[0][0][0]
@@ -573,7 +635,7 @@ def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mach
# /weekly -3
context = MagicMock()
context.args = ["-3"]
- telegram._weekly(update=update, context=context)
+ await telegram._weekly(update=update, context=context)
assert msg_mock.call_count == 1
assert 'must be an integer greater than 0' in msg_mock.call_args_list[0][0][0]
@@ -583,14 +645,14 @@ def test_weekly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mach
# /weekly this week
context = MagicMock()
context.args = ["this week"]
- telegram._weekly(update=update, context=context)
+ await telegram._weekly(update=update, context=context)
assert (
'Weekly Profit over the last 8 weeks (starting from Monday):'
in msg_mock.call_args_list[0][0][0]
)
-def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None:
+async def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_machine) -> None:
default_conf_usdt['max_open_trades'] = 1
mocker.patch(
'freqtrade.rpc.rpc.CryptoToFiatConverter._find_price',
@@ -611,7 +673,7 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac
# /monthly 2
context = MagicMock()
context.args = ["2"]
- telegram._monthly(update=update, context=context)
+ await telegram._monthly(update=update, context=context)
assert msg_mock.call_count == 1
assert 'Monthly Profit over the last 2 months:' in msg_mock.call_args_list[0][0][0]
assert 'Month ' in msg_mock.call_args_list[0][0][0]
@@ -626,7 +688,7 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac
# Reset msg_mock
msg_mock.reset_mock()
context.args = []
- telegram._monthly(update=update, context=context)
+ await telegram._monthly(update=update, context=context)
assert msg_mock.call_count == 1
# Default to 6 months
assert 'Monthly Profit over the last 6 months:' in msg_mock.call_args_list[0][0][0]
@@ -643,7 +705,7 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac
# /monthly 12
context = MagicMock()
context.args = ["12"]
- telegram._monthly(update=update, context=context)
+ await telegram._monthly(update=update, context=context)
assert msg_mock.call_count == 1
assert 'Monthly Profit over the last 12 months:' in msg_mock.call_args_list[0][0][0]
assert ' 2.74 USDT' in msg_mock.call_args_list[0][0][0]
@@ -652,7 +714,7 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac
# The one-digit months should contain a zero, Eg: September 2021 = "2021-09"
# Since we loaded the last 12 months, any month should appear
- assert str('-09') in msg_mock.call_args_list[0][0][0]
+ assert '-09' in msg_mock.call_args_list[0][0][0]
# Try invalid data
msg_mock.reset_mock()
@@ -660,7 +722,7 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac
# /monthly -3
context = MagicMock()
context.args = ["-3"]
- telegram._monthly(update=update, context=context)
+ await telegram._monthly(update=update, context=context)
assert msg_mock.call_count == 1
assert 'must be an integer greater than 0' in msg_mock.call_args_list[0][0][0]
@@ -670,12 +732,13 @@ def test_monthly_handle(default_conf_usdt, update, ticker, fee, mocker, time_mac
# /monthly february
context = MagicMock()
context.args = ["february"]
- telegram._monthly(update=update, context=context)
- assert str('Monthly Profit over the last 6 months:') in msg_mock.call_args_list[0][0][0]
+ await telegram._monthly(update=update, context=context)
+ assert 'Monthly Profit over the last 6 months:' in msg_mock.call_args_list[0][0][0]
-def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, fee,
- limit_sell_order_usdt, mocker) -> None:
+async def test_telegram_profit_handle(
+ default_conf_usdt, update, ticker_usdt, ticker_sell_up, fee,
+ limit_sell_order_usdt, mocker) -> None:
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=1.1)
mocker.patch.multiple(
EXMS,
@@ -686,7 +749,7 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf_usdt)
patch_get_signal(freqtradebot)
- telegram._profit(update=update, context=MagicMock())
+ await telegram._profit(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert 'No trades yet.' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
@@ -698,7 +761,7 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f
context = MagicMock()
# Test with invalid 2nd argument (should silently pass)
context.args = ["aaa"]
- telegram._profit(update=update, context=context)
+ await telegram._profit(update=update, context=context)
assert msg_mock.call_count == 1
assert 'No closed trade' in msg_mock.call_args_list[-1][0][0]
assert '*ROI:* All trades' in msg_mock.call_args_list[-1][0][0]
@@ -710,6 +773,7 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f
# Update the ticker with a market going up
mocker.patch(f'{EXMS}.fetch_ticker', ticker_sell_up)
# Simulate fulfilled LIMIT_SELL order for trade
+ trade = Trade.session.scalars(select(Trade)).first()
oobj = Order.parse_from_ccxt_object(
limit_sell_order_usdt, limit_sell_order_usdt['symbol'], 'sell')
trade.orders.append(oobj)
@@ -720,7 +784,7 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f
Trade.commit()
context.args = [3]
- telegram._profit(update=update, context=context)
+ await telegram._profit(update=update, context=context)
assert msg_mock.call_count == 1
assert '*ROI:* Closed trades' in msg_mock.call_args_list[-1][0][0]
assert ('∙ `5.685 USDT (9.45%) (0.57 \N{GREEK CAPITAL LETTER SIGMA}%)`'
@@ -738,7 +802,7 @@ def test_profit_handle(default_conf_usdt, update, ticker_usdt, ticker_sell_up, f
@pytest.mark.parametrize('is_short', [True, False])
-def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_short) -> None:
+async def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_short) -> None:
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
mocker.patch.multiple(
EXMS,
@@ -748,7 +812,7 @@ def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_short) ->
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot)
- telegram._stats(update=update, context=MagicMock())
+ await telegram._stats(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert 'No trades yet.' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
@@ -756,7 +820,7 @@ def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_short) ->
# Create some test data
create_mock_trades(fee, is_short=is_short)
- telegram._stats(update=update, context=MagicMock())
+ await telegram._stats(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert 'Exit Reason' in msg_mock.call_args_list[-1][0][0]
assert 'ROI' in msg_mock.call_args_list[-1][0][0]
@@ -764,7 +828,7 @@ def test_telegram_stats(default_conf, update, ticker, fee, mocker, is_short) ->
msg_mock.reset_mock()
-def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tickers) -> None:
+async def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tickers) -> None:
default_conf['dry_run'] = False
mocker.patch(f'{EXMS}.get_balances', return_value=rpc_balance)
mocker.patch(f'{EXMS}.get_tickers', tickers)
@@ -773,23 +837,32 @@ def test_telegram_balance_handle(default_conf, update, mocker, rpc_balance, tick
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot)
- telegram._balance(update=update, context=MagicMock())
+ await telegram._balance(update=update, context=MagicMock())
+ context = MagicMock()
+ context.args = ["full"]
+ await telegram._balance(update=update, context=context)
result = msg_mock.call_args_list[0][0][0]
- assert msg_mock.call_count == 1
+ result_full = msg_mock.call_args_list[1][0][0]
+ assert msg_mock.call_count == 2
assert '*BTC:*' in result
assert '*ETH:*' not in result
assert '*USDT:*' not in result
assert '*EUR:*' not in result
- assert '*LTC:*' in result
+ assert '*LTC:*' not in result
+
+ assert '*LTC:*' in result_full
assert '*XRP:*' not in result
assert 'Balance:' in result
assert 'Est. BTC:' in result
- assert 'BTC: 12' in result
+ assert 'BTC: 11' in result
+ assert 'BTC: 12' in result_full
assert "*3 Other Currencies (< 0.0001 BTC):*" in result
assert 'BTC: 0.00000309' in result
+ assert '*Estimated Value*:' in result_full
+ assert '*Estimated Value (Bot managed assets only)*:' in result
-def test_balance_handle_empty_response(default_conf, update, mocker) -> None:
+async def test_balance_handle_empty_response(default_conf, update, mocker) -> None:
default_conf['dry_run'] = False
mocker.patch(f'{EXMS}.get_balances', return_value={})
@@ -797,26 +870,26 @@ def test_balance_handle_empty_response(default_conf, update, mocker) -> None:
patch_get_signal(freqtradebot)
freqtradebot.config['dry_run'] = False
- telegram._balance(update=update, context=MagicMock())
+ await telegram._balance(update=update, context=MagicMock())
result = msg_mock.call_args_list[0][0][0]
assert msg_mock.call_count == 1
assert 'Starting capital: `0 BTC' in result
-def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None:
+async def test_balance_handle_empty_response_dry(default_conf, update, mocker) -> None:
mocker.patch(f'{EXMS}.get_balances', return_value={})
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot)
- telegram._balance(update=update, context=MagicMock())
+ await telegram._balance(update=update, context=MagicMock())
result = msg_mock.call_args_list[0][0][0]
assert msg_mock.call_count == 1
assert "*Warning:* Simulated balances in Dry Mode." in result
assert "Starting capital: `1000 BTC`" in result
-def test_balance_handle_too_large_response(default_conf, update, mocker) -> None:
+async def test_balance_handle_too_large_response(default_conf, update, mocker) -> None:
balances = []
for i in range(100):
curr = choice(ascii_uppercase) + choice(ascii_uppercase) + choice(ascii_uppercase)
@@ -825,18 +898,23 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None
'free': 1.0,
'used': 0.5,
'balance': i,
+ 'bot_owned': 0.5,
'est_stake': 1,
+ 'est_stake_bot': 1,
'stake': 'BTC',
'is_position': False,
'leverage': 1.0,
'position': 0.0,
'side': 'long',
+ 'is_bot_managed': True,
})
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_balance', return_value={
'currencies': balances,
'total': 100.0,
+ 'total_bot': 100.0,
'symbol': 100.0,
'value': 1000.0,
+ 'value_bot': 1000.0,
'starting_capital': 1000,
'starting_capital_fiat': 1000,
})
@@ -844,7 +922,7 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot)
- telegram._balance(update=update, context=MagicMock())
+ await telegram._balance(update=update, context=MagicMock())
assert msg_mock.call_count > 1
# Test if wrap happens around 4000 -
# and each single currency-output is around 120 characters long so we need
@@ -853,79 +931,79 @@ def test_balance_handle_too_large_response(default_conf, update, mocker) -> None
assert len(msg_mock.call_args_list[0][0][0]) > (4096 - 120)
-def test_start_handle(default_conf, update, mocker) -> None:
+async def test_start_handle(default_conf, update, mocker) -> None:
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
freqtradebot.state = State.STOPPED
assert freqtradebot.state == State.STOPPED
- telegram._start(update=update, context=MagicMock())
+ await telegram._start(update=update, context=MagicMock())
assert freqtradebot.state == State.RUNNING
assert msg_mock.call_count == 1
-def test_start_handle_already_running(default_conf, update, mocker) -> None:
+async def test_start_handle_already_running(default_conf, update, mocker) -> None:
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
freqtradebot.state = State.RUNNING
assert freqtradebot.state == State.RUNNING
- telegram._start(update=update, context=MagicMock())
+ await telegram._start(update=update, context=MagicMock())
assert freqtradebot.state == State.RUNNING
assert msg_mock.call_count == 1
assert 'already running' in msg_mock.call_args_list[0][0][0]
-def test_stop_handle(default_conf, update, mocker) -> None:
+async def test_stop_handle(default_conf, update, mocker) -> None:
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
freqtradebot.state = State.RUNNING
assert freqtradebot.state == State.RUNNING
- telegram._stop(update=update, context=MagicMock())
+ await telegram._stop(update=update, context=MagicMock())
assert freqtradebot.state == State.STOPPED
assert msg_mock.call_count == 1
assert 'stopping trader' in msg_mock.call_args_list[0][0][0]
-def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
+async def test_stop_handle_already_stopped(default_conf, update, mocker) -> None:
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
freqtradebot.state = State.STOPPED
assert freqtradebot.state == State.STOPPED
- telegram._stop(update=update, context=MagicMock())
+ await telegram._stop(update=update, context=MagicMock())
assert freqtradebot.state == State.STOPPED
assert msg_mock.call_count == 1
assert 'already stopped' in msg_mock.call_args_list[0][0][0]
-def test_stopbuy_handle(default_conf, update, mocker) -> None:
+async def test_stopbuy_handle(default_conf, update, mocker) -> None:
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
assert freqtradebot.config['max_open_trades'] != 0
- telegram._stopentry(update=update, context=MagicMock())
+ await telegram._stopentry(update=update, context=MagicMock())
assert freqtradebot.config['max_open_trades'] == 0
assert msg_mock.call_count == 1
assert 'No more entries will occur from now. Run /reload_config to reset.' \
in msg_mock.call_args_list[0][0][0]
-def test_reload_config_handle(default_conf, update, mocker) -> None:
+async def test_reload_config_handle(default_conf, update, mocker) -> None:
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
freqtradebot.state = State.RUNNING
assert freqtradebot.state == State.RUNNING
- telegram._reload_config(update=update, context=MagicMock())
+ await telegram._reload_config(update=update, context=MagicMock())
assert freqtradebot.state == State.RELOAD_CONFIG
assert msg_mock.call_count == 1
assert 'Reloading config' in msg_mock.call_args_list[0][0][0]
-def test_telegram_forceexit_handle(default_conf, update, ticker, fee,
- ticker_sell_up, mocker) -> None:
+async def test_telegram_forceexit_handle(default_conf, update, ticker, fee,
+ ticker_sell_up, mocker) -> None:
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
msg_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
@@ -955,7 +1033,7 @@ def test_telegram_forceexit_handle(default_conf, update, ticker, fee,
# /forceexit 1
context = MagicMock()
context.args = ["1"]
- telegram._force_exit(update=update, context=context)
+ await telegram._force_exit(update=update, context=context)
assert msg_mock.call_count == 4
last_msg = msg_mock.call_args_list[-2][0][0]
@@ -991,8 +1069,8 @@ def test_telegram_forceexit_handle(default_conf, update, ticker, fee,
} == last_msg
-def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee,
- ticker_sell_down, mocker) -> None:
+async def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee,
+ ticker_sell_down, mocker) -> None:
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
return_value=15000.0)
msg_mock = mocker.patch('freqtrade.rpc.telegram.Telegram.send_msg', MagicMock())
@@ -1027,7 +1105,7 @@ def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee,
# /forceexit 1
context = MagicMock()
context.args = ["1"]
- telegram._force_exit(update=update, context=context)
+ await telegram._force_exit(update=update, context=context)
assert msg_mock.call_count == 4
@@ -1064,7 +1142,7 @@ def test_telegram_force_exit_down_handle(default_conf, update, ticker, fee,
} == last_msg
-def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -> None:
+async def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -> None:
patch_exchange(mocker)
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
return_value=15000.0)
@@ -1090,7 +1168,7 @@ def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -> None
# /forceexit all
context = MagicMock()
context.args = ["all"]
- telegram._force_exit(update=update, context=context)
+ await telegram._force_exit(update=update, context=context)
# Called for each trade 2 times
assert msg_mock.call_count == 8
@@ -1127,7 +1205,7 @@ def test_forceexit_all_handle(default_conf, update, ticker, fee, mocker) -> None
} == msg
-def test_forceexit_handle_invalid(default_conf, update, mocker) -> None:
+async def test_forceexit_handle_invalid(default_conf, update, mocker) -> None:
mocker.patch('freqtrade.rpc.fiat_convert.CryptoToFiatConverter._find_price',
return_value=15000.0)
@@ -1139,7 +1217,7 @@ def test_forceexit_handle_invalid(default_conf, update, mocker) -> None:
# /forceexit 1
context = MagicMock()
context.args = ["1"]
- telegram._force_exit(update=update, context=context)
+ await telegram._force_exit(update=update, context=context)
assert msg_mock.call_count == 1
assert 'not running' in msg_mock.call_args_list[0][0][0]
@@ -1149,12 +1227,12 @@ def test_forceexit_handle_invalid(default_conf, update, mocker) -> None:
# /forceexit 123456
context = MagicMock()
context.args = ["123456"]
- telegram._force_exit(update=update, context=context)
+ await telegram._force_exit(update=update, context=context)
assert msg_mock.call_count == 1
assert 'invalid argument' in msg_mock.call_args_list[0][0][0]
-def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None:
+async def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None:
default_conf['max_open_trades'] = 4
mocker.patch.multiple(
EXMS,
@@ -1170,7 +1248,7 @@ def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None:
# /forceexit
context = MagicMock()
context.args = []
- telegram._force_exit(update=update, context=context)
+ await telegram._force_exit(update=update, context=context)
# No pair
assert msg_mock.call_args_list[0][1]['msg'] == 'No open trade found.'
@@ -1179,7 +1257,7 @@ def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None:
msg_mock.reset_mock()
# /forceexit
- telegram._force_exit(update=update, context=context)
+ await telegram._force_exit(update=update, context=context)
keyboard = msg_mock.call_args_list[0][1]['keyboard']
# 4 pairs + cancel
assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 5
@@ -1187,9 +1265,9 @@ def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None:
assert keyboard[1][0].callback_data == 'force_exit__2 '
update = MagicMock()
- update.callback_query = MagicMock()
+ update.callback_query = AsyncMock()
update.callback_query.data = keyboard[1][0].callback_data
- telegram._force_exit_inline(update, None)
+ await telegram._force_exit_inline(update, None)
assert update.callback_query.answer.call_count == 1
assert update.callback_query.edit_message_text.call_count == 1
assert femock.call_count == 1
@@ -1197,17 +1275,17 @@ def test_force_exit_no_pair(default_conf, update, ticker, fee, mocker) -> None:
# Retry exiting - but cancel instead
update.callback_query.reset_mock()
- telegram._force_exit(update=update, context=context)
+ await telegram._force_exit(update=update, context=context)
# Use cancel button
update.callback_query.data = keyboard[-1][0].callback_data
- telegram._force_exit_inline(update, None)
+ await telegram._force_exit_inline(update, None)
query = update.callback_query
assert query.answer.call_count == 1
assert query.edit_message_text.call_count == 1
assert query.edit_message_text.call_args_list[-1][1]['text'] == "Force exit canceled."
-def test_force_enter_handle(default_conf, update, mocker) -> None:
+async def test_force_enter_handle(default_conf, update, mocker) -> None:
mocker.patch('freqtrade.rpc.rpc.CryptoToFiatConverter._find_price', return_value=15000.0)
fbuy_mock = MagicMock(return_value=None)
@@ -1219,7 +1297,7 @@ def test_force_enter_handle(default_conf, update, mocker) -> None:
# /forcelong ETH/BTC
context = MagicMock()
context.args = ["ETH/BTC"]
- telegram._force_enter(update=update, context=context, order_side=SignalDirection.LONG)
+ await telegram._force_enter(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'
@@ -1232,7 +1310,7 @@ def test_force_enter_handle(default_conf, update, mocker) -> None:
# /forcelong ETH/BTC 0.055
context = MagicMock()
context.args = ["ETH/BTC", "0.055"]
- telegram._force_enter(update=update, context=context, order_side=SignalDirection.LONG)
+ await telegram._force_enter(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'
@@ -1240,20 +1318,19 @@ def test_force_enter_handle(default_conf, update, mocker) -> None:
assert fbuy_mock.call_args_list[0][0][1] == 0.055
-def test_force_enter_handle_exception(default_conf, update, mocker) -> None:
+async def test_force_enter_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._force_enter(update=update, context=MagicMock(), order_side=SignalDirection.LONG)
+ await telegram._force_enter(update=update, context=MagicMock(), order_side=SignalDirection.LONG)
assert msg_mock.call_count == 1
assert msg_mock.call_args_list[0][0][0] == 'Force_entry not enabled.'
-def test_force_enter_no_pair(default_conf, update, mocker) -> None:
+async def test_force_enter_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)
@@ -1265,7 +1342,7 @@ def test_force_enter_no_pair(default_conf, update, mocker) -> None:
context = MagicMock()
context.args = []
- telegram._force_enter(update=update, context=context, order_side=SignalDirection.LONG)
+ await telegram._force_enter(update=update, context=context, order_side=SignalDirection.LONG)
assert fbuy_mock.call_count == 0
assert msg_mock.call_count == 1
@@ -1275,13 +1352,13 @@ def test_force_enter_no_pair(default_conf, update, mocker) -> None:
# One additional button - cancel
assert reduce(lambda acc, x: acc + len(x), keyboard, 0) == 5
update = MagicMock()
- update.callback_query = MagicMock()
+ update.callback_query = AsyncMock()
update.callback_query.data = 'XRP/USDT_||_long'
- telegram._force_enter_inline(update, None)
+ await telegram._force_enter_inline(update, None)
assert fbuy_mock.call_count == 1
-def test_telegram_performance_handle(default_conf_usdt, update, ticker, fee, mocker) -> None:
+async def test_telegram_performance_handle(default_conf_usdt, update, ticker, fee, mocker) -> None:
mocker.patch.multiple(
EXMS,
@@ -1293,13 +1370,13 @@ def test_telegram_performance_handle(default_conf_usdt, update, ticker, fee, moc
# Create some test data
create_mock_trades_usdt(fee)
- telegram._performance(update=update, context=MagicMock())
+ await telegram._performance(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert 'Performance' in msg_mock.call_args_list[0][0][0]
assert 'XRP/USDT\t2.842 USDT (10.00%) (1)' in msg_mock.call_args_list[0][0][0]
-def test_telegram_entry_tag_performance_handle(
+async def test_telegram_entry_tag_performance_handle(
default_conf_usdt, update, ticker, fee, mocker) -> None:
mocker.patch.multiple(
EXMS,
@@ -1312,26 +1389,26 @@ def test_telegram_entry_tag_performance_handle(
create_mock_trades_usdt(fee)
context = MagicMock()
- telegram._enter_tag_performance(update=update, context=context)
+ await telegram._enter_tag_performance(update=update, context=context)
assert msg_mock.call_count == 1
assert 'Entry Tag Performance' in msg_mock.call_args_list[0][0][0]
assert 'TEST1\t3.987 USDT (5.00%) (1)' in msg_mock.call_args_list[0][0][0]
context.args = ['XRP/USDT']
- telegram._enter_tag_performance(update=update, context=context)
+ await 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_enter_tag_performance',
side_effect=RPCException('Error'))
- telegram._enter_tag_performance(update=update, context=MagicMock())
+ await 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]
-def test_telegram_exit_reason_performance_handle(default_conf_usdt, update, ticker, fee,
- mocker) -> None:
+async def test_telegram_exit_reason_performance_handle(
+ default_conf_usdt, update, ticker, fee, mocker) -> None:
mocker.patch.multiple(
EXMS,
fetch_ticker=ticker,
@@ -1343,26 +1420,26 @@ def test_telegram_exit_reason_performance_handle(default_conf_usdt, update, tick
create_mock_trades_usdt(fee)
context = MagicMock()
- telegram._exit_reason_performance(update=update, context=context)
+ await telegram._exit_reason_performance(update=update, context=context)
assert msg_mock.call_count == 1
assert 'Exit Reason Performance' in msg_mock.call_args_list[0][0][0]
assert 'roi\t2.842 USDT (10.00%) (1)' in msg_mock.call_args_list[0][0][0]
context.args = ['XRP/USDT']
- telegram._exit_reason_performance(update=update, context=context)
+ await telegram._exit_reason_performance(update=update, context=context)
assert msg_mock.call_count == 2
msg_mock.reset_mock()
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_exit_reason_performance',
side_effect=RPCException('Error'))
- telegram._exit_reason_performance(update=update, context=MagicMock())
+ await telegram._exit_reason_performance(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert "Error" in msg_mock.call_args_list[0][0][0]
-def test_telegram_mix_tag_performance_handle(default_conf_usdt, update, ticker, fee,
- mocker) -> None:
+async def test_telegram_mix_tag_performance_handle(default_conf_usdt, update, ticker, fee,
+ mocker) -> None:
mocker.patch.multiple(
EXMS,
fetch_ticker=ticker,
@@ -1375,26 +1452,26 @@ def test_telegram_mix_tag_performance_handle(default_conf_usdt, update, ticker,
create_mock_trades_usdt(fee)
context = MagicMock()
- telegram._mix_tag_performance(update=update, context=context)
+ await telegram._mix_tag_performance(update=update, context=context)
assert msg_mock.call_count == 1
assert 'Mix Tag Performance' in msg_mock.call_args_list[0][0][0]
assert ('TEST3 roi\t2.842 USDT (10.00%) (1)'
in msg_mock.call_args_list[0][0][0])
context.args = ['XRP/USDT']
- telegram._mix_tag_performance(update=update, context=context)
+ await telegram._mix_tag_performance(update=update, context=context)
assert msg_mock.call_count == 2
msg_mock.reset_mock()
mocker.patch('freqtrade.rpc.rpc.RPC._rpc_mix_tag_performance',
side_effect=RPCException('Error'))
- telegram._mix_tag_performance(update=update, context=MagicMock())
+ await telegram._mix_tag_performance(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert "Error" in msg_mock.call_args_list[0][0][0]
-def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
+async def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
mocker.patch.multiple(
EXMS,
fetch_ticker=ticker,
@@ -1404,7 +1481,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
patch_get_signal(freqtradebot)
freqtradebot.state = State.STOPPED
- telegram._count(update=update, context=MagicMock())
+ await telegram._count(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert 'not running' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
@@ -1413,7 +1490,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
# Create some test data
freqtradebot.enter_positions()
msg_mock.reset_mock()
- telegram._count(update=update, context=MagicMock())
+ await telegram._count(update=update, context=MagicMock())
msg = (' current max total stake\n--------- ----- -------------\n'
' 1 {} {}').format(
@@ -1423,7 +1500,7 @@ def test_count_handle(default_conf, update, ticker, fee, mocker) -> None:
assert msg in msg_mock.call_args_list[0][0][0]
-def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None:
+async def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None:
mocker.patch.multiple(
EXMS,
fetch_ticker=ticker,
@@ -1431,7 +1508,7 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None
)
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
patch_get_signal(freqtradebot)
- telegram._locks(update=update, context=MagicMock())
+ await telegram._locks(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert 'No active locks.' in msg_mock.call_args_list[0][0][0]
@@ -1440,7 +1517,7 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None
PairLocks.lock_pair('ETH/BTC', arrow.utcnow().shift(minutes=4).datetime, 'randreason')
PairLocks.lock_pair('XRP/BTC', arrow.utcnow().shift(minutes=20).datetime, 'deadbeef')
- telegram._locks(update=update, context=MagicMock())
+ await telegram._locks(update=update, context=MagicMock())
assert 'Pair' in msg_mock.call_args_list[0][0][0]
assert 'Until' in msg_mock.call_args_list[0][0][0]
@@ -1453,7 +1530,7 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None
context = MagicMock()
context.args = ['XRP/BTC']
msg_mock.reset_mock()
- telegram._delete_locks(update=update, context=context)
+ await telegram._delete_locks(update=update, context=context)
assert 'ETH/BTC' in msg_mock.call_args_list[0][0][0]
assert 'randreason' in msg_mock.call_args_list[0][0][0]
@@ -1461,11 +1538,11 @@ def test_telegram_lock_handle(default_conf, update, ticker, fee, mocker) -> None
assert 'deadbeef' not in msg_mock.call_args_list[0][0][0]
-def test_whitelist_static(default_conf, update, mocker) -> None:
+async def test_whitelist_static(default_conf, update, mocker) -> None:
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
- telegram._whitelist(update=update, context=MagicMock())
+ await telegram._whitelist(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert ("Using whitelist `['StaticPairList']` with 4 pairs\n"
"`ETH/BTC, LTC/BTC, XRP/BTC, NEO/BTC`" in msg_mock.call_args_list[0][0][0])
@@ -1473,33 +1550,33 @@ def test_whitelist_static(default_conf, update, mocker) -> None:
context = MagicMock()
context.args = ['sorted']
msg_mock.reset_mock()
- telegram._whitelist(update=update, context=context)
+ await telegram._whitelist(update=update, context=context)
assert ("Using whitelist `['StaticPairList']` with 4 pairs\n"
"`ETH/BTC, LTC/BTC, NEO/BTC, XRP/BTC`" in msg_mock.call_args_list[0][0][0])
context = MagicMock()
context.args = ['baseonly']
msg_mock.reset_mock()
- telegram._whitelist(update=update, context=context)
+ await telegram._whitelist(update=update, context=context)
assert ("Using whitelist `['StaticPairList']` with 4 pairs\n"
"`ETH, LTC, XRP, NEO`" in msg_mock.call_args_list[0][0][0])
context = MagicMock()
context.args = ['baseonly', 'sorted']
msg_mock.reset_mock()
- telegram._whitelist(update=update, context=context)
+ await telegram._whitelist(update=update, context=context)
assert ("Using whitelist `['StaticPairList']` with 4 pairs\n"
"`ETH, LTC, NEO, XRP`" in msg_mock.call_args_list[0][0][0])
-def test_whitelist_dynamic(default_conf, update, mocker) -> None:
+async def test_whitelist_dynamic(default_conf, update, mocker) -> None:
mocker.patch(f'{EXMS}.exchange_has', return_value=True)
default_conf['pairlists'] = [{'method': 'VolumePairList',
'number_assets': 4
}]
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
- telegram._whitelist(update=update, context=MagicMock())
+ await telegram._whitelist(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert ("Using whitelist `['VolumePairList']` with 4 pairs\n"
"`ETH/BTC, LTC/BTC, XRP/BTC, NEO/BTC`" in msg_mock.call_args_list[0][0][0])
@@ -1507,30 +1584,30 @@ def test_whitelist_dynamic(default_conf, update, mocker) -> None:
context = MagicMock()
context.args = ['sorted']
msg_mock.reset_mock()
- telegram._whitelist(update=update, context=context)
+ await telegram._whitelist(update=update, context=context)
assert ("Using whitelist `['VolumePairList']` with 4 pairs\n"
"`ETH/BTC, LTC/BTC, NEO/BTC, XRP/BTC`" in msg_mock.call_args_list[0][0][0])
context = MagicMock()
context.args = ['baseonly']
msg_mock.reset_mock()
- telegram._whitelist(update=update, context=context)
+ await telegram._whitelist(update=update, context=context)
assert ("Using whitelist `['VolumePairList']` with 4 pairs\n"
"`ETH, LTC, XRP, NEO`" in msg_mock.call_args_list[0][0][0])
context = MagicMock()
context.args = ['baseonly', 'sorted']
msg_mock.reset_mock()
- telegram._whitelist(update=update, context=context)
+ await telegram._whitelist(update=update, context=context)
assert ("Using whitelist `['VolumePairList']` with 4 pairs\n"
"`ETH, LTC, NEO, XRP`" in msg_mock.call_args_list[0][0][0])
-def test_blacklist_static(default_conf, update, mocker) -> None:
+async def test_blacklist_static(default_conf, update, mocker) -> None:
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
- telegram._blacklist(update=update, context=MagicMock())
+ await telegram._blacklist(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert ("Blacklist contains 2 pairs\n`DOGE/BTC, HOT/BTC`"
in msg_mock.call_args_list[0][0][0])
@@ -1540,7 +1617,7 @@ def test_blacklist_static(default_conf, update, mocker) -> None:
# /blacklist ETH/BTC
context = MagicMock()
context.args = ["ETH/BTC"]
- telegram._blacklist(update=update, context=context)
+ await telegram._blacklist(update=update, context=context)
assert msg_mock.call_count == 1
assert ("Blacklist contains 3 pairs\n`DOGE/BTC, HOT/BTC, ETH/BTC`"
in msg_mock.call_args_list[0][0][0])
@@ -1549,7 +1626,7 @@ def test_blacklist_static(default_conf, update, mocker) -> None:
msg_mock.reset_mock()
context = MagicMock()
context.args = ["XRP/.*"]
- telegram._blacklist(update=update, context=context)
+ await telegram._blacklist(update=update, context=context)
assert msg_mock.call_count == 1
assert ("Blacklist contains 4 pairs\n`DOGE/BTC, HOT/BTC, ETH/BTC, XRP/.*`"
@@ -1558,13 +1635,13 @@ def test_blacklist_static(default_conf, update, mocker) -> None:
msg_mock.reset_mock()
context.args = ["DOGE/BTC"]
- telegram._blacklist_delete(update=update, context=context)
+ await telegram._blacklist_delete(update=update, context=context)
assert msg_mock.call_count == 1
assert ("Blacklist contains 3 pairs\n`HOT/BTC, ETH/BTC, XRP/.*`"
in msg_mock.call_args_list[0][0][0])
-def test_telegram_logs(default_conf, update, mocker) -> None:
+async def test_telegram_logs(default_conf, update, mocker) -> None:
mocker.patch.multiple(
'freqtrade.rpc.telegram.Telegram',
_init=MagicMock(),
@@ -1575,13 +1652,13 @@ def test_telegram_logs(default_conf, update, mocker) -> None:
context = MagicMock()
context.args = []
- telegram._logs(update=update, context=context)
+ await telegram._logs(update=update, context=context)
assert msg_mock.call_count == 1
assert "freqtrade\\.rpc\\.telegram" in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
context.args = ["1"]
- telegram._logs(update=update, context=context)
+ await telegram._logs(update=update, context=context)
assert msg_mock.call_count == 1
msg_mock.reset_mock()
@@ -1589,22 +1666,22 @@ def test_telegram_logs(default_conf, update, mocker) -> None:
mocker.patch('freqtrade.rpc.telegram.MAX_MESSAGE_LENGTH', 200)
context = MagicMock()
context.args = []
- telegram._logs(update=update, context=context)
+ await telegram._logs(update=update, context=context)
# Called at least 2 times. Exact times will change with unrelated changes to setup messages
# Therefore we don't test for this explicitly.
assert msg_mock.call_count >= 2
-def test_edge_disabled(default_conf, update, mocker) -> None:
+async def test_edge_disabled(default_conf, update, mocker) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
- telegram._edge(update=update, context=MagicMock())
+ await telegram._edge(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert "Edge is not enabled." in msg_mock.call_args_list[0][0][0]
-def test_edge_enabled(edge_conf, update, mocker) -> None:
+async def test_edge_enabled(edge_conf, update, mocker) -> None:
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
return_value={
'E/F': PairInfo(-0.01, 0.66, 3.71, 0.50, 1.71, 10, 60),
@@ -1613,7 +1690,7 @@ def test_edge_enabled(edge_conf, update, mocker) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, edge_conf)
- telegram._edge(update=update, context=MagicMock())
+ await telegram._edge(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert 'Edge only validated following pairs:\n' in msg_mock.call_args_list[0][0][0]
assert 'Pair Winrate Expectancy Stoploss' in msg_mock.call_args_list[0][0][0]
@@ -1622,7 +1699,7 @@ def test_edge_enabled(edge_conf, update, mocker) -> None:
mocker.patch('freqtrade.edge.Edge._cached_pairs', mocker.PropertyMock(
return_value={}))
- telegram._edge(update=update, context=MagicMock())
+ await telegram._edge(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert 'Edge only validated following pairs:' in msg_mock.call_args_list[0][0][0]
assert 'Winrate' not in msg_mock.call_args_list[0][0][0]
@@ -1631,20 +1708,20 @@ def test_edge_enabled(edge_conf, update, mocker) -> None:
@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):
+async def test_telegram_trades(mocker, update, default_conf, fee, is_short, regex_pattern):
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
context = MagicMock()
context.args = []
- telegram._trades(update=update, context=context)
+ await telegram._trades(update=update, context=context)
assert "0 recent trades:" in msg_mock.call_args_list[0][0][0]
assert "" not in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
context.args = ['hello']
- telegram._trades(update=update, context=context)
+ await telegram._trades(update=update, context=context)
assert "0 recent trades:" in msg_mock.call_args_list[0][0][0]
assert "" not in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
@@ -1653,7 +1730,7 @@ def test_telegram_trades(mocker, update, default_conf, fee, is_short, regex_patt
context = MagicMock()
context.args = [5]
- telegram._trades(update=update, context=context)
+ await telegram._trades(update=update, context=context)
msg_mock.call_count == 1
assert "2 recent trades:" in msg_mock.call_args_list[0][0][0]
assert "Profit (" in msg_mock.call_args_list[0][0][0]
@@ -1663,13 +1740,13 @@ def test_telegram_trades(mocker, update, default_conf, fee, is_short, regex_patt
@pytest.mark.parametrize('is_short', [True, False])
-def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short):
+async def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short):
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
context = MagicMock()
context.args = []
- telegram._delete_trade(update=update, context=context)
+ await telegram._delete_trade(update=update, context=context)
assert "Trade-id not set." in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
@@ -1677,14 +1754,14 @@ def test_telegram_delete_trade(mocker, update, default_conf, fee, is_short):
context = MagicMock()
context.args = [1]
- telegram._delete_trade(update=update, context=context)
+ await telegram._delete_trade(update=update, context=context)
msg_mock.call_count == 1
assert "Deleted trade 1." in msg_mock.call_args_list[0][0][0]
assert "Please make sure to take care of this asset" in msg_mock.call_args_list[0][0][0]
@pytest.mark.parametrize('is_short', [True, False])
-def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short, ticker):
+async def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short, ticker):
mocker.patch.multiple(
EXMS,
@@ -1694,7 +1771,7 @@ def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short,
context = MagicMock()
context.args = []
- telegram._cancel_open_order(update=update, context=context)
+ await telegram._cancel_open_order(update=update, context=context)
assert "Trade-id not set." in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
@@ -1702,7 +1779,7 @@ def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short,
context = MagicMock()
context.args = [5]
- telegram._cancel_open_order(update=update, context=context)
+ await telegram._cancel_open_order(update=update, context=context)
assert "No open order for trade_id" in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
@@ -1711,43 +1788,43 @@ def test_telegram_delete_open_order(mocker, update, default_conf, fee, is_short,
mocker.patch(f'{EXMS}.fetch_order', return_value=trade.orders[-1].to_ccxt_object())
context = MagicMock()
context.args = [6]
- telegram._cancel_open_order(update=update, context=context)
+ await telegram._cancel_open_order(update=update, context=context)
assert msg_mock.call_count == 1
assert "Open order canceled." in msg_mock.call_args_list[0][0][0]
-def test_help_handle(default_conf, update, mocker) -> None:
+async def test_help_handle(default_conf, update, mocker) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
- telegram._help(update=update, context=MagicMock())
+ await telegram._help(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert '*/help:* `This help message`' in msg_mock.call_args_list[0][0][0]
-def test_version_handle(default_conf, update, mocker) -> None:
+async def test_version_handle(default_conf, update, mocker) -> None:
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
- telegram._version(update=update, context=MagicMock())
+ await telegram._version(update=update, context=MagicMock())
assert msg_mock.call_count == 1
- assert '*Version:* `{}`'.format(__version__) in msg_mock.call_args_list[0][0][0]
+ assert f'*Version:* `{__version__}`' in msg_mock.call_args_list[0][0][0]
msg_mock.reset_mock()
freqtradebot.strategy.version = lambda: '1.1.1'
- telegram._version(update=update, context=MagicMock())
+ await telegram._version(update=update, context=MagicMock())
assert msg_mock.call_count == 1
- assert '*Version:* `{}`'.format(__version__) in msg_mock.call_args_list[0][0][0]
+ assert f'*Version:* `{__version__}`' in msg_mock.call_args_list[0][0][0]
assert '*Strategy version: * `1.1.1`' in msg_mock.call_args_list[0][0][0]
-def test_show_config_handle(default_conf, update, mocker) -> None:
+async def test_show_config_handle(default_conf, update, mocker) -> None:
default_conf['runmode'] = RunMode.DRY_RUN
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
- telegram._show_config(update=update, context=MagicMock())
+ await telegram._show_config(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
@@ -1756,7 +1833,7 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
msg_mock.reset_mock()
freqtradebot.config['trailing_stop'] = True
- telegram._show_config(update=update, context=MagicMock())
+ await telegram._show_config(update=update, context=MagicMock())
assert msg_mock.call_count == 1
assert '*Mode:* `{}`'.format('Dry-run') in msg_mock.call_args_list[0][0][0]
assert '*Exchange:* `binance`' in msg_mock.call_args_list[0][0][0]
@@ -2063,7 +2140,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
telegram._rpc._fiat_converter.convert_amount = old_convamount
-def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
+async def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
@@ -2155,7 +2232,7 @@ def test_send_msg_status_notification(default_conf, mocker) -> None:
assert msg_mock.call_args[0][0] == '*Status:* `running`'
-def test_warning_notification(default_conf, mocker) -> None:
+async def test_warning_notification(default_conf, mocker) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg({
'type': RPCMessageType.WARNING,
@@ -2239,8 +2316,9 @@ def test_send_msg_buy_notification_no_fiat(
('Short', 'short_signal_01', 2.0),
])
def test_send_msg_sell_notification_no_fiat(
- default_conf, mocker, direction, enter_signal, leverage) -> None:
+ default_conf, mocker, direction, enter_signal, leverage, time_machine) -> None:
del default_conf['fiat_display_currency']
+ time_machine.move_to('2022-05-02 00:00:00 +00:00', tick=False)
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg({
@@ -2299,60 +2377,62 @@ def test__sell_emoji(default_conf, mocker, msg, expected):
assert telegram._get_sell_emoji(msg) == expected
-def test_telegram__send_msg(default_conf, mocker, caplog) -> None:
+async def test_telegram__send_msg(default_conf, mocker, caplog) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
bot = MagicMock()
+ bot.send_message = AsyncMock()
+ bot.edit_message_text = AsyncMock()
telegram, _, _ = get_telegram_testobject(mocker, default_conf, mock=False)
- telegram._updater = MagicMock()
- telegram._updater.bot = bot
+ telegram._app = MagicMock()
+ telegram._app.bot = bot
- telegram._config['telegram']['enabled'] = True
- telegram._send_msg('test')
+ await telegram._send_msg('test')
assert len(bot.method_calls) == 1
# Test update
query = MagicMock()
- telegram._send_msg('test', callback_path="DeadBeef", query=query, reload_able=True)
- edit_message_text = telegram._updater.bot.edit_message_text
+ await telegram._send_msg('test', callback_path="DeadBeef", query=query, reload_able=True)
+ edit_message_text = telegram._app.bot.edit_message_text
assert edit_message_text.call_count == 1
assert "Updated: " in edit_message_text.call_args_list[0][1]['text']
- telegram._updater.bot.edit_message_text = MagicMock(side_effect=BadRequest("not modified"))
- telegram._send_msg('test', callback_path="DeadBeef", query=query)
- assert telegram._updater.bot.edit_message_text.call_count == 1
+ telegram._app.bot.edit_message_text = AsyncMock(side_effect=BadRequest("not modified"))
+ await telegram._send_msg('test', callback_path="DeadBeef", query=query)
+ assert telegram._app.bot.edit_message_text.call_count == 1
assert not log_has_re(r"TelegramError: .*", caplog)
- telegram._updater.bot.edit_message_text = MagicMock(side_effect=BadRequest(""))
- telegram._send_msg('test2', callback_path="DeadBeef", query=query)
- assert telegram._updater.bot.edit_message_text.call_count == 1
+ telegram._app.bot.edit_message_text = AsyncMock(side_effect=BadRequest(""))
+ await telegram._send_msg('test2', callback_path="DeadBeef", query=query)
+ assert telegram._app.bot.edit_message_text.call_count == 1
assert log_has_re(r"TelegramError: .*", caplog)
- telegram._updater.bot.edit_message_text = MagicMock(side_effect=TelegramError("DeadBEEF"))
- telegram._send_msg('test3', callback_path="DeadBeef", query=query)
+ telegram._app.bot.edit_message_text = AsyncMock(side_effect=TelegramError("DeadBEEF"))
+ await telegram._send_msg('test3', callback_path="DeadBeef", query=query)
assert log_has_re(r"TelegramError: DeadBEEF! Giving up.*", caplog)
-def test__send_msg_network_error(default_conf, mocker, caplog) -> None:
+async def test__send_msg_network_error(default_conf, mocker, caplog) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
bot = MagicMock()
bot.send_message = MagicMock(side_effect=NetworkError('Oh snap'))
telegram, _, _ = get_telegram_testobject(mocker, default_conf, mock=False)
- telegram._updater = MagicMock()
- telegram._updater.bot = bot
+ telegram._app = MagicMock()
+ telegram._app.bot = bot
telegram._config['telegram']['enabled'] = True
- telegram._send_msg('test')
+ await telegram._send_msg('test')
# Bot should've tried to send it twice
assert len(bot.method_calls) == 2
assert log_has('Telegram NetworkError: Oh snap! Trying one more time.', caplog)
-def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
+@pytest.mark.filterwarnings("ignore:.*ChatPermissions")
+async def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
mocker.patch('freqtrade.rpc.telegram.Telegram._init', MagicMock())
bot = MagicMock()
- bot.send_message = MagicMock()
+ bot.send_message = AsyncMock()
freqtradebot = get_patched_freqtradebot(mocker, default_conf)
rpc = RPC(freqtradebot)
@@ -2368,14 +2448,14 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
def init_telegram(freqtradebot):
telegram = Telegram(rpc, default_conf)
- telegram._updater = MagicMock()
- telegram._updater.bot = bot
+ telegram._app = MagicMock()
+ telegram._app.bot = bot
return telegram
# no keyboard in config -> default keyboard
freqtradebot.config['telegram']['enabled'] = True
telegram = init_telegram(freqtradebot)
- telegram._send_msg('test')
+ await telegram._send_msg('test')
used_keyboard = bot.send_message.call_args[1]['reply_markup']
assert used_keyboard == default_keyboard
@@ -2392,7 +2472,7 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
freqtradebot.config['telegram']['enabled'] = True
freqtradebot.config['telegram']['keyboard'] = custom_keys_list
telegram = init_telegram(freqtradebot)
- telegram._send_msg('test')
+ await telegram._send_msg('test')
used_keyboard = bot.send_message.call_args[1]['reply_markup']
assert used_keyboard == custom_keyboard
assert log_has("using custom keyboard from config.json: "
@@ -2400,13 +2480,14 @@ def test__send_msg_keyboard(default_conf, mocker, caplog) -> None:
"'/start', '/reload_config', '/help']]", caplog)
-def test_change_market_direction(default_conf, mocker, update) -> None:
+async def test_change_market_direction(default_conf, mocker, update) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
assert telegram._rpc._freqtrade.strategy.market_direction == MarketDirection.NONE
context = MagicMock()
context.args = ["long"]
- telegram._changemarketdir(update, context)
+ await telegram._changemarketdir(update, context)
assert telegram._rpc._freqtrade.strategy.market_direction == MarketDirection.LONG
context = MagicMock()
context.args = ["invalid"]
+ await telegram._changemarketdir(update, context)
assert telegram._rpc._freqtrade.strategy.market_direction == MarketDirection.LONG
diff --git a/tests/rpc/test_rpc_webhook.py b/tests/rpc/test_rpc_webhook.py
index f55582107..d0a0f5b1e 100644
--- a/tests/rpc/test_rpc_webhook.py
+++ b/tests/rpc/test_rpc_webhook.py
@@ -17,6 +17,10 @@ def get_webhook_dict() -> dict:
"enabled": True,
"url": "https://maker.ifttt.com/trigger/freqtrade_test/with/key/c764udvJ5jfSlswVRukZZ2/",
"webhookentry": {
+ # Intentionally broken, as "entry" should have priority.
+ "value1": "Buying {pair55555}",
+ },
+ "entry": {
"value1": "Buying {pair}",
"value2": "limit {limit:8f}",
"value3": "{stake_amount:8f} {stake_currency}",
@@ -89,15 +93,15 @@ def test_send_msg_webhook(default_conf, mocker):
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
assert (msg_mock.call_args[0][0]["value1"] ==
- default_conf["webhook"]["webhookentry"]["value1"].format(**msg))
+ default_conf["webhook"]["entry"]["value1"].format(**msg))
assert (msg_mock.call_args[0][0]["value2"] ==
- default_conf["webhook"]["webhookentry"]["value2"].format(**msg))
+ default_conf["webhook"]["entry"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
- default_conf["webhook"]["webhookentry"]["value3"].format(**msg))
+ default_conf["webhook"]["entry"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
- default_conf["webhook"]["webhookentry"]["value4"].format(**msg))
+ default_conf["webhook"]["entry"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
- default_conf["webhook"]["webhookentry"]["value5"].format(**msg))
+ default_conf["webhook"]["entry"]["value5"].format(**msg))
# Test short
msg_mock.reset_mock()
@@ -116,15 +120,15 @@ def test_send_msg_webhook(default_conf, mocker):
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
assert (msg_mock.call_args[0][0]["value1"] ==
- default_conf["webhook"]["webhookentry"]["value1"].format(**msg))
+ default_conf["webhook"]["entry"]["value1"].format(**msg))
assert (msg_mock.call_args[0][0]["value2"] ==
- default_conf["webhook"]["webhookentry"]["value2"].format(**msg))
+ default_conf["webhook"]["entry"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
- default_conf["webhook"]["webhookentry"]["value3"].format(**msg))
+ default_conf["webhook"]["entry"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
- default_conf["webhook"]["webhookentry"]["value4"].format(**msg))
+ default_conf["webhook"]["entry"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
- default_conf["webhook"]["webhookentry"]["value5"].format(**msg))
+ default_conf["webhook"]["entry"]["value5"].format(**msg))
# Test buy cancel
msg_mock.reset_mock()
@@ -328,6 +332,7 @@ def test_send_msg_webhook(default_conf, mocker):
def test_exception_send_msg(default_conf, mocker, caplog):
default_conf["webhook"] = get_webhook_dict()
+ del default_conf["webhook"]["entry"]
del default_conf["webhook"]["webhookentry"]
webhook = Webhook(RPC(get_patched_freqtradebot(mocker, default_conf)), default_conf)
diff --git a/tests/strategy/strats/freqai_test_classifier.py b/tests/strategy/strats/freqai_test_classifier.py
index 61b9f0c37..a68a87b2a 100644
--- a/tests/strategy/strats/freqai_test_classifier.py
+++ b/tests/strategy/strats/freqai_test_classifier.py
@@ -82,7 +82,7 @@ class freqai_test_classifier(IStrategy):
return dataframe
def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs):
-
+ self.freqai.class_names = ["down", "up"]
dataframe['&s-up_or_down'] = np.where(dataframe["close"].shift(-100) >
dataframe["close"], 'up', 'down')
diff --git a/tests/strategy/strats/hyperoptable_strategy.py b/tests/strategy/strats/hyperoptable_strategy.py
index eadbc533f..d05e8ead2 100644
--- a/tests/strategy/strats/hyperoptable_strategy.py
+++ b/tests/strategy/strats/hyperoptable_strategy.py
@@ -50,6 +50,7 @@ class HyperoptableStrategy(StrategyTestV3):
return prot
bot_loop_started = False
+ bot_started = False
def bot_loop_start(self):
self.bot_loop_started = True
@@ -58,6 +59,7 @@ class HyperoptableStrategy(StrategyTestV3):
"""
Parameters can also be defined here ...
"""
+ self.bot_started = True
self.buy_rsi = IntParameter([0, 50], default=30, space='buy')
def informative_pairs(self):
diff --git a/tests/strategy/test_interface.py b/tests/strategy/test_interface.py
index 7b1399507..204fa996d 100644
--- a/tests/strategy/test_interface.py
+++ b/tests/strategy/test_interface.py
@@ -9,6 +9,7 @@ import pytest
from pandas import DataFrame
from freqtrade.configuration import TimeRange
+from freqtrade.constants import CUSTOM_TAG_MAX_LENGTH
from freqtrade.data.dataprovider import DataProvider
from freqtrade.data.history import load_data
from freqtrade.enums import ExitCheckTuple, ExitType, HyperoptState, SignalDirection
@@ -529,13 +530,13 @@ def test_custom_exit(default_conf, fee, caplog) -> None:
assert res[0].exit_reason == 'hello world'
caplog.clear()
- strategy.custom_exit = MagicMock(return_value='h' * 100)
+ strategy.custom_exit = MagicMock(return_value='h' * CUSTOM_TAG_MAX_LENGTH * 2)
res = strategy.should_exit(trade, 1, now,
enter=False, exit_=False,
low=None, high=None)
assert res[0].exit_type == ExitType.CUSTOM_EXIT
assert res[0].exit_flag is True
- assert res[0].exit_reason == 'h' * 64
+ assert res[0].exit_reason == 'h' * (CUSTOM_TAG_MAX_LENGTH)
assert log_has_re('Custom exit reason returned from custom_exit is too long.*', caplog)
@@ -986,7 +987,8 @@ def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog):
}
}
}
- mocker.patch('freqtrade.strategy.hyper.json_load', return_value=expected_result)
+ mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
+ return_value=expected_result)
PairLocks.timeframe = default_conf['timeframe']
strategy = StrategyResolver.load_strategy(default_conf)
assert strategy.stoploss == -0.05
@@ -1005,11 +1007,13 @@ def test_auto_hyperopt_interface_loadparams(default_conf, mocker, caplog):
}
}
- mocker.patch('freqtrade.strategy.hyper.json_load', return_value=expected_result)
+ mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
+ return_value=expected_result)
with pytest.raises(OperationalException, match="Invalid parameter file provided."):
StrategyResolver.load_strategy(default_conf)
- mocker.patch('freqtrade.strategy.hyper.json_load', MagicMock(side_effect=ValueError()))
+ mocker.patch('freqtrade.strategy.hyper.HyperoptTools.load_params',
+ MagicMock(side_effect=ValueError()))
StrategyResolver.load_strategy(default_conf)
assert log_has("Invalid parameter file format.", caplog)
diff --git a/tests/strategy/test_strategy_loading.py b/tests/strategy/test_strategy_loading.py
index 98185e152..4cdb35936 100644
--- a/tests/strategy/test_strategy_loading.py
+++ b/tests/strategy/test_strategy_loading.py
@@ -69,7 +69,7 @@ def test_load_strategy(default_conf, dataframe_1m):
def test_load_strategy_base64(dataframe_1m, caplog, default_conf):
filepath = Path(__file__).parents[2] / 'freqtrade/templates/sample_strategy.py'
encoded_string = urlsafe_b64encode(filepath.read_bytes()).decode("utf-8")
- default_conf.update({'strategy': 'SampleStrategy:{}'.format(encoded_string)})
+ default_conf.update({'strategy': f'SampleStrategy:{encoded_string}'})
strategy = StrategyResolver.load_strategy(default_conf)
assert 'rsi' in strategy.advise_indicators(dataframe_1m, {'pair': 'ETH/BTC'})
diff --git a/tests/test_configuration.py b/tests/test_configuration.py
index aab868bec..c445b989d 100644
--- a/tests/test_configuration.py
+++ b/tests/test_configuration.py
@@ -23,7 +23,8 @@ from freqtrade.configuration.load_config import (load_config_file, load_file, lo
from freqtrade.constants import DEFAULT_DB_DRYRUN_URL, DEFAULT_DB_PROD_URL, ENV_VAR_PREFIX
from freqtrade.enums import RunMode
from freqtrade.exceptions import OperationalException
-from freqtrade.loggers import FTBufferingHandler, _set_loggers, setup_logging, setup_logging_pre
+from freqtrade.loggers import (FTBufferingHandler, FTStdErrStreamHandler, _set_loggers,
+ setup_logging, setup_logging_pre)
from tests.conftest import (CURRENT_TEST_STRATEGY, log_has, log_has_re,
patched_configuration_load_config_file)
@@ -658,7 +659,7 @@ def test_set_loggers_syslog():
setup_logging(config)
assert len(logger.handlers) == 3
assert [x for x in logger.handlers if type(x) == logging.handlers.SysLogHandler]
- assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
+ assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
assert [x for x in logger.handlers if type(x) == FTBufferingHandler]
# setting up logging again should NOT cause the loggers to be added a second time.
setup_logging(config)
@@ -681,7 +682,7 @@ def test_set_loggers_Filehandler(tmpdir):
setup_logging(config)
assert len(logger.handlers) == 3
assert [x for x in logger.handlers if type(x) == logging.handlers.RotatingFileHandler]
- assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
+ assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
assert [x for x in logger.handlers if type(x) == FTBufferingHandler]
# setting up logging again should NOT cause the loggers to be added a second time.
setup_logging(config)
@@ -706,7 +707,7 @@ def test_set_loggers_journald(mocker):
setup_logging(config)
assert len(logger.handlers) == 3
assert [x for x in logger.handlers if type(x).__name__ == "JournaldLogHandler"]
- assert [x for x in logger.handlers if type(x) == logging.StreamHandler]
+ assert [x for x in logger.handlers if type(x) == FTStdErrStreamHandler]
# reset handlers to not break pytest
logger.handlers = orig_handlers
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index cea70ec48..ea99061b8 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -356,7 +356,7 @@ def test_create_trade_no_stake_amount(default_conf_usdt, ticker_usdt, fee, mocke
@pytest.mark.parametrize("is_short", [False, True])
@pytest.mark.parametrize('stake_amount,create,amount_enough,max_open_trades', [
(5.0, True, True, 99),
- (0.049, True, False, 99), # Amount will be adjusted to min - which is 0.051
+ (0.042, True, False, 99), # Amount will be adjusted to min - which is 0.051
(0, False, True, 99),
(UNLIMITED_STAKE_AMOUNT, False, True, 0),
])
@@ -1060,9 +1060,19 @@ def test_execute_entry_min_leverage(mocker, default_conf_usdt, fee, limit_order,
@pytest.mark.parametrize("is_short", [False, True])
-def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_short) -> None:
+def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_short, fee) -> None:
patch_RPCManager(mocker)
patch_exchange(mocker)
+ mocker.patch.multiple(
+ EXMS,
+ fetch_ticker=MagicMock(return_value={
+ 'bid': 1.9,
+ 'ask': 2.2,
+ 'last': 1.9
+ }),
+ create_order=MagicMock(return_value=limit_order[entry_side(is_short)]),
+ get_fee=fee,
+ )
order = limit_order[entry_side(is_short)]
mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_trade', MagicMock(return_value=True))
mocker.patch(f'{EXMS}.fetch_order', return_value=order)
@@ -1074,8 +1084,10 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_sho
freqtrade = FreqtradeBot(default_conf_usdt)
freqtrade.strategy.order_types['stoploss_on_exchange'] = True
- # TODO: should not be magicmock
- trade = MagicMock()
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
+
+ freqtrade.enter_positions()
+ trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short
trade.open_order_id = None
trade.stoploss_order_id = None
@@ -1091,7 +1103,8 @@ def test_add_stoploss_on_exchange(mocker, default_conf_usdt, limit_order, is_sho
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_short,
limit_order) -> None:
- stoploss = MagicMock(return_value={'id': 13434334})
+ stop_order_dict = {'id': "13434334"}
+ stoploss = MagicMock(return_value=stop_order_dict)
enter_order = limit_order[entry_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
patch_RPCManager(mocker)
@@ -1116,8 +1129,9 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
# First case: when stoploss is not yet set but the order is open
# should get the stoploss order id immediately
# and should return false as no trade actually happened
- # TODO: should not be magicmock
- trade = MagicMock()
+
+ freqtrade.enter_positions()
+ trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
@@ -1129,44 +1143,62 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
# Second case: when stoploss is set but it is not yet hit
# should do nothing and return false
+ stop_order_dict.update({'id': "102"})
trade.is_open = True
trade.open_order_id = None
- trade.stoploss_order_id = "100"
+ trade.stoploss_order_id = "102"
+ trade.orders.append(
+ Order(
+ ft_order_side='stoploss',
+ ft_pair=trade.pair,
+ ft_is_open=True,
+ ft_amount=trade.amount,
+ ft_price=trade.stop_loss,
+ order_id='102',
+ status='open',
+ )
+ )
hanging_stoploss_order = MagicMock(return_value={'status': 'open'})
mocker.patch(f'{EXMS}.fetch_stoploss_order', hanging_stoploss_order)
assert freqtrade.handle_stoploss_on_exchange(trade) is False
- assert trade.stoploss_order_id == "100"
+ assert trade.stoploss_order_id == "102"
# Third case: when stoploss was set but it was canceled for some reason
# should set a stoploss immediately and return False
caplog.clear()
trade.is_open = True
trade.open_order_id = None
- trade.stoploss_order_id = "100"
+ trade.stoploss_order_id = "102"
- canceled_stoploss_order = MagicMock(return_value={'status': 'canceled'})
+ canceled_stoploss_order = MagicMock(return_value={'id': '103_1', 'status': 'canceled'})
mocker.patch(f'{EXMS}.fetch_stoploss_order', canceled_stoploss_order)
stoploss.reset_mock()
+ amount_before = trade.amount
+
+ stop_order_dict.update({'id': "103_1"})
assert freqtrade.handle_stoploss_on_exchange(trade) is False
assert stoploss.call_count == 1
- assert trade.stoploss_order_id == "13434334"
+ assert trade.stoploss_order_id == "103_1"
+ assert trade.amount == amount_before
# Fourth case: when stoploss is set and it is hit
# should unset stoploss_order_id and return true
# as a trade actually happened
caplog.clear()
freqtrade.enter_positions()
+ stop_order_dict.update({'id': "104"})
+
trade = Trade.session.scalars(select(Trade)).first()
trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
- trade.stoploss_order_id = "100"
+ trade.stoploss_order_id = "104"
trade.orders.append(Order(
ft_order_side='stoploss',
- order_id='100',
+ order_id='104',
ft_pair=trade.pair,
ft_is_open=True,
ft_amount=trade.amount,
@@ -1175,7 +1207,7 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
assert trade
stoploss_order_hit = MagicMock(return_value={
- 'id': "100",
+ 'id': "104",
'status': 'closed',
'type': 'stop_loss_limit',
'price': 3,
@@ -1197,7 +1229,8 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
# Fifth case: fetch_order returns InvalidOrder
# It should try to add stoploss order
- trade.stoploss_order_id = 100
+ stop_order_dict.update({'id': "105"})
+ trade.stoploss_order_id = "105"
stoploss.reset_mock()
mocker.patch(f'{EXMS}.fetch_stoploss_order', side_effect=InvalidOrderException())
mocker.patch(f'{EXMS}.create_stoploss', stoploss)
@@ -1217,21 +1250,36 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
# Seventh case: emergency exit triggered
# Trailing stop should not act anymore
stoploss_order_cancelled = MagicMock(side_effect=[{
- 'id': "100",
+ 'id': "107",
'status': 'canceled',
'type': 'stop_loss_limit',
'price': 3,
'average': 2,
'amount': enter_order['amount'],
+ 'filled': 0,
+ 'remaining': enter_order['amount'],
'info': {'stopPrice': 22},
}])
- trade.stoploss_order_id = 100
+ trade.stoploss_order_id = "107"
trade.is_open = True
trade.stoploss_last_update = arrow.utcnow().shift(hours=-1).datetime
trade.stop_loss = 24
+ trade.exit_reason = None
+ trade.orders.append(
+ Order(
+ ft_order_side='stoploss',
+ ft_pair=trade.pair,
+ ft_is_open=True,
+ ft_amount=trade.amount,
+ ft_price=trade.stop_loss,
+ order_id='107',
+ status='open',
+ )
+ )
freqtrade.config['trailing_stop'] = True
stoploss = MagicMock(side_effect=InvalidOrderException())
+ Trade.commit()
mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result',
side_effect=InvalidOrderException())
mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_cancelled)
@@ -1242,6 +1290,137 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_
assert trade.exit_reason == str(ExitType.EMERGENCY_EXIT)
+@pytest.mark.parametrize("is_short", [False, True])
+def test_handle_stoploss_on_exchange_partial(
+ mocker, default_conf_usdt, fee, is_short, limit_order) -> None:
+ stop_order_dict = {'id': "101", "status": "open"}
+ stoploss = MagicMock(return_value=stop_order_dict)
+ enter_order = limit_order[entry_side(is_short)]
+ exit_order = limit_order[exit_side(is_short)]
+ patch_RPCManager(mocker)
+ patch_exchange(mocker)
+ mocker.patch.multiple(
+ EXMS,
+ fetch_ticker=MagicMock(return_value={
+ 'bid': 1.9,
+ 'ask': 2.2,
+ 'last': 1.9
+ }),
+ create_order=MagicMock(side_effect=[
+ enter_order,
+ exit_order,
+ ]),
+ get_fee=fee,
+ create_stoploss=stoploss
+ )
+ freqtrade = FreqtradeBot(default_conf_usdt)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
+
+ freqtrade.enter_positions()
+ trade = Trade.session.scalars(select(Trade)).first()
+ trade.is_short = is_short
+ trade.is_open = True
+ trade.open_order_id = None
+ trade.stoploss_order_id = None
+
+ assert freqtrade.handle_stoploss_on_exchange(trade) is False
+ assert stoploss.call_count == 1
+ assert trade.stoploss_order_id == "101"
+ assert trade.amount == 30
+ stop_order_dict.update({'id': "102"})
+ # Stoploss on exchange is cancelled on exchange, but filled partially.
+ # Must update trade amount to guarantee successful exit.
+ stoploss_order_hit = MagicMock(return_value={
+ 'id': "101",
+ 'status': 'canceled',
+ 'type': 'stop_loss_limit',
+ 'price': 3,
+ 'average': 2,
+ 'filled': trade.amount / 2,
+ 'remaining': trade.amount / 2,
+ 'amount': enter_order['amount'],
+ })
+ mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit)
+ assert freqtrade.handle_stoploss_on_exchange(trade) is False
+ # Stoploss filled partially ...
+ assert trade.amount == 15
+
+ assert trade.stoploss_order_id == "102"
+
+
+@pytest.mark.parametrize("is_short", [False, True])
+def test_handle_stoploss_on_exchange_partial_cancel_here(
+ mocker, default_conf_usdt, fee, is_short, limit_order, caplog) -> None:
+ stop_order_dict = {'id': "101", "status": "open"}
+ default_conf_usdt['trailing_stop'] = True
+ stoploss = MagicMock(return_value=stop_order_dict)
+ enter_order = limit_order[entry_side(is_short)]
+ exit_order = limit_order[exit_side(is_short)]
+ patch_RPCManager(mocker)
+ patch_exchange(mocker)
+ mocker.patch.multiple(
+ EXMS,
+ fetch_ticker=MagicMock(return_value={
+ 'bid': 1.9,
+ 'ask': 2.2,
+ 'last': 1.9
+ }),
+ create_order=MagicMock(side_effect=[
+ enter_order,
+ exit_order,
+ ]),
+ get_fee=fee,
+ create_stoploss=stoploss
+ )
+ freqtrade = FreqtradeBot(default_conf_usdt)
+ patch_get_signal(freqtrade, enter_short=is_short, enter_long=not is_short)
+
+ freqtrade.enter_positions()
+ trade = Trade.session.scalars(select(Trade)).first()
+ trade.is_short = is_short
+ trade.is_open = True
+ trade.open_order_id = None
+ trade.stoploss_order_id = None
+
+ assert freqtrade.handle_stoploss_on_exchange(trade) is False
+ assert stoploss.call_count == 1
+ assert trade.stoploss_order_id == "101"
+ assert trade.amount == 30
+ stop_order_dict.update({'id': "102"})
+ # Stoploss on exchange is open.
+ # Freqtrade cancels the stop - but cancel returns a partial filled order.
+ stoploss_order_hit = MagicMock(return_value={
+ 'id': "101",
+ 'status': 'open',
+ 'type': 'stop_loss_limit',
+ 'price': 3,
+ 'average': 2,
+ 'filled': 0,
+ 'remaining': trade.amount,
+ 'amount': enter_order['amount'],
+ })
+ stoploss_order_cancel = MagicMock(return_value={
+ 'id': "101",
+ 'status': 'canceled',
+ 'type': 'stop_loss_limit',
+ 'price': 3,
+ 'average': 2,
+ 'filled': trade.amount / 2,
+ 'remaining': trade.amount / 2,
+ 'amount': enter_order['amount'],
+ })
+ mocker.patch(f'{EXMS}.fetch_stoploss_order', stoploss_order_hit)
+ mocker.patch(f'{EXMS}.cancel_stoploss_order_with_result', stoploss_order_cancel)
+ trade.stoploss_last_update = arrow.utcnow().shift(minutes=-10).datetime
+
+ assert freqtrade.handle_stoploss_on_exchange(trade) is False
+ # Canceled Stoploss filled partially ...
+ assert log_has_re('Cancelling current stoploss on exchange.*', caplog)
+
+ assert trade.stoploss_order_id == "102"
+ assert trade.amount == 15
+
+
@pytest.mark.parametrize("is_short", [False, True])
def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog, is_short,
limit_order) -> None:
@@ -1273,10 +1452,21 @@ def test_handle_sle_cancel_cant_recreate(mocker, default_conf_usdt, fee, caplog,
freqtrade.enter_positions()
trade = Trade.session.scalars(select(Trade)).first()
- trade.is_short = is_short
+ assert trade.is_short == is_short
trade.is_open = True
trade.open_order_id = None
- trade.stoploss_order_id = 100
+ trade.stoploss_order_id = "100"
+ trade.orders.append(
+ Order(
+ ft_order_side='stoploss',
+ ft_pair=trade.pair,
+ ft_is_open=True,
+ ft_amount=trade.amount,
+ ft_price=trade.stop_loss,
+ order_id='100',
+ status='open',
+ )
+ )
assert trade
assert freqtrade.handle_stoploss_on_exchange(trade) is False
@@ -1395,7 +1585,7 @@ def test_handle_stoploss_on_exchange_trailing(
# When trailing stoploss is set
enter_order = limit_order[entry_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
- stoploss = MagicMock(return_value={'id': 13434334})
+ stoploss = MagicMock(return_value={'id': 13434334, 'status': 'open'})
patch_RPCManager(mocker)
mocker.patch.multiple(
EXMS,
@@ -1440,11 +1630,21 @@ def test_handle_stoploss_on_exchange_trailing(
trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
- trade.stoploss_order_id = 100
+ trade.stoploss_order_id = '100'
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-20).datetime
+ trade.orders.append(
+ Order(
+ ft_order_side='stoploss',
+ ft_pair=trade.pair,
+ ft_is_open=True,
+ ft_amount=trade.amount,
+ ft_price=trade.stop_loss,
+ order_id='100',
+ )
+ )
stoploss_order_hanging = MagicMock(return_value={
- 'id': 100,
+ 'id': '100',
'status': 'open',
'type': 'stop_loss_limit',
'price': hang_price,
@@ -1471,7 +1671,7 @@ def test_handle_stoploss_on_exchange_trailing(
)
cancel_order_mock = MagicMock()
- stoploss_order_mock = MagicMock(return_value={'id': 'so1'})
+ stoploss_order_mock = MagicMock(return_value={'id': 'so1', 'status': 'open'})
mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock)
mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock)
@@ -1483,13 +1683,14 @@ def test_handle_stoploss_on_exchange_trailing(
assert freqtrade.handle_trade(trade) is False
assert trade.stop_loss == stop_price[1]
+ trade.stoploss_order_id = '100'
# setting stoploss_on_exchange_interval to 0 seconds
freqtrade.strategy.order_types['stoploss_on_exchange_interval'] = 0
assert freqtrade.handle_stoploss_on_exchange(trade) is False
- cancel_order_mock.assert_called_once_with(100, 'ETH/USDT')
+ cancel_order_mock.assert_called_once_with('100', 'ETH/USDT')
stoploss_order_mock.assert_called_once_with(
amount=pytest.approx(amt),
pair='ETH/USDT',
@@ -1519,7 +1720,7 @@ def test_handle_stoploss_on_exchange_trailing_error(
enter_order = limit_order[entry_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
# When trailing stoploss is set
- stoploss = MagicMock(return_value={'id': 13434334})
+ stoploss = MagicMock(return_value={'id': '13434334', 'status': 'open'})
patch_exchange(mocker)
mocker.patch.multiple(
@@ -1601,7 +1802,7 @@ def test_stoploss_on_exchange_price_rounding(
EXMS,
get_fee=fee,
)
- price_mock = MagicMock(side_effect=lambda p, s: int(s))
+ price_mock = MagicMock(side_effect=lambda p, s, **kwargs: int(s))
stoploss_mock = MagicMock(return_value={'id': '13434334'})
adjust_mock = MagicMock(return_value=False)
mocker.patch.multiple(
@@ -1628,7 +1829,7 @@ def test_handle_stoploss_on_exchange_custom_stop(
enter_order = limit_order[entry_side(is_short)]
exit_order = limit_order[exit_side(is_short)]
# When trailing stoploss is set
- stoploss = MagicMock(return_value={'id': 13434334})
+ stoploss = MagicMock(return_value={'id': 13434334, 'status': 'open'})
patch_RPCManager(mocker)
mocker.patch.multiple(
EXMS,
@@ -1673,11 +1874,21 @@ def test_handle_stoploss_on_exchange_custom_stop(
trade.is_short = is_short
trade.is_open = True
trade.open_order_id = None
- trade.stoploss_order_id = 100
+ trade.stoploss_order_id = '100'
trade.stoploss_last_update = arrow.utcnow().shift(minutes=-601).datetime
+ trade.orders.append(
+ Order(
+ ft_order_side='stoploss',
+ ft_pair=trade.pair,
+ ft_is_open=True,
+ ft_amount=trade.amount,
+ ft_price=trade.stop_loss,
+ order_id='100',
+ )
+ )
stoploss_order_hanging = MagicMock(return_value={
- 'id': 100,
+ 'id': '100',
'status': 'open',
'type': 'stop_loss_limit',
'price': 3,
@@ -1703,9 +1914,10 @@ def test_handle_stoploss_on_exchange_custom_stop(
)
cancel_order_mock = MagicMock()
- stoploss_order_mock = MagicMock(return_value={'id': 'so1'})
+ stoploss_order_mock = MagicMock(return_value={'id': 'so1', 'status': 'open'})
mocker.patch(f'{EXMS}.cancel_stoploss_order', cancel_order_mock)
mocker.patch(f'{EXMS}.create_stoploss', stoploss_order_mock)
+ trade.stoploss_order_id = '100'
# stoploss should not be updated as the interval is 60 seconds
assert freqtrade.handle_trade(trade) is False
@@ -1722,7 +1934,7 @@ def test_handle_stoploss_on_exchange_custom_stop(
assert freqtrade.handle_stoploss_on_exchange(trade) is False
- cancel_order_mock.assert_called_once_with(100, 'ETH/USDT')
+ cancel_order_mock.assert_called_once_with('100', 'ETH/USDT')
# Long uses modified ask - offset, short modified bid + offset
stoploss_order_mock.assert_called_once_with(
amount=pytest.approx(trade.amount),
@@ -1751,7 +1963,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
exit_order = limit_order['sell']
# When trailing stoploss is set
- stoploss = MagicMock(return_value={'id': 13434334})
+ stoploss = MagicMock(return_value={'id': '13434334', 'status': 'open'})
patch_RPCManager(mocker)
patch_exchange(mocker)
patch_edge(mocker)
@@ -1800,11 +2012,21 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
trade = Trade.session.scalars(select(Trade)).first()
trade.is_open = True
trade.open_order_id = None
- trade.stoploss_order_id = 100
- trade.stoploss_last_update = arrow.utcnow()
+ trade.stoploss_order_id = '100'
+ trade.stoploss_last_update = arrow.utcnow().datetime
+ trade.orders.append(
+ Order(
+ ft_order_side='stoploss',
+ ft_pair=trade.pair,
+ ft_is_open=True,
+ ft_amount=trade.amount,
+ ft_price=trade.stop_loss,
+ order_id='100',
+ )
+ )
stoploss_order_hanging = MagicMock(return_value={
- 'id': 100,
+ 'id': '100',
'status': 'open',
'type': 'stop_loss_limit',
'price': 3,
@@ -1851,7 +2073,7 @@ def test_tsl_on_exchange_compatible_with_edge(mocker, edge_conf, fee, limit_orde
# stoploss should be set to 1% as trailing is on
assert trade.stop_loss == 4.4 * 0.99
- cancel_order_mock.assert_called_once_with(100, 'NEO/BTC')
+ cancel_order_mock.assert_called_once_with('100', 'NEO/BTC')
stoploss_order_mock.assert_called_once_with(
amount=pytest.approx(11.41438356),
pair='NEO/BTC',
@@ -1885,6 +2107,7 @@ def test_enter_positions(mocker, default_conf_usdt, return_value, side_effect,
assert mock_ct.call_count == len(default_conf_usdt['exchange']['pair_whitelist'])
+@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
@@ -1893,12 +2116,33 @@ def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog
mocker.patch(f'{EXMS}.fetch_order', return_value=limit_order[entry_side(is_short)])
mocker.patch(f'{EXMS}.get_trades_for_order', return_value=[])
- # TODO: should not be magicmock
- trade = MagicMock()
- trade.is_short = is_short
- trade.open_order_id = '123'
- trade.open_fee = 0.001
+ order_id = '123'
+ trade = Trade(
+ open_order_id=order_id,
+ pair='ETH/USDT',
+ fee_open=0.001,
+ fee_close=0.001,
+ open_rate=0.01,
+ open_date=arrow.utcnow().datetime,
+ stake_amount=0.01,
+ amount=11,
+ exchange="binance",
+ is_short=is_short,
+ leverage=1,
+ )
+ trade.orders.append(Order(
+ ft_order_side=entry_side(is_short),
+ price=0.01,
+ ft_pair=trade.pair,
+ ft_amount=trade.amount,
+ ft_price=trade.open_rate,
+ order_id=order_id,
+
+ ))
+ Trade.session.add(trade)
+ Trade.commit()
trades = [trade]
+ freqtrade.wallets.update()
n = freqtrade.exit_positions(trades)
assert n == 0
# Test amount not modified by fee-logic
@@ -1911,17 +2155,40 @@ def test_exit_positions(mocker, default_conf_usdt, limit_order, is_short, caplog
assert gra.call_count == 0
+@pytest.mark.usefixtures("init_persistence")
@pytest.mark.parametrize("is_short", [False, True])
def test_exit_positions_exception(mocker, default_conf_usdt, limit_order, caplog, is_short) -> None:
freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt)
order = limit_order[entry_side(is_short)]
mocker.patch(f'{EXMS}.fetch_order', return_value=order)
- # TODO: should not be magicmock
- trade = MagicMock()
- trade.is_short = is_short
+ order_id = '123'
+ trade = Trade(
+ open_order_id=order_id,
+ pair='ETH/USDT',
+ fee_open=0.001,
+ fee_close=0.001,
+ open_rate=0.01,
+ open_date=arrow.utcnow().datetime,
+ stake_amount=0.01,
+ amount=11,
+ exchange="binance",
+ is_short=is_short,
+ leverage=1,
+ )
+ trade.orders.append(Order(
+ ft_order_side=entry_side(is_short),
+ price=0.01,
+ ft_pair=trade.pair,
+ ft_amount=trade.amount,
+ ft_price=trade.open_rate,
+ order_id=order_id,
+
+ ))
trade.open_order_id = None
- trade.pair = 'ETH/USDT'
+ Trade.session.add(trade)
+ Trade.commit()
+ freqtrade.wallets.update()
trades = [trade]
# Test raise of DependencyException exception
@@ -2733,6 +3000,9 @@ def test_manage_open_orders_exit_usercustom(
assert rpc_mock.call_count == 2
assert freqtrade.strategy.check_exit_timeout.call_count == 1
assert freqtrade.strategy.check_entry_timeout.call_count == 0
+ trade = Trade.session.scalars(select(Trade)).first()
+ # cancelling didn't succeed - order-id remains open.
+ assert trade.open_order_id is not None
# 2nd canceled trade - Fail execute exit
caplog.clear()
@@ -3243,6 +3513,7 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
# TODO: should not be magicmock
trade = MagicMock()
+ trade.open_order_id = '125'
reason = CANCEL_REASON['TIMEOUT']
order = {'remaining': 1,
'id': '125',
@@ -3250,6 +3521,10 @@ def test_handle_cancel_exit_cancel_exception(mocker, default_conf_usdt) -> None:
'status': "open"}
assert not freqtrade.handle_cancel_exit(trade, order, reason)
+ # mocker.patch(f'{EXMS}.cancel_order_with_result', return_value=order)
+ # assert not freqtrade.handle_cancel_exit(trade, order, reason)
+ # assert trade.open_order_id == '125'
+
@pytest.mark.parametrize("is_short, open_rate, amt", [
(False, 2.0, 30.0),
@@ -3326,6 +3601,7 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
'profit_ratio': 0.00493809 if is_short else 0.09451372,
'stake_currency': 'USDT',
'fiat_currency': 'USD',
+ 'base_currency': 'ETH',
'sell_reason': ExitType.ROI.value,
'exit_reason': ExitType.ROI.value,
'open_date': ANY,
@@ -3389,6 +3665,7 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
'profit_amount': -5.65990099 if is_short else -0.00075,
'profit_ratio': -0.0945681 if is_short else -1.247e-05,
'stake_currency': 'USDT',
+ 'base_currency': 'ETH',
'fiat_currency': 'USD',
'sell_reason': ExitType.STOP_LOSS.value,
'exit_reason': ExitType.STOP_LOSS.value,
@@ -3474,6 +3751,7 @@ def test_execute_trade_exit_custom_exit_price(
'profit_amount': pytest.approx(profit_amount),
'profit_ratio': profit_ratio,
'stake_currency': 'USDT',
+ 'base_currency': 'ETH',
'fiat_currency': 'USD',
'sell_reason': 'foo',
'exit_reason': 'foo',
@@ -3547,6 +3825,7 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
'profit_ratio': -0.00501253 if is_short else -0.01493766,
'stake_currency': 'USDT',
'fiat_currency': 'USD',
+ 'base_currency': 'ETH',
'sell_reason': ExitType.STOP_LOSS.value,
'exit_reason': ExitType.STOP_LOSS.value,
'open_date': ANY,
@@ -3588,7 +3867,7 @@ def test_execute_trade_exit_sloe_cancel_exception(
freqtrade.execute_trade_exit(trade=trade, limit=1234,
exit_check=ExitCheckTuple(exit_type=ExitType.STOP_LOSS))
assert create_order_mock.call_count == 2
- assert log_has('Could not cancel stoploss order abcd', caplog)
+ assert log_has('Could not cancel stoploss order abcd for pair ETH/USDT', caplog)
@pytest.mark.parametrize("is_short", [False, True])
@@ -3600,10 +3879,12 @@ def test_execute_trade_exit_with_stoploss_on_exchange(
patch_exchange(mocker)
stoploss = MagicMock(return_value={
'id': 123,
+ 'status': 'open',
'info': {
'foo': 'bar'
}
})
+ mocker.patch('freqtrade.freqtradebot.FreqtradeBot.handle_order_fee')
cancel_order = MagicMock(return_value=True)
mocker.patch.multiple(
@@ -3701,12 +3982,12 @@ def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(
"lastTradeTimestamp": None,
"symbol": "BTC/USDT",
"type": "stop_loss_limit",
- "side": "sell",
+ "side": "buy" if is_short else "sell",
"price": 1.08801,
- "amount": 90.99181074,
- "cost": 99.0000000032274,
+ "amount": trade.amount,
+ "cost": 1.08801 * trade.amount,
"average": 1.08801,
- "filled": 90.99181074,
+ "filled": trade.amount,
"remaining": 0.0,
"status": "closed",
"fee": None,
@@ -3811,6 +4092,7 @@ def test_execute_trade_exit_market_order(
'profit_amount': pytest.approx(profit_amount),
'profit_ratio': profit_ratio,
'stake_currency': 'USDT',
+ 'base_currency': 'ETH',
'fiat_currency': 'USD',
'sell_reason': ExitType.ROI.value,
'exit_reason': ExitType.ROI.value,
diff --git a/tests/test_integration.py b/tests/test_integration.py
index 922285309..9fb9fd8b3 100644
--- a/tests/test_integration.py
+++ b/tests/test_integration.py
@@ -35,7 +35,7 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
"type": "stop_loss_limit",
"side": "sell",
"price": 1.08801,
- "amount": 90.99181074,
+ "amount": 91.07468123,
"cost": 0.0,
"average": 0.0,
"filled": 0.0,
@@ -49,8 +49,9 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
stoploss_order_closed['filled'] = stoploss_order_closed['amount']
# Sell first trade based on stoploss, keep 2nd and 3rd trade open
+ stop_orders = [stoploss_order_closed, stoploss_order_open, stoploss_order_open]
stoploss_order_mock = MagicMock(
- side_effect=[stoploss_order_closed, stoploss_order_open, stoploss_order_open])
+ side_effect=stop_orders)
# Sell 3rd trade (not called for the first trade)
should_sell_mock = MagicMock(side_effect=[
[],
@@ -93,13 +94,14 @@ def test_may_execute_exit_stoploss_on_exchange_multi(default_conf, ticker, fee,
wallets_mock.reset_mock()
trades = Trade.session.scalars(select(Trade)).all()
- # Make sure stoploss-order is open and trade is bought (since we mock update_trade_state)
- for trade in trades:
- stoploss_order_closed['id'] = '3'
- oobj = Order.parse_from_ccxt_object(stoploss_order_closed, trade.pair, 'stoploss')
+ # Make sure stoploss-order is open and trade is bought
+ for idx, trade in enumerate(trades):
+ stop_order = stop_orders[idx]
+ stop_order['id'] = f"stop{idx}"
+ oobj = Order.parse_from_ccxt_object(stop_order, trade.pair, 'stoploss')
trade.orders.append(oobj)
- trade.stoploss_order_id = '3'
+ trade.stoploss_order_id = f"stop{idx}"
trade.open_order_id = None
n = freqtrade.exit_positions(trades)
@@ -386,12 +388,12 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
assert trade.open_order_id is not None
assert pytest.approx(trade.stake_amount) == 60
assert trade.open_rate == 1.96
- assert trade.stop_loss_pct is None
- assert trade.stop_loss == 0.0
+ assert trade.stop_loss_pct == -0.1
+ assert pytest.approx(trade.stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
+ assert pytest.approx(trade.initial_stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
+ assert trade.initial_stop_loss_pct == -0.1
assert trade.leverage == leverage
assert trade.stake_amount == 60
- assert trade.initial_stop_loss == 0.0
- assert trade.initial_stop_loss_pct is None
# No adjustment
freqtrade.process()
trade = Trade.get_trades().first()
@@ -407,11 +409,11 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
assert trade.open_order_id is not None
# Open rate is not adjusted yet
assert trade.open_rate == 1.96
- assert trade.stop_loss_pct is None
- assert trade.stop_loss == 0.0
+ assert trade.stop_loss_pct == -0.1
+ assert pytest.approx(trade.stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
+ assert pytest.approx(trade.initial_stop_loss) == trade.open_rate * (1 - 0.1 / leverage)
assert trade.stake_amount == 60
- assert trade.initial_stop_loss == 0.0
- assert trade.initial_stop_loss_pct is None
+ assert trade.initial_stop_loss_pct == -0.1
# Fill order
mocker.patch(f'{EXMS}._dry_is_price_crossed', return_value=True)
@@ -424,7 +426,7 @@ def test_dca_order_adjust(default_conf_usdt, ticker_usdt, leverage, fee, mocker)
assert pytest.approx(trade.stake_amount) == 60
assert trade.stop_loss_pct == -0.1
assert pytest.approx(trade.stop_loss) == 1.99 * (1 - 0.1 / leverage)
- assert pytest.approx(trade.initial_stop_loss) == 1.99 * (1 - 0.1 / leverage)
+ assert pytest.approx(trade.initial_stop_loss) == 1.96 * (1 - 0.1 / leverage)
assert trade.initial_stop_loss_pct == -0.1
# 2nd order - not filling
diff --git a/tests/test_timerange.py b/tests/test_timerange.py
index 06ff1983a..993b24d95 100644
--- a/tests/test_timerange.py
+++ b/tests/test_timerange.py
@@ -10,6 +10,8 @@ from freqtrade.exceptions import OperationalException
def test_parse_timerange_incorrect():
+ timerange = TimeRange.parse_timerange('')
+ assert timerange == TimeRange(None, None, 0, 0)
timerange = TimeRange.parse_timerange('20100522-')
assert TimeRange('date', None, 1274486400, 0) == timerange
assert timerange.timerange_str == '20100522-'
diff --git a/tests/testdata/XRP_ETH-trades.feather b/tests/testdata/XRP_ETH-trades.feather
new file mode 100644
index 000000000..68e1c8467
Binary files /dev/null and b/tests/testdata/XRP_ETH-trades.feather differ