architect-py 3.2.2__py3-none-any.whl → 5.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. architect_py/__init__.py +18 -2
  2. architect_py/async_client.py +1069 -647
  3. architect_py/client.py +25 -26
  4. architect_py/client_interface.py +63 -0
  5. architect_py/common_types/__init__.py +6 -0
  6. architect_py/common_types/order_dir.py +91 -0
  7. architect_py/common_types/scalars.py +25 -0
  8. architect_py/common_types/tradable_product.py +59 -0
  9. architect_py/graphql_client/__init__.py +2 -0
  10. architect_py/graphql_client/client.py +3 -6
  11. architect_py/graphql_client/enums.py +5 -0
  12. architect_py/graphql_client/fragments.py +3 -6
  13. architect_py/graphql_client/get_fills_query.py +2 -1
  14. architect_py/graphql_client/search_symbols_query.py +2 -1
  15. architect_py/graphql_client/subscribe_orderflow.py +2 -1
  16. architect_py/graphql_client/subscribe_trades.py +2 -1
  17. architect_py/grpc/__init__.py +145 -0
  18. architect_py/grpc/client.py +94 -0
  19. architect_py/{grpc_client → grpc/models}/Accounts/AccountsRequest.py +6 -3
  20. architect_py/{grpc_client → grpc/models}/Accounts/AccountsResponse.py +1 -1
  21. architect_py/{grpc_client → grpc/models}/Accounts/__init__.py +1 -1
  22. architect_py/grpc/models/Algo/AlgoOrder.py +114 -0
  23. architect_py/grpc/models/Algo/AlgoOrderRequest.py +46 -0
  24. architect_py/grpc/models/Algo/AlgoOrdersRequest.py +72 -0
  25. architect_py/grpc/models/Algo/AlgoOrdersResponse.py +27 -0
  26. architect_py/grpc/models/Algo/CreateAlgoOrderRequest.py +56 -0
  27. architect_py/grpc/models/Algo/PauseAlgoRequest.py +42 -0
  28. architect_py/grpc/models/Algo/PauseAlgoResponse.py +20 -0
  29. architect_py/grpc/models/Algo/StartAlgoRequest.py +42 -0
  30. architect_py/grpc/models/Algo/StartAlgoResponse.py +20 -0
  31. architect_py/grpc/models/Algo/StopAlgoRequest.py +42 -0
  32. architect_py/grpc/models/Algo/StopAlgoResponse.py +20 -0
  33. architect_py/{grpc_client → grpc/models}/Algo/__init__.py +1 -1
  34. architect_py/grpc/models/Auth/CreateJwtRequest.py +47 -0
  35. architect_py/grpc/models/Auth/CreateJwtResponse.py +23 -0
  36. architect_py/{grpc_client/Cpty → grpc/models/Auth}/__init__.py +1 -1
  37. architect_py/grpc/models/Boss/DepositsRequest.py +40 -0
  38. architect_py/grpc/models/Boss/DepositsResponse.py +27 -0
  39. architect_py/grpc/models/Boss/RqdAccountStatisticsRequest.py +42 -0
  40. architect_py/grpc/models/Boss/RqdAccountStatisticsResponse.py +25 -0
  41. architect_py/grpc/models/Boss/StatementUrlRequest.py +40 -0
  42. architect_py/grpc/models/Boss/StatementUrlResponse.py +23 -0
  43. architect_py/grpc/models/Boss/StatementsRequest.py +40 -0
  44. architect_py/grpc/models/Boss/StatementsResponse.py +27 -0
  45. architect_py/grpc/models/Boss/WithdrawalsRequest.py +40 -0
  46. architect_py/grpc/models/Boss/WithdrawalsResponse.py +27 -0
  47. architect_py/{grpc_client/Folio → grpc/models/Boss}/__init__.py +1 -1
  48. architect_py/grpc/models/Core/ConfigRequest.py +37 -0
  49. architect_py/grpc/models/Core/ConfigResponse.py +25 -0
  50. architect_py/grpc/models/Core/__init__.py +2 -0
  51. architect_py/{grpc_client → grpc/models}/Cpty/CptyRequest.py +2 -2
  52. architect_py/{grpc_client → grpc/models}/Cpty/CptyResponse.py +3 -3
  53. architect_py/{grpc_client → grpc/models}/Cpty/CptyStatus.py +1 -1
  54. architect_py/{grpc_client → grpc/models}/Cpty/CptyStatusRequest.py +2 -2
  55. architect_py/{grpc_client → grpc/models}/Cpty/CptysRequest.py +2 -2
  56. architect_py/{grpc_client → grpc/models}/Cpty/CptysResponse.py +1 -1
  57. architect_py/grpc/models/Cpty/__init__.py +2 -0
  58. architect_py/{grpc_client → grpc/models}/Folio/AccountHistoryRequest.py +2 -2
  59. architect_py/{grpc_client → grpc/models}/Folio/AccountHistoryResponse.py +1 -1
  60. architect_py/{grpc_client → grpc/models}/Folio/AccountSummariesRequest.py +2 -2
  61. architect_py/{grpc_client → grpc/models}/Folio/AccountSummariesResponse.py +1 -1
  62. architect_py/{grpc_client → grpc/models}/Folio/AccountSummary.py +1 -1
  63. architect_py/{grpc_client → grpc/models}/Folio/AccountSummaryRequest.py +2 -2
  64. architect_py/{grpc_client → grpc/models}/Folio/HistoricalFillsRequest.py +7 -4
  65. architect_py/{grpc_client → grpc/models}/Folio/HistoricalFillsResponse.py +1 -1
  66. architect_py/{grpc_client → grpc/models}/Folio/HistoricalOrdersRequest.py +3 -3
  67. architect_py/{grpc_client → grpc/models}/Folio/HistoricalOrdersResponse.py +1 -1
  68. architect_py/grpc/models/Folio/__init__.py +2 -0
  69. architect_py/{grpc_client → grpc/models}/Health/HealthCheckRequest.py +2 -2
  70. architect_py/{grpc_client → grpc/models}/Health/HealthCheckResponse.py +1 -1
  71. architect_py/grpc/models/Health/__init__.py +2 -0
  72. architect_py/{grpc_client → grpc/models}/Marketdata/Candle.py +1 -1
  73. architect_py/{grpc_client → grpc/models}/Marketdata/HistoricalCandlesRequest.py +11 -8
  74. architect_py/{grpc_client → grpc/models}/Marketdata/HistoricalCandlesResponse.py +1 -1
  75. architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshot.py +52 -5
  76. architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshotRequest.py +8 -3
  77. architect_py/{grpc_client → grpc/models}/Marketdata/L1BookSnapshotsRequest.py +6 -3
  78. architect_py/{grpc_client → grpc/models}/Marketdata/L2BookSnapshot.py +1 -1
  79. architect_py/{grpc_client → grpc/models}/Marketdata/L2BookSnapshotRequest.py +2 -2
  80. architect_py/{grpc_client → grpc/models}/Marketdata/Liquidation.py +2 -2
  81. architect_py/{grpc_client → grpc/models}/Marketdata/MarketStatus.py +1 -1
  82. architect_py/{grpc_client → grpc/models}/Marketdata/MarketStatusRequest.py +2 -2
  83. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeCandlesRequest.py +2 -2
  84. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeCurrentCandlesRequest.py +3 -4
  85. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeL1BookSnapshotsRequest.py +6 -3
  86. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeL2BookUpdatesRequest.py +2 -2
  87. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeLiquidationsRequest.py +2 -2
  88. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeManyCandlesRequest.py +2 -2
  89. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeTickersRequest.py +2 -2
  90. architect_py/{grpc_client → grpc/models}/Marketdata/SubscribeTradesRequest.py +2 -2
  91. architect_py/{grpc_client → grpc/models}/Marketdata/Ticker.py +1 -1
  92. architect_py/{grpc_client → grpc/models}/Marketdata/TickerRequest.py +2 -2
  93. architect_py/{grpc_client → grpc/models}/Marketdata/TickersRequest.py +2 -2
  94. architect_py/{grpc_client → grpc/models}/Marketdata/TickersResponse.py +1 -1
  95. architect_py/{grpc_client → grpc/models}/Marketdata/Trade.py +2 -2
  96. architect_py/grpc/models/Marketdata/__init__.py +2 -0
  97. architect_py/grpc/models/Oms/Cancel.py +90 -0
  98. architect_py/{grpc_client → grpc/models}/Oms/CancelAllOrdersRequest.py +2 -2
  99. architect_py/{grpc_client → grpc/models}/Oms/CancelAllOrdersResponse.py +1 -1
  100. architect_py/{grpc_client → grpc/models}/Oms/CancelOrderRequest.py +2 -2
  101. architect_py/{grpc_client → grpc/models}/Oms/OpenOrdersRequest.py +2 -2
  102. architect_py/{grpc_client → grpc/models}/Oms/OpenOrdersResponse.py +1 -1
  103. architect_py/{grpc_client → grpc/models}/Oms/Order.py +6 -13
  104. architect_py/{grpc_client → grpc/models}/Oms/PendingCancelsRequest.py +2 -2
  105. architect_py/{grpc_client → grpc/models}/Oms/PendingCancelsResponse.py +1 -1
  106. architect_py/{grpc_client → grpc/models}/Oms/PlaceOrderRequest.py +16 -23
  107. architect_py/grpc/models/Oms/__init__.py +2 -0
  108. architect_py/grpc/models/OptionsMarketdata/OptionsChain.py +30 -0
  109. architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeks.py +30 -0
  110. architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeksRequest.py +47 -0
  111. architect_py/grpc/models/OptionsMarketdata/OptionsChainRequest.py +45 -0
  112. architect_py/grpc/models/OptionsMarketdata/OptionsExpirations.py +29 -0
  113. architect_py/grpc/models/OptionsMarketdata/OptionsExpirationsRequest.py +42 -0
  114. architect_py/grpc/models/OptionsMarketdata/__init__.py +2 -0
  115. architect_py/{grpc_client → grpc/models}/Orderflow/DropcopyRequest.py +2 -2
  116. architect_py/{grpc_client → grpc/models}/Orderflow/OrderflowRequest.py +1 -1
  117. architect_py/{grpc_client → grpc/models}/Orderflow/SubscribeOrderflowRequest.py +2 -2
  118. architect_py/grpc/models/Orderflow/__init__.py +2 -0
  119. architect_py/grpc/models/Symbology/DownloadProductCatalogRequest.py +42 -0
  120. architect_py/grpc/models/Symbology/DownloadProductCatalogResponse.py +27 -0
  121. architect_py/grpc/models/Symbology/ExecutionInfoRequest.py +47 -0
  122. architect_py/grpc/models/Symbology/ExecutionInfoResponse.py +27 -0
  123. architect_py/{grpc_client → grpc/models}/Symbology/PruneExpiredSymbolsRequest.py +2 -2
  124. architect_py/{grpc_client → grpc/models}/Symbology/PruneExpiredSymbolsResponse.py +1 -1
  125. architect_py/{grpc_client → grpc/models}/Symbology/SubscribeSymbology.py +1 -1
  126. architect_py/{grpc_client → grpc/models}/Symbology/SymbologyRequest.py +2 -2
  127. architect_py/{grpc_client → grpc/models}/Symbology/SymbologySnapshot.py +7 -2
  128. architect_py/{grpc_client → grpc/models}/Symbology/SymbologyUpdate.py +9 -2
  129. architect_py/{grpc_client → grpc/models}/Symbology/SymbolsRequest.py +2 -2
  130. architect_py/{grpc_client → grpc/models}/Symbology/SymbolsResponse.py +1 -1
  131. architect_py/grpc/models/Symbology/UploadProductCatalogRequest.py +49 -0
  132. architect_py/grpc/models/Symbology/UploadProductCatalogResponse.py +20 -0
  133. architect_py/{grpc_client → grpc/models}/Symbology/UploadSymbologyRequest.py +2 -2
  134. architect_py/{grpc_client → grpc/models}/Symbology/UploadSymbologyResponse.py +1 -1
  135. architect_py/grpc/models/Symbology/__init__.py +2 -0
  136. architect_py/grpc/models/__init__.py +2 -0
  137. architect_py/{grpc_client → grpc/models}/definitions.py +690 -841
  138. architect_py/grpc/resolve_endpoint.py +70 -0
  139. architect_py/{grpc_client/grpc_server.py → grpc/server.py} +9 -6
  140. architect_py/grpc/utils.py +32 -0
  141. architect_py/internal_utils/__init__.py +0 -0
  142. architect_py/internal_utils/no_pandas.py +3 -0
  143. architect_py/tests/conftest.py +91 -87
  144. architect_py/tests/test_book_building.py +49 -50
  145. architect_py/tests/test_marketdata.py +168 -0
  146. architect_py/tests/test_order_entry.py +37 -0
  147. architect_py/tests/test_orderflow.py +41 -0
  148. architect_py/tests/test_portfolio_management.py +23 -0
  149. architect_py/tests/test_rounding.py +28 -28
  150. architect_py/tests/test_symbology.py +37 -30
  151. architect_py/utils/nearest_tick.py +2 -5
  152. architect_py/utils/nearest_tick_2.py +1 -2
  153. architect_py/utils/orderbook.py +35 -0
  154. architect_py/utils/pandas.py +44 -0
  155. architect_py/utils/price_bands.py +0 -3
  156. architect_py/utils/symbol_parsing.py +29 -0
  157. architect_py-5.0.0.dist-info/METADATA +54 -0
  158. architect_py-5.0.0.dist-info/RECORD +214 -0
  159. {architect_py-3.2.2.dist-info → architect_py-5.0.0.dist-info}/WHEEL +2 -1
  160. architect_py-5.0.0.dist-info/top_level.txt +4 -0
  161. examples/__init__.py +0 -0
  162. examples/book_subscription.py +52 -0
  163. examples/candles.py +30 -0
  164. examples/common.py +116 -0
  165. examples/external_cpty.py +77 -0
  166. examples/funding_rate_mean_reversion_algo.py +186 -0
  167. examples/order_sending.py +91 -0
  168. examples/stream_l1_marketdata.py +25 -0
  169. examples/stream_l2_marketdata.py +38 -0
  170. examples/trades.py +21 -0
  171. examples/tutorial_async.py +86 -0
  172. examples/tutorial_sync.py +95 -0
  173. scripts/generate_functions_md.py +166 -0
  174. scripts/generate_sync_interface.py +226 -0
  175. scripts/postprocess_grpc.py +604 -0
  176. scripts/preprocess_grpc_schema.py +708 -0
  177. templates/exceptions.py +83 -0
  178. templates/juniper_base_client.py +371 -0
  179. architect_py/client_protocol.py +0 -53
  180. architect_py/grpc_client/Algo/AlgoOrderForTwapAlgo.py +0 -61
  181. architect_py/grpc_client/Algo/CreateAlgoOrderRequestForTwapAlgo.py +0 -59
  182. architect_py/grpc_client/Algo/ModifyAlgoOrderRequestForTwapAlgo.py +0 -45
  183. architect_py/grpc_client/Health/__init__.py +0 -2
  184. architect_py/grpc_client/Marketdata/__init__.py +0 -2
  185. architect_py/grpc_client/Oms/Cancel.py +0 -42
  186. architect_py/grpc_client/Oms/__init__.py +0 -2
  187. architect_py/grpc_client/Orderflow/__init__.py +0 -2
  188. architect_py/grpc_client/Symbology/__init__.py +0 -2
  189. architect_py/grpc_client/__init__.py +0 -2
  190. architect_py/grpc_client/grpc_client.py +0 -413
  191. architect_py/scalars.py +0 -172
  192. architect_py/tests/test_accounts.py +0 -31
  193. architect_py/tests/test_client.py +0 -29
  194. architect_py/tests/test_grpc_client.py +0 -30
  195. architect_py/tests/test_order_sending.py +0 -65
  196. architect_py/tests/test_snapshots.py +0 -52
  197. architect_py/tests/test_subscriptions.py +0 -126
  198. architect_py-3.2.2.dist-info/METADATA +0 -191
  199. architect_py-3.2.2.dist-info/RECORD +0 -148
  200. /architect_py/{grpc_client → grpc/models}/Marketdata/ArrayOfL1BookSnapshot.py +0 -0
  201. /architect_py/{grpc_client → grpc/models}/Marketdata/L2BookUpdate.py +0 -0
  202. /architect_py/{grpc_client → grpc/models}/Marketdata/TickerUpdate.py +0 -0
  203. /architect_py/{grpc_client → grpc/models}/Orderflow/Dropcopy.py +0 -0
  204. /architect_py/{grpc_client → grpc/models}/Orderflow/Orderflow.py +0 -0
  205. {architect_py-3.2.2.dist-info → architect_py-5.0.0.dist-info/licenses}/LICENSE +0 -0
