.
Cancel open order for tradeid
@@ -1202,10 +1252,10 @@ class Telegram(RPCHandler):
raise RPCException("Trade-id not set.")
trade_id = int(context.args[0])
self._rpc._rpc_cancel_open_order(trade_id)
- self._send_msg('Open order canceled.')
+ await self._send_msg('Open order canceled.')
@authorized_only
- def _performance(self, update: Update, context: CallbackContext) -> None:
+ async def _performance(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /performance.
Shows a performance statistic from finished trades
@@ -1223,17 +1273,17 @@ class Telegram(RPCHandler):
f"({trade['count']})\n")
if len(output + stat_line) >= MAX_MESSAGE_LENGTH:
- self._send_msg(output, parse_mode=ParseMode.HTML)
+ await self._send_msg(output, parse_mode=ParseMode.HTML)
output = stat_line
else:
output += stat_line
- self._send_msg(output, parse_mode=ParseMode.HTML,
- reload_able=True, callback_path="update_performance",
- query=update.callback_query)
+ await self._send_msg(output, parse_mode=ParseMode.HTML,
+ reload_able=True, callback_path="update_performance",
+ query=update.callback_query)
@authorized_only
- def _enter_tag_performance(self, update: Update, context: CallbackContext) -> None:
+ async def _enter_tag_performance(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /buys PAIR .
Shows a performance statistic from finished trades
@@ -1255,17 +1305,17 @@ class Telegram(RPCHandler):
f"({trade['count']})\n")
if len(output + stat_line) >= MAX_MESSAGE_LENGTH:
- self._send_msg(output, parse_mode=ParseMode.HTML)
+ await self._send_msg(output, parse_mode=ParseMode.HTML)
output = stat_line
else:
output += stat_line
- self._send_msg(output, parse_mode=ParseMode.HTML,
- reload_able=True, callback_path="update_enter_tag_performance",
- query=update.callback_query)
+ await self._send_msg(output, parse_mode=ParseMode.HTML,
+ reload_able=True, callback_path="update_enter_tag_performance",
+ query=update.callback_query)
@authorized_only
- def _exit_reason_performance(self, update: Update, context: CallbackContext) -> None:
+ async def _exit_reason_performance(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /sells.
Shows a performance statistic from finished trades
@@ -1287,17 +1337,17 @@ class Telegram(RPCHandler):
f"({trade['count']})\n")
if len(output + stat_line) >= MAX_MESSAGE_LENGTH:
- self._send_msg(output, parse_mode=ParseMode.HTML)
+ await self._send_msg(output, parse_mode=ParseMode.HTML)
output = stat_line
else:
output += stat_line
- self._send_msg(output, parse_mode=ParseMode.HTML,
- reload_able=True, callback_path="update_exit_reason_performance",
- query=update.callback_query)
+ await self._send_msg(output, parse_mode=ParseMode.HTML,
+ reload_able=True, callback_path="update_exit_reason_performance",
+ query=update.callback_query)
@authorized_only
- def _mix_tag_performance(self, update: Update, context: CallbackContext) -> None:
+ async def _mix_tag_performance(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /mix_tags.
Shows a performance statistic from finished trades
@@ -1319,17 +1369,17 @@ class Telegram(RPCHandler):
f"({trade['count']})\n")
if len(output + stat_line) >= MAX_MESSAGE_LENGTH:
- self._send_msg(output, parse_mode=ParseMode.HTML)
+ await self._send_msg(output, parse_mode=ParseMode.HTML)
output = stat_line
else:
output += stat_line
- self._send_msg(output, parse_mode=ParseMode.HTML,
- reload_able=True, callback_path="update_mix_tag_performance",
- query=update.callback_query)
+ await self._send_msg(output, parse_mode=ParseMode.HTML,
+ reload_able=True, callback_path="update_mix_tag_performance",
+ query=update.callback_query)
@authorized_only
- def _count(self, update: Update, context: CallbackContext) -> None:
+ async def _count(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /count.
Returns the number of trades running
@@ -1343,19 +1393,19 @@ class Telegram(RPCHandler):
tablefmt='simple')
message = f"{message}"
logger.debug(message)
- self._send_msg(message, parse_mode=ParseMode.HTML,
- reload_able=True, callback_path="update_count",
- query=update.callback_query)
+ await self._send_msg(message, parse_mode=ParseMode.HTML,
+ reload_able=True, callback_path="update_count",
+ query=update.callback_query)
@authorized_only
- def _locks(self, update: Update, context: CallbackContext) -> None:
+ async def _locks(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /locks.
Returns the currently active locks
"""
rpc_locks = self._rpc._rpc_locks()
if not rpc_locks['locks']:
- self._send_msg('No active locks.', parse_mode=ParseMode.HTML)
+ await self._send_msg('No active locks.', parse_mode=ParseMode.HTML)
for locks in chunks(rpc_locks['locks'], 25):
message = tabulate([[
@@ -1367,10 +1417,10 @@ class Telegram(RPCHandler):
tablefmt='simple')
message = f"{escape(message)}"
logger.debug(message)
- self._send_msg(message, parse_mode=ParseMode.HTML)
+ await self._send_msg(message, parse_mode=ParseMode.HTML)
@authorized_only
- def _delete_locks(self, update: Update, context: CallbackContext) -> None:
+ async def _delete_locks(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /delete_locks.
Returns the currently active locks
@@ -1385,10 +1435,10 @@ class Telegram(RPCHandler):
pair = arg
self._rpc._rpc_delete_lock(lockid=lockid, pair=pair)
- self._locks(update, context)
+ await self._locks(update, context)
@authorized_only
- def _whitelist(self, update: Update, context: CallbackContext) -> None:
+ async def _whitelist(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /whitelist
Shows the currently active whitelist
@@ -1405,39 +1455,39 @@ class Telegram(RPCHandler):
message += f"`{', '.join(whitelist['whitelist'])}`"
logger.debug(message)
- self._send_msg(message)
+ await self._send_msg(message)
@authorized_only
- def _blacklist(self, update: Update, context: CallbackContext) -> None:
+ async def _blacklist(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /blacklist
Shows the currently active blacklist
"""
- self.send_blacklist_msg(self._rpc._rpc_blacklist(context.args))
+ await self.send_blacklist_msg(self._rpc._rpc_blacklist(context.args))
- def send_blacklist_msg(self, blacklist: Dict):
+ async def send_blacklist_msg(self, blacklist: Dict):
errmsgs = []
for pair, error in blacklist['errors'].items():
- errmsgs.append(f"Error adding `{pair}` to blacklist: `{error['error_msg']}`")
+ errmsgs.append(f"Error: {error['error_msg']}")
if errmsgs:
- self._send_msg('\n'.join(errmsgs))
+ await self._send_msg('\n'.join(errmsgs))
message = f"Blacklist contains {blacklist['length']} pairs\n"
message += f"`{', '.join(blacklist['blacklist'])}`"
logger.debug(message)
- self._send_msg(message)
+ await self._send_msg(message)
@authorized_only
- def _blacklist_delete(self, update: Update, context: CallbackContext) -> None:
+ async def _blacklist_delete(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /bl_delete
Deletes pair(s) from current blacklist
"""
- self.send_blacklist_msg(self._rpc._rpc_blacklist_delete(context.args or []))
+ await self.send_blacklist_msg(self._rpc._rpc_blacklist_delete(context.args or []))
@authorized_only
- def _logs(self, update: Update, context: CallbackContext) -> None:
+ async def _logs(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /logs
Shows the latest logs
@@ -1456,17 +1506,17 @@ class Telegram(RPCHandler):
escape_markdown(logrec[4], version=2))
if len(msgs + msg) + 10 >= MAX_MESSAGE_LENGTH:
# Send message immediately if it would become too long
- self._send_msg(msgs, parse_mode=ParseMode.MARKDOWN_V2)
+ await self._send_msg(msgs, parse_mode=ParseMode.MARKDOWN_V2)
msgs = msg + '\n'
else:
# Append message to messages to send
msgs += msg + '\n'
if msgs:
- self._send_msg(msgs, parse_mode=ParseMode.MARKDOWN_V2)
+ await self._send_msg(msgs, parse_mode=ParseMode.MARKDOWN_V2)
@authorized_only
- def _edge(self, update: Update, context: CallbackContext) -> None:
+ async def _edge(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /edge
Shows information related to Edge
@@ -1474,17 +1524,17 @@ class Telegram(RPCHandler):
edge_pairs = self._rpc._rpc_edge()
if not edge_pairs:
message = 'Edge only validated following pairs:'
- self._send_msg(message, parse_mode=ParseMode.HTML)
+ await self._send_msg(message, parse_mode=ParseMode.HTML)
for chunk in chunks(edge_pairs, 25):
edge_pairs_tab = tabulate(chunk, headers='keys', tablefmt='simple')
message = (f'Edge only validated following pairs:\n'
f'{edge_pairs_tab}')
- self._send_msg(message, parse_mode=ParseMode.HTML)
+ await self._send_msg(message, parse_mode=ParseMode.HTML)
@authorized_only
- def _help(self, update: Update, context: CallbackContext) -> None:
+ async def _help(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /help.
Show commands of the bot
@@ -1528,7 +1578,8 @@ class Telegram(RPCHandler):
"------------\n"
"*/show_config:* `Show running configuration` \n"
"*/locks:* `Show currently locked pairs`\n"
- "*/balance:* `Show account balance per currency`\n"
+ "*/balance:* `Show bot managed balance per currency`\n"
+ "*/balance total:* `Show account balance per currency`\n"
"*/logs [limit]:* `Show latest logs - defaults to 10` \n"
"*/count:* `Show number of active trades compared to allowed number of trades`\n"
"*/edge:* `Shows validated pairs by Edge if it is enabled` \n"
@@ -1561,20 +1612,20 @@ class Telegram(RPCHandler):
"*/version:* `Show version`"
)
- self._send_msg(message, parse_mode=ParseMode.MARKDOWN)
+ await self._send_msg(message, parse_mode=ParseMode.MARKDOWN)
@authorized_only
- def _health(self, update: Update, context: CallbackContext) -> None:
+ async def _health(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /health
Shows the last process timestamp
"""
health = self._rpc.health()
message = f"Last process: `{health['last_process_loc']}`"
- self._send_msg(message)
+ await self._send_msg(message)
@authorized_only
- def _version(self, update: Update, context: CallbackContext) -> None:
+ async def _version(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /version.
Show version information
@@ -1585,12 +1636,12 @@ class Telegram(RPCHandler):
strategy_version = self._rpc._freqtrade.strategy.version()
version_string = f'*Version:* `{__version__}`'
if strategy_version is not None:
- version_string += f', *Strategy version: * `{strategy_version}`'
+ version_string += f'\n*Strategy version: * `{strategy_version}`'
- self._send_msg(version_string)
+ await self._send_msg(version_string)
@authorized_only
- def _show_config(self, update: Update, context: CallbackContext) -> None:
+ async def _show_config(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /show_config.
Show config information information
@@ -1619,7 +1670,7 @@ class Telegram(RPCHandler):
else:
pa_info = "*Position adjustment:* Off\n"
- self._send_msg(
+ await self._send_msg(
f"*Mode:* `{'Dry-run' if val['dry_run'] else 'Live'}`\n"
f"*Exchange:* `{val['exchange']}`\n"
f"*Market: * `{val['trading_mode']}`\n"
@@ -1635,8 +1686,8 @@ class Telegram(RPCHandler):
f"*Current state:* `{val['state']}`"
)
- def _update_msg(self, query: CallbackQuery, msg: str, callback_path: str = "",
- reload_able: bool = False, parse_mode: str = ParseMode.MARKDOWN) -> None:
+ async def _update_msg(self, query: CallbackQuery, msg: str, callback_path: str = "",
+ reload_able: bool = False, parse_mode: str = ParseMode.MARKDOWN) -> None:
if reload_able:
reply_markup = InlineKeyboardMarkup([
[InlineKeyboardButton("Refresh", callback_data=callback_path)],
@@ -1650,7 +1701,7 @@ class Telegram(RPCHandler):
message_id = query.message.message_id
try:
- self._updater.bot.edit_message_text(
+ await self._app.bot.edit_message_text(
chat_id=chat_id,
message_id=message_id,
text=msg,
@@ -1665,12 +1716,12 @@ class Telegram(RPCHandler):
except TelegramError as telegram_err:
logger.warning('TelegramError: %s! Giving up on that message.', telegram_err.message)
- def _send_msg(self, msg: str, parse_mode: str = ParseMode.MARKDOWN,
- disable_notification: bool = False,
- keyboard: Optional[List[List[InlineKeyboardButton]]] = None,
- callback_path: str = "",
- reload_able: bool = False,
- query: Optional[CallbackQuery] = None) -> None:
+ async def _send_msg(self, msg: str, parse_mode: str = ParseMode.MARKDOWN,
+ disable_notification: bool = False,
+ keyboard: Optional[List[List[InlineKeyboardButton]]] = None,
+ callback_path: str = "",
+ reload_able: bool = False,
+ query: Optional[CallbackQuery] = None) -> None:
"""
Send given markdown message
:param msg: message
@@ -1680,20 +1731,20 @@ class Telegram(RPCHandler):
"""
reply_markup: Union[InlineKeyboardMarkup, ReplyKeyboardMarkup]
if query:
- self._update_msg(query=query, msg=msg, parse_mode=parse_mode,
- callback_path=callback_path, reload_able=reload_able)
+ await self._update_msg(query=query, msg=msg, parse_mode=parse_mode,
+ callback_path=callback_path, reload_able=reload_able)
return
if reload_able and self._config['telegram'].get('reload', True):
reply_markup = InlineKeyboardMarkup([
[InlineKeyboardButton("Refresh", callback_data=callback_path)]])
else:
if keyboard is not None:
- reply_markup = InlineKeyboardMarkup(keyboard, resize_keyboard=True)
+ reply_markup = InlineKeyboardMarkup(keyboard)
else:
reply_markup = ReplyKeyboardMarkup(self._keyboard, resize_keyboard=True)
try:
try:
- self._updater.bot.send_message(
+ await self._app.bot.send_message(
self._config['telegram']['chat_id'],
text=msg,
parse_mode=parse_mode,
@@ -1707,7 +1758,7 @@ class Telegram(RPCHandler):
'Telegram NetworkError: %s! Trying one more time.',
network_err.message
)
- self._updater.bot.send_message(
+ await self._app.bot.send_message(
self._config['telegram']['chat_id'],
text=msg,
parse_mode=parse_mode,
@@ -1721,7 +1772,7 @@ class Telegram(RPCHandler):
)
@authorized_only
- def _changemarketdir(self, update: Update, context: CallbackContext) -> None:
+ async def _changemarketdir(self, update: Update, context: CallbackContext) -> None:
"""
Handler for /marketdir.
Updates the bot's market_direction
@@ -1744,14 +1795,14 @@ class Telegram(RPCHandler):
if new_market_dir is not None:
self._rpc._update_market_direction(new_market_dir)
- self._send_msg("Successfully updated market direction"
- f" from *{old_market_dir}* to *{new_market_dir}*.")
+ await self._send_msg("Successfully updated market direction"
+ f" from *{old_market_dir}* to *{new_market_dir}*.")
else:
raise RPCException("Invalid market direction provided. \n"
"Valid market directions: *long, short, even, none*")
elif context.args is not None and len(context.args) == 0:
old_market_dir = self._rpc._get_market_direction()
- self._send_msg(f"Currently set market direction: *{old_market_dir}*")
+ await self._send_msg(f"Currently set market direction: *{old_market_dir}*")
else:
raise RPCException("Invalid usage of command /marketdir. \n"
"Usage: */marketdir [short | long | even | none]*")
diff --git a/freqtrade/rpc/webhook.py b/freqtrade/rpc/webhook.py
index 14b881126..80690ec0c 100644
--- a/freqtrade/rpc/webhook.py
+++ b/freqtrade/rpc/webhook.py
@@ -44,8 +44,11 @@ class Webhook(RPCHandler):
def _get_value_dict(self, msg: RPCSendMsg) -> Optional[Dict[str, Any]]:
whconfig = self._config['webhook']
+ if msg['type'].value in whconfig:
+ # Explicit types should have priority
+ valuedict = whconfig.get(msg['type'].value)
# Deprecated 2022.10 - only keep generic method.
- if msg['type'] in [RPCMessageType.ENTRY]:
+ elif msg['type'] in [RPCMessageType.ENTRY]:
valuedict = whconfig.get('webhookentry')
elif msg['type'] in [RPCMessageType.ENTRY_CANCEL]:
valuedict = whconfig.get('webhookentrycancel')
@@ -62,9 +65,6 @@ class Webhook(RPCHandler):
RPCMessageType.EXCEPTION,
RPCMessageType.WARNING):
valuedict = whconfig.get('webhookstatus')
- elif msg['type'].value in whconfig:
- # Allow all types ...
- valuedict = whconfig.get(msg['type'].value)
elif msg['type'] in (
RPCMessageType.PROTECTION_TRIGGER,
RPCMessageType.PROTECTION_TRIGGER_GLOBAL,
diff --git a/freqtrade/strategy/interface.py b/freqtrade/strategy/interface.py
index 3bc766d91..7adb7a154 100644
--- a/freqtrade/strategy/interface.py
+++ b/freqtrade/strategy/interface.py
@@ -618,7 +618,7 @@ class IStrategy(ABC, HyperStrategyMixin):
return df
def feature_engineering_expand_all(self, dataframe: DataFrame, period: int,
- metadata: Dict, **kwargs):
+ metadata: Dict, **kwargs) -> DataFrame:
"""
*Only functional with FreqAI enabled strategies*
This function will automatically expand the defined features on the config defined
@@ -644,7 +644,8 @@ class IStrategy(ABC, HyperStrategyMixin):
"""
return dataframe
- def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: Dict, **kwargs):
+ def feature_engineering_expand_basic(
+ self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
"""
*Only functional with FreqAI enabled strategies*
This function will automatically expand the defined features on the config defined
@@ -673,7 +674,8 @@ class IStrategy(ABC, HyperStrategyMixin):
"""
return dataframe
- def feature_engineering_standard(self, dataframe: DataFrame, metadata: Dict, **kwargs):
+ def feature_engineering_standard(
+ self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
"""
*Only functional with FreqAI enabled strategies*
This optional function will be called once with the dataframe of the base timeframe.
@@ -697,7 +699,7 @@ class IStrategy(ABC, HyperStrategyMixin):
"""
return dataframe
- def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs):
+ def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
"""
*Only functional with FreqAI enabled strategies*
Required function to set the targets for the model.
diff --git a/freqtrade/templates/FreqaiExampleHybridStrategy.py b/freqtrade/templates/FreqaiExampleHybridStrategy.py
index 3f27ee4a1..03446d76e 100644
--- a/freqtrade/templates/FreqaiExampleHybridStrategy.py
+++ b/freqtrade/templates/FreqaiExampleHybridStrategy.py
@@ -97,7 +97,7 @@ class FreqaiExampleHybridStrategy(IStrategy):
exit_short_rsi = IntParameter(low=1, high=50, default=30, space='buy', optimize=True, load=True)
def feature_engineering_expand_all(self, dataframe: DataFrame, period: int,
- metadata: Dict, **kwargs):
+ metadata: Dict, **kwargs) -> DataFrame:
"""
*Only functional with FreqAI enabled strategies*
This function will automatically expand the defined features on the config defined
@@ -151,7 +151,8 @@ class FreqaiExampleHybridStrategy(IStrategy):
return dataframe
- def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: Dict, **kwargs):
+ def feature_engineering_expand_basic(
+ self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
"""
*Only functional with FreqAI enabled strategies*
This function will automatically expand the defined features on the config defined
@@ -183,7 +184,8 @@ class FreqaiExampleHybridStrategy(IStrategy):
dataframe["%-raw_price"] = dataframe["close"]
return dataframe
- def feature_engineering_standard(self, dataframe: DataFrame, metadata: Dict, **kwargs):
+ def feature_engineering_standard(
+ self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
"""
*Only functional with FreqAI enabled strategies*
This optional function will be called once with the dataframe of the base timeframe.
@@ -209,7 +211,7 @@ class FreqaiExampleHybridStrategy(IStrategy):
dataframe["%-hour_of_day"] = dataframe["date"].dt.hour
return dataframe
- def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs):
+ def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
"""
*Only functional with FreqAI enabled strategies*
Required function to set the targets for the model.
diff --git a/freqtrade/templates/FreqaiExampleStrategy.py b/freqtrade/templates/FreqaiExampleStrategy.py
index 0093c7f7a..493ea17f3 100644
--- a/freqtrade/templates/FreqaiExampleStrategy.py
+++ b/freqtrade/templates/FreqaiExampleStrategy.py
@@ -48,7 +48,7 @@ class FreqaiExampleStrategy(IStrategy):
[0.75, 1, 1.25, 1.5, 1.75], space="sell", default=1.25, optimize=True)
def feature_engineering_expand_all(self, dataframe: DataFrame, period: int,
- metadata: Dict, **kwargs):
+ metadata: Dict, **kwargs) -> DataFrame:
"""
*Only functional with FreqAI enabled strategies*
This function will automatically expand the defined features on the config defined
@@ -106,7 +106,8 @@ class FreqaiExampleStrategy(IStrategy):
return dataframe
- def feature_engineering_expand_basic(self, dataframe: DataFrame, metadata: Dict, **kwargs):
+ def feature_engineering_expand_basic(
+ self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
"""
*Only functional with FreqAI enabled strategies*
This function will automatically expand the defined features on the config defined
@@ -142,7 +143,8 @@ class FreqaiExampleStrategy(IStrategy):
dataframe["%-raw_price"] = dataframe["close"]
return dataframe
- def feature_engineering_standard(self, dataframe: DataFrame, metadata: Dict, **kwargs):
+ def feature_engineering_standard(
+ self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
"""
*Only functional with FreqAI enabled strategies*
This optional function will be called once with the dataframe of the base timeframe.
@@ -172,7 +174,7 @@ class FreqaiExampleStrategy(IStrategy):
dataframe["%-hour_of_day"] = dataframe["date"].dt.hour
return dataframe
- def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs):
+ def set_freqai_targets(self, dataframe: DataFrame, metadata: Dict, **kwargs) -> DataFrame:
"""
*Only functional with FreqAI enabled strategies*
Required function to set the targets for the model.
diff --git a/freqtrade/wallets.py b/freqtrade/wallets.py
index 8dcc92af4..6f86398f3 100644
--- a/freqtrade/wallets.py
+++ b/freqtrade/wallets.py
@@ -11,6 +11,7 @@ from freqtrade.constants import UNLIMITED_STAKE_AMOUNT, Config
from freqtrade.enums import RunMode, TradingMode
from freqtrade.exceptions import DependencyException
from freqtrade.exchange import Exchange
+from freqtrade.misc import safe_value_fallback
from freqtrade.persistence import LocalTrade, Trade
@@ -148,7 +149,7 @@ class Wallets:
# Position is not open ...
continue
size = self._exchange._contracts_to_amount(symbol, position['contracts'])
- collateral = position['collateral'] or 0.0
+ collateral = safe_value_fallback(position, 'collateral', 'initialMargin', 0.0)
leverage = position['leverage']
self._positions[symbol] = PositionWallet(
symbol, position=size,
diff --git a/requirements-dev.txt b/requirements-dev.txt
index fc0efcfe7..ea75bb8f2 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -7,7 +7,7 @@
-r docs/requirements-docs.txt
coveralls==3.3.1
-ruff==0.0.261
+ruff==0.0.262
mypy==1.2.0
pre-commit==3.2.2
pytest==7.3.1
@@ -18,8 +18,6 @@ pytest-random-order==1.1.0
isort==5.12.0
# For datetime mocking
time-machine==2.9.0
-# fastapi testing
-httpx==0.24.0
# Convert jupyter notebooks to markdown documents
nbconvert==7.3.1
diff --git a/requirements-hyperopt.txt b/requirements-hyperopt.txt
index c81c17f63..87b1fd3c8 100644
--- a/requirements-hyperopt.txt
+++ b/requirements-hyperopt.txt
@@ -5,4 +5,4 @@
scipy==1.10.1
scikit-learn==1.1.3
scikit-optimize==0.9.0
-filelock==3.11.0
+filelock==3.12.0
diff --git a/requirements.txt b/requirements.txt
index 7db852961..7c646aec6 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,12 +1,14 @@
-numpy==1.24.2
-pandas==1.5.3
+numpy==1.24.3
+pandas==2.0.1
pandas-ta==0.3.14b
-ccxt==3.0.69
+ccxt==3.0.75
cryptography==40.0.2
aiohttp==3.8.4
-SQLAlchemy==2.0.9
-python-telegram-bot==13.15
+SQLAlchemy==2.0.10
+python-telegram-bot==20.2
+# can't be hard-pinned due to telegram-bot pinning httpx with ~
+httpx>=0.23.3
arrow==1.2.3
cachetools==4.2.2
requests==2.28.2
@@ -40,7 +42,7 @@ pydantic==1.10.7
uvicorn==0.21.1
pyjwt==2.6.0
aiofiles==23.1.0
-psutil==5.9.4
+psutil==5.9.5
# Support for colorized terminal output
colorama==0.4.6
@@ -54,7 +56,7 @@ python-dateutil==2.8.2
schedule==1.2.0
#WS Messages
-websockets==11.0.1
+websockets==11.0.2
janus==1.0.0
ast-comments==1.0.1
diff --git a/setup.sh b/setup.sh
index 805e13237..d46569a53 100755
--- a/setup.sh
+++ b/setup.sh
@@ -50,7 +50,7 @@ function updateenv() {
SYS_ARCH=$(uname -m)
echo "pip install in-progress. Please wait..."
# Setuptools 65.5.0 is the last version that can install gym==0.21.0
- ${PYTHON} -m pip install --upgrade pip==23.0.1 wheel setuptools==65.5.1
+ ${PYTHON} -m pip install --upgrade pip==23.0.1 wheel==0.38.4 setuptools==65.5.1
REQUIREMENTS_HYPEROPT=""
REQUIREMENTS_PLOT=""
REQUIREMENTS_FREQAI=""
diff --git a/tests/conftest.py b/tests/conftest.py
index 2f2345d54..1c737b3aa 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -3,7 +3,7 @@ import json
import logging
import re
from copy import deepcopy
-from datetime import datetime, timedelta
+from datetime import timedelta
from pathlib import Path
from typing import Optional
from unittest.mock import MagicMock, Mock, PropertyMock
@@ -12,7 +12,6 @@ import arrow
import numpy as np
import pandas as pd
import pytest
-from telegram import Chat, Message, Update
from freqtrade import constants
from freqtrade.commands import Arguments
@@ -550,13 +549,6 @@ def get_default_conf_usdt(testdatadir):
return configuration
-@pytest.fixture
-def update():
- _update = Update(0)
- _update.message = Message(0, datetime.utcnow(), Chat(0, 0))
- return _update
-
-
@pytest.fixture
def fee():
return MagicMock(return_value=0.0025)
diff --git a/tests/optimize/test_optimize_reports.py b/tests/optimize/test_optimize_reports.py
index 9fdd0d61e..6428177c5 100644
--- a/tests/optimize/test_optimize_reports.py
+++ b/tests/optimize/test_optimize_reports.py
@@ -9,7 +9,7 @@ import pytest
from arrow import Arrow
from freqtrade.configuration import TimeRange
-from freqtrade.constants import DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN
+from freqtrade.constants import BACKTEST_BREAKDOWNS, DATETIME_PRINT_FORMAT, LAST_BT_RESULT_FN
from freqtrade.data import history
from freqtrade.data.btanalysis import (get_latest_backtest_filename, load_backtest_data,
load_backtest_stats)
@@ -470,6 +470,9 @@ def test__get_resample_from_period():
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_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 948973ed5..1a7d84eca 100644
--- a/tests/persistence/test_persistence.py
+++ b/tests/persistence/test_persistence.py
@@ -2481,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
@@ -2495,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/rpc/test_rpc.py b/tests/rpc/test_rpc.py
index ff08a0564..bb84ff8e9 100644
--- a/tests/rpc/test_rpc.py
+++ b/tests/rpc/test_rpc.py
@@ -546,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 58c904838..8123e4689 100644
--- a/tests/rpc/test_rpc_apiserver.py
+++ b/tests/rpc/test_rpc_apiserver.py
@@ -283,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)))
@@ -341,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()
@@ -419,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()
@@ -480,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
@@ -1872,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_telegram.py b/tests/rpc/test_rpc_telegram.py
index 7978a2a23..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,11 @@ 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
@@ -43,6 +50,28 @@ def default_conf(default_conf) -> dict:
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
@@ -56,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
"""
@@ -71,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)
@@ -95,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
@@ -140,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)
@@ -160,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
@@ -177,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)
@@ -232,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,
@@ -284,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)
@@ -296,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,
@@ -313,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,
@@ -340,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()
@@ -354,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
@@ -371,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]
@@ -386,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
@@ -398,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,
@@ -413,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()
@@ -427,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")
@@ -439,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
@@ -461,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]
@@ -475,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]
@@ -492,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
@@ -513,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]
@@ -523,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',
@@ -548,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]
@@ -564,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]
@@ -580,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]
@@ -590,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',
@@ -618,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]
@@ -633,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]
@@ -650,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]
@@ -667,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]
@@ -677,11 +732,11 @@ 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)
+ 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_telegram_profit_handle(
+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)
@@ -694,7 +749,7 @@ def test_telegram_profit_handle(
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()
@@ -706,7 +761,7 @@ def test_telegram_profit_handle(
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]
@@ -729,7 +784,7 @@ def test_telegram_profit_handle(
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}%)`'
@@ -747,7 +802,7 @@ def test_telegram_profit_handle(
@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,
@@ -757,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()
@@ -765,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]
@@ -773,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)
@@ -782,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={})
@@ -806,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)
@@ -834,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,
})
@@ -853,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
@@ -862,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())
@@ -964,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]
@@ -1000,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())
@@ -1036,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
@@ -1073,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)
@@ -1099,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
@@ -1136,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)
@@ -1148,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]
@@ -1158,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,
@@ -1179,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.'
@@ -1188,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
@@ -1196,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
@@ -1206,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)
@@ -1228,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'
@@ -1241,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'
@@ -1249,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)
@@ -1274,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
@@ -1284,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,
@@ -1302,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,
@@ -1321,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,
@@ -1352,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,
@@ -1384,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,
@@ -1413,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()
@@ -1422,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(
@@ -1432,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,
@@ -1440,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]
@@ -1449,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]
@@ -1462,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]
@@ -1470,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])
@@ -1482,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])
@@ -1516,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])
@@ -1549,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])
@@ -1558,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/.*`"
@@ -1567,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(),
@@ -1584,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()
@@ -1598,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),
@@ -1622,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]
@@ -1631,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]
@@ -1640,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()
@@ -1662,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]
@@ -1672,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()
@@ -1686,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,
@@ -1703,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()
@@ -1711,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()
@@ -1720,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 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 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]
@@ -1765,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]
@@ -2072,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)
@@ -2164,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,
@@ -2309,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)
@@ -2378,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
@@ -2402,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: "
@@ -2410,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/test_freqtradebot.py b/tests/test_freqtradebot.py
index 7bded0f82..ea99061b8 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -2107,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)
@@ -2115,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
@@ -2133,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