Merge pull request #6084 from aezomz/lev-telegram

Telegram and Webhook updates
This commit is contained in:
Matthias 2021-12-30 19:17:58 +01:00 committed by GitHub
commit 4b79d435ad
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 340 additions and 117 deletions

View File

@ -216,11 +216,14 @@ Once all positions are sold, run `/stop` to completely stop the bot.
### /status ### /status
For each open trade, the bot will send you the following message. For each open trade, the bot will send you the following message.
Enter Tag is configurable via Strategy.
> **Trade ID:** `123` `(since 1 days ago)` > **Trade ID:** `123` `(since 1 days ago)`
> **Current Pair:** CVC/BTC > **Current Pair:** CVC/BTC
> **Open Since:** `1 days ago` > **Direction:** Long
> **Leverage:** 1.0
> **Amount:** `26.64180098` > **Amount:** `26.64180098`
> **Enter Tag:** Awesome Long Signal
> **Open Rate:** `0.00007489` > **Open Rate:** `0.00007489`
> **Current Rate:** `0.00007489` > **Current Rate:** `0.00007489`
> **Current Profit:** `12.95%` > **Current Profit:** `12.95%`
@ -231,10 +234,10 @@ For each open trade, the bot will send you the following message.
Return the status of all open trades in a table format. Return the status of all open trades in a table format.
``` ```
ID Pair Since Profit ID L/S Pair Since Profit
---- -------- ------- -------- ---- -------- ------- --------
67 SC/BTC 1 d 13.33% 67 L SC/BTC 1 d 13.33%
123 CVC/BTC 1 h 12.95% 123 S CVC/BTC 1 h 12.95%
``` ```
### /count ### /count

View File

@ -98,12 +98,14 @@ Different payloads can be configured for different events. Not all fields are ne
### Webhookbuy ### Webhookbuy
The fields in `webhook.webhookbuy` are filled when the bot executes a buy. Parameters are filled using string.format. The fields in `webhook.webhookbuy` are filled when the bot executes a long/short. Parameters are filled using string.format.
Possible parameters are: Possible parameters are:
* `trade_id` * `trade_id`
* `exchange` * `exchange`
* `pair` * `pair`
* `direction`
* `leverage`
* ~~`limit` # Deprecated - should no longer be used.~~ * ~~`limit` # Deprecated - should no longer be used.~~
* `open_rate` * `open_rate`
* `amount` * `amount`
@ -117,12 +119,14 @@ Possible parameters are:
### Webhookbuycancel ### Webhookbuycancel
The fields in `webhook.webhookbuycancel` are filled when the bot cancels a buy order. Parameters are filled using string.format. The fields in `webhook.webhookbuycancel` are filled when the bot cancels a long/short order. Parameters are filled using string.format.
Possible parameters are: Possible parameters are:
* `trade_id` * `trade_id`
* `exchange` * `exchange`
* `pair` * `pair`
* `direction`
* `leverage`
* `limit` * `limit`
* `amount` * `amount`
* `open_date` * `open_date`
@ -135,12 +139,14 @@ Possible parameters are:
### Webhookbuyfill ### Webhookbuyfill
The fields in `webhook.webhookbuyfill` are filled when the bot filled a buy order. Parameters are filled using string.format. The fields in `webhook.webhookbuyfill` are filled when the bot filled a long/short order. Parameters are filled using string.format.
Possible parameters are: Possible parameters are:
* `trade_id` * `trade_id`
* `exchange` * `exchange`
* `pair` * `pair`
* `direction`
* `leverage`
* `open_rate` * `open_rate`
* `amount` * `amount`
* `open_date` * `open_date`
@ -152,13 +158,14 @@ Possible parameters are:
* `enter_tag` * `enter_tag`
### Webhooksell ### Webhooksell
The fields in `webhook.webhooksell` are filled when the bot sells a trade. Parameters are filled using string.format. The fields in `webhook.webhooksell` are filled when the bot sells a trade. Parameters are filled using string.format.
Possible parameters are: Possible parameters are:
* `trade_id` * `trade_id`
* `exchange` * `exchange`
* `pair` * `pair`
* `direction`
* `leverage`
* `gain` * `gain`
* `limit` * `limit`
* `amount` * `amount`
@ -180,6 +187,8 @@ Possible parameters are:
* `trade_id` * `trade_id`
* `exchange` * `exchange`
* `pair` * `pair`
* `direction`
* `leverage`
* `gain` * `gain`
* `close_rate` * `close_rate`
* `amount` * `amount`
@ -202,6 +211,8 @@ Possible parameters are:
* `trade_id` * `trade_id`
* `exchange` * `exchange`
* `pair` * `pair`
* `direction`
* `leverage`
* `gain` * `gain`
* `limit` * `limit`
* `amount` * `amount`

View File

@ -775,6 +775,8 @@ class FreqtradeBot(LoggingMixin):
'enter_tag': trade.enter_tag, 'enter_tag': trade.enter_tag,
'exchange': self.exchange.name.capitalize(), 'exchange': self.exchange.name.capitalize(),
'pair': trade.pair, 'pair': trade.pair,
'leverage': trade.leverage if trade.leverage else None,
'direction': 'Short' if trade.is_short else 'Long',
'limit': trade.open_rate, # Deprecated (?) 'limit': trade.open_rate, # Deprecated (?)
'open_rate': trade.open_rate, 'open_rate': trade.open_rate,
'order_type': order_type, 'order_type': order_type,
@ -802,6 +804,8 @@ class FreqtradeBot(LoggingMixin):
'enter_tag': trade.enter_tag, 'enter_tag': trade.enter_tag,
'exchange': self.exchange.name.capitalize(), 'exchange': self.exchange.name.capitalize(),
'pair': trade.pair, 'pair': trade.pair,
'leverage': trade.leverage,
'direction': 'Short' if trade.is_short else 'Long',
'limit': trade.open_rate, 'limit': trade.open_rate,
'order_type': order_type, 'order_type': order_type,
'stake_amount': trade.stake_amount, 'stake_amount': trade.stake_amount,
@ -1376,6 +1380,8 @@ class FreqtradeBot(LoggingMixin):
'trade_id': trade.id, 'trade_id': trade.id,
'exchange': trade.exchange.capitalize(), 'exchange': trade.exchange.capitalize(),
'pair': trade.pair, 'pair': trade.pair,
'leverage': trade.leverage,
'direction': 'Short' if trade.is_short else 'Long',
'gain': gain, 'gain': gain,
'limit': profit_rate, 'limit': profit_rate,
'order_type': order_type, 'order_type': order_type,
@ -1422,6 +1428,8 @@ class FreqtradeBot(LoggingMixin):
'trade_id': trade.id, 'trade_id': trade.id,
'exchange': trade.exchange.capitalize(), 'exchange': trade.exchange.capitalize(),
'pair': trade.pair, 'pair': trade.pair,
'leverage': trade.leverage,
'direction': 'Short' if trade.is_short else 'Long',
'gain': gain, 'gain': gain,
'limit': profit_rate or 0, 'limit': profit_rate or 0,
'order_type': order_type, 'order_type': order_type,

