From fd694f14c28105624a836611d66521bdb3343e10 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Jan 2023 13:53:08 +0100 Subject: [PATCH 1/3] Add new order columns, ft_amount and ft_price --- freqtrade/freqtradebot.py | 7 ++++--- freqtrade/persistence/migrations.py | 13 +++++++++---- freqtrade/persistence/trade_model.py | 15 +++++++++++++-- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/freqtrade/freqtradebot.py b/freqtrade/freqtradebot.py index 659eb2660..6e87db136 100644 --- a/freqtrade/freqtradebot.py +++ b/freqtrade/freqtradebot.py @@ -720,7 +720,7 @@ class FreqtradeBot(LoggingMixin): time_in_force=time_in_force, leverage=leverage ) - order_obj = Order.parse_from_ccxt_object(order, pair, side) + order_obj = Order.parse_from_ccxt_object(order, pair, side, amount, enter_limit_requested) order_id = order['id'] order_status = order.get('status') logger.info(f"Order #{order_id} was created for {pair} and status is {order_status}.") @@ -1094,7 +1094,8 @@ class FreqtradeBot(LoggingMixin): leverage=trade.leverage ) - order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss') + order_obj = Order.parse_from_ccxt_object(stoploss_order, trade.pair, 'stoploss', + trade.amount, stop_price) trade.orders.append(order_obj) trade.stoploss_order_id = str(stoploss_order['id']) trade.stoploss_last_update = datetime.now(timezone.utc) @@ -1595,7 +1596,7 @@ class FreqtradeBot(LoggingMixin): self.handle_insufficient_funds(trade) return False - order_obj = Order.parse_from_ccxt_object(order, trade.pair, trade.exit_side) + order_obj = Order.parse_from_ccxt_object(order, trade.pair, trade.exit_side, amount, limit) trade.orders.append(order_obj) trade.open_order_id = order['id'] diff --git a/freqtrade/persistence/migrations.py b/freqtrade/persistence/migrations.py index 44a6756d1..87b172846 100644 --- a/freqtrade/persistence/migrations.py +++ b/freqtrade/persistence/migrations.py @@ -214,17 +214,22 @@ def migrate_orders_table(engine, table_back_name: str, cols_order: List): average = get_column_def(cols_order, 'average', 'null') stop_price = get_column_def(cols_order, 'stop_price', 'null') funding_fee = get_column_def(cols_order, 'funding_fee', '0.0') + ft_amount = get_column_def(cols_order, 'ft_amount', 'coalesce(amount, 0.0)') + ft_price = get_column_def(cols_order, 'ft_price', 'coalesce(price, 0.0)') # sqlite does not support literals for booleans with engine.begin() as connection: connection.execute(text(f""" insert into orders (id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status, symbol, order_type, side, price, amount, filled, average, remaining, cost, - stop_price, order_date, order_filled_date, order_update_date, ft_fee_base, funding_fee) + stop_price, order_date, order_filled_date, order_update_date, ft_fee_base, funding_fee, + ft_amount, ft_price + ) select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id, status, symbol, order_type, side, price, amount, filled, {average} average, remaining, cost, {stop_price} stop_price, order_date, order_filled_date, - order_update_date, {ft_fee_base} ft_fee_base, {funding_fee} funding_fee + order_update_date, {ft_fee_base} ft_fee_base, {funding_fee} funding_fee, + {ft_amount} ft_amount, {ft_price} ft_price from {table_back_name} """)) @@ -311,8 +316,8 @@ def check_migrate(engine, decl_base, previous_tables) -> None: # if ('orders' not in previous_tables # or not has_column(cols_orders, 'funding_fee')): migrating = False - # if not has_column(cols_orders, 'funding_fee'): - if not has_column(cols_trades, 'max_stake_amount'): + # if not has_column(cols_trades, 'max_stake_amount'): + if not has_column(cols_orders, 'ft_price'): migrating = True logger.info(f"Running database migration for trades - " f"backup: {table_back_name}, {order_table_bak_name}") diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index f19b3808f..b6871c04a 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -49,6 +49,8 @@ class Order(_DECL_BASE): ft_order_side: str = Column(String(25), nullable=False) ft_pair: str = Column(String(25), nullable=False) ft_is_open = Column(Boolean, nullable=False, default=True, index=True) + ft_amount = Column(Float, nullable=False) + ft_price = Column(Float, nullable=False) order_id: str = Column(String(255), nullable=False, index=True) status = Column(String(255), nullable=True) @@ -227,11 +229,20 @@ class Order(_DECL_BASE): logger.warning(f"Did not find order for {order}.") @staticmethod - def parse_from_ccxt_object(order: Dict[str, Any], pair: str, side: str) -> 'Order': + def parse_from_ccxt_object( + order: Dict[str, Any], pair: str, side: str, + amount: Optional[float] = None, price: Optional[float] = None) -> 'Order': """ Parse an order from a ccxt object and return a new order Object. + Optional support for overriding amount and price is only used for test simplification. """ - o = Order(order_id=str(order['id']), ft_order_side=side, ft_pair=pair) + o = Order( + order_id=str(order['id']), + ft_order_side=side, + ft_pair=pair, + ft_amount=amount if amount else order['amount'], + ft_price=price if price else order['price'], + ) o.update_from_ccxt_object(order) return o From 305b067e4859e3a7e200afa15aa5f40537ea69d6 Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Jan 2023 13:55:09 +0100 Subject: [PATCH 2/3] Support having no Amount/Price available from the exchange initially --- freqtrade/persistence/trade_model.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/freqtrade/persistence/trade_model.py b/freqtrade/persistence/trade_model.py index b6871c04a..3013df2b8 100644 --- a/freqtrade/persistence/trade_model.py +++ b/freqtrade/persistence/trade_model.py @@ -84,9 +84,13 @@ class Order(_DECL_BASE): self.order_filled_date.replace(tzinfo=timezone.utc) if self.order_filled_date else None ) + @property + def safe_amount(self) -> float: + return self.amount or self.ft_amount + @property def safe_price(self) -> float: - return self.average or self.price or self.stop_price + return self.average or self.price or self.stop_price or self.ft_price @property def safe_filled(self) -> float: @@ -96,7 +100,7 @@ class Order(_DECL_BASE): def safe_remaining(self) -> float: return ( self.remaining if self.remaining is not None else - self.amount - (self.filled or 0.0) + self.safe_amount - (self.filled or 0.0) ) @property From ad4954194745df65888939bfff1563618f5307da Mon Sep 17 00:00:00 2001 From: Matthias Date: Sun, 8 Jan 2023 13:55:52 +0100 Subject: [PATCH 3/3] Adapt Tests for new mandatory columns --- tests/conftest.py | 6 ++++++ tests/persistence/test_persistence.py | 10 ++++++++-- tests/plugins/test_protections.py | 11 ++++++++--- tests/rpc/test_rpc_telegram.py | 2 ++ tests/test_freqtradebot.py | 3 +++ 5 files changed, 27 insertions(+), 5 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c9af5a171..90608d047 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -2606,6 +2606,8 @@ def open_trade(): ft_order_side='buy', ft_pair=trade.pair, ft_is_open=False, + ft_amount=trade.amount, + ft_price=trade.open_rate, order_id='123456789', status="closed", symbol=trade.pair, @@ -2642,6 +2644,8 @@ def open_trade_usdt(): ft_order_side='buy', ft_pair=trade.pair, ft_is_open=False, + ft_amount=trade.amount, + ft_price=trade.open_rate, order_id='123456789', status="closed", symbol=trade.pair, @@ -2659,6 +2663,8 @@ def open_trade_usdt(): ft_order_side='exit', ft_pair=trade.pair, ft_is_open=True, + ft_amount=trade.amount, + ft_price=trade.open_rate, order_id='123456789_exit', status="open", symbol=trade.pair, diff --git a/tests/persistence/test_persistence.py b/tests/persistence/test_persistence.py index 830d84288..e12e919fc 100644 --- a/tests/persistence/test_persistence.py +++ b/tests/persistence/test_persistence.py @@ -1870,11 +1870,13 @@ def test_get_exit_order_count(fee, is_short): @pytest.mark.usefixtures("init_persistence") def test_update_order_from_ccxt(caplog): # Most basic order return (only has orderid) - o = Order.parse_from_ccxt_object({'id': '1234'}, 'ADA/USDT', 'buy') + o = Order.parse_from_ccxt_object({'id': '1234'}, 'ADA/USDT', 'buy', 20.01, 1234.6) assert isinstance(o, Order) assert o.ft_pair == 'ADA/USDT' assert o.ft_order_side == 'buy' assert o.order_id == '1234' + assert o.ft_price == 1234.6 + assert o.ft_amount == 20.01 assert o.ft_is_open ccxt_order = { 'id': '1234', @@ -1888,13 +1890,15 @@ def test_update_order_from_ccxt(caplog): 'status': 'open', 'timestamp': 1599394315123 } - o = Order.parse_from_ccxt_object(ccxt_order, 'ADA/USDT', 'buy') + o = Order.parse_from_ccxt_object(ccxt_order, 'ADA/USDT', 'buy', 20.01, 1234.6) assert isinstance(o, Order) assert o.ft_pair == 'ADA/USDT' assert o.ft_order_side == 'buy' assert o.order_id == '1234' assert o.order_type == 'limit' assert o.price == 1234.5 + assert o.ft_price == 1234.6 + assert o.ft_amount == 20.01 assert o.filled == 9 assert o.remaining == 11 assert o.order_date is not None @@ -2539,6 +2543,8 @@ def test_recalc_trade_from_orders_dca(data) -> None: ft_pair=trade.pair, order_id=f"order_{order[0]}_{idx}", ft_is_open=False, + ft_amount=amount, + ft_price=price, status="closed", symbol=trade.pair, order_type="market", diff --git a/tests/plugins/test_protections.py b/tests/plugins/test_protections.py index 820eced20..2bbdf3d4f 100644 --- a/tests/plugins/test_protections.py +++ b/tests/plugins/test_protections.py @@ -39,6 +39,8 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool, order_id=f'{pair}-{trade.entry_side}-{trade.open_date}', ft_is_open=False, ft_pair=pair, + ft_amount=trade.amount, + ft_price=trade.open_rate, amount=trade.amount, filled=trade.amount, remaining=0, @@ -49,16 +51,19 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool, side=trade.entry_side, )) if not is_open: + close_price = open_rate * (2 - profit_rate if is_short else profit_rate) trade.orders.append(Order( ft_order_side=trade.exit_side, order_id=f'{pair}-{trade.exit_side}-{trade.close_date}', ft_is_open=False, ft_pair=pair, + ft_amount=trade.amount, + ft_price=trade.open_rate, amount=trade.amount, filled=trade.amount, remaining=0, - price=open_rate * (2 - profit_rate if is_short else profit_rate), - average=open_rate * (2 - profit_rate if is_short else profit_rate), + price=close_price, + average=close_price, status="closed", order_type="market", side=trade.exit_side, @@ -66,7 +71,7 @@ def generate_mock_trade(pair: str, fee: float, is_open: bool, trade.recalc_open_trade_value() if not is_open: - trade.close(open_rate * (2 - profit_rate if is_short else profit_rate)) + trade.close(close_price) trade.exit_reason = exit_reason Trade.query.session.add(trade) diff --git a/tests/rpc/test_rpc_telegram.py b/tests/rpc/test_rpc_telegram.py index 58977a94a..85475ae8e 100644 --- a/tests/rpc/test_rpc_telegram.py +++ b/tests/rpc/test_rpc_telegram.py @@ -253,6 +253,8 @@ def test_telegram_status_multi_entry(default_conf, update, mocker, fee) -> None: ft_order_side='buy', ft_pair=trade.pair, ft_is_open=False, + ft_amount=trade.amount, + ft_price=trade.open_rate, status="closed", symbol=trade.pair, order_type="market", diff --git a/tests/test_freqtradebot.py b/tests/test_freqtradebot.py index a4431358f..7efd0393d 100644 --- a/tests/test_freqtradebot.py +++ b/tests/test_freqtradebot.py @@ -1168,6 +1168,8 @@ def test_handle_stoploss_on_exchange(mocker, default_conf_usdt, fee, caplog, is_ order_id='100', ft_pair=trade.pair, ft_is_open=True, + ft_amount=trade.amount, + ft_price=0.0, )) assert trade @@ -4615,6 +4617,7 @@ def test_get_real_amount_open_trade_usdt(default_conf_usdt, fee, mocker): 'amount': amount, 'status': 'open', 'side': 'buy', + 'price': 0.245441, } freqtrade = get_patched_freqtradebot(mocker, default_conf_usdt) order_obj = Order.parse_from_ccxt_object(order, 'LTC/ETH', 'buy')