architect-py 3.2.1__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 (207) hide show
  1. architect_py/__init__.py +18 -2
  2. architect_py/async_client.py +1089 -658
  3. architect_py/client.py +36 -33
  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 +5 -4
  52. architect_py/{grpc_client → grpc/models}/Cpty/CptyResponse.py +6 -6
  53. architect_py/grpc/models/Cpty/CptyStatus.py +48 -0
  54. architect_py/grpc/models/Cpty/CptyStatusRequest.py +45 -0
  55. architect_py/grpc/models/Cpty/CptysRequest.py +37 -0
  56. architect_py/grpc/models/Cpty/CptysResponse.py +27 -0
  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 +14 -3
  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 +2 -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 +671 -934
  138. architect_py/grpc/resolve_endpoint.py +70 -0
  139. architect_py/{grpc_client/grpc_server.py → grpc/server.py} +13 -9
  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 -85
  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.1.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 -52
  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/Folio/AggregatedAccountSummariesRequest.py +0 -59
  184. architect_py/grpc_client/Folio/AggregatedAccountSummariesResponse.py +0 -27
  185. architect_py/grpc_client/Health/__init__.py +0 -2
  186. architect_py/grpc_client/Marketdata/__init__.py +0 -2
  187. architect_py/grpc_client/Oms/Cancel.py +0 -42
  188. architect_py/grpc_client/Oms/__init__.py +0 -2
  189. architect_py/grpc_client/Orderflow/__init__.py +0 -2
  190. architect_py/grpc_client/Symbology/__init__.py +0 -2
  191. architect_py/grpc_client/__init__.py +0 -2
  192. architect_py/grpc_client/grpc_client.py +0 -405
  193. architect_py/scalars.py +0 -172
  194. architect_py/tests/test_accounts.py +0 -31
  195. architect_py/tests/test_client.py +0 -29
  196. architect_py/tests/test_grpc_client.py +0 -30
  197. architect_py/tests/test_order_sending.py +0 -61
  198. architect_py/tests/test_snapshots.py +0 -52
  199. architect_py/tests/test_subscriptions.py +0 -129
  200. architect_py-3.2.1.dist-info/METADATA +0 -212
  201. architect_py-3.2.1.dist-info/RECORD +0 -146
  202. /architect_py/{grpc_client → grpc/models}/Marketdata/ArrayOfL1BookSnapshot.py +0 -0
  203. /architect_py/{grpc_client → grpc/models}/Marketdata/L2BookUpdate.py +0 -0
  204. /architect_py/{grpc_client → grpc/models}/Marketdata/TickerUpdate.py +0 -0
  205. /architect_py/{grpc_client → grpc/models}/Orderflow/Dropcopy.py +0 -0
  206. /architect_py/{grpc_client → grpc/models}/Orderflow/Orderflow.py +0 -0
  207. {architect_py-3.2.1.dist-info → architect_py-5.0.0.dist-info/licenses}/LICENSE +0 -0
@@ -1,208 +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
114
-
115
- the API key and secret can be generated on the app.architect.co website
116
-
117
- Returns:
118
- Client object
84
+ Connect to an Architect installation.
119
85
 
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 not be using this constructor directly, unless they do not want to use any subscription methods.
165
-
166
- Use the create 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,
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)
251
+
252
+ async def set_marketdata(self, venue: Venue, endpoint: str):
253
+ """
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")
301
+
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)
326
+ """
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"
200
336
  )
201
337
 
202
338
  # ------------------------------------------------------------
203
339
  # Symbology
204
340
  # ------------------------------------------------------------
205
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
+
206
360
  async def search_symbols(
207
361
  self,
208
362
  search_string: Optional[str] = None,
@@ -211,77 +365,74 @@ class AsyncClient:
211
365
  limit: int = 20,
212
366
  ) -> List[TradableProduct]:
213
367
  """
214
- Search for symbols in the Architect database.
368
+ Search for tradable products on Architect.
215
369
 
216
370
  Args:
217
- 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.
218
373
  Examples: "ES", "NQ", "GC"
219
374
  execution_venue: the execution venue to search in
220
375
  Examples: "CME"
221
- Returns:
222
- a list of TradableProduct objects
223
- """
224
- markets = (
225
- await self.graphql_client.search_symbols_query(
226
- search_string=search_string,
227
- execution_venue=execution_venue,
228
- offset=offset,
229
- limit=limit,
230
- )
231
- ).search_symbols
232
-
233
- 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
234
384
 
235
385
  async def get_product_info(self, symbol: str) -> Optional[ProductInfoFields]:
236
386
  """
237
- Get the product information (product_type, underlying, multiplier, etc.) for a symbol.
387
+ Get information about a product, e.g. product_type, underlying, multiplier.
238
388
 
239
389
  Args:
240
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
+
241
396
  Returns:
242
- ProductInfoFields object if the symbol exists
397
+ None if the symbol does not exist
243
398
  """
244
- info = await self.graphql_client.get_product_info_query(symbol)
245
- return info.product_info
399
+ res = await self.graphql_client.get_product_info_query(symbol)
400
+ return res.product_info
246
401
 
247
- @functools.lru_cache
248
402
  async def get_product_infos(
249
403
  self, symbols: Optional[list[str]]
250
404
  ) -> Sequence[ProductInfoFields]:
251
405
  """
252
- 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.
253
407
 
254
408
  Args:
255
- symbols: the symbols to get information for
256
- Returns:
257
- a list of ProductInfoFields
409
+ symbols: the symbols to get information for, or None for all symbols
258
410
 
259
- Any duplicate or invalid symbols will be ignored.
260
- The order of the symbols in the list will not necessarily be preserved in the output.
261
-
262
- 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.
263
415
  """
