Merge pull request #6084 from aezomz/lev-telegram
Telegram and Webhook updates
This commit is contained in:
commit
4b79d435ad
@ -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
|
||||||
|
@ -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`
|
||||||
|
@ -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,
|
||||||
|
@ -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(
|
||||||
|
@ -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}`",
|
||||||
|
@ -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)
|
||||||
|
@ -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', [
|
||||||
|
@ -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',
|
||||||
|
@ -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),
|
||||||
|
Loading…
Reference in New Issue
Block a user