diff --git a/docs/configuration.md b/docs/configuration.md
index c0404d647..53e554709 100644
--- a/docs/configuration.md
+++ b/docs/configuration.md
@@ -92,7 +92,9 @@ Mandatory parameters are marked as **Required**, which means that they are requi
| `webhook.enabled` | Enable usage of Webhook notifications
***Datatype:*** *Boolean*
| `webhook.url` | URL for the webhook. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *String*
| `webhook.webhookbuy` | Payload to send on buy. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *String*
+| `webhook.webhookbuycancel` | Payload to send on buy order cancel. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *String*
| `webhook.webhooksell` | Payload to send on sell. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *String*
+| `webhook.webhooksellcancel` | Payload to send on sell order cancel. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *String*
| `webhook.webhookstatus` | Payload to send on status calls. Only required if `webhook.enabled` is `true`. See the [webhook documentation](webhook-config.md) for more details.
***Datatype:*** *String*
| `api_server.enabled` | Enable usage of API Server. See the [API Server documentation](rest-api.md) for more details.
***Datatype:*** *Boolean*
| `api_server.listen_ip_address` | Bind IP address. See the [API Server documentation](rest-api.md) for more details.
***Datatype:*** *IPv4*
diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md
index ed0c21a6e..ac9cea3d6 100644
--- a/docs/telegram-usage.md
+++ b/docs/telegram-usage.md
@@ -55,7 +55,7 @@ official commands. You can ask at any moment for help with `/help`.
| `/reload_conf` | | Reloads the configuration file
| `/show_config` | | Shows part of the current configuration with relevant settings to operation
| `/status` | | Lists all open trades
-| `/status table` | | List all open trades in a table format
+| `/status table` | | List all open trades in a table format. Pending buy orders are marked with an asterisk(*)
| `/count` | | Displays number of trades used and available
| `/profit` | | Display a summary of your profit/loss from close trades and some stats about your performance
| `/forcesell ` | | Instantly sells the given trade (Ignoring `minimum_roi`).
diff --git a/docs/webhook-config.md b/docs/webhook-config.md
index 9e0a34eae..878b18e8a 100644
--- a/docs/webhook-config.md
+++ b/docs/webhook-config.md
@@ -15,11 +15,21 @@ Sample configuration (tested using IFTTT).
"value2": "limit {limit:8f}",
"value3": "{stake_amount:8f} {stake_currency}"
},
+ "webhookbuycancel": {
+ "value1": "Cancelling Buy {pair}",
+ "value2": "limit {limit:8f}",
+ "value3": "{stake_amount:8f} {stake_currency}"
+ },
"webhooksell": {
"value1": "Selling {pair}",
"value2": "limit {limit:8f}",
"value3": "profit: {profit_amount:8f} {stake_currency}"
},
+ "webhooksellcancel": {
+ "value1": "Cancelling Sell {pair}",
+ "value2": "limit {limit:8f}",
+ "value3": "profit: {profit_amount:8f} {stake_currency}"
+ },
"webhookstatus": {
"value1": "Status: {status}",
"value2": "",
@@ -40,10 +50,29 @@ Possible parameters are:
* `exchange`
* `pair`
* `limit`
+* `amount`
* `stake_amount`
* `stake_currency`
* `fiat_currency`
* `order_type`
+* `open_rate`
+* `current_rate`
+
+### Webhookbuycancel
+
+The fields in `webhook.webhookbuycancel` are filled when the bot cancels a buy order. Parameters are filled using string.format.
+Possible parameters are:
+
+* `exchange`
+* `pair`
+* `limit`
+* `amount`
+* `stake_amount`
+* `stake_currency`
+* `fiat_currency`
+* `order_type`
+* `open_rate`
+* `current_rate`
### Webhooksell
@@ -57,6 +86,7 @@ Possible parameters are:
* `amount`
* `open_rate`
* `current_rate`
+* `close_rate`
* `profit_amount`
* `profit_percent`
* `stake_currency`
@@ -66,6 +96,27 @@ Possible parameters are:
* `open_date`
* `close_date`
+### Webhooksellcancel
+
+The fields in `webhook.webhooksellcancel` are filled when the bot cancels a sell order. Parameters are filled using string.format.
+Possible parameters are:
+
+* `exchange`
+* `pair`
+* `gain`
+* `limit`
+* `amount`
+* `open_rate`
+* `current_rate`
+* `close_rate`
+* `profit_amount`
+* `profit_percent`
+* `stake_currency`
+* `fiat_currency`
+* `sell_reason`
+* `order_type`
+* `open_date`
+
### Webhookstatus
The fields in `webhook.webhookstatus` are used for regular status messages (Started / Stopped / ...). Parameters are filled using string.format.
diff --git a/freqtrade/constants.py b/freqtrade/constants.py
index e68e741af..b34805e94 100644
--- a/freqtrade/constants.py
+++ b/freqtrade/constants.py
@@ -78,7 +78,7 @@ CONF_SCHEMA = {
'amend_last_stake_amount': {'type': 'boolean', 'default': False},
'last_stake_amount_min_ratio': {
'type': 'number', 'minimum': 0.0, 'maximum': 1.0, 'default': 0.5
- },
+ },
'fiat_display_currency': {'type': 'string', 'enum': SUPPORTED_FIAT},
'dry_run': {'type': 'boolean'},
'dry_run_wallet': {'type': 'number', 'default': DRY_RUN_WALLET},
@@ -191,7 +191,9 @@ CONF_SCHEMA = {
'properties': {
'enabled': {'type': 'boolean'},
'webhookbuy': {'type': 'object'},
+ 'webhookbuycancel': {'type': 'object'},
'webhooksell': {'type': 'object'},
+ 'webhooksellcancel': {'type': 'object'},
'webhookstatus': {'type': 'object'},
},
},
diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py
index e51b3d550..2f57ca41b 100644
--- a/freqtrade/freqtradebot.py
+++ b/freqtrade/freqtradebot.py
@@ -234,7 +234,7 @@ class FreqtradeBot:
return trades_created
- def get_buy_rate(self, pair: str, tick: Dict = None) -> float:
+ def get_buy_rate(self, pair: str, refresh: bool = False, tick: Dict = None) -> float:
"""
Calculates bid target between current ask price and last price
:return: float: Price
@@ -253,7 +253,7 @@ class FreqtradeBot:
else:
if not tick:
logger.info('Using Last Ask / Last Price')
- ticker = self.exchange.fetch_ticker(pair)
+ ticker = self.exchange.fetch_ticker(pair, refresh)
else:
ticker = tick
if ticker['ask'] < ticker['last']:
@@ -404,7 +404,7 @@ class FreqtradeBot:
stake_amount = self.get_trade_stake_amount(pair)
if not stake_amount:
- logger.debug("Stake amount is 0, ignoring possible trade for {pair}.")
+ logger.debug(f"Stake amount is 0, ignoring possible trade for {pair}.")
return False
logger.info(f"Buy signal found: about create a new trade with stake_amount: "
@@ -414,10 +414,12 @@ class FreqtradeBot:
if ((bid_check_dom.get('enabled', False)) and
(bid_check_dom.get('bids_to_ask_delta', 0) > 0)):
if self._check_depth_of_market_buy(pair, bid_check_dom):
+ logger.info(f'Executed Buy for {pair}.')
return self.execute_buy(pair, stake_amount)
else:
return False
+ logger.info(f'Executed Buy for {pair}')
return self.execute_buy(pair, stake_amount)
else:
return False
@@ -450,7 +452,7 @@ class FreqtradeBot:
"""
Executes a limit buy for the given pair
:param pair: pair for which we want to create a LIMIT_BUY
- :return: None
+ :return: bool
"""
time_in_force = self.strategy.order_time_in_force['buy']
@@ -458,7 +460,7 @@ class FreqtradeBot:
buy_limit_requested = price
else:
# Calculate price
- buy_limit_requested = self.get_buy_rate(pair)
+ buy_limit_requested = self.get_buy_rate(pair, True)
min_stake_amount = self._get_min_pair_stake_amount(pair, buy_limit_requested)
if min_stake_amount is not None and min_stake_amount > stake_amount:
@@ -547,11 +549,37 @@ class FreqtradeBot:
'type': RPCMessageType.BUY_NOTIFICATION,
'exchange': self.exchange.name.capitalize(),
'pair': trade.pair,
- 'limit': trade.open_rate,
+ 'limit': trade.open_rate_requested,
'order_type': order_type,
'stake_amount': trade.stake_amount,
'stake_currency': self.config['stake_currency'],
'fiat_currency': self.config.get('fiat_display_currency', None),
+ 'amount': trade.amount,
+ 'open_date': trade.open_date or datetime.utcnow(),
+ 'current_rate': trade.open_rate_requested,
+ }
+
+ # Send the message
+ self.rpc.send_msg(msg)
+
+ def _notify_buy_cancel(self, trade: Trade, order_type: str) -> None:
+ """
+ Sends rpc notification when a buy cancel occured.
+ """
+ current_rate = self.get_buy_rate(trade.pair, False)
+
+ msg = {
+ 'type': RPCMessageType.BUY_CANCEL_NOTIFICATION,
+ 'exchange': self.exchange.name.capitalize(),
+ 'pair': trade.pair,
+ 'limit': trade.open_rate_requested,
+ 'order_type': order_type,
+ 'stake_amount': trade.stake_amount,
+ 'stake_currency': self.config['stake_currency'],
+ 'fiat_currency': self.config.get('fiat_display_currency', None),
+ 'amount': trade.amount,
+ 'open_date': trade.open_date,
+ 'current_rate': current_rate,
}
# Send the message
@@ -587,7 +615,7 @@ class FreqtradeBot:
return trades_closed
- def get_sell_rate(self, pair: str, refresh: bool) -> float:
+ def get_sell_rate(self, pair: str, refresh: bool = False) -> float:
"""
Get sell rate - either using get-ticker bid or first bid based on orderbook
The orderbook portion is only used for rpc messaging, which would otherwise fail
@@ -751,7 +779,7 @@ class FreqtradeBot:
update_beat = self.strategy.order_types.get('stoploss_on_exchange_interval', 60)
if (datetime.utcnow() - trade.stoploss_last_update).total_seconds() >= update_beat:
# cancelling the current stoploss on exchange first
- logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s})'
+ logger.info('Trailing stoploss: cancelling current stoploss on exchange (id:{%s}) '
'in order to add another one ...', order['id'])
try:
self.exchange.cancel_order(order['id'], trade.pair)
@@ -777,7 +805,7 @@ class FreqtradeBot:
if should_sell.sell_flag:
self.execute_sell(trade, sell_rate, should_sell.sell_type)
- logger.info('executed sell, reason: %s', should_sell.sell_type)
+ logger.info(f'Executed Sell for {trade.pair}. Reason: {should_sell.sell_type}')
return True
return False
@@ -820,41 +848,41 @@ class FreqtradeBot:
if ((order['side'] == 'buy' and order['status'] == 'canceled')
or (self._check_timed_out('buy', order))):
-
self.handle_timedout_limit_buy(trade, order)
self.wallets.update()
+ order_type = self.strategy.order_types['buy']
+ self._notify_buy_cancel(trade, order_type)
elif ((order['side'] == 'sell' and order['status'] == 'canceled')
or (self._check_timed_out('sell', order))):
self.handle_timedout_limit_sell(trade, order)
self.wallets.update()
+ order_type = self.strategy.order_types['sell']
+ self._notify_sell_cancel(trade, order_type)
- def handle_buy_order_full_cancel(self, trade: Trade, reason: str) -> None:
- """Close trade in database and send message"""
+ def delete_trade(self, trade: Trade) -> None:
+ """Delete trade in database"""
Trade.session.delete(trade)
Trade.session.flush()
- logger.info('Buy order %s for %s.', reason, trade)
- self.rpc.send_msg({
- 'type': RPCMessageType.STATUS_NOTIFICATION,
- 'status': f'Unfilled buy order for {trade.pair} {reason}'
- })
def handle_timedout_limit_buy(self, trade: Trade, order: Dict) -> bool:
"""
Buy timeout - cancel order
:return: True if order was fully cancelled
"""
- reason = "cancelled due to timeout"
if order['status'] != 'canceled':
+ reason = "cancelled due to timeout"
corder = self.exchange.cancel_order(trade.open_order_id, trade.pair)
+ logger.info('Buy order %s for %s.', reason, trade)
else:
# Order was cancelled already, so we can reuse the existing dict
corder = order
- reason = "canceled on Exchange"
+ reason = "cancelled on exchange"
+ logger.info('Buy order %s for %s.', reason, trade)
if corder.get('remaining', order['remaining']) == order['amount']:
# if trade is not partially completed, just delete the trade
- self.handle_buy_order_full_cancel(trade, reason)
+ self.delete_trade(trade)
return True
# if trade is partially complete, edit the stake details for the trade
@@ -878,10 +906,6 @@ class FreqtradeBot:
trade.open_order_id = None
logger.info('Partial buy order timeout for %s.', trade)
- self.rpc.send_msg({
- 'type': RPCMessageType.STATUS_NOTIFICATION,
- 'status': f'Remaining buy order for {trade.pair} cancelled due to timeout'
- })
return False
def handle_timedout_limit_sell(self, trade: Trade, order: Dict) -> bool:
@@ -889,24 +913,22 @@ class FreqtradeBot:
Sell timeout - cancel order and update trade
:return: True if order was fully cancelled
"""
+ # if trade is not partially completed, just cancel the trade
if order['remaining'] == order['amount']:
- # if trade is not partially completed, just cancel the trade
if order["status"] != "canceled":
- reason = "due to timeout"
+ reason = "cancelled due to timeout"
+ # if trade is not partially completed, just delete the trade
self.exchange.cancel_order(trade.open_order_id, trade.pair)
- logger.info('Sell order timeout for %s.', trade)
+ logger.info('Sell order %s for %s.', reason, trade)
else:
- reason = "on exchange"
- logger.info('Sell order canceled on exchange for %s.', trade)
+ reason = "cancelled on exchange"
+ logger.info('Sell order %s for %s.', reason, trade)
+
trade.close_rate = None
trade.close_profit = None
trade.close_date = None
trade.is_open = True
trade.open_order_id = None
- self.rpc.send_msg({
- 'type': RPCMessageType.STATUS_NOTIFICATION,
- 'status': f'Unfilled sell order for {trade.pair} cancelled {reason}'
- })
return True
@@ -938,13 +960,13 @@ class FreqtradeBot:
raise DependencyException(
f"Not enough amount to sell. Trade-amount: {amount}, Wallet: {wallet_amount}")
- def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> None:
+ def execute_sell(self, trade: Trade, limit: float, sell_reason: SellType) -> bool:
"""
Executes a limit sell for the given trade and limit
:param trade: Trade instance
:param limit: limit rate for the sell order
:param sellreason: Reason the sell was triggered
- :return: None
+ :return: bool
"""
sell_type = 'sell'
if sell_reason in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS):
@@ -965,7 +987,7 @@ class FreqtradeBot:
order_type = self.strategy.order_types[sell_type]
if sell_reason == SellType.EMERGENCY_SELL:
- # Emergencysells (default to market!)
+ # Emergency sells (default to market!)
order_type = self.strategy.order_types.get("emergencysell", "market")
amount = self._safe_sell_amount(trade.pair, trade.amount)
@@ -990,6 +1012,8 @@ class FreqtradeBot:
self._notify_sell(trade, order_type)
+ return True
+
def _notify_sell(self, trade: Trade, order_type: str) -> None:
"""
Sends rpc notification when a sell occured.
@@ -1006,7 +1030,7 @@ class FreqtradeBot:
'exchange': trade.exchange.capitalize(),
'pair': trade.pair,
'gain': gain,
- 'limit': trade.close_rate_requested,
+ 'limit': profit_rate,
'order_type': order_type,
'amount': trade.amount,
'open_rate': trade.open_rate,
@@ -1017,6 +1041,45 @@ class FreqtradeBot:
'open_date': trade.open_date,
'close_date': trade.close_date or datetime.utcnow(),
'stake_currency': self.config['stake_currency'],
+ 'fiat_currency': self.config.get('fiat_display_currency', None),
+ }
+
+ if 'fiat_display_currency' in self.config:
+ msg.update({
+ 'fiat_currency': self.config['fiat_display_currency'],
+ })
+
+ # Send the message
+ self.rpc.send_msg(msg)
+
+ def _notify_sell_cancel(self, trade: Trade, order_type: str) -> None:
+ """
+ Sends rpc notification when a sell cancel occured.
+ """
+ profit_rate = trade.close_rate if trade.close_rate else trade.close_rate_requested
+ profit_trade = trade.calc_profit(rate=profit_rate)
+ # Use cached ticker here - it was updated seconds ago.
+ current_rate = self.get_sell_rate(trade.pair, False)
+ profit_percent = trade.calc_profit_ratio(profit_rate)
+ gain = "profit" if profit_percent > 0 else "loss"
+
+ msg = {
+ 'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
+ 'exchange': trade.exchange.capitalize(),
+ 'pair': trade.pair,
+ 'gain': gain,
+ 'limit': profit_rate,
+ 'order_type': order_type,
+ 'amount': trade.amount,
+ 'open_rate': trade.open_rate,
+ 'current_rate': current_rate,
+ 'profit_amount': profit_trade,
+ 'profit_percent': profit_percent,
+ 'sell_reason': trade.sell_reason,
+ 'open_date': trade.open_date,
+ 'close_date': trade.close_date,
+ 'stake_currency': self.config['stake_currency'],
+ 'fiat_currency': self.config.get('fiat_display_currency', None),
}
if 'fiat_display_currency' in self.config:
diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py
index 7f5cfc101..c1efea79e 100644
--- a/freqtrade/rpc/rpc.py
+++ b/freqtrade/rpc/rpc.py
@@ -26,7 +26,9 @@ class RPCMessageType(Enum):
WARNING_NOTIFICATION = 'warning'
CUSTOM_NOTIFICATION = 'custom'
BUY_NOTIFICATION = 'buy'
+ BUY_CANCEL_NOTIFICATION = 'buy_cancel'
SELL_NOTIFICATION = 'sell'
+ SELL_CANCEL_NOTIFICATION = 'sell_cancel'
def __repr__(self):
return self.value
@@ -39,6 +41,7 @@ class RPCException(Exception):
raise RPCException('*Status:* `no active trade`')
"""
+
def __init__(self, message: str) -> None:
super().__init__(self)
self.message = message
@@ -157,15 +160,16 @@ class RPC:
profit_str = f'{trade_perc:.2f}%'
if self._fiat_converter:
fiat_profit = self._fiat_converter.convert_amount(
- trade_profit,
- stake_currency,
- fiat_display_currency
- )
+ trade_profit,
+ stake_currency,
+ fiat_display_currency
+ )
if fiat_profit and not isnan(fiat_profit):
profit_str += f" ({fiat_profit:.2f})"
trades_list.append([
trade.id,
- trade.pair,
+ trade.pair + ['', '*'][trade.open_order_id is not None
+ and trade.close_rate_requested is None],
shorten_date(arrow.get(trade.open_date).humanize(only_distance=True)),
profit_str
])
diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py
index e9ecdcff6..0dd7a8ffd 100644
--- a/freqtrade/rpc/telegram.py
+++ b/freqtrade/rpc/telegram.py
@@ -134,13 +134,18 @@ class Telegram(RPC):
msg['stake_amount_fiat'] = 0
message = ("*{exchange}:* Buying {pair}\n"
- "at rate `{limit:.8f}\n"
- "({stake_amount:.6f} {stake_currency}").format(**msg)
+ "*Amount:* `{amount:.8f}`\n"
+ "*Open Rate:* `{limit:.8f}`\n"
+ "*Current Rate:* `{current_rate:.8f}`\n"
+ "*Total:* `({stake_amount:.6f} {stake_currency}").format(**msg)
if msg.get('fiat_currency', None):
- message += ",{stake_amount_fiat:.3f} {fiat_currency}".format(**msg)
+ message += ", {stake_amount_fiat:.3f} {fiat_currency}".format(**msg)
message += ")`"
+ elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION:
+ message = "*{exchange}:* Cancelling Buy {pair}".format(**msg)
+
elif msg['type'] == RPCMessageType.SELL_NOTIFICATION:
msg['amount'] = round(msg['amount'], 8)
msg['profit_percent'] = round(msg['profit_percent'] * 100, 2)
@@ -149,10 +154,10 @@ class Telegram(RPC):
msg['duration_min'] = msg['duration'].total_seconds() / 60
message = ("*{exchange}:* Selling {pair}\n"
- "*Rate:* `{limit:.8f}`\n"
"*Amount:* `{amount:.8f}`\n"
"*Open Rate:* `{open_rate:.8f}`\n"
"*Current Rate:* `{current_rate:.8f}`\n"
+ "*Close Rate:* `{limit:.8f}`\n"
"*Sell Reason:* `{sell_reason}`\n"
"*Duration:* `{duration} ({duration_min:.1f} min)`\n"
"*Profit:* `{profit_percent:.2f}%`").format(**msg)
@@ -163,8 +168,11 @@ class Telegram(RPC):
and self._fiat_converter):
msg['profit_fiat'] = self._fiat_converter.convert_amount(
msg['profit_amount'], msg['stake_currency'], msg['fiat_currency'])
- message += ('` ({gain}: {profit_amount:.8f} {stake_currency}`'
- '` / {profit_fiat:.3f} {fiat_currency})`').format(**msg)
+ message += (' `({gain}: {profit_amount:.8f} {stake_currency}'
+ ' / {profit_fiat:.3f} {fiat_currency})`').format(**msg)
+
+ elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION:
+ message = "*{exchange}:* Cancelling Sell {pair}".format(**msg)
elif msg['type'] == RPCMessageType.STATUS_NOTIFICATION:
message = '*Status:* `{status}`'.format(**msg)
diff --git a/freqtrade/rpc/webhook.py b/freqtrade/rpc/webhook.py
index 37ca466de..1309663d4 100644
--- a/freqtrade/rpc/webhook.py
+++ b/freqtrade/rpc/webhook.py
@@ -41,8 +41,12 @@ class Webhook(RPC):
if msg['type'] == RPCMessageType.BUY_NOTIFICATION:
valuedict = self._config['webhook'].get('webhookbuy', None)
+ elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION:
+ valuedict = self._config['webhook'].get('webhookbuycancel', None)
elif msg['type'] == RPCMessageType.SELL_NOTIFICATION:
valuedict = self._config['webhook'].get('webhooksell', None)
+ elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION:
+ valuedict = self._config['webhook'].get('webhooksellcancel', None)
elif msg['type'] in(RPCMessageType.STATUS_NOTIFICATION,
RPCMessageType.CUSTOM_NOTIFICATION,
RPCMessageType.WARNING_NOTIFICATION):
diff --git a/tests/rpc/test_rpc.py b/tests/rpc/test_rpc.py
index 36fce1797..a35bfa0d6 100644
--- a/tests/rpc/test_rpc.py
+++ b/tests/rpc/test_rpc.py
@@ -122,7 +122,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
assert "Since" in headers
assert "Pair" in headers
assert 'instantly' == result[0][2]
- assert 'ETH/BTC' == result[0][1]
+ assert 'ETH/BTC' in result[0][1]
assert '-0.59%' == result[0][3]
# Test with fiatconvert
@@ -131,7 +131,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
assert "Since" in headers
assert "Pair" in headers
assert 'instantly' == result[0][2]
- assert 'ETH/BTC' == result[0][1]
+ assert 'ETH/BTC' in result[0][1]
assert '-0.59% (-0.09)' == result[0][3]
mocker.patch('freqtrade.exchange.Exchange.fetch_ticker',
@@ -140,7 +140,7 @@ def test_rpc_status_table(default_conf, ticker, fee, mocker) -> None:
rpc._freqtrade.exchange._cached_ticker = {}
result, headers = rpc._rpc_status_table(default_conf['stake_currency'], 'USD')
assert 'instantly' == result[0][2]
- assert 'ETH/BTC' == result[0][1]
+ assert 'ETH/BTC' in result[0][1]
assert 'nan%' == result[0][3]
diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py
index ffc29ee12..ae9c0c4dc 100644
--- a/tests/rpc/test_rpc_telegram.py
+++ b/tests/rpc/test_rpc_telegram.py
@@ -284,7 +284,7 @@ def test_status_table_handle(default_conf, update, ticker, fee, mocker) -> None:
fields = re.sub('[ ]+', ' ', line[2].strip()).split(' ')
assert int(fields[0]) == 1
- assert fields[1] == 'ETH/BTC'
+ assert 'ETH/BTC' in fields[1]
assert msg_mock.call_count == 1
@@ -1200,12 +1200,35 @@ def test_send_msg_buy_notification(default_conf, mocker) -> None:
'stake_amount': 0.001,
'stake_amount_fiat': 0.0,
'stake_currency': 'BTC',
- 'fiat_currency': 'USD'
+ 'fiat_currency': 'USD',
+ 'current_rate': 1.099e-05,
+ 'amount': 1333.3333333333335,
+ 'open_date': arrow.utcnow().shift(hours=-1)
})
assert msg_mock.call_args[0][0] \
== '*Bittrex:* Buying ETH/BTC\n' \
- 'at rate `0.00001099\n' \
- '(0.001000 BTC,0.000 USD)`'
+ '*Amount:* `1333.33333333`\n' \
+ '*Open Rate:* `0.00001099`\n' \
+ '*Current Rate:* `0.00001099`\n' \
+ '*Total:* `(0.001000 BTC, 0.000 USD)`'
+
+
+def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None:
+ msg_mock = MagicMock()
+ mocker.patch.multiple(
+ 'freqtrade.rpc.telegram.Telegram',
+ _init=MagicMock(),
+ _send_msg=msg_mock
+ )
+ freqtradebot = get_patched_freqtradebot(mocker, default_conf)
+ telegram = Telegram(freqtradebot)
+ telegram.send_msg({
+ 'type': RPCMessageType.BUY_CANCEL_NOTIFICATION,
+ 'exchange': 'Bittrex',
+ 'pair': 'ETH/BTC',
+ })
+ assert msg_mock.call_args[0][0] \
+ == ('*Bittrex:* Cancelling Buy ETH/BTC')
def test_send_msg_sell_notification(default_conf, mocker) -> None:
@@ -1239,13 +1262,13 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
})
assert msg_mock.call_args[0][0] \
== ('*Binance:* Selling KEY/ETH\n'
- '*Rate:* `0.00003201`\n'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Current Rate:* `0.00003201`\n'
+ '*Close Rate:* `0.00003201`\n'
'*Sell Reason:* `stop_loss`\n'
'*Duration:* `1:00:00 (60.0 min)`\n'
- '*Profit:* `-57.41%`` (loss: -0.05746268 ETH`` / -24.812 USD)`')
+ '*Profit:* `-57.41%` `(loss: -0.05746268 ETH / -24.812 USD)`')
msg_mock.reset_mock()
telegram.send_msg({
@@ -1267,10 +1290,10 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
})
assert msg_mock.call_args[0][0] \
== ('*Binance:* Selling KEY/ETH\n'
- '*Rate:* `0.00003201`\n'
'*Amount:* `1333.33333333`\n'
'*Open Rate:* `0.00007500`\n'
'*Current Rate:* `0.00003201`\n'
+ '*Close Rate:* `0.00003201`\n'
'*Sell Reason:* `stop_loss`\n'
'*Duration:* `1 day, 2:30:00 (1590.0 min)`\n'
'*Profit:* `-57.41%`')
@@ -1278,6 +1301,37 @@ def test_send_msg_sell_notification(default_conf, mocker) -> None:
telegram._fiat_converter.convert_amount = old_convamount
+def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None:
+ msg_mock = MagicMock()
+ mocker.patch.multiple(
+ 'freqtrade.rpc.telegram.Telegram',
+ _init=MagicMock(),
+ _send_msg=msg_mock
+ )
+ freqtradebot = get_patched_freqtradebot(mocker, default_conf)
+ telegram = Telegram(freqtradebot)
+ old_convamount = telegram._fiat_converter.convert_amount
+ telegram._fiat_converter.convert_amount = lambda a, b, c: -24.812
+ telegram.send_msg({
+ 'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
+ 'exchange': 'Binance',
+ 'pair': 'KEY/ETH',
+ })
+ assert msg_mock.call_args[0][0] \
+ == ('*Binance:* Cancelling Sell KEY/ETH')
+
+ msg_mock.reset_mock()
+ telegram.send_msg({
+ 'type': RPCMessageType.SELL_CANCEL_NOTIFICATION,
+ 'exchange': 'Binance',
+ 'pair': 'KEY/ETH',
+ })
+ assert msg_mock.call_args[0][0] \
+ == ('*Binance:* Cancelling Sell KEY/ETH')
+ # Reset singleton function to avoid random breaks
+ telegram._fiat_converter.convert_amount = old_convamount
+
+
def test_send_msg_status_notification(default_conf, mocker) -> None:
msg_mock = MagicMock()
mocker.patch.multiple(
@@ -1360,12 +1414,17 @@ def test_send_msg_buy_notification_no_fiat(default_conf, mocker) -> None:
'stake_amount': 0.001,
'stake_amount_fiat': 0.0,
'stake_currency': 'BTC',
- 'fiat_currency': None
+ 'fiat_currency': None,
+ 'current_rate': 1.099e-05,
+ 'amount': 1333.3333333333335,
+ 'open_date': arrow.utcnow().shift(hours=-1)
})
assert msg_mock.call_args[0][0] \
== '*Bittrex:* Buying ETH/BTC\n' \
- 'at rate `0.00001099\n' \
- '(0.001000 BTC)`'
+ '*Amount:* `1333.33333333`\n' \
+ '*Open Rate:* `0.00001099`\n' \
+ '*Current Rate:* `0.00001099`\n' \
+ '*Total:* `(0.001000 BTC)`'
def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
@@ -1398,10 +1457,10 @@ def test_send_msg_sell_notification_no_fiat(default_conf, mocker) -> None:
})
assert msg_mock.call_args[0][0] \
== '*Binance:* Selling KEY/ETH\n' \
- '*Rate:* `0.00003201`\n' \
'*Amount:* `1333.33333333`\n' \
'*Open Rate:* `0.00007500`\n' \
'*Current Rate:* `0.00003201`\n' \
+ '*Close Rate:* `0.00003201`\n' \
'*Sell Reason:* `stop_loss`\n' \
'*Duration:* `2:35:03 (155.1 min)`\n' \
'*Profit:* `-57.41%`'
diff --git a/tests/rpc/test_rpc_webhook.py b/tests/rpc/test_rpc_webhook.py
index c066aa8e7..3b9ce3f0d 100644
--- a/tests/rpc/test_rpc_webhook.py
+++ b/tests/rpc/test_rpc_webhook.py
@@ -13,24 +13,34 @@ from tests.conftest import get_patched_freqtradebot, log_has
def get_webhook_dict() -> dict:
return {
- "enabled": True,
- "url": "https://maker.ifttt.com/trigger/freqtrade_test/with/key/c764udvJ5jfSlswVRukZZ2/",
- "webhookbuy": {
- "value1": "Buying {pair}",
- "value2": "limit {limit:8f}",
- "value3": "{stake_amount:8f} {stake_currency}"
- },
- "webhooksell": {
- "value1": "Selling {pair}",
- "value2": "limit {limit:8f}",
- "value3": "profit: {profit_amount:8f} {stake_currency}"
- },
- "webhookstatus": {
- "value1": "Status: {status}",
- "value2": "",
- "value3": ""
- }
- }
+ "enabled": True,
+ "url": "https://maker.ifttt.com/trigger/freqtrade_test/with/key/c764udvJ5jfSlswVRukZZ2/",
+ "webhookbuy": {
+ "value1": "Buying {pair}",
+ "value2": "limit {limit:8f}",
+ "value3": "{stake_amount:8f} {stake_currency}"
+ },
+ "webhookbuycancel": {
+ "value1": "Cancelling Buy {pair}",
+ "value2": "limit {limit:8f}",
+ "value3": "{stake_amount:8f} {stake_currency}"
+ },
+ "webhooksell": {
+ "value1": "Selling {pair}",
+ "value2": "limit {limit:8f}",
+ "value3": "profit: {profit_amount:8f} {stake_currency}"
+ },
+ "webhooksellcancel": {
+ "value1": "Cancelling Sell {pair}",
+ "value2": "limit {limit:8f}",
+ "value3": "profit: {profit_amount:8f} {stake_currency}"
+ },
+ "webhookstatus": {
+ "value1": "Status: {status}",
+ "value2": "",
+ "value3": ""
+ }
+ }
def test__init__(mocker, default_conf):
diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py
index f334e4eb0..429d3599d 100644
--- a/tests/test_freqtradebot.py
+++ b/tests/test_freqtradebot.py
@@ -300,7 +300,7 @@ def test_edge_overrides_stoploss(limit_buy_order, fee, caplog, mocker, edge_conf
# stoploss shoud be hit
assert freqtrade.handle_trade(trade) is True
- assert log_has('executed sell, reason: SellType.STOP_LOSS', caplog)
+ assert log_has('Executed Sell for NEO/BTC. Reason: SellType.STOP_LOSS', caplog)
assert trade.sell_reason == SellType.STOP_LOSS.value
@@ -1964,7 +1964,7 @@ def test_check_handle_cancelled_buy(default_conf, ticker, limit_buy_order_old, o
trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all()
nb_trades = len(trades)
assert nb_trades == 0
- assert log_has_re("Buy order canceled on Exchange for Trade.*", caplog)
+ assert log_has_re("Buy order cancelled on exchange for Trade.*", caplog)
def test_check_handle_timedout_buy_exception(default_conf, ticker, limit_buy_order_old, open_trade,
@@ -2045,7 +2045,7 @@ def test_check_handle_cancelled_sell(default_conf, ticker, limit_sell_order_old,
assert cancel_order_mock.call_count == 0
assert rpc_mock.call_count == 1
assert open_trade.is_open is True
- assert log_has_re("Sell order canceled on exchange for Trade.*", caplog)
+ assert log_has_re("Sell order cancelled on exchange for Trade.*", caplog)
def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old_partial,