264
- infos = await self.graphql_client.get_product_infos_query(symbols)
265
- return infos.product_infos
416
+ res = await self.graphql_client.get_product_infos_query(symbols)
417
+ return res.product_infos
266
418
 
267
- @functools.lru_cache
268
419
  async def get_execution_info(
269
- self, symbol: TradableProduct, execution_venue: str
420
+ self, symbol: TradableProduct | str, execution_venue: str
270
421
  ) -> Optional[ExecutionInfoFields]:
271
422
  """
272
- 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.
273
425
 
274
426
  Args:
275
427
  symbol: the symbol to get execution information for
276
428
  execution_venue: the execution venue e.g. "CME"
277
429
 
278
430
  Returns:
279
- ExecutionInfoFields
280
-
431
+ None if the symbol doesn't exist
281
432
  """
282
433
  try:
283
434
  execution_info = await self.graphql_client.get_execution_info_query(
284
- symbol, execution_venue
435
+ TradableProduct(symbol), execution_venue
285
436
  )
286
437
  return execution_info.execution_info
287
438
  except GraphQLClientGraphQLMultiError:
@@ -291,63 +442,73 @@ class AsyncClient:
291
442
 
292
443
  async def get_execution_infos(
293
444
  self,
294
- symbols: Optional[list[TradableProduct]],
445
+ symbols: Optional[list[TradableProduct | str]],
295
446
  execution_venue: Optional[str] = None,
296
447
  ) -> Sequence[ExecutionInfoFields]:
297
448
  """
298
- 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.
299
451
 
300
452
  Args:
301
- symbols: the symbols to get execution information for
453
+ symbols: the symbols to get execution information for, or None for all symbols
302
454
  execution_venue: the execution venue e.g. "CME"
303
455
 
304
456
  Returns:
305
- a list of ExecutionInfoFields
306
-
307
- 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.
308
460
  """
309
- execution_infos = await self.graphql_client.get_execution_infos_query(
310
- symbols, execution_venue
311
- )
312
- 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
313
467
 
314
468
  async def get_cme_first_notice_date(self, symbol: str) -> Optional[date]:
315
469
  """
470
+ @deprecated(reason="Use get_product_info instead; first_notice_date is now a field")
471
+
316
472
  Get the first notice date for a CME future.
317
473
 
318
474
  Args:
319
475
  symbol: the symbol to get the first notice date for a CME future
320
476
 
321
477
  Returns:
322
- the first notice date as a date object if it exists
478
+ The first notice date as a date object if it exists
323
479
  """
324
- notice = await self.graphql_client.get_first_notice_date_query(symbol)
325
- 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:
326
482
  return None
327
- return notice.product_info.first_notice_date
483
+ return res.product_info.first_notice_date
328
484
 
329
485
  async def get_future_series(self, series_symbol: str) -> list[str]:
330
486
  """
331
- 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.
332
495
 
333
496
  Args:
334
- series_symbol: the symbol to get the series for
335
- 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
336
499
  Returns:
337
- a list of symbols in the series
500
+ List of futures products
338
501
  """
339
- assert (
340
- " " in series_symbol
341
- ), 'series_symbol must have the venue in it, e.g. "ES CME Futures" or "GC CME Futures"'
342
-
343
- futures_series = await self.graphql_client.get_future_series_query(
344
- series_symbol
345
- )
346
- 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
347
506
 
348
507
  @staticmethod
349
- def get_expiration_from_CME_name(name: str) -> date:
508
+ def get_expiration_from_CME_name(name: str) -> Optional[date]:
350
509
  """
510
+ @deprecated(reason="Use utils.symbol_parsing.nominative_expiration instead")
511
+
351
512
  Get the expiration date from a CME future name.
352
513
 
353
514
  Args:
@@ -356,12 +517,13 @@ class AsyncClient:
356
517
  Returns:
357
518
  the expiration date as a date object
358
519
  """
359
- _, d, *_ = name.split(" ")
360
- return datetime.strptime(d, "%Y%m%d").date()
520
+ return nominative_expiration(name)
361
521
 
362
522
  async def get_cme_futures_series(self, series: str) -> list[tuple[date, str]]:
363
523
  """
364
- 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.
365
527
 
366
528
  Args:
367
529
  series: the series to get the futures for
@@ -371,143 +533,547 @@ class AsyncClient:
371
533
  the symbol for each future in the series
372
534
 
373
535
  e.g.
374
- [(datetime.date(2025, 3, 21), 'ES 20250321 CME Future'),
375
- (datetime.date(2025, 6, 20), 'ES 20250620 CME Future'),
376
- (datetime.date(2025, 9, 19), 'ES 20250919 CME Future'),
377
- ...
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
+ # ...
378
542
  ]
543
+ ```
544
+ """
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
553
+
554
+ async def get_cme_future_from_root_month_year(
555
+ self, root: str, month: int, year: int
556
+ ) -> str:
379
557
  """
380
- markets = await self.get_future_series(
381
- series,
558
+ Get the symbol for a CME future from the root, month, and year.
559
+ This is a simple wrapper around search_symbols.
560
+
561
+ Args:
562
+ root: the root symbol for the future e.g. "ES"
563
+ month: the month of the future
564
+ year: the year of the future
565
+ Returns:
566
+ The future symbol if it exists and is unique.
567
+ """
568
+ [market] = [
569
+ market
570
+ for market in await self.search_symbols(
571
+ f"{root} {year}{month:02d}",
572
+ execution_venue="CME",
573
+ )
574
+ if market.startswith(f"{root} {year}{month:02d}")
575
+ ]
576
+ return market
577
+
578
+ # ------------------------------------------------------------
579
+ # Marketdata
580
+ # ------------------------------------------------------------
581
+
582
+ async def get_market_status(
583
+ self, symbol: TradableProduct | str, venue: Venue
584
+ ) -> MarketStatus:
585
+ """
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")
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
608
+ Returns:
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]
382
628
  )
383
629
 
384
- filtered_markets = [
385
- (self.get_expiration_from_CME_name(market), market) for market in markets
386
- ]
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]
387
870
 
388
- filtered_markets.sort(key=lambda x: x[0])
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.
389
878
 
390
- return filtered_markets
879
+ If a subscription is already active, the existing reference will be
880
+ returned; otherwise, a new subscription will be created.
391
881
 
392
- async def get_cme_future_from_root_month_year(
393
- self, root: str, month: int, year: int
394
- ) -> str:
395
- """
396
- Get the symbol for a CME future from the root, month, and year.
882
+ Snapshots will have an initial value of timestamp=0 and bids/asks=[].
397
883
 
