From f3b1161640507d91e7694f03e80095304bfdaf0a Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Sat, 8 Feb 2020 21:02:52 +0100 Subject: [PATCH 01/20] wide notifications fixes --- docs/configuration.md | 2 + docs/telegram-usage.md | 2 +- docs/webhook-config.md | 51 ++++++++++++ freqtrade/constants.py | 4 +- freqtrade/freqtradebot.py | 137 ++++++++++++++++++++++++--------- freqtrade/rpc/rpc.py | 14 ++-- freqtrade/rpc/telegram.py | 20 +++-- freqtrade/rpc/webhook.py | 4 + tests/rpc/test_rpc.py | 6 +- tests/rpc/test_rpc_telegram.py | 81 ++++++++++++++++--- tests/rpc/test_rpc_webhook.py | 46 ++++++----- tests/test_freqtradebot.py | 6 +- 12 files changed, 288 insertions(+), 85 deletions(-) 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, From 4fad7a462cc067c6c91aaa5b970de881025a9f07 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Sat, 8 Feb 2020 21:19:07 +0100 Subject: [PATCH 02/20] fixes in webhook-config docs --- docs/webhook-config.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/webhook-config.md b/docs/webhook-config.md index 878b18e8a..b287fa71e 100644 --- a/docs/webhook-config.md +++ b/docs/webhook-config.md @@ -51,11 +51,11 @@ Possible parameters are: * `pair` * `limit` * `amount` +* `open_date` * `stake_amount` * `stake_currency` * `fiat_currency` * `order_type` -* `open_rate` * `current_rate` ### Webhookbuycancel @@ -67,11 +67,11 @@ Possible parameters are: * `pair` * `limit` * `amount` +* `open_date` * `stake_amount` * `stake_currency` * `fiat_currency` * `order_type` -* `open_rate` * `current_rate` ### Webhooksell @@ -86,7 +86,6 @@ Possible parameters are: * `amount` * `open_rate` * `current_rate` -* `close_rate` * `profit_amount` * `profit_percent` * `stake_currency` @@ -108,7 +107,6 @@ Possible parameters are: * `amount` * `open_rate` * `current_rate` -* `close_rate` * `profit_amount` * `profit_percent` * `stake_currency` @@ -116,6 +114,7 @@ Possible parameters are: * `sell_reason` * `order_type` * `open_date` +* `close_date` ### Webhookstatus From 879b5138228cc2052ab6bff3fd913080686828e6 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Sat, 8 Feb 2020 21:31:36 +0100 Subject: [PATCH 03/20] enhanced method description --- freqtrade/freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 2f57ca41b..5f1024f8c 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -452,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: bool + :return: True if a buy order is created, false if it fails. """ time_in_force = self.strategy.order_time_in_force['buy'] @@ -966,7 +966,7 @@ class FreqtradeBot: :param trade: Trade instance :param limit: limit rate for the sell order :param sellreason: Reason the sell was triggered - :return: bool + :return: True if it succeeds (supported) False (not supported) """ sell_type = 'sell' if sell_reason in (SellType.STOP_LOSS, SellType.TRAILING_STOP_LOSS): From 2796d3d8a0c1af2c297170f030cc5cfbb4b9c924 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Sun, 9 Feb 2020 00:11:58 +0100 Subject: [PATCH 04/20] added missing tests to increase coverage --- tests/rpc/test_rpc_webhook.py | 53 +++++++++++++++++++++++++++++++++-- 1 file changed, 50 insertions(+), 3 deletions(-) diff --git a/tests/rpc/test_rpc_webhook.py b/tests/rpc/test_rpc_webhook.py index 3b9ce3f0d..ab40047c0 100644 --- a/tests/rpc/test_rpc_webhook.py +++ b/tests/rpc/test_rpc_webhook.py @@ -54,6 +54,9 @@ def test_send_msg(default_conf, mocker): msg_mock = MagicMock() mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) webhook = Webhook(get_patched_freqtradebot(mocker, default_conf)) + # Test buy + msg_mock = MagicMock() + mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) msg = { 'type': RPCMessageType.BUY_NOTIFICATION, 'exchange': 'Bittrex', @@ -64,8 +67,6 @@ def test_send_msg(default_conf, mocker): 'stake_currency': 'BTC', 'fiat_currency': 'EUR' } - msg_mock = MagicMock() - mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) webhook.send_msg(msg=msg) assert msg_mock.call_count == 1 assert (msg_mock.call_args[0][0]["value1"] == @@ -74,6 +75,27 @@ def test_send_msg(default_conf, mocker): default_conf["webhook"]["webhookbuy"]["value2"].format(**msg)) assert (msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhookbuy"]["value3"].format(**msg)) + # Test buy cancel + msg_mock = MagicMock() + mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) + msg = { + 'type': RPCMessageType.BUY_CANCEL_NOTIFICATION, + 'exchange': 'Bittrex', + 'pair': 'ETH/BTC', + '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)) # Test sell msg_mock = MagicMock() mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) @@ -100,7 +122,32 @@ def test_send_msg(default_conf, mocker): default_conf["webhook"]["webhooksell"]["value2"].format(**msg)) assert (msg_mock.call_args[0][0]["value3"] == default_conf["webhook"]["webhooksell"]["value3"].format(**msg)) - + # Test sell cancel + msg_mock = MagicMock() + mocker.patch("freqtrade.rpc.webhook.Webhook._send_msg", msg_mock) + msg = { + 'type': RPCMessageType.SELL_CANCEL_NOTIFICATION, + 'exchange': 'Bittrex', + 'pair': 'ETH/BTC', + 'gain': "profit", + 'limit': 0.005, + 'amount': 0.8, + 'order_type': 'limit', + 'open_rate': 0.004, + 'current_rate': 0.005, + 'profit_amount': 0.001, + 'profit_percent': 0.20, + 'stake_currency': 'BTC', + 'sell_reason': SellType.STOP_LOSS.value + } + webhook.send_msg(msg=msg) + assert msg_mock.call_count == 1 + assert (msg_mock.call_args[0][0]["value1"] == + default_conf["webhook"]["webhooksellcancel"]["value1"].format(**msg)) + assert (msg_mock.call_args[0][0]["value2"] == + default_conf["webhook"]["webhooksellcancel"]["value2"].format(**msg)) + assert (msg_mock.call_args[0][0]["value3"] == + default_conf["webhook"]["webhooksellcancel"]["value3"].format(**msg)) for msgtype in [RPCMessageType.STATUS_NOTIFICATION, RPCMessageType.WARNING_NOTIFICATION, RPCMessageType.CUSTOM_NOTIFICATION]: From d69ddd2ac37b18251b55f6f6e40995b1fac04402 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Mon, 10 Feb 2020 11:54:12 +0100 Subject: [PATCH 05/20] Apply suggestions from code review Committed 1 code suggestion in code review. Co-Authored-By: hroff-1902 <47309513+hroff-1902@users.noreply.github.com> --- docs/telegram-usage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index ac9cea3d6..c8ded4af5 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. Pending buy orders are marked with an asterisk(*) +| `/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`). From f99d1c38298b717a3e1d1299477338e41746c756 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 11 Feb 2020 15:44:47 +0100 Subject: [PATCH 06/20] fixed open_rate instead of open_rate_requested --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 5f1024f8c..aa617a386 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -549,7 +549,7 @@ class FreqtradeBot: 'type': RPCMessageType.BUY_NOTIFICATION, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, - 'limit': trade.open_rate_requested, + 'limit': trade.open_rate, 'order_type': order_type, 'stake_amount': trade.stake_amount, 'stake_currency': self.config['stake_currency'], From 7f4b90c68f9fd1a116ba3179b335add64c66c3cd Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 11 Feb 2020 15:45:35 +0100 Subject: [PATCH 07/20] fixed actual open_rate in notify_buy_cancel --- freqtrade/freqtradebot.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index aa617a386..6d1122aa1 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -572,7 +572,7 @@ class FreqtradeBot: 'type': RPCMessageType.BUY_CANCEL_NOTIFICATION, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, - 'limit': trade.open_rate_requested, + 'limit': trade.open_rate, 'order_type': order_type, 'stake_amount': trade.stake_amount, 'stake_currency': self.config['stake_currency'], From 867b736b8477507f933ce8b9768a433de5721615 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 11 Feb 2020 15:49:57 +0100 Subject: [PATCH 08/20] Fixed to Executing Buys & Sells --- freqtrade/freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 6d1122aa1..85ddb0da1 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -414,7 +414,7 @@ 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}.') + logger.info(f'Executing Buy for {pair}.') return self.execute_buy(pair, stake_amount) else: return False @@ -804,8 +804,8 @@ class FreqtradeBot: ) if should_sell.sell_flag: + logger.info(f'Executing Sell for {trade.pair}. Reason: {should_sell.sell_type}') self.execute_sell(trade, sell_rate, should_sell.sell_type) - logger.info(f'Executed Sell for {trade.pair}. Reason: {should_sell.sell_type}') return True return False From fc29564974d77e54599bf759dd1d1ed4d2df884a Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 11 Feb 2020 15:58:40 +0100 Subject: [PATCH 09/20] Fixed messages and readability --- docs/webhook-config.md | 4 ++-- freqtrade/rpc/rpc.py | 4 ++-- freqtrade/rpc/telegram.py | 4 ++-- tests/rpc/test_rpc_telegram.py | 6 +++--- tests/rpc/test_rpc_webhook.py | 4 ++-- 5 files changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/webhook-config.md b/docs/webhook-config.md index b287fa71e..e53aa8af5 100644 --- a/docs/webhook-config.md +++ b/docs/webhook-config.md @@ -16,7 +16,7 @@ Sample configuration (tested using IFTTT). "value3": "{stake_amount:8f} {stake_currency}" }, "webhookbuycancel": { - "value1": "Cancelling Buy {pair}", + "value1": "Cancelling Open Buy Order for {pair}", "value2": "limit {limit:8f}", "value3": "{stake_amount:8f} {stake_currency}" }, @@ -26,7 +26,7 @@ Sample configuration (tested using IFTTT). "value3": "profit: {profit_amount:8f} {stake_currency}" }, "webhooksellcancel": { - "value1": "Cancelling Sell {pair}", + "value1": "Cancelling Open Sell Order for {pair}", "value2": "limit {limit:8f}", "value3": "profit: {profit_amount:8f} {stake_currency}" }, diff --git a/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index c1efea79e..07631f258 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -168,8 +168,8 @@ class RPC: profit_str += f" ({fiat_profit:.2f})" trades_list.append([ trade.id, - trade.pair + ['', '*'][trade.open_order_id is not None - and trade.close_rate_requested is None], + trade.pair + '*' if (trade.open_order_id is not None + and trade.close_rate_requested is None) else '', 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 0dd7a8ffd..e3d4f54e7 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -144,7 +144,7 @@ class Telegram(RPC): message += ")`" elif msg['type'] == RPCMessageType.BUY_CANCEL_NOTIFICATION: - message = "*{exchange}:* Cancelling Buy {pair}".format(**msg) + message = "*{exchange}:* Cancelling Open Buy Order for {pair}".format(**msg) elif msg['type'] == RPCMessageType.SELL_NOTIFICATION: msg['amount'] = round(msg['amount'], 8) @@ -172,7 +172,7 @@ class Telegram(RPC): ' / {profit_fiat:.3f} {fiat_currency})`').format(**msg) elif msg['type'] == RPCMessageType.SELL_CANCEL_NOTIFICATION: - message = "*{exchange}:* Cancelling Sell {pair}".format(**msg) + message = "*{exchange}:* Cancelling Open Sell Order for {pair}".format(**msg) elif msg['type'] == RPCMessageType.STATUS_NOTIFICATION: message = '*Status:* `{status}`'.format(**msg) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index ae9c0c4dc..a8b8e0c5a 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -1228,7 +1228,7 @@ def test_send_msg_buy_cancel_notification(default_conf, mocker) -> None: 'pair': 'ETH/BTC', }) assert msg_mock.call_args[0][0] \ - == ('*Bittrex:* Cancelling Buy ETH/BTC') + == ('*Bittrex:* Cancelling Open Buy Order for ETH/BTC') def test_send_msg_sell_notification(default_conf, mocker) -> None: @@ -1318,7 +1318,7 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None: 'pair': 'KEY/ETH', }) assert msg_mock.call_args[0][0] \ - == ('*Binance:* Cancelling Sell KEY/ETH') + == ('*Binance:* Cancelling Open Sell Order for KEY/ETH') msg_mock.reset_mock() telegram.send_msg({ @@ -1327,7 +1327,7 @@ def test_send_msg_sell_cancel_notification(default_conf, mocker) -> None: 'pair': 'KEY/ETH', }) assert msg_mock.call_args[0][0] \ - == ('*Binance:* Cancelling Sell KEY/ETH') + == ('*Binance:* Cancelling Open Sell Order for KEY/ETH') # Reset singleton function to avoid random breaks telegram._fiat_converter.convert_amount = old_convamount diff --git a/tests/rpc/test_rpc_webhook.py b/tests/rpc/test_rpc_webhook.py index ab40047c0..3f3f36766 100644 --- a/tests/rpc/test_rpc_webhook.py +++ b/tests/rpc/test_rpc_webhook.py @@ -21,7 +21,7 @@ def get_webhook_dict() -> dict: "value3": "{stake_amount:8f} {stake_currency}" }, "webhookbuycancel": { - "value1": "Cancelling Buy {pair}", + "value1": "Cancelling Open Buy Order for {pair}", "value2": "limit {limit:8f}", "value3": "{stake_amount:8f} {stake_currency}" }, @@ -31,7 +31,7 @@ def get_webhook_dict() -> dict: "value3": "profit: {profit_amount:8f} {stake_currency}" }, "webhooksellcancel": { - "value1": "Cancelling Sell {pair}", + "value1": "Cancelling Open Sell Order for {pair}", "value2": "limit {limit:8f}", "value3": "profit: {profit_amount:8f} {stake_currency}" }, From 4fedf1e564212b43b359a1bc533f0d571b9ce926 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 11 Feb 2020 16:05:44 +0100 Subject: [PATCH 10/20] default refresh TRUE on get_buy_rate and get_sell_Rate --- freqtrade/freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 85ddb0da1..c04e3077e 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, refresh: bool = False, tick: Dict = None) -> float: + def get_buy_rate(self, pair: str, refresh: bool = True, tick: Dict = None) -> float: """ Calculates bid target between current ask price and last price :return: float: Price @@ -615,7 +615,7 @@ class FreqtradeBot: return trades_closed - def get_sell_rate(self, pair: str, refresh: bool = False) -> float: + def get_sell_rate(self, pair: str, refresh: bool = True) -> 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 From 5f4c209fca844f6ea66f7302c22a64d7375812db Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 11 Feb 2020 16:14:49 +0100 Subject: [PATCH 11/20] fixed one more occurence of executed buy, and test --- freqtrade/freqtradebot.py | 2 +- tests/test_freqtradebot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index c04e3077e..0d1105b2f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -419,7 +419,7 @@ class FreqtradeBot: else: return False - logger.info(f'Executed Buy for {pair}') + logger.info(f'Executing Buy for {pair}') return self.execute_buy(pair, stake_amount) else: return False diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 429d3599d..18cd81aed 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 for NEO/BTC. Reason: SellType.STOP_LOSS', caplog) + assert log_has('Executing Sell for NEO/BTC. Reason: SellType.STOP_LOSS', caplog) assert trade.sell_reason == SellType.STOP_LOSS.value From cde1b2b56c7fa6285af72ec3d1758968f4d9b88d Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 11 Feb 2020 16:28:48 +0100 Subject: [PATCH 12/20] readded rpc status message for partial buys --- freqtrade/freqtradebot.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 0d1105b2f..ffd951ee2 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -906,6 +906,10 @@ 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: From 899de8b27c3f35273f090534b0bb2a9d131270f6 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Tue, 11 Feb 2020 16:50:18 +0100 Subject: [PATCH 13/20] modified tests for double partial call --- tests/test_freqtradebot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index 18cd81aed..c0af1f015 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2067,7 +2067,7 @@ def test_check_handle_timedout_partial(default_conf, ticker, limit_buy_order_old # note this is for a partially-complete buy order freqtrade.check_handle_timedout() assert cancel_order_mock.call_count == 1 - assert rpc_mock.call_count == 1 + assert rpc_mock.call_count == 2 trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() assert len(trades) == 1 assert trades[0].amount == 23.0 @@ -2101,7 +2101,7 @@ def test_check_handle_timedout_partial_fee(default_conf, ticker, open_trade, cap assert log_has_re(r"Applying fee on amount for Trade.* Order", caplog) assert cancel_order_mock.call_count == 1 - assert rpc_mock.call_count == 1 + assert rpc_mock.call_count == 2 trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() assert len(trades) == 1 # Verify that tradehas been updated @@ -2140,7 +2140,7 @@ def test_check_handle_timedout_partial_except(default_conf, ticker, open_trade, assert log_has_re(r"Could not update trade amount: .*", caplog) assert cancel_order_mock.call_count == 1 - assert rpc_mock.call_count == 1 + assert rpc_mock.call_count == 2 trades = Trade.query.filter(Trade.open_order_id.is_(open_trade.open_order_id)).all() assert len(trades) == 1 # Verify that tradehas been updated From 47874a452787360b610ab0f3d038de6e53cc5d53 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Wed, 12 Feb 2020 21:45:55 +0100 Subject: [PATCH 14/20] added logic to differentiate sell orders with double asterisk --- docs/telegram-usage.md | 2 +- freqtrade/rpc/rpc.py | 3 ++- freqtrade/rpc/telegram.py | 2 ++ 3 files changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/telegram-usage.md b/docs/telegram-usage.md index c8ded4af5..f683ae8da 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. Pending buy orders are marked with an asterisk (*) +| `/status table` | | List all open trades in a table format. Pending buy orders are marked with an asterisk (*) Pending sell orders are marked with a double 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/freqtrade/rpc/rpc.py b/freqtrade/rpc/rpc.py index 07631f258..c182aad2b 100644 --- a/freqtrade/rpc/rpc.py +++ b/freqtrade/rpc/rpc.py @@ -169,7 +169,8 @@ class RPC: trades_list.append([ trade.id, 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 '', 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 e3d4f54e7..d4ed5b189 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -561,6 +561,8 @@ class Telegram(RPC): "*/stop:* `Stops the trader`\n" \ "*/status [table]:* `Lists all open trades`\n" \ " *table :* `will display trades in a table`\n" \ + " pending buy orders are marked with an asterisk (*)\n" + " pending sell orders are marked with a double asterisk (**)\n" \ "*/profit:* `Lists cumulative profit from all finished trades`\n" \ "*/forcesell |all:* `Instantly sells the given trade or all trades, " \ "regardless of profit`\n" \ From f6db784a859b9a70a320e2c2da12dc47afce678e Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Wed, 12 Feb 2020 21:50:33 +0100 Subject: [PATCH 15/20] removed default to refresh argument in get_buy_rate and get_sell_rate --- freqtrade/freqtradebot.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index ffd951ee2..455396352 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, refresh: bool = True, tick: Dict = None) -> float: + def get_buy_rate(self, pair: str, refresh: bool, tick: Dict = None) -> float: """ Calculates bid target between current ask price and last price :return: float: Price @@ -615,7 +615,7 @@ class FreqtradeBot: return trades_closed - def get_sell_rate(self, pair: str, refresh: bool = True) -> float: + def get_sell_rate(self, pair: str, refresh: bool) -> 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 From 2e3b8cdba758a33abef2ce5f349335ff8194aa8a Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Wed, 12 Feb 2020 21:51:58 +0100 Subject: [PATCH 16/20] fixed flake8 issues on /help output --- freqtrade/rpc/telegram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index d4ed5b189..5603ab03c 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -561,8 +561,8 @@ class Telegram(RPC): "*/stop:* `Stops the trader`\n" \ "*/status [table]:* `Lists all open trades`\n" \ " *table :* `will display trades in a table`\n" \ - " pending buy orders are marked with an asterisk (*)\n" - " pending sell orders are marked with a double asterisk (**)\n" \ + " pending buy orders are marked with an asterisk (*)\n" \ + " pending sell orders are marked with a double asterisk (**)\n" \ "*/profit:* `Lists cumulative profit from all finished trades`\n" \ "*/forcesell |all:* `Instantly sells the given trade or all trades, " \ "regardless of profit`\n" \ From f09af888b153794ff135de9acedf493666124e9c Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Wed, 12 Feb 2020 21:55:38 +0100 Subject: [PATCH 17/20] modified get_buy/sell_rate refresh to true on notify_sell_cancel and notify_buy_cancel --- freqtrade/freqtradebot.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 455396352..158b631c1 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -566,7 +566,7 @@ class FreqtradeBot: """ Sends rpc notification when a buy cancel occured. """ - current_rate = self.get_buy_rate(trade.pair, False) + current_rate = self.get_buy_rate(trade.pair, True) msg = { 'type': RPCMessageType.BUY_CANCEL_NOTIFICATION, @@ -1062,8 +1062,7 @@ class FreqtradeBot: """ 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) + current_rate = self.get_sell_rate(trade.pair, True) profit_percent = trade.calc_profit_ratio(profit_rate) gain = "profit" if profit_percent > 0 else "loss" From 007cc94474c5f4bc0970f61b4716b00d8a8b8c92 Mon Sep 17 00:00:00 2001 From: Yazeed Al Oyoun Date: Wed, 12 Feb 2020 22:03:56 +0100 Subject: [PATCH 18/20] fixed tests to send refresh, since its no longer defaulted --- tests/test_freqtradebot.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index c0af1f015..5ed4d296c 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -921,7 +921,7 @@ def test_get_buy_rate(mocker, default_conf, ask, last, last_ab, expected) -> Non mocker.patch('freqtrade.exchange.Exchange.fetch_ticker', MagicMock(return_value={'ask': ask, 'last': last})) - assert freqtrade.get_buy_rate('ETH/BTC') == expected + assert freqtrade.get_buy_rate('ETH/BTC', True) == expected def test_execute_buy(mocker, default_conf, fee, limit_buy_order) -> None: @@ -3524,7 +3524,7 @@ def test_order_book_bid_strategy1(mocker, default_conf, order_book_l2) -> None: default_conf['telegram']['enabled'] = False freqtrade = FreqtradeBot(default_conf) - assert freqtrade.get_buy_rate('ETH/BTC') == 0.043935 + assert freqtrade.get_buy_rate('ETH/BTC', True) == 0.043935 assert ticker_mock.call_count == 0 @@ -3549,7 +3549,7 @@ def test_order_book_bid_strategy2(mocker, default_conf, order_book_l2) -> None: freqtrade = FreqtradeBot(default_conf) # orderbook shall be used even if tickers would be lower. - assert freqtrade.get_buy_rate('ETH/BTC') != 0.042 + assert freqtrade.get_buy_rate('ETH/BTC', True) != 0.042 assert ticker_mock.call_count == 0 From 86592c3ba1c0f4b626d7d4edb471a320d120dd13 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Feb 2020 06:51:52 +0100 Subject: [PATCH 19/20] Fix /help from telegram --- freqtrade/rpc/telegram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index 5603ab03c..abd322293 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -561,8 +561,8 @@ class Telegram(RPC): "*/stop:* `Stops the trader`\n" \ "*/status [table]:* `Lists all open trades`\n" \ " *table :* `will display trades in a table`\n" \ - " pending buy orders are marked with an asterisk (*)\n" \ - " pending sell orders are marked with a double asterisk (**)\n" \ + " `pending buy orders are marked with an asterisk (*)`\n" \ + " `pending sell orders are marked with a double asterisk (**)`\n" \ "*/profit:* `Lists cumulative profit from all finished trades`\n" \ "*/forcesell |all:* `Instantly sells the given trade or all trades, " \ "regardless of profit`\n" \ From ccc923975138e47440e5ce48fd6fb38c1f0691c1 Mon Sep 17 00:00:00 2001 From: Matthias Date: Thu, 13 Feb 2020 07:02:12 +0100 Subject: [PATCH 20/20] Reduce indentation of help --- freqtrade/rpc/telegram.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/freqtrade/rpc/telegram.py b/freqtrade/rpc/telegram.py index abd322293..e3958b31a 100644 --- a/freqtrade/rpc/telegram.py +++ b/freqtrade/rpc/telegram.py @@ -561,8 +561,8 @@ class Telegram(RPC): "*/stop:* `Stops the trader`\n" \ "*/status [table]:* `Lists all open trades`\n" \ " *table :* `will display trades in a table`\n" \ - " `pending buy orders are marked with an asterisk (*)`\n" \ - " `pending sell orders are marked with a double asterisk (**)`\n" \ + " `pending buy orders are marked with an asterisk (*)`\n" \ + " `pending sell orders are marked with a double asterisk (**)`\n" \ "*/profit:* `Lists cumulative profit from all finished trades`\n" \ "*/forcesell |all:* `Instantly sells the given trade or all trades, " \ "regardless of profit`\n" \