Merge pull request #6437 from freqtrade/update_trade

Migrate trade updating to use order model
This commit is contained in:
Matthias
2022-02-23 19:56:02 +01:00
committed by GitHub
9 changed files with 252 additions and 131 deletions

View File

@@ -1357,9 +1357,14 @@ class FreqtradeBot(LoggingMixin):
# Handling of this will happen in check_handle_timedout.
return True
order = self.handle_order_fee(trade, order)
order_obj = trade.select_order_by_order_id(order_id)
if not order_obj:
raise DependencyException(
f"Order_obj not found for {order_id}. This should not have happened.")
self.handle_order_fee(trade, order_obj, order)
trade.update(order)
trade.update_trade(order_obj)
# TODO: is the below necessary? it's already done in update_trade for filled buys
trade.recalc_trade_from_orders()
Trade.commit()
@@ -1411,17 +1416,15 @@ class FreqtradeBot(LoggingMixin):
return real_amount
return amount
def handle_order_fee(self, trade: Trade, order: Dict[str, Any]) -> Dict[str, Any]:
def handle_order_fee(self, trade: Trade, order_obj: Order, order: Dict[str, Any]) -> None:
# Try update amount (binance-fix)
try:
new_amount = self.get_real_amount(trade, order)
if not isclose(safe_value_fallback(order, 'filled', 'amount'), new_amount,
abs_tol=constants.MATH_CLOSE_PREC):
order['amount'] = new_amount
order.pop('filled', None)
order_obj.ft_fee_base = trade.amount - new_amount
except DependencyException as exception:
logger.warning("Could not update trade amount: %s", exception)
return order
def get_real_amount(self, trade: Trade, order: Dict) -> float:
"""

View File

@@ -57,7 +57,7 @@ def set_sequence_ids(engine, order_id, trade_id):
def migrate_trades_and_orders_table(
decl_base, inspector, engine,
trade_back_name: str, cols: List,
order_back_name: str):
order_back_name: str, cols_order: List):
fee_open = get_column_def(cols, 'fee_open', 'fee')
fee_open_cost = get_column_def(cols, 'fee_open_cost', 'null')
fee_open_currency = get_column_def(cols, 'fee_open_currency', 'null')
@@ -141,7 +141,7 @@ def migrate_trades_and_orders_table(
from {trade_back_name}
"""))
migrate_orders_table(engine, order_back_name, cols)
migrate_orders_table(engine, order_back_name, cols_order)
set_sequence_ids(engine, order_id, trade_id)
@@ -171,17 +171,19 @@ def drop_orders_table(engine, table_back_name: str):
connection.execute(text("drop table orders"))
def migrate_orders_table(engine, table_back_name: str, cols: List):
def migrate_orders_table(engine, table_back_name: str, cols_order: List):
ft_fee_base = get_column_def(cols_order, 'ft_fee_base', 'null')
# let SQLAlchemy create the schema as required
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,
order_date, order_filled_date, order_update_date)
order_date, order_filled_date, order_update_date, ft_fee_base)
select id, ft_trade_id, ft_order_side, ft_pair, ft_is_open, order_id,
status, symbol, order_type, side, price, amount, filled, null average, remaining, cost,
order_date, order_filled_date, order_update_date
order_date, order_filled_date, order_update_date, {ft_fee_base}
from {table_back_name}
"""))
@@ -200,6 +202,7 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
inspector = inspect(engine)
cols = inspector.get_columns('trades')
cols_orders = inspector.get_columns('orders')
tabs = get_table_names_for_table(inspector, 'trades')
table_back_name = get_backup_name(tabs, 'trades_bak')
order_tabs = get_table_names_for_table(inspector, 'orders')
@@ -207,14 +210,12 @@ def check_migrate(engine, decl_base, previous_tables) -> None:
# Check if migration necessary
# Migrates both trades and orders table!
if not has_column(cols, 'buy_tag'):
# if not has_column(cols, 'buy_tag'):
if 'orders' not in previous_tables or not has_column(cols_orders, 'ft_fee_base'):
logger.info(f"Running database migration for trades - "
f"backup: {table_back_name}, {order_table_bak_name}")
migrate_trades_and_orders_table(
decl_base, inspector, engine, table_back_name, cols, order_table_bak_name)
# Reread columns - the above recreated the table!
inspector = inspect(engine)
cols = inspector.get_columns('trades')
decl_base, inspector, engine, table_back_name, cols, order_table_bak_name, cols_orders)
if 'orders' not in previous_tables and 'trades' in previous_tables:
logger.info('Moving open orders to Orders table.')

View File

@@ -16,7 +16,6 @@ from sqlalchemy.sql.schema import UniqueConstraint
from freqtrade.constants import DATETIME_PRINT_FORMAT, NON_OPEN_EXCHANGE_STATES
from freqtrade.enums import SellType
from freqtrade.exceptions import DependencyException, OperationalException
from freqtrade.misc import safe_value_fallback
from freqtrade.persistence.migrations import check_migrate
@@ -116,14 +115,15 @@ class Order(_DECL_BASE):
trade = relationship("Trade", back_populates="orders")
ft_order_side = Column(String(25), nullable=False)
ft_pair = Column(String(25), nullable=False)
# order_side can only be 'buy', 'sell' or 'stoploss'
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)
order_id = Column(String(255), nullable=False, index=True)
status = Column(String(255), nullable=True)
symbol = Column(String(25), nullable=True)
order_type = Column(String(50), nullable=True)
order_type: str = Column(String(50), nullable=True)
side = Column(String(25), nullable=True)
price = Column(Float, nullable=True)
average = Column(Float, nullable=True)
@@ -135,10 +135,29 @@ class Order(_DECL_BASE):
order_filled_date = Column(DateTime, nullable=True)
order_update_date = Column(DateTime, nullable=True)
ft_fee_base = Column(Float, nullable=True)
@property
def order_date_utc(self):
def order_date_utc(self) -> datetime:
""" Order-date with UTC timezoneinfo"""
return self.order_date.replace(tzinfo=timezone.utc)
@property
def safe_price(self) -> float:
return self.average or self.price
@property
def safe_filled(self) -> float:
return self.filled or self.amount or 0.0
@property
def safe_fee_base(self) -> float:
return self.ft_fee_base or 0.0
@property
def safe_amount_after_fee(self) -> float:
return self.safe_filled - self.safe_fee_base
def __repr__(self):
return (f'Order(id={self.id}, order_id={self.order_id}, trade_id={self.ft_trade_id}, '
@@ -455,40 +474,39 @@ class LocalTrade():
f"Trailing stoploss saved us: "
f"{float(self.stop_loss) - float(self.initial_stop_loss):.8f}.")
def update(self, order: Dict) -> None:
def update_trade(self, order: Order) -> None:
"""
Updates this entity with amount and actual open/close rates.
:param order: order retrieved by exchange.fetch_order()
:return: None
"""
order_type = order['type']
# Ignore open and cancelled orders
if order['status'] == 'open' or safe_value_fallback(order, 'average', 'price') is None:
if order.status == 'open' or order.safe_price is None:
return
logger.info('Updating trade (id=%s) ...', self.id)
logger.info(f'Updating trade (id={self.id}) ...')
if order_type in ('market', 'limit') and order['side'] == 'buy':
if order.ft_order_side == 'buy':
# Update open rate and actual amount
self.open_rate = float(safe_value_fallback(order, 'average', 'price'))
self.amount = float(safe_value_fallback(order, 'filled', 'amount'))
self.open_rate = order.safe_price
self.amount = order.safe_amount_after_fee
if self.is_open:
logger.info(f'{order_type.upper()}_BUY has been fulfilled for {self}.')
logger.info(f'{order.order_type.upper()}_BUY has been fulfilled for {self}.')
self.open_order_id = None
self.recalc_trade_from_orders()
elif order_type in ('market', 'limit') and order['side'] == 'sell':
elif order.ft_order_side == 'sell':
if self.is_open:
logger.info(f'{order_type.upper()}_SELL has been fulfilled for {self}.')
self.close(safe_value_fallback(order, 'average', 'price'))
elif order_type in ('stop_loss_limit', 'stop-loss', 'stop-loss-limit', 'stop'):
logger.info(f'{order.order_type.upper()}_SELL has been fulfilled for {self}.')
self.close(order.safe_price)
elif order.ft_order_side == 'stoploss':
self.stoploss_order_id = None
self.close_rate_requested = self.stop_loss
self.sell_reason = SellType.STOPLOSS_ON_EXCHANGE.value
if self.is_open:
logger.info(f'{order_type.upper()} is hit for {self}.')
self.close(safe_value_fallback(order, 'average', 'price'))
logger.info(f'{order.order_type.upper()} is hit for {self}.')
self.close(order.safe_price)
else:
raise ValueError(f'Unknown order type: {order_type}')
raise ValueError(f'Unknown order type: {order.order_type}')
Trade.commit()
def close(self, rate: float, *, show_msg: bool = True) -> None:
@@ -631,7 +649,7 @@ class LocalTrade():
(o.status not in NON_OPEN_EXCHANGE_STATES)):
continue
tmp_amount = o.amount
tmp_amount = o.safe_amount_after_fee
tmp_price = o.average or o.price
if o.filled is not None:
tmp_amount = o.filled