398
884
  Args:
399
- root: the root symbol for the future e.g. "ES"
400
- month: the month of the future
401
- year: the year of the future
402
- Returns:
403
- the symbol for the future
885
+ symbol: the symbol to subscribe to
886
+ venue: the marketdata venue
404
887
 
405
- Errors if the result is not unique
406
- This is a simple wrapper around search_symbols
888
+ Return:
889
+ An L2 book object that is constantly updating in the background.
407
890
  """
408
- [market] = [
409
- market
410
- for market in await self.search_symbols(
411
- f"{root} {year}{month:02d}",
412
- execution_venue="CME",
413
- )
414
- if market.startswith(f"{root} {year}{month:02d}")
415
- ]
891
+ symbol = TradableProduct(symbol)
416
892
 
417
- return market
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
418
908
 
419
- # ------------------------------------------------------------
420
- # Account Management
421
- # ------------------------------------------------------------
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]
422
951
 
423
- async def who_am_i(self) -> tuple[str, str]:
952
+ async def stream_trades(
953
+ self, symbol: TradableProduct | str, venue: Venue
954
+ ) -> AsyncGenerator[Trade, None]:
424
955
  """
425
- Gets the user_id and user_email for the user that the API key belongs to.
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
426
962
 
427
- Returns:
428
- (user_id, user_email)
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.
429
971
  """
430
- 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
431
980
 
432
- return user_id.user_id, user_id.user_email
981
+ # ------------------------------------------------------------
982
+ # Portfolio management
983
+ # ------------------------------------------------------------
433
984
 
434
- async def list_accounts(self) -> Sequence[AccountWithPermissionsFields]:
985
+ async def list_accounts(self) -> List[AccountWithPermissions]:
435
986
  """
436
987
  List accounts for the user that the API key belongs to.
437
988
 
438
989
  Returns:
439
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
440
992
  (use who_am_i to get the user_id / email)
441
993
  """
442
- accounts = await self.graphql_client.list_accounts_query()
443
- 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
444
998
 
445
- async def get_account_summary(self, account: str) -> AccountSummaryFields:
999
+ async def get_account_summary(self, account: str) -> AccountSummary:
446
1000
  """
447
- Gets the account summary for the given account.
1001
+ Get account summary, including balances, positions, pnls, etc.
448
1002
 
449
1003
  Args:
