From 7840aaf8f780c1e1fef7df49a37ff6358f1c6278 Mon Sep 17 00:00:00 2001 From: Daniel Shapira Date: Thu, 12 Sep 2024 00:50:22 +0300 Subject: [PATCH] feat: Add SignlePnL and OrderId support (#131) - adding the ability to set OrderId so we can place complex orders i.e bracket order or other parent/child orders - adding support to request single position PnL --------- Co-authored-by: Chip Kent <5250374+chipkent@users.noreply.github.com> --- src/deephaven_ib/__init__.py | 37 ++++++++++++++++++++++++++-- src/deephaven_ib/_tws/tws_client.py | 38 +++++++++++++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/src/deephaven_ib/__init__.py b/src/deephaven_ib/__init__.py index c0bd83fb..39402d00 100644 --- a/src/deephaven_ib/__init__.py +++ b/src/deephaven_ib/__init__.py @@ -384,7 +384,8 @@ class IbSessionTws: * **accounts_summary**: account summary. Automatically populated. * **accounts_positions**: account positions. Automatically populated. * **accounts_pnl**: account PNL. Automatically populated. - + * **accounts_pnl_single**: single PNL. populated by calling request_single_pnl() on a specific contract. + #### # News #### @@ -608,6 +609,15 @@ def deephaven_ib_parse_note(note:str, key:str) -> Optional[str]: .move_columns_up(["RequestId", "ReceiveTime", "Account", "ModelCode"]) \ .drop_columns("Note") \ .last_by("RequestId"), + "accounts_pnl_single": tables_raw["raw_accounts_pnl_single"] \ + .natural_join(tables_raw["raw_requests"], on="RequestId", joins="Note") \ + .update([ + "Account=(String)deephaven_ib_parse_note(Note,`account`)", + "ModelCode=(String)deephaven_ib_parse_note(Note,`model_code`)", + "ConId=(String)deephaven_ib_parse_note(Note,`conid`)"]) \ + .move_columns_up(["RequestId", "ReceiveTime", "Account", "ModelCode", "ConId"]) \ + .drop_columns("Note") \ + .last_by("RequestId"), "contracts_matching": tables_raw["raw_contracts_matching"] \ .natural_join(tables_raw["raw_requests"], on="RequestId", joins="Pattern=Note") \ .move_columns_up(["RequestId", "ReceiveTime", "Pattern"]) \ @@ -776,6 +786,24 @@ def request_account_positions(self, account: str, model_code: str = "") -> Reque req_id = self._client.request_account_positions(account, model_code) return Request(request_id=req_id) + def request_single_pnl(self, contract: RegisteredContract, account: str, model_code: str = "") -> Request: + """Request PNL updates for a single position. Results are returned in the ``accounts_pnl_single`` table. + + Args: + contract (RegisteredContract): contract data is requested for. + account (str): Account to request PNL for. + model_code (str): Model portfolio code to request PNL for. + + Returns: + A Request. + + Raises: + Exception: problem executing action. + """ + self._assert_connected() + req_id = self._client.request_single_pnl(account, model_code, contract.contract_details[0].contract.conId) + return Request(request_id=req_id) + #################################################################################################################### #################################################################################################################### @@ -1156,7 +1184,12 @@ def order_place(self, contract: RegisteredContract, order: Order) -> Request: raise Exception( f"RegisteredContracts with multiple contract details are not supported for orders: {contract}") - req_id = self._client.next_order_id() + if order.orderId == 0 or order.orderId is None: + req_id = self._client.next_order_id() + order.orderId = req_id + else: + req_id = order.orderId + cd = contract.contract_details[0] self._client.log_request(req_id, "PlaceOrder", cd.contract, {"order": f"Order({order})"}) self._client.placeOrder(req_id, cd.contract, order) diff --git a/src/deephaven_ib/_tws/tws_client.py b/src/deephaven_ib/_tws/tws_client.py index c666c5f9..a7b36cd3 100644 --- a/src/deephaven_ib/_tws/tws_client.py +++ b/src/deephaven_ib/_tws/tws_client.py @@ -182,6 +182,10 @@ def _build_table_writers() -> Dict[str, TableWriter]: table_writers["accounts_pnl"] = TableWriter( ["RequestId", "DailyPnl", "UnrealizedPnl", "RealizedPnl"], [dtypes.int64, dtypes.float64, dtypes.float64, dtypes.float64]) + + table_writers["accounts_pnl_single"] = TableWriter( + ["RequestId", "Position", "DailyPnL", "UnrealizedPnL", "RealizedPnL", "Value"], + [dtypes.int64, dtypes.float64, dtypes.float64, dtypes.float64, dtypes.float64, dtypes.float64]) #### # News @@ -590,6 +594,25 @@ def request_account_positions(self, account: str, model_code: str = "") -> int: self.reqPositionsMulti(reqId=req_id, account=account, modelCode=model_code) return req_id + def request_single_pnl(self, account: str, model_code: str, conid: int) -> int: + """Request PNL updates for a single position. Results are returned in the `accounts_pnl_single` table. + + Args: + account (str): Account to request PNL for. + model_code (str): Model portfolio code to request PNL for. + con_id (int): Contract ID of the position to request PNL for. + + Returns: + Request ID + + Raises: + Exception + """ + + req_id = self.request_id_manager.next_id() + self.log_request(req_id, "PnlSingle", None, {"account": account, "model_code": model_code, "conid": conid}) + self.reqPnLSingle(reqId=req_id, account=account, modelCode=model_code, conid=conid) + return req_id #### # reqManagedAccts @@ -605,6 +628,7 @@ def managedAccounts(self, accountsList: str): self.request_account_pnl(account) self.request_account_overview(account) self.request_account_positions(account) + #### # reqFamilyCodes @@ -698,6 +722,20 @@ def accountSummary(self, reqId: int, account: str, tag: str, value: str, currenc self._table_writers["accounts_summary"].write_row([reqId, account, tag, value, currency]) #### + # reqPnLSingle + #### + + def pnlSingle(self, reqId: int, pos: decimal.Decimal, dailyPnL: float, unrealizedPnL: float, realizedPnL: float, value: float): + EWrapper.pnlSingle(self, reqId, pos, dailyPnL, unrealizedPnL, realizedPnL, value) + self._table_writers["accounts_pnl_single"].write_row([ + reqId, + pos, + dailyPnL, + unrealizedPnL, + realizedPnL, + value + ]) + # reqPositions ####