@@ -1,216 +1,362 @@
1
- """
2
- This file composes the GraphQLClient class to provide a higher-level interface
3
- for order entry with the Architect API.
4
-
5
- These are not required to send orders, but provide typed interfaces for the
6
- various order types and algorithms that can be sent to the OMS.
7
-
8
-
9
- The functions to send orders will return the order ID string
10
- After sending the order, this string can be used to retrieve the order status
11
-
12
- send_limit_order -> get_order
13
-
14
- The individual graphql types are subject to change, so it is not recommended to use them directly.
15
- """
16
-
17
1
  import asyncio
18
- import functools
19
2
  import logging
20
- from datetime import date, datetime
3
+ import re
4
+ from datetime import date, datetime, timedelta, timezone
21
5
  from decimal import Decimal
22
- from typing import Any, AsyncIterator, List, Optional, Sequence
23
-
24
- from architect_py.graphql_client.exceptions import GraphQLClientGraphQLMultiError
25
- from architect_py.graphql_client.get_fills_query import (
26
- GetFillsQueryFolioHistoricalFills,
6
+ from typing import (
7
+ Any,
8
+ AsyncGenerator,
9
+ AsyncIterator,
10
+ List,
11
+ Literal,
12
+ Optional,
13
+ Sequence,
14
+ Union,
15
+ overload,
27
16
  )
28
17
 
29
- from architect_py.grpc_client.Marketdata.Candle import Candle
30
- from architect_py.grpc_client.Marketdata.HistoricalCandlesRequest import (
31
- HistoricalCandlesRequest,
32
- )
33
- from architect_py.grpc_client.Marketdata.HistoricalCandlesResponse import (
34
- HistoricalCandlesResponse,
18
+ import grpc
19
+ import pandas as pd
20
+
21
+ from architect_py.grpc.models.Oms.Cancel import Cancel
22
+ from architect_py.grpc.models.Oms.CancelAllOrdersRequest import CancelAllOrdersRequest
23
+ from architect_py.grpc.models.Oms.CancelOrderRequest import CancelOrderRequest
24
+ from architect_py.grpc.models.Oms.OpenOrdersRequest import OpenOrdersRequest
25
+ from architect_py.grpc.models.Oms.Order import Order
26
+ from architect_py.grpc.models.Oms.PlaceOrderRequest import (
27
+ PlaceOrderRequest,
35
28
  )
36
- import architect_py.grpc_client.definitions as grpc_definitions
37
- from architect_py.graphql_client.place_order_mutation import PlaceOrderMutationOms
38
- from architect_py.grpc_client.Marketdata.L1BookSnapshot import L1BookSnapshot
39
- from architect_py.grpc_client.Marketdata.L2BookSnapshot import L2BookSnapshot
40
- from architect_py.grpc_client.Marketdata.L2BookUpdate import L2BookUpdate
41
- from architect_py.grpc_client.Marketdata.SubscribeCandlesRequest import (
42
- SubscribeCandlesRequest,
29
+ from architect_py.grpc.models.Orderflow.Orderflow import Orderflow
30
+ from architect_py.grpc.models.Orderflow.OrderflowRequest import (
31
+ OrderflowRequest,
32
+ OrderflowRequest_route,
33
+ OrderflowRequestUnannotatedResponseType,
43
34
  )
44
- from architect_py.grpc_client.Marketdata.SubscribeTradesRequest import (
45
- SubscribeTradesRequest,
35
+ from architect_py.grpc.models.Orderflow.SubscribeOrderflowRequest import (
36
+ SubscribeOrderflowRequest,
46
37
  )
47
- from architect_py.grpc_client.Marketdata.Trade import Trade
48
- from architect_py.scalars import OrderDir, TradableProduct
49
- from architect_py.utils.nearest_tick import TickRoundMethod
50
38
 
39
+ from .common_types import OrderDir, TradableProduct, Venue
51
40
  from .graphql_client import GraphQLClient
52
- from .graphql_client.enums import (
53
- OrderType,
54
- TimeInForce,
55
- )
41
+ from .graphql_client.exceptions import GraphQLClientGraphQLMultiError
56
42
  from .graphql_client.fragments import (
57
- AccountSummaryFields,
58
- AccountWithPermissionsFields,
59
- CancelFields,
60
43
  ExecutionInfoFields,
61
- L2BookFields,
62
- MarketStatusFields,
63
- MarketTickerFields,
64
- OrderFields,
65
44
  ProductInfoFields,
66
45
  )
67
-
68
- # from .graphql_client.input_types import (
69
- # CreateMMAlgo,
70
- # CreateOrder,
71
- # CreatePovAlgo,
72
- # CreateSmartOrderRouterAlgo,
73
- # CreateSpreadAlgo,
74
- # CreateSpreadAlgoHedgeMarket,
75
- # CreateTimeInForce,
76
- # CreateTimeInForceInstruction,
77
- # CreateTwapAlgo,
78
- # )
79
- from .grpc_client import GRPCClient
80
-
46
+ from .grpc import *
47
+ from .grpc.client import GrpcClient
48
+ from .utils.nearest_tick import TickRoundMethod
49
+ from .utils.orderbook import update_orderbook_side
50
+ from .utils.pandas import candles_to_dataframe
81
51
  from .utils.price_bands import price_band_pairs
82
-
83
- logger = logging.getLogger(__name__)
52
+ from .utils.symbol_parsing import nominative_expiration
84
53
 
85
54
 
86
55
  class AsyncClient:
56
+ api_key: Optional[str] = None
57
+ api_secret: Optional[str] = None
58
+ paper_trading: bool
87
59
  graphql_client: GraphQLClient
88
- grpc_client: GRPCClient
60
+ grpc_core: Optional[GrpcClient] = None
61
+ grpc_marketdata: dict[Venue, GrpcClient] = {}
62
+ grpc_hmart: Optional[GrpcClient] = None
63
+ jwt: str | None = None
64
+ jwt_expiration: datetime | None = None
65
+
66
+ l1_books: dict[Venue, dict[TradableProduct, tuple[L1BookSnapshot, asyncio.Task]]]
67
+ l2_books: dict[Venue, dict[TradableProduct, tuple[L2BookSnapshot, asyncio.Task]]]
89
68
 
90
69
  # ------------------------------------------------------------
91
- # Initialization
70
+ # Initialization and configuration
92
71
  # ------------------------------------------------------------
93
72
 
94
73
  @staticmethod
95
74
  async def connect(
96
75
  *,
97
- api_key: str,
98
- api_secret: str,
76
+ api_key: Optional[str] = None,
77
+ api_secret: Optional[str] = None,
99
78
  paper_trading: bool,
100
- host: str = "app.architect.co",
101
- grpc_endpoint: str = "cme.marketdata.architect.co",
102
- _port: Optional[int] = None,
79
+ endpoint: str = "https://app.architect.co",
80
+ graphql_port: Optional[int] = None,
103
81
  **kwargs: Any,
104
82
  ) -> "AsyncClient":
105
83
  """
106
- The main way to create an AsyncClient object.
107
-
108
- Args:
109
- api_key: API key for the user
110
- api_secret: API secret for the user
111
- host: Host for the GraphQL server, defaults to "app.architect.co"
112
- paper_trading: Whether to use the paper trading environment, defaults to True
113
- _port: Port for the GraphQL server, more for debugging purposes, do not set this unless you are sure of the port
84
+ Connect to an Architect installation.
114
85
 
115
- the API key and secret can be generated on the app.architect.co website
116
-
117
- Returns:
118
- Client object
119
-
120
- Raises:
121
- ValueError: If the API key or secret are not the correct length or contain invalid characters
86
+ Raises ValueError if the API key and secret are not the correct length or contain invalid characters.
87
+ """
88
+ if paper_trading:
89
+ COLOR = "\033[30;43m"
90
+ RESET = "\033[0m"
91
+ print(f"🧻 {COLOR} YOU ARE IN PAPER TRADING MODE {RESET}")
122
92
 
123
- For any request, if you get a "GraphQLClientHttpError: HTTP status code: 500" it likely means that your
124
- API key and secret are incorrect. Please double check your credentials.
93
+ if "grpc_endpoint" in kwargs:
94
+ logging.warning(
95
+ "as of v5.0.0: grpc_endpoint parameter is deprecated; ignored"
96
+ )
97
+ if "host" in kwargs:
98
+ logging.warning(
99
+ "as of v5.0.0: host parameter is deprecated, use endpoint instead; setting endpoint to %s",
100
+ kwargs["endpoint"],
101
+ )
102
+ endpoint = kwargs["endpoint"]
125
103
 
126
- If you get a "GraphQLClientHttpError: HTTP status code: 400", please contact support so we can fix the function.
104
+ grpc_host, grpc_port, use_ssl = await resolve_endpoint(endpoint)
105
+ logging.info(
106
+ f"Resolved endpoint {endpoint}: {grpc_host}:{grpc_port} use_ssl={use_ssl}"
107
+ )
127
108
 
128
- If you get an AttributeError on the grpc_client, it means that the GRPC client has not been initialized
129
- likely due to the client not being instantiated with the connect method
130
- """
109
+ # Sanity check paper trading on prod environments
131
110
  if paper_trading:
132
- logger.critical(
133
- "You are using the paper trading environment. Please make sure to switch to the live environment when you are ready."
134
- )
111
+ if grpc_host == "app.architect.co" or grpc_host == "staging.architect.co":
112
+ if grpc_port != 10081:
113
+ raise ValueError("Wrong gRPC port for paper trading")
114
+ if graphql_port is not None and graphql_port != 5678:
115
+ raise ValueError("Wrong GraphQL port for paper trading")
135
116
 
136
- async_client = AsyncClient(
117
+ client = AsyncClient(
137
118
  api_key=api_key,
138
119
  api_secret=api_secret,
139
- host=host,
140
120
  paper_trading=paper_trading,
141
- _port=_port,
121
+ grpc_host=grpc_host,
122
+ grpc_port=grpc_port,
123
+ graphql_port=graphql_port,
124
+ use_ssl=use_ssl,
142
125
  _i_know_what_i_am_doing=True,
143
- **kwargs,
144
126
  )
145
127
 
146
- async_client.grpc_client = GRPCClient(
147
- async_client.graphql_client, grpc_endpoint
148
- )
149
- await async_client.grpc_client.initialize()
150
- return async_client
128
+ logging.info("Exchanging credentials...")
129
+ await client.refresh_jwt()
130
+
131
+ logging.info("Discovering marketdata endpoints...")
132
+ await client.discover_marketdata()
133
+
134
+ return client
151
135
 
152
136
  def __init__(
153
137
  self,
154
138
  *,
155
- api_key: str,
156
- api_secret: str,
157
- host: str = "app.architect.co",
158
- paper_trading: bool = True,
159
- _port: Optional[int] = None,
139
+ api_key: Optional[str] = None,
140
+ api_secret: Optional[str] = None,
141
+ paper_trading: bool,
142
+ grpc_host: str = "app.architect.co",
143
+ grpc_port: int,
144
+ graphql_port: Optional[int] = None,
145
+ use_ssl: bool = True,
160
146
  _i_know_what_i_am_doing: bool = False,
161
- **kwargs: Any,
162
147
  ):
163
148
  """
164
- Users should essentially never be using this constructor directly.
165
-
166
- Use the connect method instead.
167
- See self.connect for arg explanations
149
+ Use AsyncClient.connect instead.
168
150
  """
169
-
170
151
  if not _i_know_what_i_am_doing:
171
- raise ValueError(
172
- "Please use the connect method to create an AsyncClient object."
173
- )
152
+ raise ValueError("Use AsyncClient.connect to create an AsyncClient object.")
174
153
 
175
- if not api_key.isalnum():
176
- raise ValueError(
177
- "API key must be alphanumeric, please double check your credentials."
178
- )
179
- elif "," in api_key or "," in api_secret:
180
- raise ValueError(
181
- "API key and secret cannot contain commas, please double check your credentials."
182
- )
183
- elif " " in api_key or " " in api_secret:
184
- raise ValueError(
185
- "API key and secret cannot contain spaces, please double check your credentials."
186
- )
187
- elif len(api_key) != 24 or len(api_secret) != 44:
154
+ if api_key is not None and not re.match(r"^[a-zA-Z0-9]{24}$", api_key):
155
+ raise ValueError("API key must be exactly 24 alphanumeric characters")
156
+ if api_secret is not None and not re.match(
157
+ r"^[a-zA-Z0-9+\/=]{44}$", api_secret
158
+ ):
188
159
  raise ValueError(
189
- "API key and secret are not the correct length, please double check your credentials."
160
+ "API secret must be a Base64-encoded string, 44 characters long"
190
161
  )
191
162
 
192
- if _port is None:
163
+ if graphql_port is None:
193
164
  if paper_trading:
194
- _port = 5678
165
+ graphql_port = 5678
195
166
  else:
196
- _port = 4567
167
+ graphql_port = 4567
197
168
 
169
+ self.api_key = api_key
170
+ self.api_secret = api_secret
171
+ self.paper_trading = paper_trading
198
172
  self.graphql_client = GraphQLClient(
199
- api_key=api_key, api_secret=api_secret, host=host, port=_port, **kwargs
173
+ host=grpc_host,
174
+ port=graphql_port,
175
+ use_tls=use_ssl,
176
+ api_key=api_key,
177
+ api_secret=api_secret,
200
178
  )
179
+ self.grpc_core = GrpcClient(host=grpc_host, port=grpc_port, use_ssl=use_ssl)
180
+
181
+ async def refresh_jwt(self, force: bool = False):
182
+ """
183
+ Refresh the JWT for the gRPC channel if it's nearing expiration (within 1 minute).
184
+ If force=True, refresh the JWT unconditionally.
185
+
186
+ Query methods on AsyncClient that require auth will call this method internally.
187
+ """
188
+ if not self.api_key or not self.api_secret:
189
+ raise ValueError("API key and secret not set")
190
+ if self.grpc_core is None:
191
+ raise ValueError("gRPC client to Architect not initialized")
192
+
193
+ if (
194
+ force
195
+ or self.jwt_expiration is None
196
+ or datetime.now() > self.jwt_expiration - timedelta(minutes=1)
197
+ ):
198
+ try:
199
+ req = CreateJwtRequest(api_key=self.api_key, api_secret=self.api_secret)
200
+ res: CreateJwtResponse = await self.grpc_core.unary_unary(req)
201
+ self.jwt = res.jwt
202
+ # CR alee: actually read the JWT to get the expiration time;
203
+ # for now, we just "know" that the JWTs are granted for an hour
204
+ self.jwt_expiration = datetime.now() + timedelta(hours=1)
205
+ except Exception as e:
206
+ logging.error("Failed to refresh gRPC credentials: %s", e)
207
+
208
+ def set_jwt(self, jwt: str | None, jwt_expiration: datetime | None = None):
209
+ """
210
+ Manually set the JWT for gRPC authentication.
211
+
212
+ Args:
213
+ jwt: the JWT to set;
214
+ None to clear the JWT
215
+ jwt_expiration: when to expire the JWT
216
+ """
217
+ self.jwt = jwt
218
+ self.jwt_expiration = jwt_expiration
219
+
220
+ async def discover_marketdata(self):
221
+ """
222
+ Load marketdata endpoints from the server config.
223
+
224
+ The Architect core is responsible for telling you where to find marketdata as per
225
+ its configuration. You can also manually set marketdata endpoints by calling
226
+ set_marketdata directly.
227
+
228
+ This method is called on AsyncClient.connect.
229
+ """
230
+ try:
231
+ grpc_client = await self.core()
232
+ req = ConfigRequest()
233
+ res: ConfigResponse = await grpc_client.unary_unary(req)
234
+ for venue, endpoint in res.marketdata.items():
235
+ try:
236
+ grpc_host, grpc_port, use_ssl = await resolve_endpoint(endpoint)
237
+ logging.info(
238
+ "Setting marketdata endpoint for %s: %s:%d use_ssl=%s",
239
+ venue,
240
+ grpc_host,
241
+ grpc_port,
242
+ use_ssl,
243
+ )
244
+ self.grpc_marketdata[venue] = GrpcClient(
245
+ host=grpc_host, port=grpc_port, use_ssl=use_ssl
246
+ )
247
+ except Exception as e:
248
+ logging.error("Failed to set marketdata endpoint: %s", e)
249
+ except Exception as e:
250
+ logging.error("Failed to get marketdata config: %s", e)
201
251
 
202
- async def enable_orderflow(self):
252
+ async def set_marketdata(self, venue: Venue, endpoint: str):
203
253
  """
204
- Load and cache product and execution info so that the SDK can send orders.
254
+ Manually set the marketdata endpoint for a venue.
255
+ """
256
+ try:
257
+ grpc_host, grpc_port, use_ssl = await resolve_endpoint(endpoint)
258
+ self.grpc_marketdata[venue] = GrpcClient(
259
+ host=grpc_host, port=grpc_port, use_ssl=use_ssl
260
+ )
261
+ except Exception as e:
262
+ logging.error("Failed to set marketdata endpoint: %s", e)
263
+
264
+ async def marketdata(self, venue: Venue) -> GrpcClient:
265
+ """
266
+ Get the marketdata client for a venue.
267
+ """
268
+ if venue not in self.grpc_marketdata:
269
+ raise ValueError(f"Marketdata not configured for venue: {venue}")
270
+
271
+ await self.refresh_jwt()
272
+ self.grpc_marketdata[venue].set_jwt(self.jwt)
273
+ return self.grpc_marketdata[venue]
274
+
275
+ async def set_hmart(self, endpoint: str):
276
+ """
277
+ Manually set the hmart (historical marketdata service) endpoint.
278
+ """
279
+ try:
280
+ grpc_host, grpc_port, use_ssl = await resolve_endpoint(endpoint)
281
+ logging.info(
282
+ "Resolved hmart endpoint %s: %s:%d use_ssl=%s",
283
+ endpoint,
284
+ grpc_host,
285
+ grpc_port,
286
+ use_ssl,
287
+ )
288
+ self.grpc_hmart = GrpcClient(
289
+ host=grpc_host, port=grpc_port, use_ssl=use_ssl
290
+ )
291
+ except Exception as e:
292
+ logging.error("Failed to set hmart endpoint: %s", e)
293
+
294
+ async def hmart(self) -> GrpcClient:
295
+ """
296
+ Get the hmart (historical marketdata service) client.
297
+ """
298
+ if self.grpc_hmart is None:
299
+ # default to historical.marketdata.architect.co
300
+ await self.set_hmart("https://historical.marketdata.architect.co")
205
301
 
206
- CR alee: determine if this is better than @functools.lru_cache
302
+ if self.grpc_hmart is None:
303
+ raise ValueError("hmart client not initialized")
304
+
305
+ await self.refresh_jwt()
306
+ self.grpc_hmart.set_jwt(self.jwt)
307
+ return self.grpc_hmart
308
+
309
+ async def core(self) -> GrpcClient:
310
+ """
311
+ Get the core client.
312
+ """
313
+ if self.grpc_core is None:
314
+ raise ValueError("gRPC client to Architect not initialized")
315
+
316
+ await self.refresh_jwt()
317
+ self.grpc_core.set_jwt(self.jwt)
318
+ return self.grpc_core
319
+
320
+ async def who_am_i(self) -> tuple[str, str]:
321
+ """
322
+ Gets the user_id and user_email for the user that the API key belongs to.
323
+
324
+ Returns:
325
+ (user_id, user_email)
207
326
  """
208
- pass
327
+ res = await self.graphql_client.user_id_query()
328
+ return res.user_id, res.user_email
329
+
330
+ def enable_orderflow(self):
331
+ """
332
+ @deprecated(reason="No longer needed; orderflow is enabled by default")
333
+ """
334
+ logging.warning(
335
+ "as of v5.0.0: enable_orderflow is deprecated; orderflow is enabled by default"
336
+ )
209
337
 
210
338
  # ------------------------------------------------------------
211
339
  # Symbology
212
340
  # ------------------------------------------------------------
213
341
 
342
+ async def list_symbols(self, *, marketdata: Optional[Venue] = None) -> list[str]:
343
+ """
344
+ List all symbols.
345
+
346
+ Args:
347
+ marketdata: query marketdata endpoint for the specified venue directly;
348
+ If provided, query the venue's marketdata endpoint directly,
349
+ instead of the Architect core. This is sometimes useful for
350
+ cross-referencing symbols or checking availability.
351
+ """
352
+ if marketdata is not None:
353
+ grpc_client = await self.marketdata(marketdata)
354
+ else:
355
+ grpc_client = await self.core()
356
+ req = SymbolsRequest()
357
+ res: SymbolsResponse = await grpc_client.unary_unary(req)
358
+ return res.symbols
359
+
214
360
  async def search_symbols(
215
361
  self,
216
362
  search_string: Optional[str] = None,
@@ -219,75 +365,74 @@ class AsyncClient:
219
365
  limit: int = 20,
220
366
  ) -> List[TradableProduct]:
221
367
  """
222
- Search for symbols in the Architect database.
368
+ Search for tradable products on Architect.
223
369
 
224
370
  Args:
225
- search_string: a string to search for in the symbol. Can be "*" for wild card search.
371
+ search_string: a string to search for in the symbol
372
+ Can be "*" for wild card search.
226
373
  Examples: "ES", "NQ", "GC"
227
374
  execution_venue: the execution venue to search in
228
375
  Examples: "CME"
229
- Returns:
230
- a list of TradableProduct objects
231
- """
232
- markets = (
233
- await self.graphql_client.search_symbols_query(
234
- search_string=search_string,
235
- execution_venue=execution_venue,
236
- offset=offset,
237
- limit=limit,
238
- )
239
- ).search_symbols
240
-
241
- return markets
376
+ """
377
+ res = await self.graphql_client.search_symbols_query(
378
+ search_string=search_string,
379
+ execution_venue=execution_venue,
380
+ offset=offset,
381
+ limit=limit,
382
+ )
383
+ return res.search_symbols
242
384
 
243
385
  async def get_product_info(self, symbol: str) -> Optional[ProductInfoFields]:
244
386
  """
245
- Get the product information (product_type, underlying, multiplier, etc.) for a symbol.
387
+ Get information about a product, e.g. product_type, underlying, multiplier.
246
388
 
247
389
  Args:
248
390
  symbol: the symbol to get information for
391
+ the symbol should *not* have a quote,
392
+ ie "ES 20250620 CME Future" instead of "ES 20250620 CME Future/USD"
393
+
394
+ If you used TradableProduct, you can use the base() method to get the symbol
395
+
249
396
  Returns:
250
- ProductInfoFields object if the symbol exists
397
+ None if the symbol does not exist
251
398
  """
252
- info = await self.graphql_client.get_product_info_query(symbol)
253
- return info.product_info
399
+ res = await self.graphql_client.get_product_info_query(symbol)
400
+ return res.product_info
254
401
 
255
402
  async def get_product_infos(
256
403
  self, symbols: Optional[list[str]]
257
404
  ) -> Sequence[ProductInfoFields]:
258
405
  """
259
- Get the product information (product_type, underlying, multiplier, etc.) for a list of symbols.
406
+ Get information about products, e.g. product_type, underlying, multiplier.
260
407
 
261
408
  Args:
262
- symbols: the symbols to get information for
263
- Returns:
264
- a list of ProductInfoFields
265
-
266
- Any duplicate or invalid symbols will be ignored.
267
- The order of the symbols in the list will not necessarily be preserved in the output.
409
+ symbols: the symbols to get information for, or None for all symbols
268
410
 
269
- If you want the entire universe of symbols, pass in None
411
+ Returns:
412
+ Product infos for each symbol. Not guaranteed to contain all symbols
413
+ that were asked for, or in the same order; any duplicates or invalid
414
+ symbols will be ignored.
270
415
  """
271
- infos = await self.graphql_client.get_product_infos_query(symbols)
272
- return infos.product_infos
416
+ res = await self.graphql_client.get_product_infos_query(symbols)
417
+ return res.product_infos
273
418
 
274
419
  async def get_execution_info(
275
- self, symbol: TradableProduct, execution_venue: str
420
+ self, symbol: TradableProduct | str, execution_venue: str
276
421
  ) -> Optional[ExecutionInfoFields]:
277
422
  """
278
- Get the execution information (tick_size, step_size, margin, etc.) for a symbol.
423
+ Get information about tradable product execution, e.g. tick_size,
424
+ step_size, margins.
279
425
 
280
426
  Args:
281
427
  symbol: the symbol to get execution information for
282
428
  execution_venue: the execution venue e.g. "CME"
283
429
 
284
430
  Returns:
285
- ExecutionInfoFields
286
-
431
+ None if the symbol doesn't exist
287
432
  """
288
433
  try:
289
434
  execution_info = await self.graphql_client.get_execution_info_query(
290
- symbol, execution_venue
435
+ TradableProduct(symbol), execution_venue
291
436
  )
292
437
  return execution_info.execution_info
293
438
  except GraphQLClientGraphQLMultiError:
@@ -297,63 +442,73 @@ class AsyncClient:
297
442
 
298
443
  async def get_execution_infos(
299
444
  self,
300
- symbols: Optional[list[TradableProduct]],
445
+ symbols: Optional[list[TradableProduct | str]],
301
446
  execution_venue: Optional[str] = None,
302
447
  ) -> Sequence[ExecutionInfoFields]:
303
448
  """
304
- Get the execution information (tick_size, step_size, etc.) for a list of symbols.
449
+ Get information about tradable product execution, e.g. tick_size,
450
+ step_size, margins, for many symbols.
305
451
 
306
452
  Args:
307
- symbols: the symbols to get execution information for
453
+ symbols: the symbols to get execution information for, or None for all symbols
308
454
  execution_venue: the execution venue e.g. "CME"
309
455
 
310
456
  Returns:
311
- a list of ExecutionInfoFields
312
-
313
- If you want the entire universe of execution infos, pass in None
457
+ Execution infos for each symbol. Not guaranteed to contain all symbols
458
+ that were asked for, or in the same order; any duplicates or invalid
459
+ symbols will be ignored.
314
460
  """
315
- execution_infos = await self.graphql_client.get_execution_infos_query(
316
- symbols, execution_venue
317
- )
318
- return execution_infos.execution_infos
461
+ if symbols is not None:
462
+ tps = [TradableProduct(symbol) for symbol in symbols]
463
+ else:
464
+ tps = None
465
+ res = await self.graphql_client.get_execution_infos_query(tps, execution_venue)
466
+ return res.execution_infos
319
467
 
320
468
  async def get_cme_first_notice_date(self, symbol: str) -> Optional[date]:
321
469
  """
470
+ @deprecated(reason="Use get_product_info instead; first_notice_date is now a field")
471
+
322
472
  Get the first notice date for a CME future.
323
473
 
324
474
  Args:
325
475
  symbol: the symbol to get the first notice date for a CME future
326
476
 
327
477
  Returns:
328
- the first notice date as a date object if it exists
478
+ The first notice date as a date object if it exists
329
479
  """
330
- notice = await self.graphql_client.get_first_notice_date_query(symbol)
331
- if notice is None or notice.product_info is None:
480
+ res = await self.graphql_client.get_first_notice_date_query(symbol)
481
+ if res is None or res.product_info is None:
332
482
  return None
333
- return notice.product_info.first_notice_date
483
+ return res.product_info.first_notice_date
334
484
 
335
485
  async def get_future_series(self, series_symbol: str) -> list[str]:
336
486
  """
337
- Get the series of futures for a given series symbol.
487
+ @deprecated(reason="Use get_futures_series instead")
488
+ """
489
+ futures = await self.get_futures_series(series_symbol)
490
+ return futures
491
+
492
+ async def get_futures_series(self, series_symbol: str) -> list[str]:
493
+ """
494
+ List all futures in a given series.
338
495
 
339
496
  Args:
340
- series_symbol: the symbol to get the series for
341
- e.g. ES CME Futures" would yield a list of all the ES futures
497
+ series_symbol: the futures series
498
+ e.g. "ES CME Futures" would yield a list of all the ES futures
342
499
  Returns:
343
- a list of symbols in the series
500
+ List of futures products
344
501
  """
345
- assert (
346
- " " in series_symbol
347
- ), 'series_symbol must have the venue in it, e.g. "ES CME Futures" or "GC CME Futures"'
348
-
349
- futures_series = await self.graphql_client.get_future_series_query(
350
- series_symbol
351
- )
352
- return futures_series.futures_series
502
+ if not series_symbol.endswith("Futures"):
503
+ raise ValueError("series_symbol must end with 'Futures'")
504
+ res = await self.graphql_client.get_future_series_query(series_symbol)
505
+ return res.futures_series
353
506
 
354
507
  @staticmethod
355
- def get_expiration_from_CME_name(name: str) -> date:
508
+ def get_expiration_from_CME_name(name: str) -> Optional[date]:
356
509
  """
510
+ @deprecated(reason="Use utils.symbol_parsing.nominative_expiration instead")
511
+
357
512
  Get the expiration date from a CME future name.
358
513
 
359
514
  Args:
@@ -362,12 +517,13 @@ class AsyncClient:
362
517
  Returns:
363
518
  the expiration date as a date object
364
519
  """
365
- _, d, *_ = name.split(" ")
366
- return datetime.strptime(d, "%Y%m%d").date()
520
+ return nominative_expiration(name)
367
521
 
368
522
  async def get_cme_futures_series(self, series: str) -> list[tuple[date, str]]:
369
523
  """
370
- Get the futures in a series from the CME.
524
+ @deprecated(reason="Use get_futures_series instead")
525
+
526
+ List all futures in a given CME series.
371
527
 
372
528
  Args:
373
529
  series: the series to get the futures for
@@ -377,39 +533,37 @@ class AsyncClient:
377
533
  the symbol for each future in the series
378
534
 
379
535
  e.g.
380
- [(datetime.date(2025, 3, 21), 'ES 20250321 CME Future'),
381
- (datetime.date(2025, 6, 20), 'ES 20250620 CME Future'),
382
- (datetime.date(2025, 9, 19), 'ES 20250919 CME Future'),
383
- ...
536
+ ```
537
+ [
538
+ (datetime.date(2025, 3, 21), 'ES 20250321 CME Future'),
539
+ (datetime.date(2025, 6, 20), 'ES 20250620 CME Future'),
540
+ (datetime.date(2025, 9, 19), 'ES 20250919 CME Future'),
541
+ # ...
384
542
  ]
543
+ ```
385
544
  """
386
- markets = await self.get_future_series(
387
- series,
388
- )
389
-
390
- filtered_markets = [
391
- (self.get_expiration_from_CME_name(market), market) for market in markets
392
- ]
393
-
394
- filtered_markets.sort(key=lambda x: x[0])
395
-
396
- return filtered_markets
545
+ futures = await self.get_futures_series(series)
546
+ exp_and_futures = []
547
+ for future in futures:
548
+ exp = nominative_expiration(future)
549
+ if exp is not None:
550
+ exp_and_futures.append((exp, future))
551
+ exp_and_futures.sort(key=lambda x: x[0])
552
+ return exp_and_futures
397
553
 
398
554
  async def get_cme_future_from_root_month_year(
399
555
  self, root: str, month: int, year: int
400
556
  ) -> str:
401
557
  """
402
558
  Get the symbol for a CME future from the root, month, and year.
559
+ This is a simple wrapper around search_symbols.
403
560
 
404
561
  Args:
405
562
  root: the root symbol for the future e.g. "ES"
406
563
  month: the month of the future
407
564
  year: the year of the future
408
565
  Returns:
409
- the symbol for the future
410
-
411
- Errors if the result is not unique
412
- This is a simple wrapper around search_symbols
566
+ The future symbol if it exists and is unique.
413
567
  """
414
568
  [market] = [
415
569
  market
@@ -419,101 +573,507 @@ class AsyncClient:
419
573
  )
420
574
  if market.startswith(f"{root} {year}{month:02d}")
421
575
  ]
422
-
423
576
  return market
424
577
 
425
578
  # ------------------------------------------------------------
426
- # Account Management
579
+ # Marketdata
427
580
  # ------------------------------------------------------------
428
581
 
429
- async def who_am_i(self) -> tuple[str, str]:
582
+ async def get_market_status(
583
+ self, symbol: TradableProduct | str, venue: Venue
584
+ ) -> MarketStatus:
430
585
  """
431
- Gets the user_id and user_email for the user that the API key belongs to.
586
+ Returns market status for symbol (e.g. if it's currently quoting or trading).
587
+
588
+ Args:
589
+ symbol: the symbol to get the market status for, e.g. "ES 20250321 CME Future/USD"
590
+ venue: the venue that the symbol is traded at, e.g. CME
591
+ """
592
+ grpc_client = await self.marketdata(venue)
593
+ req = MarketStatusRequest(symbol=str(symbol), venue=venue)
594
+ res: MarketStatus = await grpc_client.unary_unary(req)
595
+ return res
596
+
597
+ async def get_market_snapshot(
598
+ self, symbol: TradableProduct | str, venue: Venue
599
+ ) -> L1BookSnapshot:
600
+ """
601
+ @deprecated(reason="Use get_l1_snapshot instead")
432
602
 
603
+ This is an alias for l1_book_snapshot.
604
+
605
+ Args:
606
+ symbol: the symbol to get the market snapshot for, e.g. "ES 20250321 CME Future/USD"
607
+ venue: the venue that the symbol is traded at, e.g. CME
433
608
  Returns:
434
- (user_id, user_email)
609
+ L1BookSnapshot for the symbol
610
+ """
611
+ return await self.get_l1_book_snapshot(symbol=symbol, venue=venue)
612
+
613
+ async def get_market_snapshots(
614
+ self, symbols: list[TradableProduct | str], venue: Venue
615
+ ) -> Sequence[L1BookSnapshot]:
616
+ """
617
+ @deprecated(reason="Use get_l1_snapshots instead")
618
+
619
+ This is an alias for l1_book_snapshots.
620
+
621
+ Args:
622
+ symbols: the symbols to get the market snapshots for
623
+ venue: the venue that the symbols are traded at
624
+ """
625
+ return await self.get_l1_book_snapshots(
626
+ venue=venue,
627
+ symbols=symbols, # pyright: ignore[reportArgumentType]
628
+ )
629
+
630
+ @overload
631
+ async def get_historical_candles(
632
+ self,
633
+ symbol: TradableProduct | str,
634
+ venue: Venue,
635
+ candle_width: CandleWidth,
636
+ start: datetime,
637
+ end: datetime,
638
+ *,
639
+ as_dataframe: Literal[True],
640
+ ) -> pd.DataFrame: ...
641
+
642
+ @overload
643
+ async def get_historical_candles(
644
+ self,
645
+ symbol: TradableProduct | str,
646
+ venue: Venue,
647
+ candle_width: CandleWidth,
648
+ start: datetime,
649
+ end: datetime,
650
+ ) -> List[Candle]: ...
651
+
652
+ async def get_historical_candles(
653
+ self,
654
+ symbol: TradableProduct | str,
655
+ venue: Venue,
656
+ candle_width: CandleWidth,
657
+ start: datetime,
658
+ end: datetime,
659
+ *,
660
+ as_dataframe: bool = False,
661
+ ) -> Union[List[Candle], pd.DataFrame]:
662
+ """
663
+ Gets historical candles for a symbol.
664
+
665
+ Args:
666
+ symbol: the symbol to get the candles for
667
+ venue: the venue of the symbol
668
+ candle_width: the width of the candles
669
+ start: the start date to get candles for;
670
+ end: the end date to get candles for;
671
+ as_dataframe: if True, return a pandas DataFrame
672
+
673
+ """
674
+ grpc_client = await self.hmart()
675
+ if start.tzinfo is not timezone.utc:
676
+ raise ValueError(
677
+ "start must be a utc datetime:\n"
678
+ "for example datetime.now(timezone.utc) or \n"
679
+ "dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
680
+ )
681
+ if end.tzinfo is not timezone.utc:
682
+ raise ValueError(
683
+ "end must be a utc datetime:\n"
684
+ "for example datetime.now(timezone.utc) or \n"
685
+ "dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
686
+ )
687
+ req = HistoricalCandlesRequest(
688
+ venue=venue,
689
+ symbol=str(symbol),
690
+ candle_width=candle_width,
691
+ start_date=start,
692
+ end_date=end,
693
+ )
694
+ res: HistoricalCandlesResponse = await grpc_client.unary_unary(req)
695
+
696
+ if as_dataframe:
697
+ return candles_to_dataframe(res.candles)
698
+ else:
699
+ return res.candles
700
+
701
+ async def get_l1_book_snapshot(
702
+ self,
703
+ symbol: TradableProduct | str,
704
+ venue: Venue,
705
+ ) -> L1BookSnapshot:
706
+ """
707
+ Gets the L1 book snapshot for a symbol.
708
+
709
+ Args:
710
+ symbol: the symbol to get the l1 book snapshot for
711
+ venue: the venue that the symbol is traded at
712
+ """
713
+ grpc_client = await self.marketdata(venue)
714
+ req = L1BookSnapshotRequest(symbol=str(symbol), venue=venue)
715
+ res: L1BookSnapshot = await grpc_client.unary_unary(req)
716
+ return res
717
+
718
+ async def get_l1_book_snapshots(
719
+ self, symbols: list[TradableProduct | str], venue: Venue
720
+ ) -> Sequence[L1BookSnapshot]:
721
+ """
722
+ Gets the L1 book snapshots for a list of symbols.
723
+
724
+ Args:
725
+ symbols: the symbols to get the l1 book snapshots for
726
+ venue: the venue that the symbols are traded at
727
+ """
728
+ grpc_client = await self.marketdata(venue)
729
+ req = L1BookSnapshotsRequest(symbols=symbols)
730
+ res: ArrayOfL1BookSnapshot = await grpc_client.unary_unary(
731
+ req # pyright: ignore[reportArgumentType]
732
+ )
733
+ return res
734
+
735
+ async def get_l2_book_snapshot(
736
+ self, symbol: TradableProduct | str, venue: Venue
737
+ ) -> L2BookSnapshot:
738
+ """
739
+ Gets the L2 book snapshot for a symbol.
740
+
741
+ Args:
742
+ symbol: the symbol to get the l2 book snapshot for
743
+ venue: the venue that the symbol is traded at
744
+ """
745
+ grpc_client = await self.marketdata(venue)
746
+ req = L2BookSnapshotRequest(symbol=str(symbol), venue=venue)
747
+ res: L2BookSnapshot = await grpc_client.unary_unary(req)
748
+ return res
749
+
750
+ async def get_ticker(self, symbol: TradableProduct | str, venue: Venue) -> Ticker:
751
+ """
752
+ Gets the ticker for a symbol.
753
+ """
754
+ grpc_client = await self.marketdata(venue)
755
+ req = TickerRequest(symbol=str(symbol), venue=venue)
756
+ res: Ticker = await grpc_client.unary_unary(req)
757
+ return res
758
+
759
+ async def stream_l1_book_snapshots(
760
+ self, symbols: Sequence[TradableProduct | str], venue: Venue
761
+ ) -> AsyncGenerator[L1BookSnapshot, None]:
762
+ """
763
+ Subscribe to the stream of L1BookSnapshots for a symbol.
764
+
765
+ Args:
766
+ symbols: the symbols to subscribe to;
767
+ If symbols=None, subscribe to all symbols available for the venue.
768
+ venue: the venue to subscribe to
769
+ """
770
+ grpc_client = await self.marketdata(venue)
771
+ req = SubscribeL1BookSnapshotsRequest(symbols=list(symbols), venue=venue)
772
+ async for res in grpc_client.unary_stream(req):
773
+ yield res
774
+
775
+ async def stream_l2_book_updates(
776
+ self, symbol: TradableProduct | str, venue: Venue
777
+ ) -> AsyncGenerator[L2BookUpdate, None]:
778
+ """
779
+ Subscribe to the stream of L2BookUpdates for a symbol.
780
+
781
+ This stream is a diff stream; to construct and maintain the actual state of
782
+ the L2 book, apply the updates stream using the method described.
783
+
784
+ Args:
785
+ symbol: the symbol to subscribe to
786
+ venue: the marketdata venue
787
+ """
788
+ grpc_client = await self.marketdata(venue)
789
+ req = SubscribeL2BookUpdatesRequest(symbol=str(symbol), venue=venue)
790
+ async for res in grpc_client.unary_stream(
791
+ req # pyright: ignore[reportArgumentType]
792
+ ):
793
+ yield res
794
+
795
+ async def subscribe_l1_book(
796
+ self, symbol: TradableProduct | str, venue: Venue
797
+ ) -> L1BookSnapshot:
798
+ """
799
+ Subscribe to the L1 stream for a symbol in the background.
800
+
801
+ If a subscription is already active, the existing reference will be
802
+ returned; otherwise, a new subscription will be created.
803
+
804
+ Snapshots will have an initial value of timestamp=0 and bid/ask=None.
805
+
806
+ Args:
807
+ symbol: the symbol to subscribe to
808
+ venue: the marketdata venue
809
+
810
+ Return:
811
+ An L1 book object that is constantly updating in the background.
812
+ """
813
+ symbol = TradableProduct(symbol)
814
+
815
+ if venue in self.l1_books:
816
+ if symbol in self.l1_books[venue]:
817
+ return self.l1_books[venue][symbol][0]
818
+ else:
819
+ self.l1_books[venue] = {}
820
+
821
+ grpc_client = await self.marketdata(venue)
822
+ book = L1BookSnapshot(symbol, 0, 0)
823
+ self.l1_books[venue][symbol] = (
824
+ book,
825
+ asyncio.create_task(
826
+ self.__subscribe_l1_book_task(symbol, venue, grpc_client, book)
827
+ ),
828
+ )
829
+ return book
830
+
831
+ async def unsubscribe_l1_book(self, symbol: TradableProduct | str, venue: Venue):
832
+ """
833
+ Unsubscribe from the L1 stream for a symbol, ie undoes subscribe_l1_book.
834
+ """
835
+ symbol = TradableProduct(symbol)
836
+ try:
837
+ task = self.l1_books[venue][symbol][1]
838
+ task.cancel()
839
+ except Exception as e:
840
+ logging.error(
841
+ f"Error unsubscribing from L1 book for {symbol}, venue {venue}: {e}"
842
+ )
843
+ finally:
844
+ if venue in self.l1_books and symbol in self.l1_books[venue]:
845
+ del self.l1_books[venue][symbol]
846
+
847
+ async def __subscribe_l1_book_task(
848
+ self,
849
+ symbol: TradableProduct,
850
+ venue: Venue,
851
+ grpc_client: GrpcClient,
852
+ book: L1BookSnapshot,
853
+ ):
854
+ try:
855
+ req = SubscribeL1BookSnapshotsRequest(symbols=[symbol], venue=venue)
856
+ stream = grpc_client.unary_stream(req)
857
+ async for snap in stream:
858
+ book.tn = snap.tn
859
+ book.ts = snap.ts
860
+ book.a = snap.a
861
+ book.b = snap.b
862
+ book.rt = snap.rt
863
+ book.rtn = snap.rtn
864
+ except Exception as e:
865
+ logging.error(
866
+ f"Error subscribing to L1 book for {symbol}, venue {venue}: {e}"
867
+ )
868
+ finally:
869
+ del self.l1_books[venue][symbol]
870
+
871
+ async def subscribe_l2_book(
872
+ self,
873
+ symbol: TradableProduct | str,
874
+ venue: Venue,
875
+ ) -> L2BookSnapshot:
876
+ """
877
+ Subscribe to the L2 stream for a symbol in the background.
878
+
879
+ If a subscription is already active, the existing reference will be
880
+ returned; otherwise, a new subscription will be created.
881
+
882
+ Snapshots will have an initial value of timestamp=0 and bids/asks=[].
883
+
884
+ Args:
885
+ symbol: the symbol to subscribe to
886
+ venue: the marketdata venue
887
+
888
+ Return:
889
+ An L2 book object that is constantly updating in the background.
890
+ """
891
+ symbol = TradableProduct(symbol)
892
+
893
+ if venue in self.l2_books:
894
+ if symbol in self.l2_books[venue]:
895
+ return self.l2_books[venue][symbol][0]
896
+ else:
897
+ self.l2_books[venue] = {}
898
+
899
+ grpc_client = await self.marketdata(venue)
900
+ book = L2BookSnapshot([], [], 0, 0, 0, 0)
901
+ self.l2_books[venue][symbol] = (
902
+ book,
903
+ asyncio.create_task(
904
+ self.__subscribe_l2_book_task(symbol, venue, grpc_client, book)
905
+ ),
906
+ )
907
+ return book
908
+
909
+ async def __subscribe_l2_book_task(
910
+ self,
911
+ symbol: TradableProduct,
912
+ venue: Venue,
913
+ grpc_client: GrpcClient,
914
+ book: L2BookSnapshot,
915
+ ):
916
+ try:
917
+ req = SubscribeL2BookUpdatesRequest(symbol=str(symbol), venue=venue)
918
+ stream = grpc_client.unary_stream(
919
+ req # pyright: ignore[reportArgumentType]
920
+ )
921
+ async for up in stream:
922
+ if isinstance(up, L2BookDiff):
923
+ if (
924
+ up.sequence_id != book.sequence_id
925
+ or up.sequence_number != book.sequence_number + 1
926
+ ):
927
+ raise ValueError(
928
+ f"Received update out-of-order for L2 book: {symbol}"
929
+ )
930
+ book.sid = up.sid
931
+ book.sn = up.sn
932
+ book.ts = up.ts
933
+ book.tn = up.tn
934
+ for px, sz in up.bids:
935
+ update_orderbook_side(book.bids, px, sz, ascending=False)
936
+ for px, sz in up.asks:
937
+ update_orderbook_side(book.asks, px, sz, ascending=True)
938
+ elif isinstance(up, L2BookSnapshot):
939
+ book.sid = up.sid
940
+ book.sn = up.sn
941
+ book.ts = up.ts
942
+ book.tn = up.tn
943
+ book.a = up.a
944
+ book.b = up.b
945
+ except Exception as e:
946
+ logging.error(
947
+ f"Error subscribing to L2 book for {symbol}, venue {venue}: {e}"
948
+ )
949
+ finally:
950
+ del self.l2_books[venue][symbol]
951
+
952
+ async def stream_trades(
953
+ self, symbol: TradableProduct | str, venue: Venue
954
+ ) -> AsyncGenerator[Trade, None]:
955
+ """
956
+ Subscribe to a stream of trades for a symbol.
957
+ """
958
+ grpc_client = await self.marketdata(venue)
959
+ req = SubscribeTradesRequest(symbol=str(symbol), venue=venue)
960
+ async for res in grpc_client.unary_stream(req):
961
+ yield res
962
+
963
+ async def stream_candles(
964
+ self,
965
+ symbol: TradableProduct | str,
966
+ venue: Venue,
967
+ candle_widths: Optional[list[CandleWidth]],
968
+ ) -> AsyncGenerator[Candle, None]:
969
+ """
970
+ Subscribe to a stream of candles for a symbol.
435
971
  """
436
- user_id = await self.graphql_client.user_id_query()
972
+ grpc_client = await self.marketdata(venue)
973
+ req = SubscribeCandlesRequest(
974
+ symbol=str(symbol),
975
+ venue=venue,
976
+ candle_widths=candle_widths,
977
+ )
978
+ async for res in grpc_client.unary_stream(req):
979
+ yield res
437
980
 
438
- return user_id.user_id, user_id.user_email
981
+ # ------------------------------------------------------------
982
+ # Portfolio management
983
+ # ------------------------------------------------------------
439
984
 
440
- async def list_accounts(self) -> Sequence[AccountWithPermissionsFields]:
985
+ async def list_accounts(self) -> List[AccountWithPermissions]:
441
986
  """
442
987
  List accounts for the user that the API key belongs to.
443
988
 
444
989
  Returns:
445
990
  a list of AccountWithPermissionsFields for the user that the API key belongs to
991
+ a list of AccountWithPermissions for the user that the API key belongs to
446
992
  (use who_am_i to get the user_id / email)
447
993
  """
448
- accounts = await self.graphql_client.list_accounts_query()
449
- return accounts.accounts
994
+ grpc_client = await self.core()
995
+ req = AccountsRequest(paper=self.paper_trading)
996
+ res = await grpc_client.unary_unary(req)
997
+ return res.accounts
450
998
 
451
- async def get_account_summary(self, account: str) -> AccountSummaryFields:
999
+ async def get_account_summary(self, account: str) -> AccountSummary:
452
1000
  """
453
- Gets the account summary for the given account.
1001
+ Get account summary, including balances, positions, pnls, etc.
454
1002
 
455
1003
  Args:
456
- account: the account to get the summary for,
457
- can be the account id( (a UUID) or the account name (e.g. CQG:00000)
458
- Returns:
459
- AccountSummaryFields for the account
1004
+ account: account uuid or name
1005
+ Examples: "00000000-0000-0000-0000-000000000000", "STONEX:000000/JDoe"
460
1006
  """
461
- summary = await self.graphql_client.get_account_summary_query(account=account)
462
- return summary.account_summary
1007
+ grpc_client = await self.core()
1008
+ req = AccountSummaryRequest(account=account)
1009
+ res = await grpc_client.unary_unary(req)
1010
+ return res
463
1011
 
464
1012
  async def get_account_summaries(
465
1013
  self,
466
1014
  accounts: Optional[list[str]] = None,
467
1015
  trader: Optional[str] = None,
468
- ) -> Sequence[AccountSummaryFields]:
1016
+ ) -> list[AccountSummary]:
469
1017
  """
470
- Gets the account summaries for the given accounts and trader.
1018
+ Get account summaries for accounts matching the filters.
471
1019
 
472
1020
  Args:
473
- accounts: a list of account ids to get summaries for,
474
- can be the account id( (a UUID) or the account name (e.g. CQG:00000)
475
- trader: the trader / userId to get summaries for
1021
+ accounts: list of account uuids or names
1022
+ trader: if specified, return summaries for all accounts for this trader
476
1023
 
477
- if both arguments are given, the accounts are all appended and returned together
478
- Returns:
479
- a list of AccountSummary for the accounts
1024
+ If both arguments are given, the union of matching accounts are returned.
480
1025
  """
481
- summaries = await self.graphql_client.get_account_summaries_query(
482
- trader=trader, accounts=accounts
1026
+ grpc_client = await self.core()
1027
+ request = AccountSummariesRequest(
1028
+ accounts=accounts,
1029
+ trader=trader,
483
1030
  )
484
- return summaries.account_summaries
1031
+ res = await grpc_client.unary_unary(request)
1032
+ return res.account_summaries
485
1033
 
486
1034
  async def get_account_history(
487
1035
  self,
488
1036
  account: str,
489
1037
  from_inclusive: Optional[datetime] = None,
490
1038
  to_exclusive: Optional[datetime] = None,
491
- ) -> Sequence[AccountSummaryFields]:
1039
+ ) -> list[AccountSummary]:
492
1040
  """
493
- Gets the account history for the given account and dates.
494
-
495
- Returns:
496
- a list of AccountSummaryFields for the account for the given dates
497
- use timestamp to get the time of the of the summary
1041
+ Get historical sequence of account summaries for the given account.
498
1042
  """
499
- history = await self.graphql_client.get_account_history_query(
1043
+ grpc_client = await self.core()
1044
+ if from_inclusive is not None:
1045
+ assert from_inclusive.tzinfo is timezone.utc, (
1046
+ "from_inclusive must be a utc datetime:\n"
1047
+ "for example datetime.now(timezone.utc) or \n"
1048
+ "dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
1049
+ )
1050
+
1051
+ if to_exclusive is not None:
1052
+ assert to_exclusive.tzinfo is timezone.utc, (
1053
+ "to_exclusive must be a utc datetime:\n"
1054
+ "for example datetime.now(timezone.utc) or \n"
1055
+ "dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
1056
+ )
1057
+
1058
+ req = AccountHistoryRequest(
500
1059
  account=account, from_inclusive=from_inclusive, to_exclusive=to_exclusive
501
1060
  )
502
- return history.account_history
1061
+ res = await grpc_client.unary_unary(req)
1062
+ return res.history
503
1063
 
504
1064
  # ------------------------------------------------------------
505
- # Order Management
1065
+ # Order management
506
1066
  # ------------------------------------------------------------
507
1067
 
508
1068
  async def get_open_orders(
509
1069
  self,
510
- order_ids: Optional[list[str]] = None,
1070
+ order_ids: Optional[list[OrderId]] = None,
511
1071
  venue: Optional[str] = None,
512
1072
  account: Optional[str] = None,
513
1073
  trader: Optional[str] = None,
514
1074
  symbol: Optional[str] = None,
515
- parent_order_id: Optional[str] = None,
516
- ) -> Sequence[OrderFields]:
1075
+ parent_order_id: Optional[OrderId] = None,
1076
+ ) -> list[Order]:
517
1077
  """
518
1078
  Returns a list of open orders for the user that match the filters.
519
1079
 
@@ -525,12 +1085,11 @@ class AsyncClient:
525
1085
  symbol: the symbol to get orders for
526
1086
  parent_order_id: the parent order id to get orders for
527
1087
 
528
- these filters are combinewd via OR statements so if you pass
529
- in multiple arguments, it will return the union of the results
530
1088
  Returns:
531
- a list of OrderFields of the open orders that match the union of the filters
1089
+ Open orders that match the union of the filters
532
1090
  """
533
- orders = await self.graphql_client.get_open_orders_query(
1091
+ grpc_client = await self.core()
1092
+ open_orders_request = OpenOrdersRequest(
534
1093
  venue=venue,
535
1094
  account=account,
536
1095
  trader=trader,
@@ -538,46 +1097,63 @@ class AsyncClient:
538
1097
  parent_order_id=parent_order_id,
539
1098
  order_ids=order_ids,
540
1099
  )
541
- return orders.open_orders
542
1100
 
543
- async def get_all_open_orders(self) -> Sequence[OrderFields]:
1101
+ open_orders = await grpc_client.unary_unary(open_orders_request)
1102
+ return open_orders.open_orders
1103
+
1104
+ async def get_all_open_orders(self) -> list[Order]:
544
1105
  """
545
- Returns a list of all open orders for the user.
1106
+ @deprecated(reason="Use get_open_orders with no parameters instead")
546
1107
 
547
- Returns:
548
- a list of OrderFields of all the open orders for the user
1108
+ Returns a list of all open orders for the authenticated user.
549
1109
  """
550
- orders = await self.graphql_client.get_open_orders_query()
551
- return orders.open_orders
1110
+ return await self.get_open_orders()
552
1111
 
553
1112
  async def get_historical_orders(
554
1113
  self,
555
- order_ids: Optional[list[str]] = None,
1114
+ order_ids: Optional[list[OrderId]] = None,
556
1115
  from_inclusive: Optional[datetime] = None,
557
1116
  to_exclusive: Optional[datetime] = None,
558
1117
  venue: Optional[str] = None,
559
1118
  account: Optional[str] = None,
560
- parent_order_id: Optional[str] = None,
561
- ) -> Sequence[OrderFields]:
1119
+ parent_order_id: Optional[OrderId] = None,
1120
+ ) -> list[Order]:
562
1121
  """
563
- Gets the historical orders that match the filters.
1122
+ Returns a list of all historical orders that match the filters.
1123
+
1124
+ Historical orders are orders that are not open, having been filled,
1125
+ canceled, expired, or outed.
564
1126
 
565
1127
  Args:
566
1128
  order_ids: a list of order ids to get
567
1129
  from_inclusive: the start date to get orders for
568
1130
  to_exclusive: the end date to get orders for
569
1131
  venue: the venue to get orders for, e.g. CME
570
- account: the account to get orders for,
571
- can be the account id( (a UUID) or the account name (e.g. CQG:00000)
1132
+ account: account uuid or name
572
1133
  parent_order_id: the parent order id to get orders for
573
1134
  Returns:
574
- a list of OrderFields of the historical orders that match the filters
1135
+ Historical orders that match the union of the filters.
575
1136
 
576
- either the order_ids parameter needs to be filled
577
- OR
578
- the from_inclusive and to_exclusive parameters need to be filled
1137
+ If order_ids is not specified, then from_inclusive and to_exclusive
1138
+ MUST be specified.
579
1139
  """
580
- orders = await self.graphql_client.get_historical_orders_query(
1140
+ grpc_client = await self.core()
1141
+
1142
+ if from_inclusive is not None:
1143
+ assert from_inclusive.tzinfo is timezone.utc, (
1144
+ "from_inclusive must be a utc datetime:\n"
1145
+ "for example datetime.now(timezone.utc) or \n"
1146
+ "dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
1147
+ )
1148
+
1149
+ if to_exclusive is not None:
1150
+ assert to_exclusive.tzinfo is timezone.utc, (
1151
+ "to_exclusive must be a utc datetime:\n"
1152
+ "for example datetime.now(timezone.utc) or \n"
1153
+ "dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
1154
+ )
1155
+
1156
+ historical_orders_request = HistoricalOrdersRequest.new(
581
1157
  order_ids=order_ids,
582
1158
  venue=venue,
583
1159
  account=account,
@@ -585,406 +1161,221 @@ class AsyncClient:
585
1161
  from_inclusive=from_inclusive,
586
1162
  to_exclusive=to_exclusive,
587
1163
  )
588
- return orders.historical_orders
1164
+ orders = await grpc_client.unary_unary(historical_orders_request)
1165
+ return orders.orders
589
1166
 
590
- async def get_order(self, order_id: str) -> Optional[OrderFields]:
1167
+ async def get_order(self, order_id: OrderId) -> Optional[Order]:
591
1168
  """
592
- Returns the OrderFields object for the specified order.
593
- Useful for looking at past sent orders.
1169
+ Returns the specified order. Useful for looking at past sent orders.
1170
+ Queries open_orders first, then queries historical_orders.
594
1171
 
595
1172
  Args:
596
1173
  order_id: the order id to get
597
- Returns:
598
- the OrderFields object for the order
599
-
600
- Queries open_orders first then queries historical_orders
601
1174
  """
602
- open_orders = await self.graphql_client.get_open_orders_query(
603
- order_ids=[order_id]
1175
+ grpc_client = await self.core()
1176
+ req = OpenOrdersRequest.new(
1177
+ order_ids=[order_id],
604
1178
  )
605
-
606
- for open_order in open_orders.open_orders:
1179
+ res = await grpc_client.unary_unary(req)
1180
+ for open_order in res.open_orders:
607
1181
  if open_order.id == order_id:
608
1182
  return open_order
609
1183
 
610
- historical_orders = await self.graphql_client.get_historical_orders_query(
611
- order_ids=[order_id]
1184
+ req = HistoricalOrdersRequest.new(
1185
+ order_ids=[order_id],
612
1186
  )
1187
+ res = await grpc_client.unary_unary(req)
1188
+ if res.orders and len(res.orders) == 1:
1189
+ return res.orders[0]
613
1190
 
614
- if historical_orders.historical_orders:
615
- return historical_orders.historical_orders[0]
616
-
617
- async def get_orders(self, order_ids: list[str]) -> list[Optional[OrderFields]]:
1191
+ async def get_orders(self, order_ids: list[OrderId]) -> list[Optional[Order]]:
618
1192
  """
619
- Returns a list of OrderFields objects for the specified orders.
620
- Useful for looking at past sent orders.
1193
+ Returns the specified orders. Useful for looking at past sent orders.
1194
+ Plural form of get_order.
621
1195
 
622
1196
  Args:
623
1197
  order_ids: a list of order ids to get
624
- Returns:
625
- a list of OrderFields objects for the orders
626
-
627
- Plural form of get_order
628
1198
  """
629
- orders_dict: dict[str, Optional[OrderFields]] = {
1199
+ grpc_client = await self.core()
1200
+ orders_dict: dict[OrderId, Optional[Order]] = {
630
1201
  order_id: None for order_id in order_ids
631
1202
  }
1203
+ req = OpenOrdersRequest.new(
1204
+ order_ids=order_ids,
1205
+ )
632
1206
 
633
- open_orders = (
634
- await self.graphql_client.get_open_orders_query(order_ids=order_ids)
635
- ).open_orders
636
- for open_order in open_orders:
1207
+ res = await grpc_client.unary_unary(req)
1208
+ for open_order in res.open_orders:
637
1209
  orders_dict[open_order.id] = open_order
638
1210
 
639
1211
  not_open_order_ids = [
640
1212
  order_id for order_id in order_ids if orders_dict[order_id] is None
641
1213
  ]
642
1214
 
643
- historical_orders = (
644
- await self.graphql_client.get_historical_orders_query(
645
- order_ids=not_open_order_ids
646
- )
647
- ).historical_orders
648
- for historical_order in historical_orders:
1215
+ req = HistoricalOrdersRequest.new(
1216
+ order_ids=not_open_order_ids,
1217
+ )
1218
+ res = await grpc_client.unary_unary(req)
1219
+ for historical_order in res.orders:
649
1220
  orders_dict[historical_order.id] = historical_order
650
1221
 
651
1222
  return [orders_dict[order_id] for order_id in order_ids]
652
1223
 
653
1224
  async def get_fills(
654
1225
  self,
655
- from_inclusive: Optional[datetime],
656
- to_exclusive: Optional[datetime],
1226
+ from_inclusive: Optional[datetime] = None,
1227
+ to_exclusive: Optional[datetime] = None,
657
1228
  venue: Optional[str] = None,
658
1229
  account: Optional[str] = None,
659
- order_id: Optional[str] = None,
660
- ) -> GetFillsQueryFolioHistoricalFills:
1230
+ order_id: Optional[OrderId] = None,
1231
+ limit: Optional[int] = None,
1232
+ ) -> HistoricalFillsResponse:
661
1233
  """
662
- Returns a list of fills for the given filters.
1234
+ Returns all fills matching the given filters.
663
1235
 
664
1236
  Args:
665
1237
  from_inclusive: the start date to get fills for
666
1238
  to_exclusive: the end date to get fills for
667
1239
  venue: the venue to get fills for, e.g. "CME"
668
- account: the account to get fills for,
669
- can be the account id( (a UUID) or the account name (e.g. CQG:00000)
1240
+ account: account uuid or name
670
1241
  order_id: the order id to get fills for
671
- Returns:
672
- a list of GetFillsQueryFolioHistoricalFills
673
- """
674
- fills = await self.graphql_client.get_fills_query(
675
- venue, account, order_id, from_inclusive, to_exclusive
676
- )
677
- return fills.historical_fills
678
-
679
- # ------------------------------------------------------------
680
- # Market Data
681
- # ------------------------------------------------------------
682
-
683
- async def get_market_status(
684
- self, symbol: TradableProduct, venue: str
685
- ) -> MarketStatusFields:
686
- """
687
- Returns market status for symbol (ie if it is quoting and trading).
688
-
689
- Args:
690
- symbol: the symbol to get the market status for, e.g. "ES 20250321 CME Future/USD"
691
- venue: the venue that the symbol is traded at, e.g. CME
692
- Returns:
693
- MarketStatusFields for the symbol
694
- """
695
- market_status = await self.graphql_client.get_market_status_query(symbol, venue)
696
- return market_status.market_status
697
-
698
- async def get_market_snapshot(
699
- self, symbol: TradableProduct, venue: str
700
- ) -> MarketTickerFields:
701
- """
702
- This is an alias for l1_book_snapshot.
703
-
704
- Args:
705
- symbol: the symbol to get the market snapshot for, e.g. "ES 20250321 CME Future/USD"
706
- venue: the venue that the symbol is traded at, e.g. CME
707
- Returns:
708
- MarketTickerFields for the symbol
709
- """
710
- return await self.get_l1_book_snapshot(symbol=symbol, venue=venue)
711
-
712
- async def get_market_snapshots(
713
- self, symbols: list[TradableProduct], venue: str
714
- ) -> Sequence[MarketTickerFields]:
715
- """
716
- This is an alias for l1_book_snapshot.
717
-
718
- Args:
719
- symbols: the symbols to get the market snapshots for
720
- venue: the venue that the symbols are traded at
721
- Returns:
722
- a list of MarketTickerFields for the symbols
723
- """
724
- return await self.get_l1_book_snapshots(
725
- venue=venue, symbols=symbols # type: ignore
726
- )
727
-
728
- async def get_historical_candles(
729
- self,
730
- symbol: str,
731
- candle_width: grpc_definitions.CandleWidth,
732
- start: datetime,
733
- end: datetime,
734
- ) -> HistoricalCandlesResponse:
735
1242
  """
736
- Gets the historical candles for a symbol.
1243
+ grpc_client = await self.core()
1244
+ if from_inclusive is not None:
1245
+ assert from_inclusive.tzinfo is timezone.utc, (
1246
+ "from_inclusive must be a utc datetime:\n"
1247
+ "for example datetime.now(timezone.utc) or \n"
1248
+ "dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
1249
+ )
737
1250
 
738
- Args:
739
- symbol: the symbol to get the candles for
740
- venue: the venue of the symbol
741
- candle_width: the width of the candles
742
- start: the start date to get candles for
743
- end: the end date to get candles for
744
- Returns:
745
- a list of CandleFields for the specified candles
746
- """
747
- request = HistoricalCandlesRequest(
748
- symbol=symbol,
749
- candle_width=candle_width,
750
- start_date=start,
751
- end_date=end,
1251
+ if to_exclusive is not None:
1252
+ assert to_exclusive.tzinfo is timezone.utc, (
1253
+ "to_exclusive must be a utc datetime:\n"
1254
+ "for example datetime.now(timezone.utc) or \n"
1255
+ "dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
1256
+ )
1257
+ req = HistoricalFillsRequest(
1258
+ account=account,
1259
+ from_inclusive=from_inclusive,
1260
+ limit=limit,
1261
+ order_id=order_id,
1262
+ to_exclusive=to_exclusive,
1263
+ venue=venue,
752
1264
  )
1265
+ res = await grpc_client.unary_unary(req)
1266
+ return res
753
1267
 
754
- return await self.grpc_client.request(request)
755
-
756
- async def get_l1_book_snapshot(
1268
+ async def orderflow(
757
1269
  self,
758
- symbol: str,
759
- venue: str,
760
- ) -> MarketTickerFields:
1270
+ request_iterator: AsyncIterator[OrderflowRequest],
1271
+ ) -> AsyncGenerator[Orderflow, None]:
761
1272
  """
762
- Gets the L1 book snapshot for a symbol.
1273
+ A two-way channel for both order entry and listening to order updates (fills, acks, outs, etc.).
763
1274
 
764
- Args:
765
- symbol: the symbol to get the l1 book snapshot for
766
- venue: the venue that the symbol is traded at
767
- Returns:
768
- MarketTickerFields for the symbol
769
- """
770
- snapshot = await self.graphql_client.get_l_1_book_snapshot_query(
771
- symbol=symbol, venue=venue
772
- )
773
- return snapshot.ticker
1275
+ This is considered the most efficient way to trade in this SDK.
774
1276
 
775
- async def get_l1_book_snapshots(
776
- self, symbols: list[str], venue: str
777
- ) -> Sequence[MarketTickerFields]:
778
- """
779
- Gets the L1 book snapshots for a list of symbols.
1277
+ Example:
1278
+ See test_orderflow.py for an example.
780
1279
 
781
- Args:
782
- symbols: the symbols to get the l1 book snapshots for
783
- venue: the venue that the symbols are traded at
784
- Returns:
785
- a list of MarketTickerFields for the symbols
1280
+ This WILL block the event loop until the stream is closed.
786
1281
  """
787
- snapshot = await self.graphql_client.get_l_1_book_snapshots_query(
788
- venue=venue, symbols=symbols
1282
+ grpc_client = await self.core()
1283
+ decoder = grpc_client.get_decoder(OrderflowRequestUnannotatedResponseType)
1284
+ stub: grpc.aio.StreamStreamMultiCallable = grpc_client.channel.stream_stream(
1285
+ OrderflowRequest_route,
1286
+ request_serializer=grpc_client.encoder().encode,
1287
+ response_deserializer=decoder.decode,
789
1288
  )
790
- return snapshot.tickers
791
-
792
- async def get_l2_book_snapshot(self, symbol: str, venue: str) -> L2BookFields:
793
- """
794
- Gets the L2 book snapshot for a symbol.
795
-
796
- Args:
797
- symbol: the symbol to get the l2 book snapshot for
798
- venue: the venue that the symbol is traded at
799
- Returns:
800
- L2BookFields for the symbol
801
-
802
- Note: this does NOT update, it is a snapshot at a given time
803
- For an object that updates, use subscribe_l2_book
804
- """
805
- l2_book = await self.graphql_client.get_l_2_book_snapshot_query(
806
- symbol=symbol, venue=venue
1289
+ call: grpc.aio._base_call.StreamStreamCall[OrderflowRequest, Orderflow] = stub(
1290
+ request_iterator, metadata=(("authorization", f"Bearer {grpc_client.jwt}"),)
807
1291
  )
808
- return l2_book.l_2_book_snapshot
809
-
810
- async def subscribe_l1_book_stream(
811
- self, symbols: list[TradableProduct], venue: str
812
- ) -> AsyncIterator[L1BookSnapshot]:
813
- """
814
- Subscribe to the stream of L1BookSnapshots for a symbol.
815
-
816
- Args:
817
- symbol: the symbol to subscribe to
818
- venue: the venue to subscribe to
819
- Returns:
820
- an async iterator that yields L1BookSnapshot, representing the l1 book updates
821
- """
822
- async for snapshot in await self.grpc_client.subscribe_l1_books_stream(
823
- symbols=[str(s) for s in symbols]
824
- ):
825
- yield snapshot
826
-
827
- async def subscribe_l2_book_stream(
828
- self, symbol: TradableProduct, venue: str
829
- ) -> AsyncIterator[L2BookUpdate]:
830
- """
831
- Subscribe to the stream of L2BookUpdates for a symbol.
832
-
833
- IMPORTANT: note that the Snapshot is a different type than
834
- L2BookSnapshot
835
- Args:
836
- symbol: the symbol to subscribe to
837
- venue: the venue to subscribe to
838
- Returns:
839
- an async iterator that yields L2BookFields
840
- L2BookFields is either a Snapshot or a Diff
841
- See the grpc_client code for how to handle the different types
842
- """
843
- async for snapshot in self.grpc_client.subscribe_l2_books_stream(
844
- symbol=symbol, venue=venue
845
- ):
846
- yield snapshot
847
-
848
- async def subscribe_l1_book(
849
- self, symbols: list[TradableProduct]
850
- ) -> list[L1BookSnapshot]:
851
- """
852
- Returns a L1BookSnapshot object that is constantly updating in the background.
1292
+ async for update in call:
1293
+ yield update
853
1294
 
854
- Args:
855
- symbols: the symbols to subscribe to
856
- Return:
857
- a list of L1BookSnapshot objects that are constantly updating in the background
858
- For the duration of the program, the client will be subscribed to the stream
859
- and be updating the L1BookSnapshot.
860
-
861
- IMPORTANT: The L1BookSnapshot will be initialized with
862
- a timestamp (field tn and ts) of 0
863
- along with None for bid and ask
864
-
865
- The reference to the object should be kept, but can also be referenced via
866
- client.grpc_client.l1_books.get(symbol)
867
-
868
- If you want direct access to the stream to do on_update type code, you can
869
- call client.grpc_client.stream_l1_books
870
- """
871
- books = self.grpc_client.initialize_l1_books(symbols)
872
- asyncio.create_task(self.grpc_client.watch_l1_books(symbols=symbols))
873
- i = 0
874
- while not all(book.ts > 0 for book in books) and i < 10:
875
- await asyncio.sleep(0.2)
876
- i += 1
877
- if i == 10:
878
- raise ValueError(
879
- f"Could not get L1 books for {symbols}. Check if market is quoting via client.get_market_status."
880
- )
881
-
882
- return books
883
-
884
- async def subscribe_l2_book(
1295
+ async def stream_orderflow(
885
1296
  self,
886
- symbol: TradableProduct,
887
- venue: Optional[str],
888
- ) -> L2BookSnapshot:
1297
+ account: Optional[AccountIdOrName] = None,
1298
+ execution_venue: Optional[str] = None,
1299
+ trader: Optional[TraderIdOrEmail] = None,
1300
+ ) -> AsyncGenerator[Orderflow, None]:
889
1301
  """
890
- Returns a L2BookSnapshot object that is constantly updating in the background.
891
-
892
- Args:
893
- symbols: the symbols to subscribe to
894
- Return:
895
- a list of L2BookSnapshot object that is constantly updating in the background
896
- For the duration of the program, the client will be subscribed to the stream
897
- and be updating the L2BookSnapshot.
898
-
899
- IMPORTANT: The LBBookSnapshot will be initialized with
900
- a timestamp (field tn and ts) of 0
901
- along with None for bid and ask
902
-
903
- The reference to the object should be kept, but can also be referenced via
904
- client.grpc_client.l1_books.get(symbol)
905
-
906
- If you want direct access to the stream to do on_update type code, you can
907
- call client.grpc_client.stream_l2_book
908
- """
909
- book = self.grpc_client.initialize_l2_book(symbol, venue)
910
- asyncio.create_task(self.grpc_client.watch_l2_book(symbol, venue))
911
- i = 0
912
- while book.ts == 0 and i < 10:
913
- await asyncio.sleep(0.2)
914
- i += 1
915
- if book.ts == 0:
916
- if venue:
917
- market_status = await self.get_market_status(symbol, venue)
918
- if not market_status.is_quoting:
919
- raise ValueError(
920
- f"Market {symbol} is currently closed, cannot get L2."
921
- )
922
- raise ValueError(f"Could not get L2 book for {symbol}.")
923
-
924
- return book
1302
+ A stream for listening to order updates (fills, acks, outs, etc.).
925
1303
 
926
- def subscribe_trades_stream(
927
- self, symbol: TradableProduct, venue: Optional[str]
928
- ) -> AsyncIterator[Trade]:
929
- """
930
- Subscribe to a stream of trades for a symbol.
931
- """
932
- request = SubscribeTradesRequest(symbol=symbol, venue=venue)
933
- return self.grpc_client.subscribe(request)
1304
+ Example:
1305
+ ```python
1306
+ request = SubscribeOrderflowRequest.new()
1307
+ async for of in client.subscribe_orderflow_stream(request):
1308
+ print(of)
1309
+ ```
934
1310
 
935
- def subscribe_candles_stream(
936
- self,
937
- symbol: TradableProduct,
938
- venue: Optional[str],
939
- candle_widths: Optional[list[grpc_definitions.CandleWidth]],
940
- ) -> AsyncIterator[Candle]:
941
- """
942
- Subscribe to a stream of candles for a symbol.
1311
+ This WILL block the event loop until the stream is closed.
943
1312
  """
944
- request = SubscribeCandlesRequest(
945
- symbol=str(symbol),
946
- venue=venue,
947
- candle_widths=candle_widths,
1313
+ grpc_client = await self.core()
1314
+ request: SubscribeOrderflowRequest = SubscribeOrderflowRequest(
1315
+ account=account, execution_venue=execution_venue, trader=trader
1316
+ )
1317
+ grpc_client = await self.core()
1318
+ decoder = grpc_client.get_decoder(SubscribeOrderflowRequest)
1319
+ stub: grpc.aio.UnaryStreamMultiCallable[
1320
+ SubscribeOrderflowRequest, Orderflow
1321
+ ] = grpc_client.channel.unary_stream(
1322
+ SubscribeOrderflowRequest.get_route(),
1323
+ request_serializer=grpc_client.encoder().encode,
1324
+ response_deserializer=decoder.decode,
948
1325
  )
949
- return self.grpc_client.subscribe(request)
1326
+ call: grpc.aio._base_call.UnaryStreamCall[
1327
+ SubscribeOrderflowRequest, Orderflow
1328
+ ] = stub(request, metadata=(("authorization", f"Bearer {grpc_client.jwt}"),))
1329
+ async for update in call:
1330
+ yield update
950
1331
 
951
1332
  # ------------------------------------------------------------
952
- # Order Entry and Cancellation
1333
+ # Order entry
953
1334
  # ------------------------------------------------------------
954
1335
 
955
1336
  async def send_limit_order(
1337
+ self,
1338
+ *args,
1339
+ **kwargs,
1340
+ ) -> Order:
1341
+ """
1342
+ @deprecated(reason="Use place_limit_order instead")
1343
+ """
1344
+ return await self.place_limit_order(*args, **kwargs)
1345
+
1346
+ async def place_limit_order(
956
1347
  self,
957
1348
  *,
958
- id: Optional[str] = None,
959
- symbol: TradableProduct,
960
- execution_venue: Optional[str],
961
- odir: OrderDir,
1349
+ id: Optional[OrderId] = None,
1350
+ symbol: TradableProduct | str,
1351
+ execution_venue: Optional[str] = None,
1352
+ dir: Optional[OrderDir] = None,
962
1353
  quantity: Decimal,
963
1354
  limit_price: Decimal,
964
1355
  order_type: OrderType = OrderType.LIMIT,
965
- time_in_force: TimeInForce = TimeInForce.DAY,
966
- good_til_date: Optional[datetime] = None,
1356
+ time_in_force: TimeInForce = TimeInForceEnum.DAY,
967
1357
  price_round_method: Optional[TickRoundMethod] = None,
968
1358
  account: Optional[str] = None,
969
1359
  trader: Optional[str] = None,
970
1360
  post_only: bool = False,
971
1361
  trigger_price: Optional[Decimal] = None,
972
- ) -> OrderFields:
1362
+ **kwargs: Any,
1363
+ ) -> Order:
973
1364
  """
974
1365
  Sends a regular limit order.
975
1366
 
976
1367
  Args:
1368
+ id: in case user wants to generate their own order id, otherwise it will be generated automatically
977
1369
  symbol: the symbol to send the order for
978
1370
  execution_venue: the execution venue to send the order to,
979
1371
  if execution_venue is set to None, the OMS will send the order to the primary_exchange
980
1372
  the primary_exchange can be deduced from `get_product_info`
981
- odir: the direction of the order
1373
+ dir: the direction of the order, BUY or SELL
982
1374
  quantity: the quantity of the order
983
1375
  limit_price: the limit price of the order
984
1376
  It is highly recommended to make this a Decimal object from the decimal module to avoid floating point errors
985
1377
  order_type: the type of the order
986
1378
  time_in_force: the time in force of the order
987
- good_til_date: the date the order is good until, only relevant for time_in_force = "GTD"
988
1379
  price_round_method: the method to round the price to the nearest tick, will not round if None
989
1380
  account: the account to send the order for
990
1381
  While technically optional, for most order types, the account is required
@@ -993,13 +1384,21 @@ class AsyncClient:
993
1384
  post_only: whether the order should be post only, not supported by all exchanges
994
1385
  trigger_price: the trigger price for the order, only relevant for stop / take_profit orders
995
1386
  Returns:
996
- the OrderFields object for the order
1387
+ the Order object for the order
997
1388
  The order.status should be "PENDING" until the order is "OPEN" / "REJECTED" / "OUT" / "CANCELED" / "STALE"
998
1389
 
999
1390
  If the order is rejected, the order.reject_reason and order.reject_message will be set
1000
1391
  """
1392
+ grpc_client = await self.core()
1001
1393
  assert quantity > 0, "quantity must be positive"
1002
1394
 
1395
+ if dir is None:
1396
+ if "odir" in kwargs and isinstance(kwargs["odir"], OrderDir):
1397
+ logging.warning("odir is deprecated, use dir instead")
1398
+ dir = kwargs["odir"]
1399
+ else:
1400
+ raise ValueError("dir is required")
1401
+
1003
1402
  if price_round_method is not None:
1004
1403
  if execution_venue is None:
1005
1404
  product_info = await self.get_product_info(symbol)
@@ -1025,44 +1424,43 @@ class AsyncClient:
1025
1424
  else:
1026
1425
  raise ValueError(f"Could not find market information for {symbol}")
1027
1426
 
1028
- if not isinstance(trigger_price, Decimal) and trigger_price is not None:
1029
- trigger_price = Decimal(trigger_price)
1030
-
1031
- order: PlaceOrderMutationOms = await self.graphql_client.place_order_mutation(
1032
- symbol,
1033
- odir,
1034
- quantity,
1035
- order_type,
1036
- time_in_force,
1037
- id,
1038
- trader,
1039
- account,
1040
- limit_price,
1041
- post_only,
1042
- trigger_price,
1043
- good_til_date,
1044
- execution_venue,
1427
+ req: PlaceOrderRequest = PlaceOrderRequest.new(
1428
+ dir=dir,
1429
+ quantity=quantity,
1430
+ symbol=symbol,
1431
+ time_in_force=time_in_force,
1432
+ limit_price=limit_price,
1433
+ order_type=order_type,
1434
+ account=account,
1435
+ id=id,
1436
+ parent_id=None,
1437
+ source=OrderSource.API,
1438
+ trader=trader,
1439
+ execution_venue=execution_venue,
1440
+ post_only=post_only,
1441
+ trigger_price=trigger_price,
1045
1442
  )
1046
-
1047
- return order.place_order
1443
+ res = await grpc_client.unary_unary(req)
1444
+ return res
1048
1445
 
1049
1446
  async def send_market_pro_order(
1050
1447
  self,
1051
1448
  *,
1052
- id: Optional[str] = None,
1053
- symbol: TradableProduct,
1449
+ id: Optional[OrderId] = None,
1450
+ symbol: TradableProduct | str,
1054
1451
  execution_venue: str,
1055
1452
  odir: OrderDir,
1056
1453
  quantity: Decimal,
1057
- time_in_force: TimeInForce = TimeInForce.DAY,
1454
+ time_in_force: TimeInForce = TimeInForceEnum.DAY,
1058
1455
  account: Optional[str] = None,
1059
1456
  fraction_through_market: Decimal = Decimal("0.001"),
1060
- ) -> OrderFields:
1457
+ ) -> Order:
1061
1458
  """
1062
1459
  Sends a market-order like limit price based on the BBO.
1063
1460
  Meant to behave as a market order but with more protections.
1064
1461
 
1065
1462
  Args:
1463
+ id: in case user wants to generate their own order id, otherwise it will be generated automatically
1066
1464
  symbol: the symbol to send the order for
1067
1465
  execution_venue: the execution venue to send the order to
1068
1466
  odir: the direction of the order
@@ -1078,36 +1476,33 @@ class AsyncClient:
1078
1476
  If the order is rejected, the order.reject_reason and order.reject_message will be set
1079
1477
  """
1080
1478
 
1081
- # Check for GQL failures
1082
- bbo_snapshot = await self.get_market_snapshot(
1083
- symbol=symbol, venue=execution_venue
1084
- )
1085
- if bbo_snapshot is None:
1479
+ ticker = await self.get_ticker(symbol, execution_venue)
1480
+ if ticker is None:
1086
1481
  raise ValueError(
1087
- f"Failed to send market order with reason: no market snapshot for {symbol}"
1482
+ f"Failed to send market order with reason: no ticker for {symbol}"
1088
1483
  )
1089
1484
 
1090
1485
  price_band = price_band_pairs.get(symbol, None)
1091
1486
 
1092
1487
  if odir == OrderDir.BUY:
1093
- if bbo_snapshot.ask_price is None:
1488
+ if ticker.ask_price is None:
1094
1489
  raise ValueError(
1095
1490
  f"Failed to send market order with reason: no ask price for {symbol}"
1096
1491
  )
1097
- limit_price = bbo_snapshot.ask_price * (1 + fraction_through_market)
1492
+ limit_price = ticker.ask_price * (1 + fraction_through_market)
1098
1493
 
1099
- if price_band and bbo_snapshot.last_price:
1100
- price_band_reference_price = bbo_snapshot.last_price + price_band
1494
+ if price_band and ticker.last_price:
1495
+ price_band_reference_price = ticker.last_price + price_band
1101
1496
  limit_price = min(limit_price, price_band_reference_price)
1102
1497
 
1103
1498
  else:
1104
- if bbo_snapshot.bid_price is None:
1499
+ if ticker.bid_price is None:
1105
1500
  raise ValueError(
1106
1501
  f"Failed to send market order with reason: no bid price for {symbol}"
1107
1502
  )
1108
- limit_price = bbo_snapshot.bid_price * (1 - fraction_through_market)
1109
- if price_band and bbo_snapshot.last_price:
1110
- price_band_reference_price = bbo_snapshot.last_price - price_band
1503
+ limit_price = ticker.bid_price * (1 - fraction_through_market)
1504
+ if price_band and ticker.last_price:
1505
+ price_band_reference_price = ticker.last_price - price_band
1111
1506
  limit_price = min(limit_price, price_band_reference_price)
1112
1507
 
1113
1508
  # Conservatively round price to nearest tick
@@ -1125,7 +1520,7 @@ class AsyncClient:
1125
1520
  ):
1126
1521
  limit_price = tick_round_method(limit_price, tick_size)
1127
1522
 
1128
- return await self.send_limit_order(
1523
+ return await self.place_limit_order(
1129
1524
  id=id,
1130
1525
  symbol=symbol,
1131
1526
  execution_venue=execution_venue,
@@ -1137,7 +1532,7 @@ class AsyncClient:
1137
1532
  time_in_force=time_in_force,
1138
1533
  )
1139
1534
 
1140
- async def cancel_order(self, order_id: str) -> CancelFields:
1535
+ async def cancel_order(self, order_id: OrderId) -> Cancel:
1141
1536
  """
1142
1537
  Cancels an order by order id.
1143
1538
 
@@ -1146,10 +1541,17 @@ class AsyncClient:
1146
1541
  Returns:
1147
1542
  the CancelFields object
1148
1543
  """
1149
- cancel = await self.graphql_client.cancel_order_mutation(order_id)
1150
- return cancel.cancel_order
1544
+ grpc_client = await self.core()
1545
+ req = CancelOrderRequest(id=order_id)
1546
+ res = await grpc_client.unary_unary(req)
1547
+ return res
1151
1548
 
1152
- async def cancel_all_orders(self) -> bool:
1549
+ async def cancel_all_orders(
1550
+ self,
1551
+ account: Optional[AccountIdOrName] = None,
1552
+ execution_venue: Optional[str] = None,
1553
+ trader: Optional[TraderIdOrEmail] = None,
1554
+ ) -> bool:
1153
1555
  """
1154
1556
  Cancels all open orders.
1155
1557
 
@@ -1157,5 +1559,25 @@ class AsyncClient:
1157
1559
  True if all orders were cancelled successfully
1158
1560
  False if there was an error
1159
1561
  """
1160
- b = await self.graphql_client.cancel_all_orders_mutation()
1161
- return b.cancel_all_orders
1562
+
1563
+ open_orders = await self.get_open_orders(
1564
+ account=account,
1565
+ venue=execution_venue,
1566
+ trader=trader,
1567
+ )
1568
+ outputs = await asyncio.gather(
1569
+ *(self.cancel_order(order.id) for order in open_orders)
1570
+ )
1571
+
1572
+ for cancel in outputs:
1573
+ if cancel.reject_reason is not None:
1574
+ return False
1575
+ return True
1576
+ grpc_client = await self.core()
1577
+ req = CancelAllOrdersRequest(
1578
+ account=account,
1579
+ execution_venue=execution_venue,
1580
+ trader=trader,
1581
+ )
1582
+ res = await grpc_client.unary_unary(req)
1583
+ return res