View File

@ -233,6 +233,7 @@ class RPC:
current_rate = NAN current_rate = NAN
trade_profit = trade.calc_profit(current_rate) trade_profit = trade.calc_profit(current_rate)
profit_str = f'{trade.calc_profit_ratio(current_rate):.2%}' profit_str = f'{trade.calc_profit_ratio(current_rate):.2%}'
direction_str = 'S' if trade.is_short else 'L'
if self._fiat_converter: if self._fiat_converter:
fiat_profit = self._fiat_converter.convert_amount( fiat_profit = self._fiat_converter.convert_amount(
trade_profit, trade_profit,
@ -244,7 +245,7 @@ class RPC:
fiat_profit_sum = fiat_profit if isnan(fiat_profit_sum) \ fiat_profit_sum = fiat_profit if isnan(fiat_profit_sum) \
else fiat_profit_sum + fiat_profit else fiat_profit_sum + fiat_profit
trades_list.append([ trades_list.append([
trade.id, f'{trade.id} {direction_str}',
trade.pair + ('*' if (trade.open_order_id is not None trade.pair + ('*' if (trade.open_order_id is not None
and trade.close_rate_requested is None) else '') and trade.close_rate_requested is None) else '')
+ ('**' if (trade.close_rate_requested is not None) else ''), + ('**' if (trade.close_rate_requested is not None) else ''),
@ -255,7 +256,7 @@ class RPC:
if self._fiat_converter: if self._fiat_converter:
profitcol += " (" + fiat_display_currency + ")" profitcol += " (" + fiat_display_currency + ")"
columns = ['ID', 'Pair', 'Since', profitcol] columns = ['ID L/S', 'Pair', 'Since', profitcol]
return trades_list, columns, fiat_profit_sum return trades_list, columns, fiat_profit_sum
def _rpc_daily_profit( def _rpc_daily_profit(

View File

@ -221,20 +221,25 @@ class Telegram(RPCHandler):
msg['stake_amount'], msg['stake_currency'], msg['fiat_currency']) msg['stake_amount'], msg['stake_currency'], msg['fiat_currency'])
else: else:
msg['stake_amount_fiat'] = 0 msg['stake_amount_fiat'] = 0
is_fill = msg['type'] == RPCMessageType.BUY_FILL is_fill = msg['type'] in [RPCMessageType.BUY_FILL, RPCMessageType.SHORT_FILL]
emoji = '\N{CHECK MARK}' if is_fill else '\N{LARGE BLUE CIRCLE}' emoji = '\N{CHECK MARK}' if is_fill else '\N{LARGE BLUE CIRCLE}'
enter_side = ({'enter': 'Long', 'entered': 'Longed'} if msg['type']
in [RPCMessageType.BUY_FILL, RPCMessageType.BUY]
else {'enter': 'Short', 'entered': 'Shorted'})
message = ( message = (
f"{emoji} *{msg['exchange']}:* {'Bought' if is_fill else 'Buying'} {msg['pair']}" f"{emoji} *{msg['exchange']}:*"
f" {enter_side['entered'] if is_fill else enter_side['enter']} {msg['pair']}"
f" (#{msg['trade_id']})\n" f" (#{msg['trade_id']})\n"
) )
message += f"*Enter Tag:* `{msg['enter_tag']}`\n" if msg.get('enter_tag', None) else "" message += f"*Enter Tag:* `{msg['enter_tag']}`\n" if msg.get('enter_tag', None) else ""
message += f"*Amount:* `{msg['amount']:.8f}`\n" message += f"*Amount:* `{msg['amount']:.8f}`\n"
if msg.get('leverage') and msg.get('leverage', 1.0) != 1.0:
message += f"*Leverage:* `{msg['leverage']}`\n"
if msg['type'] == RPCMessageType.BUY_FILL: if msg['type'] in [RPCMessageType.BUY_FILL, RPCMessageType.SHORT_FILL]:
message += f"*Open Rate:* `{msg['open_rate']:.8f}`\n" message += f"*Open Rate:* `{msg['open_rate']:.8f}`\n"
elif msg['type'] in [RPCMessageType.BUY, RPCMessageType.SHORT]:
elif msg['type'] == RPCMessageType.BUY:
message += f"*Open Rate:* `{msg['limit']:.8f}`\n"\ message += f"*Open Rate:* `{msg['limit']:.8f}`\n"\
f"*Current Rate:* `{msg['current_rate']:.8f}`\n" f"*Current Rate:* `{msg['current_rate']:.8f}`\n"
@ -255,6 +260,9 @@ class Telegram(RPCHandler):
msg['enter_tag'] = msg['enter_tag'] if "enter_tag" in msg.keys() else None msg['enter_tag'] = msg['enter_tag'] if "enter_tag" in msg.keys() else None
msg['emoji'] = self._get_sell_emoji(msg) msg['emoji'] = self._get_sell_emoji(msg)
msg['leverage_text'] = (f"*Leverage:* `{msg['leverage']:.1f}`\n"
if msg.get('leverage', None) and msg.get('leverage', 1.0) != 1.0
else "")
# Check if all sell properties are available. # Check if all sell properties are available.
# This might not be the case if the message origin is triggered by /forcesell # This might not be the case if the message origin is triggered by /forcesell
@ -270,15 +278,17 @@ class Telegram(RPCHandler):
is_fill = msg['type'] == RPCMessageType.SELL_FILL is_fill = msg['type'] == RPCMessageType.SELL_FILL
message = ( message = (
f"{msg['emoji']} *{msg['exchange']}:* " f"{msg['emoji']} *{msg['exchange']}:* "
f"{'Sold' if is_fill else 'Selling'} {msg['pair']} (#{msg['trade_id']})\n" f"{'Exited' if is_fill else 'Exiting'} {msg['pair']} (#{msg['trade_id']})\n"
f"*{'Profit' if is_fill else 'Unrealized Profit'}:* " f"*{'Profit' if is_fill else 'Unrealized Profit'}:* "
f"`{msg['profit_ratio']:.2%}{msg['profit_extra']}`\n" f"`{msg['profit_ratio']:.2%}{msg['profit_extra']}`\n"
f"*Enter Tag:* `{msg['enter_tag']}`\n" f"*Enter Tag:* `{msg['enter_tag']}`\n"
f"*Sell Reason:* `{msg['sell_reason']}`\n" f"*Exit Reason:* `{msg['sell_reason']}`\n"
f"*Duration:* `{msg['duration']} ({msg['duration_min']:.1f} min)`\n" f"*Duration:* `{msg['duration']} ({msg['duration_min']:.1f} min)`\n"
f"*Direction:* `{msg['direction']}`\n"
f"{msg['leverage_text']}"
f"*Amount:* `{msg['amount']:.8f}`\n" f"*Amount:* `{msg['amount']:.8f}`\n"
f"*Open Rate:* `{msg['open_rate']:.8f}`\n") f"*Open Rate:* `{msg['open_rate']:.8f}`\n"
)
if msg['type'] == RPCMessageType.SELL: if msg['type'] == RPCMessageType.SELL:
message += (f"*Current Rate:* `{msg['current_rate']:.8f}`\n" message += (f"*Current Rate:* `{msg['current_rate']:.8f}`\n"
f"*Close Rate:* `{msg['limit']:.8f}`") f"*Close Rate:* `{msg['limit']:.8f}`")
@ -289,16 +299,19 @@ class Telegram(RPCHandler):
return message return message
def compose_message(self, msg: Dict[str, Any], msg_type: RPCMessageType) -> str: def compose_message(self, msg: Dict[str, Any], msg_type: RPCMessageType) -> str:
if msg_type in [RPCMessageType.BUY, RPCMessageType.BUY_FILL]: if msg_type in [RPCMessageType.BUY, RPCMessageType.BUY_FILL, RPCMessageType.SHORT,
RPCMessageType.SHORT_FILL]:
message = self._format_buy_msg(msg) message = self._format_buy_msg(msg)
elif msg_type in [RPCMessageType.SELL, RPCMessageType.SELL_FILL]: elif msg_type in [RPCMessageType.SELL, RPCMessageType.SELL_FILL]:
message = self._format_sell_msg(msg) message = self._format_sell_msg(msg)
elif msg_type in (RPCMessageType.BUY_CANCEL, RPCMessageType.SELL_CANCEL): elif msg_type in (RPCMessageType.BUY_CANCEL, RPCMessageType.SHORT_CANCEL,
msg['message_side'] = 'buy' if msg_type == RPCMessageType.BUY_CANCEL else 'sell' RPCMessageType.SELL_CANCEL):
msg['message_side'] = 'enter' if msg_type in [RPCMessageType.BUY_CANCEL,
RPCMessageType.SHORT_CANCEL] else 'exit'
message = ("\N{WARNING SIGN} *{exchange}:* " message = ("\N{WARNING SIGN} *{exchange}:* "
"Cancelling open {message_side} Order for {pair} (#{trade_id}). " "Cancelling {message_side} Order for {pair} (#{trade_id}). "
"Reason: {reason}.".format(**msg)) "Reason: {reason}.".format(**msg))
elif msg_type == RPCMessageType.PROTECTION_TRIGGER: elif msg_type == RPCMessageType.PROTECTION_TRIGGER:
@ -398,6 +411,8 @@ class Telegram(RPCHandler):
lines = [ lines = [
"*Trade ID:* `{trade_id}` `(since {open_date_hum})`", "*Trade ID:* `{trade_id}` `(since {open_date_hum})`",
"*Current Pair:* {pair}", "*Current Pair:* {pair}",
"*Direction:* " + ("`Short`" if r.get('is_short') else "`Long`"),
"*Leverage:* `{leverage}`" if r.get('leverage') else "",
"*Amount:* `{amount} ({stake_amount} {base_currency})`", "*Amount:* `{amount} ({stake_amount} {base_currency})`",
"*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "", "*Enter Tag:* `{enter_tag}`" if r['enter_tag'] else "",
"*Open Rate:* `{open_rate:.8f}`", "*Open Rate:* `{open_rate:.8f}`",

View File

@ -44,11 +44,11 @@ class Webhook(RPCHandler):
""" Send a message to telegram channel """ """ Send a message to telegram channel """
try: try:
if msg['type'] == RPCMessageType.BUY: if msg['type'] in [RPCMessageType.BUY, RPCMessageType.SHORT]:
valuedict = self._config['webhook'].get('webhookbuy', None) valuedict = self._config['webhook'].get('webhookbuy', None)
elif msg['type'] == RPCMessageType.BUY_CANCEL: elif msg['type'] in [RPCMessageType.BUY_CANCEL, RPCMessageType.SHORT_CANCEL]:
valuedict = self._config['webhook'].get('webhookbuycancel', None) valuedict = self._config['webhook'].get('webhookbuycancel', None)
elif msg['type'] == RPCMessageType.BUY_FILL: elif msg['type'] in [RPCMessageType.BUY_FILL, RPCMessageType.SHORT_FILL]:
valuedict = self._config['webhook'].get('webhookbuyfill', None) valuedict = self._config['webhook'].get('webhookbuyfill', None)
elif msg['type'] == RPCMessageType.SELL: elif msg['type'] == RPCMessageType.SELL:
valuedict = self._config['webhook'].get('webhooksell', None) valuedict = self._config['webhook'].get('webhooksell', None)

View File

@ -202,7 +202,8 @@ def test_telegram_status(default_conf, update, mocker) -> None:
'stoploss_current_dist_ratio': -0.0002, 'stoploss_current_dist_ratio': -0.0002,
'stop_loss_ratio': -0.0001, 'stop_loss_ratio': -0.0001,
'open_order': '(limit buy rem=0.00000000)', 'open_order': '(limit buy rem=0.00000000)',
'is_open': True 'is_open': True,
'is_short': False
}]), }]),
) )
@ -316,7 +317,8 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ') fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ')
assert int(fields[0]) == 1 assert int(fields[0]) == 1
assert 'ETH/BTC' in fields[1] assert 'L' in fields[1]
assert 'ETH/BTC' in fields[2]
assert msg_mock.call_count == 1 assert msg_mock.call_count == 1
@ -946,11 +948,13 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'gain': 'profit', 'gain': 'profit',
'leverage': 1.0,
'limit': 1.173e-05, 'limit': 1.173e-05,
'amount': 91.07468123, 'amount': 91.07468123,
'order_type': 'limit', 'order_type': 'limit',
'open_rate': 1.098e-05, 'open_rate': 1.098e-05,
'current_rate': 1.173e-05, 'current_rate': 1.173e-05,
'direction': 'Long',
'profit_amount': 6.314e-05, 'profit_amount': 6.314e-05,
'profit_ratio': 0.0629778, 'profit_ratio': 0.0629778,
'stake_currency': 'BTC', 'stake_currency': 'BTC',
@ -1011,11 +1015,13 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'gain': 'loss', 'gain': 'loss',
'leverage': 1.0,
'limit': 1.043e-05, 'limit': 1.043e-05,
'amount': 91.07468123, 'amount': 91.07468123,
'order_type': 'limit', 'order_type': 'limit',
'open_rate': 1.098e-05, 'open_rate': 1.098e-05,
'current_rate': 1.043e-05, 'current_rate': 1.043e-05,
'direction': 'Long',
'profit_amount': -5.497e-05, 'profit_amount': -5.497e-05,
'profit_ratio': -0.05482878, 'profit_ratio': -0.05482878,
'stake_currency': 'BTC', 'stake_currency': 'BTC',
@ -1066,11 +1072,13 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'gain': 'loss', 'gain': 'loss',
'leverage': 1.0,
'limit': 1.099e-05, 'limit': 1.099e-05,
'amount': 91.07468123, 'amount': 91.07468123,
'order_type': 'limit', 'order_type': 'limit',
'open_rate': 1.098e-05, 'open_rate': 1.098e-05,
'current_rate': 1.099e-05, 'current_rate': 1.099e-05,
'direction': 'Long',
'profit_amount': -4.09e-06, 'profit_amount': -4.09e-06,
'profit_ratio': -0.00408133, 'profit_ratio': -0.00408133,
'stake_currency': 'BTC', 'stake_currency': 'BTC',
@ -1643,14 +1651,21 @@ def test_show_config_handle(default_conf, update, mocker) -> None:
assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0] assert '*Initial Stoploss:* `-0.1`' in msg_mock.call_args_list[0][0][0]
def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None: @pytest.mark.parametrize('message_type,enter,enter_signal,leverage', [
(RPCMessageType.BUY, 'Long', 'long_signal_01', None),
(RPCMessageType.BUY, 'Long', 'long_signal_01', 1.0),
(RPCMessageType.BUY, 'Long', 'long_signal_01', 5.0),
(RPCMessageType.SHORT, 'Short', 'short_signal_01', 2.0)])
def test_send_msg_buy_notification(default_conf, mocker, caplog, message_type,
enter, enter_signal, leverage) -> None:
msg = { msg = {
'type': RPCMessageType.BUY, 'type': message_type,
'trade_id': 1, 'trade_id': 1,
'enter_tag': 'buy_signal_01', 'enter_tag': enter_signal,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'leverage': leverage,
'limit': 1.099e-05, 'limit': 1.099e-05,
'order_type': 'limit', 'order_type': 'limit',
'stake_amount': 0.001, 'stake_amount': 0.001,
@ -1664,13 +1679,17 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, freqtradebot, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg(msg) telegram.send_msg(msg)
assert msg_mock.call_args[0][0] \ leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
== '\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n' \
'*Enter Tag:* `buy_signal_01`\n' \ assert msg_mock.call_args[0][0] == (
'*Amount:* `1333.33333333`\n' \ f'\N{LARGE BLUE CIRCLE} *Binance:* {enter} ETH/BTC (#1)\n'
'*Open Rate:* `0.00001099`\n' \ f'*Enter Tag:* `{enter_signal}`\n'
'*Current Rate:* `0.00001099`\n' \ '*Amount:* `1333.33333333`\n'
'*Total:* `(0.00100000 BTC, 12.345 USD)`' f'{leverage_text}'
'*Open Rate:* `0.00001099`\n'
'*Current Rate:* `0.00001099`\n'
'*Total:* `(0.00100000 BTC, 12.345 USD)`'
)
freqtradebot.config['telegram']['notification_settings'] = {'buy': 'off'} freqtradebot.config['telegram']['notification_settings'] = {'buy': 'off'}
caplog.clear() caplog.clear()
@ -1688,20 +1707,23 @@ def test_send_msg_buy_notification(default_conf, mocker, caplog) -> None:
msg_mock.call_args_list[0][1]['disable_notification'] is True msg_mock.call_args_list[0][1]['disable_notification'] is True
def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None: @pytest.mark.parametrize('message_type,enter_signal', [
(RPCMessageType.BUY_CANCEL, 'long_signal_01'),
(RPCMessageType.SHORT_CANCEL, 'short_signal_01')])
def test_send_msg_buy_cancel_notification(default_conf, mocker, message_type, enter_signal) -> None:
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg({ telegram.send_msg({
'type': RPCMessageType.BUY_CANCEL, 'type': message_type,
'enter_tag': 'buy_signal_01', 'enter_tag': enter_signal,
'trade_id': 1, 'trade_id': 1,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'reason': CANCEL_REASON['TIMEOUT'] 'reason': CANCEL_REASON['TIMEOUT']
}) })
assert (msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Binance:* ' assert (msg_mock.call_args[0][0] == '\N{WARNING SIGN} *Binance:* '
'Cancelling open buy Order for ETH/BTC (#1). ' 'Cancelling enter Order for ETH/BTC (#1). '
'Reason: cancelled due to timeout.') 'Reason: cancelled due to timeout.')
@ -1733,17 +1755,24 @@ def test_send_msg_protection_notification(default_conf, mocker, time_machine) ->
"*All pairs* will be locked until `2021-09-01 06:45:00`.") "*All pairs* will be locked until `2021-09-01 06:45:00`.")
def test_send_msg_buy_fill_notification(default_conf, mocker) -> None: @pytest.mark.parametrize('message_type,entered,enter_signal,leverage', [
(RPCMessageType.BUY_FILL, 'Longed', 'long_signal_01', 1.0),
(RPCMessageType.BUY_FILL, 'Longed', 'long_signal_02', 2.0),
(RPCMessageType.SHORT_FILL, 'Shorted', 'short_signal_01', 2.0),
])
def test_send_msg_buy_fill_notification(default_conf, mocker, message_type, entered,
enter_signal, leverage) -> None:
default_conf['telegram']['notification_settings']['buy_fill'] = 'on' default_conf['telegram']['notification_settings']['buy_fill'] = 'on'
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg({ telegram.send_msg({
'type': RPCMessageType.BUY_FILL, 'type': message_type,
'trade_id': 1, 'trade_id': 1,
'enter_tag': 'buy_signal_01', 'enter_tag': enter_signal,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'leverage': leverage,
'stake_amount': 0.001, 'stake_amount': 0.001,
# 'stake_amount_fiat': 0.0, # 'stake_amount_fiat': 0.0,
'stake_currency': 'BTC', 'stake_currency': 'BTC',
@ -1752,13 +1781,15 @@ def test_send_msg_buy_fill_notification(default_conf, mocker) -> None:
'amount': 1333.3333333333335, 'amount': 1333.3333333333335,
'open_date': arrow.utcnow().shift(hours=-1) 'open_date': arrow.utcnow().shift(hours=-1)
}) })
leverage_text = f'*Leverage:* `{leverage}`\n' if leverage != 1.0 else ''
assert msg_mock.call_args[0][0] \ assert msg_mock.call_args[0][0] == (
== '\N{CHECK MARK} *Binance:* Bought ETH/BTC (#1)\n' \ f'\N{CHECK MARK} *Binance:* {entered} ETH/BTC (#1)\n'
'*Enter Tag:* `buy_signal_01`\n' \ f'*Enter Tag:* `{enter_signal}`\n'
'*Amount:* `1333.33333333`\n' \ '*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00001099`\n' \ f"{leverage_text}"
'*Total:* `(0.00100000 BTC, 12.345 USD)`' '*Open Rate:* `0.00001099`\n'
'*Total:* `(0.00100000 BTC, 12.345 USD)`'
)
def test_send_msg_sell_notification(default_conf, mocker) -> None: def test_send_msg_sell_notification(default_conf, mocker) -> None:
@ -1772,6 +1803,8 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'trade_id': 1, 'trade_id': 1,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'KEY/ETH', 'pair': 'KEY/ETH',
'leverage': 1.0,
'direction': 'Long',
'gain': 'loss', 'gain': 'loss',
'limit': 3.201e-05, 'limit': 3.201e-05,
'amount': 1333.3333333333335, 'amount': 1333.3333333333335,
@ -1787,17 +1820,18 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'open_date': arrow.utcnow().shift(hours=-1), 'open_date': arrow.utcnow().shift(hours=-1),
'close_date': arrow.utcnow(), 'close_date': arrow.utcnow(),
}) })
assert msg_mock.call_args[0][0] \ assert msg_mock.call_args[0][0] == (
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n' '\N{WARNING SIGN} *Binance:* Exiting KEY/ETH (#1)\n'
'*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n' '*Unrealized Profit:* `-57.41% (loss: -0.05746268 ETH / -24.812 USD)`\n'
'*Enter Tag:* `buy_signal1`\n' '*Enter Tag:* `buy_signal1`\n'
'*Sell Reason:* `stop_loss`\n' '*Exit Reason:* `stop_loss`\n'
'*Duration:* `1:00:00 (60.0 min)`\n' '*Duration:* `1:00:00 (60.0 min)`\n'
'*Amount:* `1333.33333333`\n' '*Direction:* `Long`\n'
'*Open Rate:* `0.00007500`\n' '*Amount:* `1333.33333333`\n'
'*Current Rate:* `0.00003201`\n' '*Open Rate:* `0.00007500`\n'
'*Close Rate:* `0.00003201`' '*Current Rate:* `0.00003201`\n'
) '*Close Rate:* `0.00003201`'
)
msg_mock.reset_mock() msg_mock.reset_mock()
telegram.send_msg({ telegram.send_msg({
@ -1805,6 +1839,7 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'trade_id': 1, 'trade_id': 1,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'KEY/ETH', 'pair': 'KEY/ETH',
'direction': 'Long',
'gain': 'loss', 'gain': 'loss',
'limit': 3.201e-05, 'limit': 3.201e-05,
'amount': 1333.3333333333335, 'amount': 1333.3333333333335,
@ -1819,17 +1854,18 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30), 'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
'close_date': arrow.utcnow(), 'close_date': arrow.utcnow(),
}) })
assert msg_mock.call_args[0][0] \ assert msg_mock.call_args[0][0] == (
== ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n' '\N{WARNING SIGN} *Binance:* Exiting KEY/ETH (#1)\n'
'*Unrealized Profit:* `-57.41%`\n' '*Unrealized Profit:* `-57.41%`\n'
'*Enter Tag:* `buy_signal1`\n' '*Enter Tag:* `buy_signal1`\n'
'*Sell Reason:* `stop_loss`\n' '*Exit Reason:* `stop_loss`\n'
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n' '*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
'*Amount:* `1333.33333333`\n' '*Direction:* `Long`\n'
'*Open Rate:* `0.00007500`\n' '*Amount:* `1333.33333333`\n'
'*Current Rate:* `0.00003201`\n' '*Open Rate:* `0.00007500`\n'
'*Close Rate:* `0.00003201`' '*Current Rate:* `0.00003201`\n'
) '*Close Rate:* `0.00003201`'
)
# Reset singleton function to avoid random breaks # Reset singleton function to avoid random breaks
telegram._rpc._fiat_converter.convert_amount = old_convamount telegram._rpc._fiat_converter.convert_amount = old_convamount
@ -1847,9 +1883,9 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
'pair': 'KEY/ETH', 'pair': 'KEY/ETH',
'reason': 'Cancelled on exchange' 'reason': 'Cancelled on exchange'
}) })
assert msg_mock.call_args[0][0] \ assert msg_mock.call_args[0][0] == (
== ('\N{WARNING SIGN} *Binance:* Cancelling open sell Order for KEY/ETH (#1).' '\N{WARNING SIGN} *Binance:* Cancelling exit Order for KEY/ETH (#1).'
' Reason: Cancelled on exchange.') ' Reason: Cancelled on exchange.')
msg_mock.reset_mock() msg_mock.reset_mock()
telegram.send_msg({ telegram.send_msg({
@ -1859,14 +1895,19 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
'pair': 'KEY/ETH', 'pair': 'KEY/ETH',
'reason': 'timeout' 'reason': 'timeout'
}) })
assert msg_mock.call_args[0][0] \ assert msg_mock.call_args[0][0] == (
== ('\N{WARNING SIGN} *Binance:* Cancelling open sell Order for KEY/ETH (#1).' '\N{WARNING SIGN} *Binance:* Cancelling exit Order for KEY/ETH (#1). Reason: timeout.')
' Reason: timeout.')
# Reset singleton function to avoid random breaks # Reset singleton function to avoid random breaks
telegram._rpc._fiat_converter.convert_amount = old_convamount telegram._rpc._fiat_converter.convert_amount = old_convamount
def test_send_msg_sell_fill_notification(default_conf, mocker) -> None: @pytest.mark.parametrize('direction,enter_signal,leverage', [
('Long', 'long_signal_01', None),
('Long', 'long_signal_01', 1.0),
('Long', 'long_signal_01', 5.0),
('Short', 'short_signal_01', 2.0)])
def test_send_msg_sell_fill_notification(default_conf, mocker, direction,
enter_signal, leverage) -> None:
default_conf['telegram']['notification_settings']['sell_fill'] = 'on' default_conf['telegram']['notification_settings']['sell_fill'] = 'on'
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
@ -1876,6 +1917,8 @@ def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
'trade_id': 1, 'trade_id': 1,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'KEY/ETH', 'pair': 'KEY/ETH',
'leverage': leverage,
'direction': direction,
'gain': 'loss', 'gain': 'loss',
'limit': 3.201e-05, 'limit': 3.201e-05,
'amount': 1333.3333333333335, 'amount': 1333.3333333333335,
@ -1885,21 +1928,25 @@ def test_send_msg_sell_fill_notification(default_conf, mocker) -> None:
'profit_amount': -0.05746268, 'profit_amount': -0.05746268,
'profit_ratio': -0.57405275, 'profit_ratio': -0.57405275,
'stake_currency': 'ETH', 'stake_currency': 'ETH',
'enter_tag': 'buy_signal1', 'enter_tag': enter_signal,
'sell_reason': SellType.STOP_LOSS.value, 'sell_reason': SellType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30), 'open_date': arrow.utcnow().shift(days=-1, hours=-2, minutes=-30),
'close_date': arrow.utcnow(), 'close_date': arrow.utcnow(),
}) })
assert msg_mock.call_args[0][0] \
== ('\N{WARNING SIGN} *Binance:* Sold KEY/ETH (#1)\n' leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
'*Profit:* `-57.41%`\n' assert msg_mock.call_args[0][0] == (
'*Enter Tag:* `buy_signal1`\n' '\N{WARNING SIGN} *Binance:* Exited KEY/ETH (#1)\n'
'*Sell Reason:* `stop_loss`\n' '*Profit:* `-57.41%`\n'
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n' f'*Enter Tag:* `{enter_signal}`\n'
'*Amount:* `1333.33333333`\n' '*Exit Reason:* `stop_loss`\n'
'*Open Rate:* `0.00007500`\n' '*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
'*Close Rate:* `0.00003201`' f"*Direction:* `{direction}`\n"
) f"{leverage_text}"
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Close Rate:* `0.00003201`'
)
def test_send_msg_status_notification(default_conf, mocker) -> None: def test_send_msg_status_notification(default_conf, mocker) -> None:
@ -1938,16 +1985,22 @@ def test_send_msg_unknown_type(default_conf, mocker) -> None:
}) })
def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None: @pytest.mark.parametrize('message_type,enter,enter_signal,leverage', [
(RPCMessageType.BUY, 'Long', 'long_signal_01', None),
(RPCMessageType.BUY, 'Long', 'long_signal_01', 2.0),
(RPCMessageType.SHORT, 'Short', 'short_signal_01', 2.0)])
def test_send_msg_buy_notification_no_fiat(
default_conf, mocker, message_type, enter, enter_signal, leverage) -> None:
del default_conf['fiat_display_currency'] del default_conf['fiat_display_currency']
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
telegram.send_msg({ telegram.send_msg({
'type': RPCMessageType.BUY, 'type': message_type,
'enter_tag': 'buy_signal_01', 'enter_tag': enter_signal,
'trade_id': 1, 'trade_id': 1,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'leverage': leverage,
'limit': 1.099e-05, 'limit': 1.099e-05,
'order_type': 'limit', 'order_type': 'limit',
'stake_amount': 0.001, 'stake_amount': 0.001,
@ -1958,15 +2011,27 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
'amount': 1333.3333333333335, 'amount': 1333.3333333333335,
'open_date': arrow.utcnow().shift(hours=-1) 'open_date': arrow.utcnow().shift(hours=-1)
}) })
assert msg_mock.call_args[0][0] == ('\N{LARGE BLUE CIRCLE} *Binance:* Buying ETH/BTC (#1)\n'
'*Enter Tag:* `buy_signal_01`\n' leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
'*Amount:* `1333.33333333`\n' assert msg_mock.call_args[0][0] == (
'*Open Rate:* `0.00001099`\n' f'\N{LARGE BLUE CIRCLE} *Binance:* {enter} ETH/BTC (#1)\n'
'*Current Rate:* `0.00001099`\n' f'*Enter Tag:* `{enter_signal}`\n'
'*Total:* `(0.00100000 BTC)`') '*Amount:* `1333.33333333`\n'
f'{leverage_text}'
'*Open Rate:* `0.00001099`\n'
'*Current Rate:* `0.00001099`\n'
'*Total:* `(0.00100000 BTC)`'
)
def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None: @pytest.mark.parametrize('direction,enter_signal,leverage', [
('Long', 'long_signal_01', None),
('Long', 'long_signal_01', 1.0),
('Long', 'long_signal_01', 5.0),
('Short', 'short_signal_01', 2.0),
])
def test_send_msg_sell_notification_no_fiat(
default_conf, mocker, direction, enter_signal, leverage) -> None:
del default_conf['fiat_display_currency'] del default_conf['fiat_display_currency']
telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf) telegram, _, msg_mock = get_telegram_testobject(mocker, default_conf)
@ -1976,6 +2041,8 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'KEY/ETH', 'pair': 'KEY/ETH',
'gain': 'loss', 'gain': 'loss',
'leverage': leverage,
'direction': direction,
'limit': 3.201e-05, 'limit': 3.201e-05,
'amount': 1333.3333333333335, 'amount': 1333.3333333333335,
'order_type': 'limit', 'order_type': 'limit',
@ -1985,21 +2052,26 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
'profit_ratio': -0.57405275, 'profit_ratio': -0.57405275,
'stake_currency': 'ETH', 'stake_currency': 'ETH',
'fiat_currency': 'USD', 'fiat_currency': 'USD',
'enter_tag': 'buy_signal1', 'enter_tag': enter_signal,
'sell_reason': SellType.STOP_LOSS.value, 'sell_reason': SellType.STOP_LOSS.value,
'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3), 'open_date': arrow.utcnow().shift(hours=-2, minutes=-35, seconds=-3),
'close_date': arrow.utcnow(), 'close_date': arrow.utcnow(),
}) })
assert msg_mock.call_args[0][0] == ('\N{WARNING SIGN} *Binance:* Selling KEY/ETH (#1)\n'
'*Unrealized Profit:* `-57.41%`\n' leverage_text = f'*Leverage:* `{leverage}`\n' if leverage and leverage != 1.0 else ''
'*Enter Tag:* `buy_signal1`\n' assert msg_mock.call_args[0][0] == (
'*Sell Reason:* `stop_loss`\n' '\N{WARNING SIGN} *Binance:* Exiting KEY/ETH (#1)\n'
'*Duration:* `2:35:03 (155.1 min)`\n' '*Unrealized Profit:* `-57.41%`\n'
'*Amount:* `1333.33333333`\n' f'*Enter Tag:* `{enter_signal}`\n'
'*Open Rate:* `0.00007500`\n' '*Exit Reason:* `stop_loss`\n'
'*Current Rate:* `0.00003201`\n' '*Duration:* `2:35:03 (155.1 min)`\n'
'*Close Rate:* `0.00003201`' f'*Direction:* `{direction}`\n'
) f'{leverage_text}'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Current Rate:* `0.00003201`\n'
'*Close Rate:* `0.00003201`'
)
@pytest.mark.parametrize('msg,expected', [ @pytest.mark.parametrize('msg,expected', [

View File

@ -18,17 +18,23 @@ def get_webhook_dict() -> dict:
"webhookbuy": { "webhookbuy": {
"value1": "Buying {pair}", "value1": "Buying {pair}",
"value2": "limit {limit:8f}", "value2": "limit {limit:8f}",
"value3": "{stake_amount:8f} {stake_currency}" "value3": "{stake_amount:8f} {stake_currency}",
"value4": "leverage {leverage:.1f}",
"value5": "direction {direction}"
}, },
"webhookbuycancel": { "webhookbuycancel": {
"value1": "Cancelling Open Buy Order for {pair}", "value1": "Cancelling Open Buy Order for {pair}",
"value2": "limit {limit:8f}", "value2": "limit {limit:8f}",
"value3": "{stake_amount:8f} {stake_currency}" "value3": "{stake_amount:8f} {stake_currency}",
"value4": "leverage {leverage:.1f}",
"value5": "direction {direction}"
}, },
"webhookbuyfill": { "webhookbuyfill": {
"value1": "Buy Order for {pair} filled", "value1": "Buy Order for {pair} filled",
"value2": "at {open_rate:8f}", "value2": "at {open_rate:8f}",
"value3": "{stake_amount:8f} {stake_currency}" "value3": "{stake_amount:8f} {stake_currency}",
"value4": "leverage {leverage:.1f}",
"value5": "direction {direction}"
}, },
"webhooksell": { "webhooksell": {
"value1": "Selling {pair}", "value1": "Selling {pair}",
@ -71,6 +77,8 @@ def test_send_msg_webhook(default_conf, mocker):
'type': RPCMessageType.BUY, 'type': RPCMessageType.BUY,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'leverage': 1.0,
'direction': 'Long',
'limit': 0.005, 'limit': 0.005,
'stake_amount': 0.8, 'stake_amount': 0.8,
'stake_amount_fiat': 500, 'stake_amount_fiat': 500,
@ -85,6 +93,37 @@ def test_send_msg_webhook(default_conf, mocker):
default_conf["webhook"]["webhookbuy"]["value2"].format(**msg)) default_conf["webhook"]["webhookbuy"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] == assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuy"]["value3"].format(**msg)) default_conf["webhook"]["webhookbuy"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
default_conf["webhook"]["webhookbuy"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
default_conf["webhook"]["webhookbuy"]["value5"].format(**msg))
# Test short
msg_mock.reset_mock()
msg = {
'type': RPCMessageType.SHORT,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': 2.0,
'direction': 'Short',
'limit': 0.005,
'stake_amount': 0.8,
'stake_amount_fiat': 500,
'stake_currency': 'BTC',
'fiat_currency': 'EUR'
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
assert (msg_mock.call_args[0][0]["value1"] ==
default_conf["webhook"]["webhookbuy"]["value1"].format(**msg))
assert (msg_mock.call_args[0][0]["value2"] ==
default_conf["webhook"]["webhookbuy"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuy"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
default_conf["webhook"]["webhookbuy"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
default_conf["webhook"]["webhookbuy"]["value5"].format(**msg))
# Test buy cancel # Test buy cancel
msg_mock.reset_mock() msg_mock.reset_mock()
@ -92,6 +131,8 @@ def test_send_msg_webhook(default_conf, mocker):
'type': RPCMessageType.BUY_CANCEL, 'type': RPCMessageType.BUY_CANCEL,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'leverage': 1.0,
'direction': 'Long',
'limit': 0.005, 'limit': 0.005,
'stake_amount': 0.8, 'stake_amount': 0.8,
'stake_amount_fiat': 500, 'stake_amount_fiat': 500,
@ -106,6 +147,33 @@ def test_send_msg_webhook(default_conf, mocker):
default_conf["webhook"]["webhookbuycancel"]["value2"].format(**msg)) default_conf["webhook"]["webhookbuycancel"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] == assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuycancel"]["value3"].format(**msg)) default_conf["webhook"]["webhookbuycancel"]["value3"].format(**msg))
# Test short cancel
msg_mock.reset_mock()
msg = {
'type': RPCMessageType.SHORT_CANCEL,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': 2.0,
'direction': 'Short',
'limit': 0.005,
'stake_amount': 0.8,
'stake_amount_fiat': 500,
'stake_currency': 'BTC',
'fiat_currency': 'EUR'
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
assert (msg_mock.call_args[0][0]["value1"] ==
default_conf["webhook"]["webhookbuycancel"]["value1"].format(**msg))
assert (msg_mock.call_args[0][0]["value2"] ==
default_conf["webhook"]["webhookbuycancel"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuycancel"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
default_conf["webhook"]["webhookbuycancel"]["value5"].format(**msg))
# Test buy fill # Test buy fill
msg_mock.reset_mock() msg_mock.reset_mock()
@ -113,6 +181,8 @@ def test_send_msg_webhook(default_conf, mocker):
'type': RPCMessageType.BUY_FILL, 'type': RPCMessageType.BUY_FILL,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/BTC', 'pair': 'ETH/BTC',
'leverage': 1.0,
'direction': 'Long',
'open_rate': 0.005, 'open_rate': 0.005,
'stake_amount': 0.8, 'stake_amount': 0.8,
'stake_amount_fiat': 500, 'stake_amount_fiat': 500,
@ -127,8 +197,40 @@ def test_send_msg_webhook(default_conf, mocker):
default_conf["webhook"]["webhookbuyfill"]["value2"].format(**msg)) default_conf["webhook"]["webhookbuyfill"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] == assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuyfill"]["value3"].format(**msg)) default_conf["webhook"]["webhookbuyfill"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
default_conf["webhook"]["webhookbuycancel"]["value5"].format(**msg))
# Test short fill
msg_mock.reset_mock()
msg = {
'type': RPCMessageType.SHORT_FILL,
'exchange': 'Binance',
'pair': 'ETH/BTC',
'leverage': 2.0,
'direction': 'Short',
'open_rate': 0.005,
'stake_amount': 0.8,
'stake_amount_fiat': 500,
'stake_currency': 'BTC',
'fiat_currency': 'EUR'
}
webhook.send_msg(msg=msg)
assert msg_mock.call_count == 1
assert (msg_mock.call_args[0][0]["value1"] ==
default_conf["webhook"]["webhookbuyfill"]["value1"].format(**msg))
assert (msg_mock.call_args[0][0]["value2"] ==
default_conf["webhook"]["webhookbuyfill"]["value2"].format(**msg))
assert (msg_mock.call_args[0][0]["value3"] ==
default_conf["webhook"]["webhookbuyfill"]["value3"].format(**msg))
assert (msg_mock.call_args[0][0]["value4"] ==
default_conf["webhook"]["webhookbuycancel"]["value4"].format(**msg))
assert (msg_mock.call_args[0][0]["value5"] ==
default_conf["webhook"]["webhookbuycancel"]["value5"].format(**msg))
# Test sell # Test sell
msg_mock.reset_mock() msg_mock.reset_mock()
msg = { msg = {
'type': RPCMessageType.SELL, 'type': RPCMessageType.SELL,
'exchange': 'Binance', 'exchange': 'Binance',

View File

@ -2874,6 +2874,8 @@ def test_execute_trade_exit_up(default_conf_usdt, ticker_usdt, fee, ticker_usdt_
'amount': amt, 'amount': amt,
'order_type': 'limit', 'order_type': 'limit',
'buy_tag': None, 'buy_tag': None,
'direction': 'Short' if trade.is_short else 'Long',
'leverage': 1.0,
'enter_tag': None, 'enter_tag': None,
'open_rate': open_rate, 'open_rate': open_rate,
'current_rate': 2.01 if is_short else 2.3, 'current_rate': 2.01 if is_short else 2.3,
@ -2926,6 +2928,8 @@ def test_execute_trade_exit_down(default_conf_usdt, ticker_usdt, fee, ticker_usd
'trade_id': 1, 'trade_id': 1,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/USDT', 'pair': 'ETH/USDT',
'direction': 'Short' if trade.is_short else 'Long',
'leverage': 1.0,
'gain': 'loss', 'gain': 'loss',
'limit': 2.2 if is_short else 2.01, 'limit': 2.2 if is_short else 2.01,
'amount': 29.70297029 if is_short else 30.0, 'amount': 29.70297029 if is_short else 30.0,
@ -3004,6 +3008,8 @@ def test_execute_trade_exit_custom_exit_price(
'type': RPCMessageType.SELL, 'type': RPCMessageType.SELL,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/USDT', 'pair': 'ETH/USDT',
'direction': 'Short' if trade.is_short else 'Long',
'leverage': 1.0,
'gain': profit_or_loss, 'gain': profit_or_loss,
'limit': limit, 'limit': limit,
'amount': amount, 'amount': amount,
@ -3069,6 +3075,8 @@ def test_execute_trade_exit_down_stoploss_on_exchange_dry_run(
'trade_id': 1, 'trade_id': 1,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/USDT', 'pair': 'ETH/USDT',
'direction': 'Short' if trade.is_short else 'Long',
'leverage': 1.0,
'gain': 'loss', 'gain': 'loss',
'limit': 2.02 if is_short else 1.98, 'limit': 2.02 if is_short else 1.98,
'amount': 29.70297029 if is_short else 30.0, 'amount': 29.70297029 if is_short else 30.0,
@ -3182,6 +3190,7 @@ def test_execute_trade_exit_with_stoploss_on_exchange(
assert rpc_mock.call_count == 3 assert rpc_mock.call_count == 3
# TODO-lev: add short, RPC short, short fill
def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf_usdt, ticker_usdt, fee, def test_may_execute_trade_exit_after_stoploss_on_exchange_hit(default_conf_usdt, ticker_usdt, fee,
mocker) -> None: mocker) -> None:
default_conf_usdt['exchange']['name'] = 'binance' default_conf_usdt['exchange']['name'] = 'binance'
@ -3323,6 +3332,8 @@ def test_execute_trade_exit_market_order(
'trade_id': 1, 'trade_id': 1,
'exchange': 'Binance', 'exchange': 'Binance',
'pair': 'ETH/USDT', 'pair': 'ETH/USDT',
'direction': 'Short' if trade.is_short else 'Long',
'leverage': 1.0,
'gain': profit_or_loss, 'gain': profit_or_loss,
'limit': limit, 'limit': limit,
'amount': round(amount, 9), 'amount': round(amount, 9),