450
- account: the account to get the summary for,
451
- can be the account id( (a UUID) or the account name (e.g. CQG:00000)
452
- Returns:
453
- AccountSummaryFields for the account
1004
+ account: account uuid or name
1005
+ Examples: "00000000-0000-0000-0000-000000000000", "STONEX:000000/JDoe"
454
1006
  """
455
- summary = await self.graphql_client.get_account_summary_query(account=account)
456
- 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
457
1011
 
458
1012
  async def get_account_summaries(
459
1013
  self,
460
1014
  accounts: Optional[list[str]] = None,
461
1015
  trader: Optional[str] = None,
462
- ) -> Sequence[AccountSummaryFields]:
1016
+ ) -> list[AccountSummary]:
463
1017
  """
464
- Gets the account summaries for the given accounts and trader.
1018
+ Get account summaries for accounts matching the filters.
465
1019
 
466
1020
  Args:
467
- accounts: a list of account ids to get summaries for,
468
- can be the account id( (a UUID) or the account name (e.g. CQG:00000)
469
- 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
470
1023
 
471
- if both arguments are given, the accounts are all appended and returned together
472
- Returns:
473
- a list of AccountSummary for the accounts
1024
+ If both arguments are given, the union of matching accounts are returned.
474
1025
  """
475
- summaries = await self.graphql_client.get_account_summaries_query(
476
- trader=trader, accounts=accounts
1026
+ grpc_client = await self.core()
1027
+ request = AccountSummariesRequest(
1028
+ accounts=accounts,
1029
+ trader=trader,
477
1030
  )
478
- return summaries.account_summaries
1031
+ res = await grpc_client.unary_unary(request)
1032
+ return res.account_summaries
479
1033
 
480
1034
  async def get_account_history(
481
1035
  self,
482
1036
  account: str,
483
1037
  from_inclusive: Optional[datetime] = None,
484
1038
  to_exclusive: Optional[datetime] = None,
485
- ) -> Sequence[AccountSummaryFields]:
1039
+ ) -> list[AccountSummary]:
486
1040
  """
487
- Gets the account history for the given account and dates.
488
-
489
- Returns:
490
- a list of AccountSummaryFields for the account for the given dates
491
- use timestamp to get the time of the of the summary
1041
+ Get historical sequence of account summaries for the given account.
492
1042
  """
493
- 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(
494
1059
  account=account, from_inclusive=from_inclusive, to_exclusive=to_exclusive
495
1060
  )
496
- return history.account_history
1061
+ res = await grpc_client.unary_unary(req)
1062
+ return res.history
497
1063
 
498
1064
  # ------------------------------------------------------------
499
- # Order Management
1065
+ # Order management
500
1066
  # ------------------------------------------------------------
501
1067
 
502
1068
  async def get_open_orders(
503
1069
  self,
504
- order_ids: Optional[list[str]] = None,
1070
+ order_ids: Optional[list[OrderId]] = None,
505
1071
  venue: Optional[str] = None,
506
1072
  account: Optional[str] = None,
507
1073
  trader: Optional[str] = None,
508
1074
  symbol: Optional[str] = None,
509
- parent_order_id: Optional[str] = None,
510
- ) -> Sequence[OrderFields]:
1075
+ parent_order_id: Optional[OrderId] = None,
1076
+ ) -> list[Order]:
511
1077
  """
512
1078
  Returns a list of open orders for the user that match the filters.
513
1079
 
@@ -519,12 +1085,11 @@ class AsyncClient:
519
1085
  symbol: the symbol to get orders for
520
1086
  parent_order_id: the parent order id to get orders for
521
1087
 
522
- these filters are combinewd via OR statements so if you pass
523
- in multiple arguments, it will return the union of the results
524
1088
  Returns:
525
- 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
526
1090
  """
527
- orders = await self.graphql_client.get_open_orders_query(
1091
+ grpc_client = await self.core()
1092
+ open_orders_request = OpenOrdersRequest(
528
1093
  venue=venue,
529
1094
  account=account,
530
1095
  trader=trader,
@@ -532,46 +1097,63 @@ class AsyncClient:
532
1097
  parent_order_id=parent_order_id,
533
1098
  order_ids=order_ids,
534
1099
  )
535
- return orders.open_orders
536
1100
 
537
- 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]:
538
1105
  """
539
- Returns a list of all open orders for the user.
1106
+ @deprecated(reason="Use get_open_orders with no parameters instead")
540
1107
 
541
- Returns:
542
- a list of OrderFields of all the open orders for the user
1108
+ Returns a list of all open orders for the authenticated user.
543
1109
  """
544
- orders = await self.graphql_client.get_open_orders_query()
545
- return orders.open_orders
1110
+ return await self.get_open_orders()
546
1111
 
547
1112
  async def get_historical_orders(
548
1113
  self,
549
- order_ids: Optional[list[str]] = None,
1114
+ order_ids: Optional[list[OrderId]] = None,
550
1115
  from_inclusive: Optional[datetime] = None,
551
1116
  to_exclusive: Optional[datetime] = None,
552
1117
  venue: Optional[str] = None,
553
1118
  account: Optional[str] = None,
554
- parent_order_id: Optional[str] = None,
555
- ) -> Sequence[OrderFields]:
1119
+ parent_order_id: Optional[OrderId] = None,
1120
+ ) -> list[Order]:
556
1121
  """
557
- 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.
558
1126
 
559
1127
  Args:
560
1128
  order_ids: a list of order ids to get
561
1129
  from_inclusive: the start date to get orders for
562
1130
  to_exclusive: the end date to get orders for
563
1131
  venue: the venue to get orders for, e.g. CME
564
- account: the account to get orders for,
565
- can be the account id( (a UUID) or the account name (e.g. CQG:00000)
1132
+ account: account uuid or name
566
1133
  parent_order_id: the parent order id to get orders for
567
1134
  Returns:
568
- a list of OrderFields of the historical orders that match the filters
1135
+ Historical orders that match the union of the filters.
569
1136
 
570
- either the order_ids parameter needs to be filled
571
- OR
572
- 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.
573
1139
  """
574
- 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(
575
1157
  order_ids=order_ids,
576
1158
  venue=venue,
577
1159
  account=account,
@@ -579,405 +1161,221 @@ class AsyncClient:
579
1161
  from_inclusive=from_inclusive,
580
1162
  to_exclusive=to_exclusive,
581
1163
  )
582
- return orders.historical_orders
1164
+ orders = await grpc_client.unary_unary(historical_orders_request)
1165
+ return orders.orders
583
1166
 
584
- async def get_order(self, order_id: str) -> Optional[OrderFields]:
1167
+ async def get_order(self, order_id: OrderId) -> Optional[Order]:
585
1168
  """
586
- Returns the OrderFields object for the specified order.
587
- 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.
588
1171
 
589
1172
  Args:
590
1173
  order_id: the order id to get
591
- Returns:
592
- the OrderFields object for the order
593
-
594
- Queries open_orders first then queries historical_orders
595
1174
  """
596
- open_orders = await self.graphql_client.get_open_orders_query(
597
- order_ids=[order_id]
1175
+ grpc_client = await self.core()
1176
+ req = OpenOrdersRequest.new(
1177
+ order_ids=[order_id],
598
1178
  )
599
-
600
- for open_order in open_orders.open_orders:
1179
+ res = await grpc_client.unary_unary(req)
1180
+ for open_order in res.open_orders:
601
1181
  if open_order.id == order_id:
602
1182
  return open_order
603
1183
 
604
- historical_orders = await self.graphql_client.get_historical_orders_query(
605
- order_ids=[order_id]
1184
+ req = HistoricalOrdersRequest.new(
1185
+ order_ids=[order_id],
606
1186
  )
1187
+ res = await grpc_client.unary_unary(req)
1188
+ if res.orders and len(res.orders) == 1:
1189
+ return res.orders[0]
607
1190
 
608
- if historical_orders.historical_orders:
609
- return historical_orders.historical_orders[0]
610
-
611
- 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]]:
612
1192
  """
613
- Returns a list of OrderFields objects for the specified orders.
614
- 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.
615
1195
 
616
1196
  Args:
617
1197
  order_ids: a list of order ids to get
618
- Returns:
619
- a list of OrderFields objects for the orders
620
-
621
- Plural form of get_order
622
1198
  """
623
- orders_dict: dict[str, Optional[OrderFields]] = {
1199
+ grpc_client = await self.core()
1200
+ orders_dict: dict[OrderId, Optional[Order]] = {
624
1201
  order_id: None for order_id in order_ids
625
1202
  }
1203
+ req = OpenOrdersRequest.new(
1204
+ order_ids=order_ids,
1205
+ )
626
1206
 
627
- open_orders = (
628
- await self.graphql_client.get_open_orders_query(order_ids=order_ids)
629
- ).open_orders
630
- for open_order in open_orders:
1207
+ res = await grpc_client.unary_unary(req)
1208
+ for open_order in res.open_orders:
631
1209
  orders_dict[open_order.id] = open_order
632
1210
 
633
1211
  not_open_order_ids = [
634
1212
  order_id for order_id in order_ids if orders_dict[order_id] is None
635
1213
  ]
636
1214
 
637
- historical_orders = (
638
- await self.graphql_client.get_historical_orders_query(
639
- order_ids=not_open_order_ids
640
- )
641
- ).historical_orders
642
- 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:
643
1220
  orders_dict[historical_order.id] = historical_order
644
1221
 
645
1222
  return [orders_dict[order_id] for order_id in order_ids]
646
1223
 
647
1224
  async def get_fills(
648
1225
  self,
649
- from_inclusive: Optional[datetime],
650
- to_exclusive: Optional[datetime],
1226
+ from_inclusive: Optional[datetime] = None,
1227
+ to_exclusive: Optional[datetime] = None,
651
1228
  venue: Optional[str] = None,
652
1229
  account: Optional[str] = None,
653
- order_id: Optional[str] = None,
654
- ) -> GetFillsQueryFolioHistoricalFills:
1230
+ order_id: Optional[OrderId] = None,
1231
+ limit: Optional[int] = None,
1232
+ ) -> HistoricalFillsResponse:
655
1233
  """
656
- Returns a list of fills for the given filters.
1234
+ Returns all fills matching the given filters.
657
1235
 
658
1236
  Args:
659
1237
  from_inclusive: the start date to get fills for
660
1238
  to_exclusive: the end date to get fills for
661
1239
  venue: the venue to get fills for, e.g. "CME"
662
- account: the account to get fills for,
663
- can be the account id( (a UUID) or the account name (e.g. CQG:00000)
1240
+ account: account uuid or name
664
1241
  order_id: the order id to get fills for
665
- Returns:
666
- a list of GetFillsQueryFolioHistoricalFills
667
- """
668
- fills = await self.graphql_client.get_fills_query(
669
- venue, account, order_id, from_inclusive, to_exclusive
670
- )
671
- return fills.historical_fills
672
-
673
- # ------------------------------------------------------------
674
- # Market Data
675
- # ------------------------------------------------------------
676
-
677
- async def get_market_status(
678
- self, symbol: TradableProduct, venue: str
679
- ) -> MarketStatusFields:
680
- """
681
- Returns market status for symbol (ie if it is quoting and trading).
682
-
683
- Args:
684
- symbol: the symbol to get the market status for, e.g. "ES 20250321 CME Future/USD"
685
- venue: the venue that the symbol is traded at, e.g. CME
686
- Returns:
687
- MarketStatusFields for the symbol
688
- """
689
- market_status = await self.graphql_client.get_market_status_query(symbol, venue)
690
- return market_status.market_status
691
-
692
- async def get_market_snapshot(
693
- self, symbol: TradableProduct, venue: str
694
- ) -> MarketTickerFields:
695
- """
696
- This is an alias for l1_book_snapshot.
697
-
698
- Args:
699
- symbol: the symbol to get the market snapshot for, e.g. "ES 20250321 CME Future/USD"
700
- venue: the venue that the symbol is traded at, e.g. CME
701
- Returns:
702
- MarketTickerFields for the symbol
703
- """
704
- return await self.get_l1_book_snapshot(symbol=symbol, venue=venue)
705
-
706
- async def get_market_snapshots(
707
- self, symbols: list[TradableProduct], venue: str
708
- ) -> Sequence[MarketTickerFields]:
709
- """
710
- This is an alias for l1_book_snapshot.
711
-
712
- Args:
713
- symbols: the symbols to get the market snapshots for
714
- venue: the venue that the symbols are traded at
715
- Returns:
716
- a list of MarketTickerFields for the symbols
717
- """
718
- return await self.get_l1_book_snapshots(
719
- venue=venue, symbols=symbols # type: ignore
720
- )
721
-
722
- async def get_historical_candles(
723
- self,
724
- symbol: str,
725
- candle_width: grpc_definitions.CandleWidth,
726
- start: datetime,
727
- end: datetime,
728
- ) -> HistoricalCandlesResponse:
729
1242
  """
730
- 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
+ )
731
1250
 
732
- Args:
733
- symbol: the symbol to get the candles for
734
- venue: the venue of the symbol
735
- candle_width: the width of the candles
736
- start: the start date to get candles for
737
- end: the end date to get candles for
738
- Returns:
739
- a list of CandleFields for the specified candles
740
- """
741
- request = HistoricalCandlesRequest(
742
- symbol=symbol,
743
- candle_width=candle_width,
744
- start_date=start,
745
- 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,
746
1264
  )
1265
+ res = await grpc_client.unary_unary(req)
1266
+ return res
747
1267
 
748
- return await self.grpc_client.request(request)
749
-
750
- async def get_l1_book_snapshot(
1268
+ async def orderflow(
751
1269
  self,
752
- symbol: str,
753
- venue: str,
754
- ) -> MarketTickerFields:
1270
+ request_iterator: AsyncIterator[OrderflowRequest],
1271
+ ) -> AsyncGenerator[Orderflow, None]:
755
1272
  """
756
- 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.).
757
1274
 
758
- Args:
759
- symbol: the symbol to get the l1 book snapshot for
760
- venue: the venue that the symbol is traded at
761
- Returns:
762
- MarketTickerFields for the symbol
763
- """
764
- snapshot = await self.graphql_client.get_l_1_book_snapshot_query(
765
- symbol=symbol, venue=venue
766
- )
767
- return snapshot.ticker
1275
+ This is considered the most efficient way to trade in this SDK.
768
1276
 
769
- async def get_l1_book_snapshots(
770
- self, symbols: list[str], venue: str
771
- ) -> Sequence[MarketTickerFields]:
772
- """
773
- Gets the L1 book snapshots for a list of symbols.
1277
+ Example:
1278
+ See test_orderflow.py for an example.
774
1279
 
775
- Args:
776
- symbols: the symbols to get the l1 book snapshots for
777
- venue: the venue that the symbols are traded at
778
- Returns:
779
- a list of MarketTickerFields for the symbols
1280
+ This WILL block the event loop until the stream is closed.
780
1281
  """
781
- snapshot = await self.graphql_client.get_l_1_book_snapshots_query(
782
- 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,
783
1288
  )
784
- return snapshot.tickers
785
-
786
- async def get_l2_book_snapshot(self, symbol: str, venue: str) -> L2BookFields:
787
- """
788
- Gets the L2 book snapshot for a symbol.
789
-
790
- Args:
791
- symbol: the symbol to get the l2 book snapshot for
792
- venue: the venue that the symbol is traded at
793
- Returns:
794
- L2BookFields for the symbol
795
-
796
- Note: this does NOT update, it is a snapshot at a given time
797
- For an object that updates, use subscribe_l2_book
798
- """
799
- l2_book = await self.graphql_client.get_l_2_book_snapshot_query(
800
- symbol=symbol, venue=venue
1289
+ call: grpc.aio._base_call.StreamStreamCall[OrderflowRequest, Orderflow] = stub(
1290
+ request_iterator, metadata=(("authorization", f"Bearer {grpc_client.jwt}"),)
801
1291
  )
802
- return l2_book.l_2_book_snapshot
803
-
804
- async def subscribe_l1_book_stream(
805
- self, symbols: list[TradableProduct], venue: str
806
- ) -> AsyncIterator[L1BookSnapshot]:
807
- """
808
- Subscribe to the stream of L1BookSnapshots for a symbol.
809
-
810
- Args:
811
- symbol: the symbol to subscribe to
812
- venue: the venue to subscribe to
813
- Returns:
814
- an async iterator that yields L1BookSnapshot, representing the l1 book updates
815
- """
816
- async for snapshot in await self.grpc_client.subscribe_l1_books_stream(
817
- symbols=[str(s) for s in symbols]
818
- ):
819
- yield snapshot
820
-
821
- async def subscribe_l2_book_stream(
822
- self, symbol: TradableProduct, venue: str
823
- ) -> AsyncIterator[L2BookUpdate]:
824
- """
825
- Subscribe to the stream of L2BookUpdates for a symbol.
826
-
827
- IMPORTANT: note that the Snapshot is a different type than
828
- L2BookSnapshot
829
- Args:
830
- symbol: the symbol to subscribe to
831
- venue: the venue to subscribe to
832
- Returns:
833
- an async iterator that yields L2BookFields
834
- L2BookFields is either a Snapshot or a Diff
835
- See the grpc_client code for how to handle the different types
836
- """
837
- async for snapshot in self.grpc_client.subscribe_l2_books_stream(
838
- symbol=symbol, venue=venue
839
- ):
840
- yield snapshot
841
-
842
- async def subscribe_l1_book(
843
- self, symbols: list[TradableProduct]
844
- ) -> list[L1BookSnapshot]:
845
- """
846
- Returns a L1BookSnapshot object that is constantly updating in the background.
847
-
848
- Args:
849
- symbols: the symbols to subscribe to
850
- Return:
851
- a list of L1BookSnapshot objects that are constantly updating in the background
852
- For the duration of the program, the client will be subscribed to the stream
853
- and be updating the L1BookSnapshot.
854
-
855
- IMPORTANT: The L1BookSnapshot will be initialized with
856
- a timestamp (field tn and ts) of 0
857
- along with None for bid and ask
858
-
859
- The reference to the object should be kept, but can also be referenced via
860
- client.grpc_client.l1_books.get(symbol)
861
-
862
- If you want direct access to the stream to do on_update type code, you can
863
- call client.grpc_client.stream_l1_books
864
- """
865
- books = self.grpc_client.initialize_l1_books(symbols)
866
- asyncio.create_task(self.grpc_client.watch_l1_books(symbols=symbols))
867
- i = 0
868
- while not all(book.ts > 0 for book in books) and i < 10:
869
- await asyncio.sleep(0.2)
870
- i += 1
871
- if i == 10:
872
- raise ValueError(
873
- f"Could not get L1 books for {symbols}. Check if market is quoting via client.get_market_status."
874
- )
875
-
876
- return books
1292
+ async for update in call:
1293
+ yield update
877
1294
 
878
- async def subscribe_l2_book(
1295
+ async def stream_orderflow(
879
1296
  self,
880
- symbol: TradableProduct,
881
- venue: Optional[str],
882
- ) -> L2BookSnapshot:
1297
+ account: Optional[AccountIdOrName] = None,
1298
+ execution_venue: Optional[str] = None,
1299
+ trader: Optional[TraderIdOrEmail] = None,
1300
+ ) -> AsyncGenerator[Orderflow, None]:
883
1301
  """
884
- Returns a L2BookSnapshot object that is constantly updating in the background.
885
-
886
- Args:
887
- symbols: the symbols to subscribe to
888
- Return:
889
- a list of L2BookSnapshot object that is constantly updating in the background
890
- For the duration of the program, the client will be subscribed to the stream
891
- and be updating the L2BookSnapshot.
892
-
893
- IMPORTANT: The LBBookSnapshot will be initialized with
894
- a timestamp (field tn and ts) of 0
895
- along with None for bid and ask
896
-
897
- The reference to the object should be kept, but can also be referenced via
898
- client.grpc_client.l1_books.get(symbol)
899
-
900
- If you want direct access to the stream to do on_update type code, you can
901
- call client.grpc_client.stream_l2_book
902
- """
903
- book = self.grpc_client.initialize_l2_book(symbol, venue)
904
- asyncio.create_task(self.grpc_client.watch_l2_book(symbol, venue))
905
- i = 0
906
- while book.ts == 0 and i < 10:
907
- await asyncio.sleep(0.2)
908
- i += 1
909
- if book.ts == 0:
910
- if venue:
911
- market_status = await self.get_market_status(symbol, venue)
912
- if not market_status.is_quoting:
913
- raise ValueError(
914
- f"Market {symbol} is currently closed, cannot get L2."
915
- )
916
- raise ValueError(f"Could not get L2 book for {symbol}.")
917
-
918
- return book
1302
+ A stream for listening to order updates (fills, acks, outs, etc.).
919
1303
 
920
- def subscribe_trades_stream(
921
- self, symbol: TradableProduct, venue: Optional[str]
922
- ) -> AsyncIterator[Trade]:
923
- """
924
- Subscribe to a stream of trades for a symbol.
925
- """
926
- request = SubscribeTradesRequest(symbol=symbol, venue=venue)
927
- 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
+ ```
928
1310
 
929
- def subscribe_candles_stream(
930
- self,
931
- symbol: TradableProduct,
932
- venue: Optional[str],
933
- candle_widths: Optional[list[grpc_definitions.CandleWidth]],
934
- ) -> AsyncIterator[Candle]:
1311
+ This WILL block the event loop until the stream is closed.
935
1312
  """
936
- Subscribe to a stream of candles for a symbol.
937
- """
938
- request = SubscribeCandlesRequest(
939
- symbol=str(symbol),
940
- venue=venue,
941
- 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,
942
1325
  )
943
- 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
944
1331
 
945
1332
  # ------------------------------------------------------------
946
- # Order Entry and Cancellation
1333
+ # Order entry
947
1334
  # ------------------------------------------------------------
948
1335
 
949
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(
950
1347
  self,
951
1348
  *,
952
- symbol: TradableProduct,
953
- execution_venue: Optional[str],
954
- odir: OrderDir,
1349
+ id: Optional[OrderId] = None,
1350
+ symbol: TradableProduct | str,
1351
+ execution_venue: Optional[str] = None,
1352
+ dir: Optional[OrderDir] = None,
955
1353
  quantity: Decimal,
956
1354
  limit_price: Decimal,
957
1355
  order_type: OrderType = OrderType.LIMIT,
958
- time_in_force: TimeInForce = TimeInForce.DAY,
959
- good_til_date: Optional[datetime] = None,
1356
+ time_in_force: TimeInForce = TimeInForceEnum.DAY,
960
1357
  price_round_method: Optional[TickRoundMethod] = None,
961
1358
  account: Optional[str] = None,
962
1359
  trader: Optional[str] = None,
963
1360
  post_only: bool = False,
964
1361
  trigger_price: Optional[Decimal] = None,
965
- ) -> OrderFields:
1362
+ **kwargs: Any,
1363
+ ) -> Order:
966
1364
  """
967
1365
  Sends a regular limit order.
968
1366
 
969
1367
  Args:
1368
+ id: in case user wants to generate their own order id, otherwise it will be generated automatically
970
1369
  symbol: the symbol to send the order for
971
1370
  execution_venue: the execution venue to send the order to,
972
1371
  if execution_venue is set to None, the OMS will send the order to the primary_exchange
973
1372
  the primary_exchange can be deduced from `get_product_info`
974
- odir: the direction of the order
1373
+ dir: the direction of the order, BUY or SELL
975
1374
  quantity: the quantity of the order
976
1375
  limit_price: the limit price of the order
977
1376
  It is highly recommended to make this a Decimal object from the decimal module to avoid floating point errors
978
1377
  order_type: the type of the order
979
1378
  time_in_force: the time in force of the order
980
- good_til_date: the date the order is good until, only relevant for time_in_force = "GTD"
981
1379
  price_round_method: the method to round the price to the nearest tick, will not round if None
982
1380
  account: the account to send the order for
983
1381
  While technically optional, for most order types, the account is required
@@ -986,13 +1384,21 @@ class AsyncClient:
986
1384
  post_only: whether the order should be post only, not supported by all exchanges
987
1385
  trigger_price: the trigger price for the order, only relevant for stop / take_profit orders
988
1386
  Returns:
989
- the OrderFields object for the order
1387
+ the Order object for the order
990
1388
  The order.status should be "PENDING" until the order is "OPEN" / "REJECTED" / "OUT" / "CANCELED" / "STALE"
991
1389
 
992
1390
  If the order is rejected, the order.reject_reason and order.reject_message will be set
993
1391
  """
1392
+ grpc_client = await self.core()
994
1393
  assert quantity > 0, "quantity must be positive"
995
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
+
996
1402
  if price_round_method is not None:
997
1403
  if execution_venue is None:
998
1404
  product_info = await self.get_product_info(symbol)
@@ -1018,43 +1424,43 @@ class AsyncClient:
1018
1424
  else:
1019
1425
  raise ValueError(f"Could not find market information for {symbol}")
1020
1426
 
1021
- if not isinstance(trigger_price, Decimal) and trigger_price is not None:
1022
- trigger_price = Decimal(trigger_price)
1023
-
1024
- order: PlaceOrderMutationOms = await self.graphql_client.place_order_mutation(
1025
- symbol,
1026
- odir,
1027
- quantity,
1028
- order_type,
1029
- time_in_force,
1030
- None,
1031
- trader,
1032
- account,
1033
- limit_price,
1034
- post_only,
1035
- trigger_price,
1036
- good_til_date,
1037
- 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,
1038
1442
  )
1039
-
1040
- return order.place_order
1443
+ res = await grpc_client.unary_unary(req)
1444
+ return res
1041
1445
 
1042
1446
  async def send_market_pro_order(
1043
1447
  self,
1044
1448
  *,
1045
- symbol: TradableProduct,
1449
+ id: Optional[OrderId] = None,
1450
+ symbol: TradableProduct | str,
1046
1451
  execution_venue: str,
1047
1452
  odir: OrderDir,
1048
1453
  quantity: Decimal,
1049
- time_in_force: TimeInForce = TimeInForce.DAY,
1454
+ time_in_force: TimeInForce = TimeInForceEnum.DAY,
1050
1455
  account: Optional[str] = None,
1051
1456
  fraction_through_market: Decimal = Decimal("0.001"),
1052
- ) -> OrderFields:
1457
+ ) -> Order:
1053
1458
  """
1054
1459
  Sends a market-order like limit price based on the BBO.
1055
1460
  Meant to behave as a market order but with more protections.
1056
1461
 
1057
1462
  Args:
1463
+ id: in case user wants to generate their own order id, otherwise it will be generated automatically
1058
1464
  symbol: the symbol to send the order for
1059
1465
  execution_venue: the execution venue to send the order to
1060
1466
  odir: the direction of the order
@@ -1070,36 +1476,33 @@ class AsyncClient:
1070
1476
  If the order is rejected, the order.reject_reason and order.reject_message will be set
1071
1477
  """
1072
1478
 
1073
- # Check for GQL failures
1074
- bbo_snapshot = await self.get_market_snapshot(
1075
- symbol=symbol, venue=execution_venue
1076
- )
1077
- if bbo_snapshot is None:
1479
+ ticker = await self.get_ticker(symbol, execution_venue)
1480
+ if ticker is None:
1078
1481
  raise ValueError(
1079
- 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}"
1080
1483
  )
1081
1484
 
1082
1485
  price_band = price_band_pairs.get(symbol, None)
1083
1486
 
1084
1487
  if odir == OrderDir.BUY:
1085
- if bbo_snapshot.ask_price is None:
1488
+ if ticker.ask_price is None:
1086
1489
  raise ValueError(
1087
1490
  f"Failed to send market order with reason: no ask price for {symbol}"
1088
1491
  )
1089
- limit_price = bbo_snapshot.ask_price * (1 + fraction_through_market)
1492
+ limit_price = ticker.ask_price * (1 + fraction_through_market)
1090
1493
 
1091
- if price_band and bbo_snapshot.last_price:
1092
- 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
1093
1496
  limit_price = min(limit_price, price_band_reference_price)
1094
1497
 
1095
1498
  else:
1096
- if bbo_snapshot.bid_price is None:
1499
+ if ticker.bid_price is None:
1097
1500
  raise ValueError(
1098
1501
  f"Failed to send market order with reason: no bid price for {symbol}"
1099
1502
  )
1100
- limit_price = bbo_snapshot.bid_price * (1 - fraction_through_market)
1101
- if price_band and bbo_snapshot.last_price:
1102
- 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
1103
1506
  limit_price = min(limit_price, price_band_reference_price)
1104
1507
 
1105
1508
  # Conservatively round price to nearest tick
@@ -1117,7 +1520,8 @@ class AsyncClient:
1117
1520
  ):
1118
1521
  limit_price = tick_round_method(limit_price, tick_size)
1119
1522
 
1120
- return await self.send_limit_order(
1523
+ return await self.place_limit_order(
1524
+ id=id,
1121
1525
  symbol=symbol,
1122
1526
  execution_venue=execution_venue,
1123
1527
  odir=odir,
@@ -1128,7 +1532,7 @@ class AsyncClient:
1128
1532
  time_in_force=time_in_force,
1129
1533
  )
1130
1534
 
1131
- async def cancel_order(self, order_id: str) -> CancelFields:
1535
+ async def cancel_order(self, order_id: OrderId) -> Cancel:
1132
1536
  """
1133
1537
  Cancels an order by order id.
1134
1538
 
@@ -1137,10 +1541,17 @@ class AsyncClient:
1137
1541
  Returns:
1138
1542
  the CancelFields object
1139
1543
  """
1140
- cancel = await self.graphql_client.cancel_order_mutation(order_id)
1141
- 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
1142
1548
 
1143
- 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:
1144
1555
  """
1145
1556
  Cancels all open orders.
1146
1557
 
@@ -1148,5 +1559,25 @@ class AsyncClient:
1148
1559
  True if all orders were cancelled successfully
1149
1560
  False if there was an error
1150
1561
  """
1151
- b = await self.graphql_client.cancel_all_orders_mutation()
1152
- 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