From d4a1b9ceffcc3cc9bf2eea6f81f33ace8c96641d Mon Sep 17 00:00:00 2001 From: Raphael Deem Date: Wed, 27 Nov 2024 10:21:06 -0800 Subject: [PATCH 1/5] use broker id for order management --- brokers/base_broker.py | 4 ++++ brokers/tastytrade_broker.py | 10 +++++++++- brokers/tradier_broker.py | 1 + database/models.py | 1 + order_manager/manager.py | 3 ++- 5 files changed, 17 insertions(+), 2 deletions(-) diff --git a/brokers/base_broker.py b/brokers/base_broker.py index 7100a75..a3104ca 100644 --- a/brokers/base_broker.py +++ b/brokers/base_broker.py @@ -403,6 +403,8 @@ async def _place_order_generic( response = broker_order_func( symbol, quantity, side, price, order_type) + broker_id = response.get('order_id', None) + logger.info( 'Order placed successfully', extra={ @@ -410,6 +412,7 @@ async def _place_order_generic( 'symbol': symbol, 'quantity': quantity, 'side': side, + 'broker_id': broker_id, 'strategy': strategy}) # Extract price if not given @@ -423,6 +426,7 @@ async def _place_order_generic( executed_price=price, side=side, status='open', + broker_id=broker_id, timestamp=datetime.now(), broker=self.broker_name, strategy=strategy, diff --git a/brokers/tastytrade_broker.py b/brokers/tastytrade_broker.py index 565ed9c..486526c 100644 --- a/brokers/tastytrade_broker.py +++ b/brokers/tastytrade_broker.py @@ -244,6 +244,9 @@ async def _place_option_order(self, symbol, quantity, side, price=None, order_ty return {'filled_price': None} response = account.place_order(self.session, order, dry_run=False) + # TODO: refactor as part of introducing generic order method + if hasattr(response, 'order'): + return response.order return response async def _place_order(self, symbol, quantity, side, price=None, order_type='limit'): @@ -317,7 +320,12 @@ async def _place_order(self, symbol, quantity, side, price=None, order_type='lim else: logger.info('Order likely still open', extra={ 'order_data': response, 'symbol': symbol, 'quantity': quantity, 'side': side, 'price': price, 'order_type': order_type}) - return {'filled_price': price, 'order_id': getattr(response, 'id', 0)} + if hasattr(response, 'order'): + return {'filled_price': price, 'order_id': response.order.id} + else: + logger.error('Order placement failed', extra={'response': str( + response), 'symbol': symbol, 'quantity': quantity, 'side': side, 'price': price, 'order_type': order_type}) + return response except Exception as e: logger.error('Failed to place order', extra={'error': str(e)}) diff --git a/brokers/tradier_broker.py b/brokers/tradier_broker.py index 590053d..28d0c6a 100644 --- a/brokers/tradier_broker.py +++ b/brokers/tradier_broker.py @@ -188,6 +188,7 @@ def _place_order(self, symbol, quantity, side, price=None, order_type='limit'): data = order_json or {} if data.get('filled_price') is None: data['filled_price'] = price + data['order_id'] = order_id logger.info('Order execution complete', extra={'order_data': data}) return data except Exception as e: diff --git a/database/models.py b/database/models.py index 8b13fca..90bf3f3 100644 --- a/database/models.py +++ b/database/models.py @@ -9,6 +9,7 @@ class Trade(Base): __tablename__ = 'trades' id = Column(Integer, primary_key=True) + broker_id = Column(String, nullable=False) symbol = Column(String, nullable=False) quantity = Column(Integer, nullable=False) price = Column(Float, nullable=False) diff --git a/order_manager/manager.py b/order_manager/manager.py index e548a3d..38413ac 100644 --- a/order_manager/manager.py +++ b/order_manager/manager.py @@ -20,6 +20,7 @@ async def reconcile_orders(self, orders): async def reconcile_order(self, order): logger.info(f'Reconciling order {order.id}', extra={ 'order_id': order.id, + 'broker_id': order.broker_id, 'broker': order.broker, 'symbol': order.symbol, 'quantity': order.quantity, @@ -43,7 +44,7 @@ async def reconcile_order(self, order): # If the order is not stale, reconcile it broker = self.brokers[order.broker] - filled = await broker.is_order_filled(order.id) + filled = await broker.is_order_filled(order.broker_id) if filled: try: await self.db_manager.set_trade_filled(order.id) From 0ec775e4db121a94dfcffc1f7bdd8b9f5fc249f1 Mon Sep 17 00:00:00 2001 From: Raphael Deem Date: Wed, 27 Nov 2024 10:24:49 -0800 Subject: [PATCH 2/5] nullable field --- database/models.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/database/models.py b/database/models.py index 90bf3f3..78e0746 100644 --- a/database/models.py +++ b/database/models.py @@ -9,7 +9,8 @@ class Trade(Base): __tablename__ = 'trades' id = Column(Integer, primary_key=True) - broker_id = Column(String, nullable=False) + # TODO: make non-nullable + broker_id = Column(String, nullable=True) symbol = Column(String, nullable=False) quantity = Column(Integer, nullable=False) price = Column(Float, nullable=False) From e5723addcec9e27e2e86216eef634f41ab0583e4 Mon Sep 17 00:00:00 2001 From: Raphael Deem Date: Wed, 27 Nov 2024 10:28:38 -0800 Subject: [PATCH 3/5] fix type --- database/models.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/models.py b/database/models.py index 78e0746..f791b32 100644 --- a/database/models.py +++ b/database/models.py @@ -10,7 +10,7 @@ class Trade(Base): id = Column(Integer, primary_key=True) # TODO: make non-nullable - broker_id = Column(String, nullable=True) + broker_id = Column(Integer, nullable=True) symbol = Column(String, nullable=False) quantity = Column(Integer, nullable=False) price = Column(Float, nullable=False) From 219089f51a7d6ab30a614b3dce88907ff772326d Mon Sep 17 00:00:00 2001 From: Raphael Deem Date: Wed, 27 Nov 2024 10:30:21 -0800 Subject: [PATCH 4/5] mark stale --- order_manager/manager.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/order_manager/manager.py b/order_manager/manager.py index 38413ac..d5a27d3 100644 --- a/order_manager/manager.py +++ b/order_manager/manager.py @@ -44,6 +44,11 @@ async def reconcile_order(self, order): # If the order is not stale, reconcile it broker = self.brokers[order.broker] + if order.broker_id is None: + # If the order has no broker_id, mark it as stale + logger.info(f'Marking order {order.id} as stale, + missing broker_id', extra={'order_id': order.id}) + await self.db_manager.update_trade_status(order.id, 'stale') filled = await broker.is_order_filled(order.broker_id) if filled: try: From f3d535e561eddff15dccb73ca7d8855245010e94 Mon Sep 17 00:00:00 2001 From: Raphael Deem Date: Wed, 27 Nov 2024 10:31:09 -0800 Subject: [PATCH 5/5] format --- order_manager/manager.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/order_manager/manager.py b/order_manager/manager.py index d5a27d3..404f871 100644 --- a/order_manager/manager.py +++ b/order_manager/manager.py @@ -46,8 +46,7 @@ async def reconcile_order(self, order): broker = self.brokers[order.broker] if order.broker_id is None: # If the order has no broker_id, mark it as stale - logger.info(f'Marking order {order.id} as stale, - missing broker_id', extra={'order_id': order.id}) + logger.info(f'Marking order {order.id} as stale, missing broker_id', extra={'order_id': order.id}) await self.db_manager.update_trade_status(order.id, 'stale') filled = await broker.is_order_filled(order.broker_id) if filled: