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

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