From 542963c7a60e699c1ebd0602ecd4b5be7a86f6a7 Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Nov 2021 19:45:20 +0100 Subject: [PATCH 1/3] Reduce code complexity by combining buy and buy_fill methods --- docs/webhook-config.md | 5 ++++- freqtrade/freqtradebot.py | 26 ++++++-------------------- mkdocs.yml | 1 + 3 files changed, 11 insertions(+), 21 deletions(-) diff --git a/docs/webhook-config.md b/docs/webhook-config.md index bea555385..40915c988 100644 --- a/docs/webhook-config.md +++ b/docs/webhook-config.md @@ -104,7 +104,8 @@ Possible parameters are: * `trade_id` * `exchange` * `pair` -* `limit` +* ~~`limit` # Deprecated - should no longer be used.~~ +* `open_rate` * `amount` * `open_date` * `stake_amount` @@ -146,6 +147,8 @@ Possible parameters are: * `stake_amount` * `stake_currency` * `fiat_currency` +* `order_type` +* `current_rate` * `buy_tag` ### Webhooksell diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index a6d1b36b9..32f08c178 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -592,17 +592,19 @@ class FreqtradeBot(LoggingMixin): return True - def _notify_enter(self, trade: Trade, order_type: str) -> None: + def _notify_enter(self, trade: Trade, order_type: Optional[str] = None, + fill: bool = False) -> None: """ Sends rpc notification when a buy occurred. """ msg = { 'trade_id': trade.id, - 'type': RPCMessageType.BUY, + 'type': RPCMessageType.BUY_FILL if fill else RPCMessageType.BUY, 'buy_tag': trade.buy_tag, 'exchange': self.exchange.name.capitalize(), 'pair': trade.pair, - 'limit': trade.open_rate, + 'limit': trade.open_rate, # Deprecated (?) + 'open_rate': trade.open_rate, 'order_type': order_type, 'stake_amount': trade.stake_amount, 'stake_currency': self.config['stake_currency'], @@ -641,22 +643,6 @@ class FreqtradeBot(LoggingMixin): # Send the message self.rpc.send_msg(msg) - def _notify_enter_fill(self, trade: Trade) -> None: - msg = { - 'trade_id': trade.id, - 'type': RPCMessageType.BUY_FILL, - 'buy_tag': trade.buy_tag, - 'exchange': self.exchange.name.capitalize(), - 'pair': trade.pair, - 'open_rate': trade.open_rate, - '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, - } - self.rpc.send_msg(msg) - # # SELL / exit positions / close trades logic and methods # @@ -1312,7 +1298,7 @@ class FreqtradeBot(LoggingMixin): self.wallets.update() elif not trade.open_order_id: # Buy fill - self._notify_enter_fill(trade) + self._notify_enter(trade, fill=True) return False diff --git a/mkdocs.yml b/mkdocs.yml index 9eebd75e3..fb1b80ebf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -86,4 +86,5 @@ markdown_extensions: alternate_style: true - pymdownx.tasklist: custom_checkbox: true + - pymdownx.tilde - mdx_truly_sane_lists From 5ce1eeecf5b7cdd892191c599ac257f7705cf70e Mon Sep 17 00:00:00 2001 From: Matthias Date: Tue, 30 Nov 2021 20:19:59 +0100 Subject: [PATCH 2/3] Reorder messages to be sent in correct order buy first, then buy fill, sell first, then sell fill. --- freqtrade/freqtradebot.py | 30 ++++++++++++++++-------------- tests/rpc/test_rpc_telegram.py | 6 +++--- tests/test_freqtradebot.py | 2 +- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 32f08c178..7d8e0ec2f 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -278,7 +278,8 @@ class FreqtradeBot(LoggingMixin): if order: logger.info(f"Updating sell-fee on trade {trade} for order {order.order_id}.") self.update_trade_state(trade, order.order_id, - stoploss_order=order.ft_order_side == 'stoploss') + stoploss_order=order.ft_order_side == 'stoploss', + send_msg=False) trades: List[Trade] = Trade.get_open_trades_without_assigned_fees() for trade in trades: @@ -286,7 +287,7 @@ class FreqtradeBot(LoggingMixin): order = trade.select_order('buy', False) if order: logger.info(f"Updating buy-fee on trade {trade} for order {order.order_id}.") - self.update_trade_state(trade, order.order_id) + self.update_trade_state(trade, order.order_id, send_msg=False) def handle_insufficient_funds(self, trade: Trade): """ @@ -308,7 +309,7 @@ class FreqtradeBot(LoggingMixin): order = trade.select_order('buy', False) if order: logger.info(f"Updating buy-fee on trade {trade} for order {order.order_id}.") - self.update_trade_state(trade, order.order_id) + self.update_trade_state(trade, order.order_id, send_msg=False) def refind_lost_order(self, trade): """ @@ -578,10 +579,6 @@ class FreqtradeBot(LoggingMixin): ) trade.orders.append(order_obj) - # Update fees if order is closed - if order_status == 'closed': - self.update_trade_state(trade, order_id, order) - Trade.query.session.add(trade) Trade.commit() @@ -590,6 +587,10 @@ class FreqtradeBot(LoggingMixin): self._notify_enter(trade, order_type) + # Update fees if order is closed + if order_status == 'closed': + self.update_trade_state(trade, order_id, order) + return True def _notify_enter(self, trade: Trade, order_type: Optional[str] = None, @@ -1140,16 +1141,16 @@ class FreqtradeBot(LoggingMixin): trade.sell_order_status = '' trade.close_rate_requested = limit trade.sell_reason = exit_tag or sell_reason.sell_reason - # In case of market sell orders the order can be closed immediately - if order.get('status', 'unknown') in ('closed', 'expired'): - self.update_trade_state(trade, trade.open_order_id, order) - Trade.commit() # Lock pair for one candle to prevent immediate re-buys self.strategy.lock_pair(trade.pair, datetime.now(timezone.utc), reason='Auto lock') self._notify_exit(trade, order_type) + # In case of market sell orders the order can be closed immediately + if order.get('status', 'unknown') in ('closed', 'expired'): + self.update_trade_state(trade, trade.open_order_id, order) + Trade.commit() return True @@ -1246,13 +1247,14 @@ class FreqtradeBot(LoggingMixin): # def update_trade_state(self, trade: Trade, order_id: str, action_order: Dict[str, Any] = None, - stoploss_order: bool = False) -> bool: + stoploss_order: bool = False, send_msg: bool = True) -> bool: """ Checks trades with open orders and updates the amount if necessary Handles closing both buy and sell orders. :param trade: Trade object of the trade we're analyzing :param order_id: Order-id of the order we're analyzing :param action_order: Already acquired order object + :param send_msg: Send notification - should always be True except in "recovery" methods :return: True if order has been cancelled without being filled partially, False otherwise """ if not order_id: @@ -1292,11 +1294,11 @@ class FreqtradeBot(LoggingMixin): # Updating wallets when order is closed if not trade.is_open: - if not stoploss_order and not trade.open_order_id: + if send_msg and not stoploss_order and not trade.open_order_id: self._notify_exit(trade, '', True) self.handle_protections(trade.pair) self.wallets.update() - elif not trade.open_order_id: + elif send_msg and not trade.open_order_id: # Buy fill self._notify_enter(trade, fill=True) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 6c32e59fc..6adce7b4d 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -937,7 +937,7 @@ def test_telegram_forcesell_handle(default_conf, update, ticker, fee, telegram._forcesell(update=update, context=context) assert msg_mock.call_count == 4 - last_msg = msg_mock.call_args_list[-1][0][0] + last_msg = msg_mock.call_args_list[-2][0][0] assert { 'type': RPCMessageType.SELL, 'trade_id': 1, @@ -1001,7 +1001,7 @@ def test_telegram_forcesell_down_handle(default_conf, update, ticker, fee, assert msg_mock.call_count == 4 - last_msg = msg_mock.call_args_list[-1][0][0] + last_msg = msg_mock.call_args_list[-2][0][0] assert { 'type': RPCMessageType.SELL, 'trade_id': 1, @@ -1055,7 +1055,7 @@ def test_forcesell_all_handle(default_conf, update, ticker, fee, mocker) -> None # Called for each trade 2 times assert msg_mock.call_count == 8 - msg = msg_mock.call_args_list[1][0][0] + msg = msg_mock.call_args_list[0][0][0] assert { 'type': RPCMessageType.SELL, 'trade_id': 1, diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index e5dae5461..dd1fcd6e2 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -2979,7 +2979,7 @@ def test_execute_trade_exit_market_order(default_conf_usdt, ticker_usdt, fee, assert trade.close_profit == 0.09451372 assert rpc_mock.call_count == 3 - last_msg = rpc_mock.call_args_list[-1][0][0] + last_msg = rpc_mock.call_args_list[-2][0][0] assert { 'type': RPCMessageType.SELL, 'trade_id': 1, From 0375a083029ff1ba2a88944b0dd8d79b929651e3 Mon Sep 17 00:00:00 2001 From: Matthias Date: Wed, 1 Dec 2021 20:32:23 +0100 Subject: [PATCH 3/3] use to_hdf instead of HDFStore --- freqtrade/data/history/hdf5datahandler.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/freqtrade/data/history/hdf5datahandler.py b/freqtrade/data/history/hdf5datahandler.py index dd60530aa..1ede3de98 100644 --- a/freqtrade/data/history/hdf5datahandler.py +++ b/freqtrade/data/history/hdf5datahandler.py @@ -61,10 +61,10 @@ class HDF5DataHandler(IDataHandler): filename = self._pair_data_filename(self._datadir, pair, timeframe) - ds = pd.HDFStore(filename, mode='a', complevel=9, complib='blosc') - ds.put(key, _data.loc[:, self._columns], format='table', data_columns=['date']) - - ds.close() + _data.loc[:, self._columns].to_hdf( + filename, key, mode='a', complevel=9, complib='blosc', + format='table', data_columns=['date'] + ) def _ohlcv_load(self, pair: str, timeframe: str, timerange: Optional[TimeRange] = None) -> pd.DataFrame: @@ -142,11 +142,11 @@ class HDF5DataHandler(IDataHandler): """ key = self._pair_trades_key(pair) - ds = pd.HDFStore(self._pair_trades_filename(self._datadir, pair), - mode='a', complevel=9, complib='blosc') - ds.put(key, pd.DataFrame(data, columns=DEFAULT_TRADES_COLUMNS), - format='table', data_columns=['timestamp']) - ds.close() + pd.DataFrame(data, columns=DEFAULT_TRADES_COLUMNS).to_hdf( + self._pair_trades_filename(self._datadir, pair), key, + mode='a', complevel=9, complib='blosc', + format='table', data_columns=['timestamp'] + ) def trades_append(self, pair: str, data: TradeList): """