architect-py 5.0.0b1__py3-none-any.whl → 5.0.0b3__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 (74) hide show
  1. architect_py/__init__.py +10 -3
  2. architect_py/async_client.py +291 -174
  3. architect_py/client_interface.py +19 -18
  4. architect_py/common_types/order_dir.py +12 -6
  5. architect_py/graphql_client/__init__.py +2 -0
  6. architect_py/graphql_client/enums.py +5 -0
  7. architect_py/grpc/__init__.py +25 -7
  8. architect_py/grpc/client.py +13 -5
  9. architect_py/grpc/models/Accounts/AccountsRequest.py +4 -1
  10. architect_py/grpc/models/Algo/AlgoOrder.py +114 -0
  11. architect_py/grpc/models/Algo/{ModifyAlgoOrderRequestForTwapAlgo.py → AlgoOrderRequest.py} +11 -10
  12. architect_py/grpc/models/Algo/AlgoOrdersRequest.py +72 -0
  13. architect_py/grpc/models/Algo/AlgoOrdersResponse.py +27 -0
  14. architect_py/grpc/models/Algo/CreateAlgoOrderRequest.py +56 -0
  15. architect_py/grpc/models/Algo/PauseAlgoRequest.py +42 -0
  16. architect_py/grpc/models/Algo/PauseAlgoResponse.py +20 -0
  17. architect_py/grpc/models/Algo/StartAlgoRequest.py +42 -0
  18. architect_py/grpc/models/Algo/StartAlgoResponse.py +20 -0
  19. architect_py/grpc/models/Algo/StopAlgoRequest.py +42 -0
  20. architect_py/grpc/models/Algo/StopAlgoResponse.py +20 -0
  21. architect_py/grpc/models/Boss/DepositsRequest.py +40 -0
  22. architect_py/grpc/models/Boss/DepositsResponse.py +27 -0
  23. architect_py/grpc/models/Boss/RqdAccountStatisticsRequest.py +42 -0
  24. architect_py/grpc/models/Boss/RqdAccountStatisticsResponse.py +25 -0
  25. architect_py/grpc/models/Boss/StatementUrlRequest.py +40 -0
  26. architect_py/grpc/models/Boss/StatementUrlResponse.py +23 -0
  27. architect_py/grpc/models/Boss/StatementsRequest.py +40 -0
  28. architect_py/grpc/models/Boss/StatementsResponse.py +27 -0
  29. architect_py/grpc/models/Boss/WithdrawalsRequest.py +40 -0
  30. architect_py/grpc/models/Boss/WithdrawalsResponse.py +27 -0
  31. architect_py/grpc/models/Boss/__init__.py +2 -0
  32. architect_py/grpc/models/Folio/HistoricalFillsRequest.py +4 -1
  33. architect_py/grpc/models/Marketdata/L1BookSnapshot.py +16 -2
  34. architect_py/grpc/models/Oms/Cancel.py +67 -19
  35. architect_py/grpc/models/Oms/Order.py +4 -11
  36. architect_py/grpc/models/Oms/PlaceOrderRequest.py +13 -20
  37. architect_py/grpc/models/OptionsMarketdata/OptionsChain.py +30 -0
  38. architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeks.py +30 -0
  39. architect_py/grpc/models/OptionsMarketdata/OptionsChainGreeksRequest.py +47 -0
  40. architect_py/grpc/models/OptionsMarketdata/OptionsChainRequest.py +45 -0
  41. architect_py/grpc/models/OptionsMarketdata/OptionsExpirations.py +29 -0
  42. architect_py/grpc/models/OptionsMarketdata/OptionsExpirationsRequest.py +42 -0
  43. architect_py/grpc/models/OptionsMarketdata/__init__.py +2 -0
  44. architect_py/grpc/models/Symbology/ExecutionInfoRequest.py +47 -0
  45. architect_py/grpc/models/Symbology/ExecutionInfoResponse.py +27 -0
  46. architect_py/grpc/models/definitions.py +457 -790
  47. architect_py/grpc/resolve_endpoint.py +4 -1
  48. architect_py/internal_utils/__init__.py +0 -0
  49. architect_py/internal_utils/no_pandas.py +3 -0
  50. architect_py/tests/conftest.py +11 -6
  51. architect_py/tests/test_marketdata.py +19 -19
  52. architect_py/tests/test_orderflow.py +31 -28
  53. {architect_py-5.0.0b1.dist-info → architect_py-5.0.0b3.dist-info}/METADATA +2 -3
  54. {architect_py-5.0.0b1.dist-info → architect_py-5.0.0b3.dist-info}/RECORD +72 -42
  55. {architect_py-5.0.0b1.dist-info → architect_py-5.0.0b3.dist-info}/WHEEL +1 -1
  56. examples/book_subscription.py +2 -3
  57. examples/candles.py +3 -3
  58. examples/common.py +29 -20
  59. examples/external_cpty.py +4 -4
  60. examples/funding_rate_mean_reversion_algo.py +14 -20
  61. examples/order_sending.py +32 -33
  62. examples/stream_l1_marketdata.py +2 -2
  63. examples/stream_l2_marketdata.py +1 -3
  64. examples/trades.py +2 -2
  65. examples/tutorial_async.py +9 -7
  66. examples/tutorial_sync.py +5 -5
  67. scripts/generate_functions_md.py +3 -1
  68. scripts/generate_sync_interface.py +30 -11
  69. scripts/postprocess_grpc.py +21 -11
  70. scripts/preprocess_grpc_schema.py +174 -113
  71. architect_py/grpc/models/Algo/AlgoOrderForTwapAlgo.py +0 -61
  72. architect_py/grpc/models/Algo/CreateAlgoOrderRequestForTwapAlgo.py +0 -59
  73. {architect_py-5.0.0b1.dist-info → architect_py-5.0.0b3.dist-info}/licenses/LICENSE +0 -0
  74. {architect_py-5.0.0b1.dist-info → architect_py-5.0.0b3.dist-info}/top_level.txt +0 -0
@@ -1,9 +1,11 @@
1
1
  import asyncio
2
2
  import logging
3
3
  import re
4
- from datetime import date, datetime, timedelta
4
+ from datetime import date, datetime, timedelta, timezone
5
5
  from decimal import Decimal
6
6
  from typing import (
7
+ Any,
8
+ AsyncGenerator,
7
9
  AsyncIterator,
8
10
  List,
9
11
  Literal,
@@ -13,6 +15,17 @@ from typing import (
13
15
  overload,
14
16
  )
15
17
 
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,
28
+ )
16
29
  from architect_py.grpc.models.Orderflow.Orderflow import Orderflow
17
30
  from architect_py.grpc.models.Orderflow.OrderflowRequest import (
18
31
  OrderflowRequest,
@@ -25,38 +38,19 @@ from architect_py.grpc.models.Orderflow.SubscribeOrderflowRequest import (
25
38
 
26
39
  from .common_types import OrderDir, TradableProduct, Venue
27
40
  from .graphql_client import GraphQLClient
28
- from .graphql_client.enums import (
29
- OrderType,
30
- TimeInForce,
31
- )
32
41
  from .graphql_client.exceptions import GraphQLClientGraphQLMultiError
33
42
  from .graphql_client.fragments import (
34
- AccountSummaryFields,
35
- AccountWithPermissionsFields,
36
- CancelFields,
37
43
  ExecutionInfoFields,
38
- OrderFields,
39
44
  ProductInfoFields,
40
45
  )
41
- from .graphql_client.get_fills_query import GetFillsQueryFolioHistoricalFills
42
- from .graphql_client.place_order_mutation import PlaceOrderMutationOms
43
46
  from .grpc import *
44
47
  from .grpc.client import GrpcClient
45
- from .grpc.models import definitions as grpc_definitions
46
48
  from .utils.nearest_tick import TickRoundMethod
47
49
  from .utils.orderbook import update_orderbook_side
50
+ from .utils.pandas import candles_to_dataframe
48
51
  from .utils.price_bands import price_band_pairs
49
52
  from .utils.symbol_parsing import nominative_expiration
50
53
 
51
- try:
52
- import pandas as pd
53
-
54
- from .utils.pandas import candles_to_dataframe
55
-
56
- FEATURE_PANDAS = True
57
- except ImportError:
58
- FEATURE_PANDAS = False
59
-
60
54
 
61
55
  class AsyncClient:
62
56
  api_key: Optional[str] = None
@@ -84,6 +78,7 @@ class AsyncClient:
84
78
  paper_trading: bool,
85
79
  endpoint: str = "https://app.architect.co",
86
80
  graphql_port: Optional[int] = None,
81
+ **kwargs: Any,
87
82
  ) -> "AsyncClient":
88
83
  """
89
84
  Connect to an Architect installation.
@@ -95,11 +90,30 @@ class AsyncClient:
95
90
  RESET = "\033[0m"
96
91
  print(f"🧻 {COLOR} YOU ARE IN PAPER TRADING MODE {RESET}")
97
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"],
101
+ )
102
+ endpoint = kwargs["endpoint"]
103
+
98
104
  grpc_host, grpc_port, use_ssl = await resolve_endpoint(endpoint)
99
105
  logging.info(
100
106
  f"Resolved endpoint {endpoint}: {grpc_host}:{grpc_port} use_ssl={use_ssl}"
101
107
  )
102
108
 
109
+ # Sanity check paper trading on prod environments
110
+ if paper_trading:
111
+ if grpc_host == "app.architect.co" or grpc_host == "staging.architect.co":
112
+ if grpc_port != 10081:
113
+ raise ValueError("Wrong gRPC port for paper trading")
114
+ if graphql_port is not None and graphql_port != 5678:
115
+ raise ValueError("Wrong GraphQL port for paper trading")
116
+
103
117
  client = AsyncClient(
104
118
  api_key=api_key,
105
119
  api_secret=api_secret,
@@ -146,11 +160,6 @@ class AsyncClient:
146
160
  "API secret must be a Base64-encoded string, 44 characters long"
147
161
  )
148
162
 
149
- if paper_trading and (graphql_port is not None or grpc_port is not None):
150
- raise ValueError(
151
- "If paper_trading is True, graphql_port and grpc_port must be None"
152
- )
153
-
154
163
  if graphql_port is None:
155
164
  if paper_trading:
156
165
  graphql_port = 5678
@@ -318,6 +327,14 @@ class AsyncClient:
318
327
  res = await self.graphql_client.user_id_query()
319
328
  return res.user_id, res.user_email
320
329
 
330
+ def enable_orderflow(self):
331
+ """
332
+ @deprecated(reason="No longer needed; orderflow is enabled by default")
333
+ """
334
+ logging.warning(
335
+ "as of v5.0.0: enable_orderflow is deprecated; orderflow is enabled by default"
336
+ )
337
+
321
338
  # ------------------------------------------------------------
322
339
  # Symbology
323
340
  # ------------------------------------------------------------
@@ -371,6 +388,10 @@ class AsyncClient:
371
388
 
372
389
  Args:
373
390
  symbol: the symbol to get information for
391
+ the symbol should *not* have a quote,
392
+ ie "ES 20250620 CME Future" instead of "ES 20250620 CME Future/USD"
393
+
394
+ If you used TradableProduct, you can use the base() method to get the symbol
374
395
 
375
396
  Returns:
376
397
  None if the symbol does not exist
@@ -459,6 +480,13 @@ class AsyncClient:
459
480
  return None
460
481
  return res.product_info.first_notice_date
461
482
 
483
+ async def get_future_series(self, series_symbol: str) -> list[str]:
484
+ """
485
+ @deprecated(reason="Use get_futures_series instead")
486
+ """
487
+ futures = await self.get_futures_series(series_symbol)
488
+ return futures
489
+
462
490
  async def get_futures_series(self, series_symbol: str) -> list[str]:
463
491
  """
464
492
  List all futures in a given series.
@@ -503,11 +531,14 @@ class AsyncClient:
503
531
  the symbol for each future in the series
504
532
 
505
533
  e.g.
506
- [(datetime.date(2025, 3, 21), 'ES 20250321 CME Future'),
507
- (datetime.date(2025, 6, 20), 'ES 20250620 CME Future'),
508
- (datetime.date(2025, 9, 19), 'ES 20250919 CME Future'),
509
- ...
534
+ ```
535
+ [
536
+ (datetime.date(2025, 3, 21), 'ES 20250321 CME Future'),
537
+ (datetime.date(2025, 6, 20), 'ES 20250620 CME Future'),
538
+ (datetime.date(2025, 9, 19), 'ES 20250919 CME Future'),
539
+ # ...
510
540
  ]
541
+ ```
511
542
  """
512
543
  futures = await self.get_futures_series(series)
513
544
  exp_and_futures = []
@@ -573,7 +604,7 @@ class AsyncClient:
573
604
  symbol: the symbol to get the market snapshot for, e.g. "ES 20250321 CME Future/USD"
574
605
  venue: the venue that the symbol is traded at, e.g. CME
575
606
  Returns:
576
- MarketTickerFields for the symbol
607
+ L1BookSnapshot for the symbol
577
608
  """
578
609
  return await self.get_l1_book_snapshot(symbol=symbol, venue=venue)
579
610
 
@@ -591,7 +622,7 @@ class AsyncClient:
591
622
  """
592
623
  return await self.get_l1_book_snapshots(
593
624
  venue=venue,
594
- symbols=symbols, # type: ignore
625
+ symbols=symbols, # pyright: ignore[reportArgumentType]
595
626
  )
596
627
 
597
628
  @overload
@@ -634,13 +665,23 @@ class AsyncClient:
634
665
  venue: the venue of the symbol
635
666
  candle_width: the width of the candles
636
667
  start: the start date to get candles for;
637
- For naive datetimes, the server will assume UTC.
638
668
  end: the end date to get candles for;
639
- For naive datetimes, the server will assume UTC.
640
669
  as_dataframe: if True, return a pandas DataFrame
641
670
 
642
671
  """
643
672
  grpc_client = await self.hmart()
673
+ if start.tzinfo is not timezone.utc:
674
+ raise ValueError(
675
+ "start must be a utc datetime:\n"
676
+ "for example datetime.now(timezone.utc) or \n"
677
+ "dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
678
+ )
679
+ if end.tzinfo is not timezone.utc:
680
+ raise ValueError(
681
+ "end must be a utc datetime:\n"
682
+ "for example datetime.now(timezone.utc) or \n"
683
+ "dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
684
+ )
644
685
  req = HistoricalCandlesRequest(
645
686
  venue=venue,
646
687
  symbol=str(symbol),
@@ -650,10 +691,8 @@ class AsyncClient:
650
691
  )
651
692
  res: HistoricalCandlesResponse = await grpc_client.unary_unary(req)
652
693
 
653
- if as_dataframe and FEATURE_PANDAS:
694
+ if as_dataframe:
654
695
  return candles_to_dataframe(res.candles)
655
- elif as_dataframe and not FEATURE_PANDAS:
656
- raise RuntimeError("as_dataframe is True but pandas is not installed")
657
696
  else:
658
697
  return res.candles
659
698
 
@@ -686,7 +725,9 @@ class AsyncClient:
686
725
  """
687
726
  grpc_client = await self.marketdata(venue)
688
727
  req = L1BookSnapshotsRequest(symbols=symbols)
689
- res: ArrayOfL1BookSnapshot = await grpc_client.unary_unary(req) # type: ignore
728
+ res: ArrayOfL1BookSnapshot = await grpc_client.unary_unary(
729
+ req # pyright: ignore[reportArgumentType]
730
+ )
690
731
  return res
691
732
 
692
733
  async def get_l2_book_snapshot(
@@ -715,7 +756,7 @@ class AsyncClient:
715
756
 
716
757
  async def stream_l1_book_snapshots(
717
758
  self, symbols: Sequence[TradableProduct | str], venue: Venue
718
- ) -> AsyncIterator[L1BookSnapshot]:
759
+ ) -> AsyncGenerator[L1BookSnapshot, None]:
719
760
  """
720
761
  Subscribe to the stream of L1BookSnapshots for a symbol.
721
762
 
@@ -726,11 +767,12 @@ class AsyncClient:
726
767
  """
727
768
  grpc_client = await self.marketdata(venue)
728
769
  req = SubscribeL1BookSnapshotsRequest(symbols=list(symbols), venue=venue)
729
- return grpc_client.unary_stream(req)
770
+ async for res in grpc_client.unary_stream(req):
771
+ yield res
730
772
 
731
773
  async def stream_l2_book_updates(
732
774
  self, symbol: TradableProduct | str, venue: Venue
733
- ) -> AsyncIterator[L2BookUpdate]:
775
+ ) -> AsyncGenerator[L2BookUpdate, None]:
734
776
  """
735
777
  Subscribe to the stream of L2BookUpdates for a symbol.
736
778
 
@@ -743,7 +785,10 @@ class AsyncClient:
743
785
  """
744
786
  grpc_client = await self.marketdata(venue)
745
787
  req = SubscribeL2BookUpdatesRequest(symbol=str(symbol), venue=venue)
746
- return grpc_client.unary_stream(req) # type: ignore
788
+ async for res in grpc_client.unary_stream(
789
+ req # pyright: ignore[reportArgumentType]
790
+ ):
791
+ yield res
747
792
 
748
793
  async def subscribe_l1_book(
749
794
  self, symbol: TradableProduct | str, venue: Venue
@@ -782,6 +827,9 @@ class AsyncClient:
782
827
  return book
783
828
 
784
829
  async def unsubscribe_l1_book(self, symbol: TradableProduct | str, venue: Venue):
830
+ """
831
+ Unsubscribe from the L1 stream for a symbol, ie undoes subscribe_l1_book.
832
+ """
785
833
  symbol = TradableProduct(symbol)
786
834
  try:
787
835
  task = self.l1_books[venue][symbol][1]
@@ -865,7 +913,9 @@ class AsyncClient:
865
913
  ):
866
914
  try:
867
915
  req = SubscribeL2BookUpdatesRequest(symbol=str(symbol), venue=venue)
868
- stream = grpc_client.unary_stream(req) # type: ignore
916
+ stream = grpc_client.unary_stream(
917
+ req # pyright: ignore[reportArgumentType]
918
+ )
869
919
  async for up in stream:
870
920
  if isinstance(up, L2BookDiff):
871
921
  if (
@@ -899,20 +949,21 @@ class AsyncClient:
899
949
 
900
950
  async def stream_trades(
901
951
  self, symbol: TradableProduct | str, venue: Venue
902
- ) -> AsyncIterator[Trade]:
952
+ ) -> AsyncGenerator[Trade, None]:
903
953
  """
904
954
  Subscribe to a stream of trades for a symbol.
905
955
  """
906
956
  grpc_client = await self.marketdata(venue)
907
957
  req = SubscribeTradesRequest(symbol=str(symbol), venue=venue)
908
- return grpc_client.unary_stream(req)
958
+ async for res in grpc_client.unary_stream(req):
959
+ yield res
909
960
 
910
961
  async def stream_candles(
911
962
  self,
912
963
  symbol: TradableProduct | str,
913
964
  venue: Venue,
914
965
  candle_widths: Optional[list[CandleWidth]],
915
- ) -> AsyncIterator[Candle]:
966
+ ) -> AsyncGenerator[Candle, None]:
916
967
  """
917
968
  Subscribe to a stream of candles for a symbol.
918
969
  """
@@ -922,13 +973,14 @@ class AsyncClient:
922
973
  venue=venue,
923
974
  candle_widths=candle_widths,
924
975
  )
925
- return grpc_client.unary_stream(req)
976
+ async for res in grpc_client.unary_stream(req):
977
+ yield res
926
978
 
927
979
  # ------------------------------------------------------------
928
980
  # Portfolio management
929
981
  # ------------------------------------------------------------
930
982
 
931
- async def list_accounts(self) -> Sequence[AccountWithPermissionsFields]:
983
+ async def list_accounts(self) -> List[AccountWithPermissions]:
932
984
  """
933
985
  List accounts for the user that the API key belongs to.
934
986
 
@@ -937,17 +989,12 @@ class AsyncClient:
937
989
  a list of AccountWithPermissions for the user that the API key belongs to
938
990
  (use who_am_i to get the user_id / email)
939
991
  """
940
- res = await self.graphql_client.list_accounts_query()
992
+ grpc_client = await self.core()
993
+ req = AccountsRequest(paper=self.paper_trading)
994
+ res = await grpc_client.unary_unary(req)
941
995
  return res.accounts
942
996
 
943
- """
944
- async def list_accounts(self) -> List[grpc_definitions.AccountWithPermissions]:
945
- request = AccountsRequest()
946
- accounts = await self.grpc_client.request(request)
947
- return accounts.accounts
948
- """
949
-
950
- async def get_account_summary(self, account: str) -> AccountSummaryFields:
997
+ async def get_account_summary(self, account: str) -> AccountSummary:
951
998
  """
952
999
  Get account summary, including balances, positions, pnls, etc.
953
1000
 
@@ -955,20 +1002,16 @@ class AsyncClient:
955
1002
  account: account uuid or name
956
1003
  Examples: "00000000-0000-0000-0000-000000000000", "STONEX:000000/JDoe"
957
1004
  """
958
- res = await self.graphql_client.get_account_summary_query(account=account)
959
- return res.account_summary
960
- """
961
- async def get_account_summary(self, account: str) -> AccountSummary:
962
- request = AccountSummaryRequest(account=account)
963
- account_summary = await self.grpc_client.request(request)
964
- return account_summary
965
- """
1005
+ grpc_client = await self.core()
1006
+ req = AccountSummaryRequest(account=account)
1007
+ res = await grpc_client.unary_unary(req)
1008
+ return res
966
1009
 
967
1010
  async def get_account_summaries(
968
1011
  self,
969
1012
  accounts: Optional[list[str]] = None,
970
1013
  trader: Optional[str] = None,
971
- ) -> Sequence[AccountSummaryFields]:
1014
+ ) -> list[AccountSummary]:
972
1015
  """
973
1016
  Get account summaries for accounts matching the filters.
974
1017
 
@@ -978,45 +1021,24 @@ class AsyncClient:
978
1021
 
979
1022
  If both arguments are given, the union of matching accounts are returned.
980
1023
  """
981
- res = await self.graphql_client.get_account_summaries_query(
982
- trader=trader, accounts=accounts
983
- )
984
- return res.account_summaries
985
- """
986
- async def get_account_summaries(
987
- self,
988
- accounts: Optional[list[str]] = None,
989
- trader: Optional[str] = None,
990
- ) -> list[AccountSummary]:
1024
+ grpc_client = await self.core()
991
1025
  request = AccountSummariesRequest(
992
1026
  accounts=accounts,
993
1027
  trader=trader,
994
1028
  )
995
- account_summaries = await self.grpc_client.request(request)
996
- return account_summaries.account_summaries
997
- """
1029
+ res = await grpc_client.unary_unary(request)
1030
+ return res.account_summaries
998
1031
 
999
1032
  async def get_account_history(
1000
1033
  self,
1001
1034
  account: str,
1002
1035
  from_inclusive: Optional[datetime] = None,
1003
1036
  to_exclusive: Optional[datetime] = None,
1004
- ) -> Sequence[AccountSummaryFields]:
1037
+ ) -> list[AccountSummary]:
1005
1038
  """
1006
1039
  Get historical sequence of account summaries for the given account.
1007
1040
  """
1008
- res = await self.graphql_client.get_account_history_query(
1009
- account=account, from_inclusive=from_inclusive, to_exclusive=to_exclusive
1010
- )
1011
- return res.account_history
1012
-
1013
- """
1014
- async def get_account_history(
1015
- self,
1016
- account: str,
1017
- from_inclusive: Optional[datetime] = None,
1018
- to_exclusive: Optional[datetime] = None,
1019
- ) -> list[AccountSummary]:
1041
+ grpc_client = await self.core()
1020
1042
  if from_inclusive is not None:
1021
1043
  assert from_inclusive.tzinfo is timezone.utc, (
1022
1044
  "from_inclusive must be a utc datetime:\n"
@@ -1031,12 +1053,11 @@ class AsyncClient:
1031
1053
  "dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
1032
1054
  )
1033
1055
 
1034
- request = AccountHistoryRequest(
1056
+ req = AccountHistoryRequest(
1035
1057
  account=account, from_inclusive=from_inclusive, to_exclusive=to_exclusive
1036
1058
  )
1037
- history = await self.grpc_client.request(request)
1038
- return history.history
1039
- """
1059
+ res = await grpc_client.unary_unary(req)
1060
+ return res.history
1040
1061
 
1041
1062
  # ------------------------------------------------------------
1042
1063
  # Order management
@@ -1044,13 +1065,13 @@ class AsyncClient:
1044
1065
 
1045
1066
  async def get_open_orders(
1046
1067
  self,
1047
- order_ids: Optional[list[str]] = None,
1068
+ order_ids: Optional[list[OrderId]] = None,
1048
1069
  venue: Optional[str] = None,
1049
1070
  account: Optional[str] = None,
1050
1071
  trader: Optional[str] = None,
1051
1072
  symbol: Optional[str] = None,
1052
- parent_order_id: Optional[str] = None,
1053
- ) -> Sequence[OrderFields]:
1073
+ parent_order_id: Optional[OrderId] = None,
1074
+ ) -> list[Order]:
1054
1075
  """
1055
1076
  Returns a list of open orders for the user that match the filters.
1056
1077
 
@@ -1065,7 +1086,8 @@ class AsyncClient:
1065
1086
  Returns:
1066
1087
  Open orders that match the union of the filters
1067
1088
  """
1068
- res = await self.graphql_client.get_open_orders_query(
1089
+ grpc_client = await self.core()
1090
+ open_orders_request = OpenOrdersRequest(
1069
1091
  venue=venue,
1070
1092
  account=account,
1071
1093
  trader=trader,
@@ -1073,26 +1095,27 @@ class AsyncClient:
1073
1095
  parent_order_id=parent_order_id,
1074
1096
  order_ids=order_ids,
1075
1097
  )
1076
- return res.open_orders
1077
1098
 
1078
- async def get_all_open_orders(self) -> Sequence[OrderFields]:
1099
+ open_orders = await grpc_client.unary_unary(open_orders_request)
1100
+ return open_orders.open_orders
1101
+
1102
+ async def get_all_open_orders(self) -> list[Order]:
1079
1103
  """
1080
1104
  @deprecated(reason="Use get_open_orders with no parameters instead")
1081
1105
 
1082
1106
  Returns a list of all open orders for the authenticated user.
1083
1107
  """
1084
- res = await self.graphql_client.get_open_orders_query()
1085
- return res.open_orders
1108
+ return await self.get_open_orders()
1086
1109
 
1087
1110
  async def get_historical_orders(
1088
1111
  self,
1089
- order_ids: Optional[list[str]] = None,
1112
+ order_ids: Optional[list[OrderId]] = None,
1090
1113
  from_inclusive: Optional[datetime] = None,
1091
1114
  to_exclusive: Optional[datetime] = None,
1092
1115
  venue: Optional[str] = None,
1093
1116
  account: Optional[str] = None,
1094
- parent_order_id: Optional[str] = None,
1095
- ) -> Sequence[OrderFields]:
1117
+ parent_order_id: Optional[OrderId] = None,
1118
+ ) -> list[Order]:
1096
1119
  """
1097
1120
  Returns a list of all historical orders that match the filters.
1098
1121
 
@@ -1112,7 +1135,23 @@ class AsyncClient:
1112
1135
  If order_ids is not specified, then from_inclusive and to_exclusive
1113
1136
  MUST be specified.
1114
1137
  """
1115
- res = await self.graphql_client.get_historical_orders_query(
1138
+ grpc_client = await self.core()
1139
+
1140
+ if from_inclusive is not None:
1141
+ assert from_inclusive.tzinfo is timezone.utc, (
1142
+ "from_inclusive must be a utc datetime:\n"
1143
+ "for example datetime.now(timezone.utc) or \n"
1144
+ "dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
1145
+ )
1146
+
1147
+ if to_exclusive is not None:
1148
+ assert to_exclusive.tzinfo is timezone.utc, (
1149
+ "to_exclusive must be a utc datetime:\n"
1150
+ "for example datetime.now(timezone.utc) or \n"
1151
+ "dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
1152
+ )
1153
+
1154
+ historical_orders_request = HistoricalOrdersRequest.new(
1116
1155
  order_ids=order_ids,
1117
1156
  venue=venue,
1118
1157
  account=account,
@@ -1120,9 +1159,10 @@ class AsyncClient:
1120
1159
  from_inclusive=from_inclusive,
1121
1160
  to_exclusive=to_exclusive,
1122
1161
  )
1123
- return res.historical_orders
1162
+ orders = await grpc_client.unary_unary(historical_orders_request)
1163
+ return orders.orders
1124
1164
 
1125
- async def get_order(self, order_id: str) -> Optional[OrderFields]:
1165
+ async def get_order(self, order_id: OrderId) -> Optional[Order]:
1126
1166
  """
1127
1167
  Returns the specified order. Useful for looking at past sent orders.
1128
1168
  Queries open_orders first, then queries historical_orders.
@@ -1130,18 +1170,23 @@ class AsyncClient:
1130
1170
  Args:
1131
1171
  order_id: the order id to get
1132
1172
  """
1133
- res = await self.graphql_client.get_open_orders_query(order_ids=[order_id])
1173
+ grpc_client = await self.core()
1174
+ req = OpenOrdersRequest.new(
1175
+ order_ids=[order_id],
1176
+ )
1177
+ res = await grpc_client.unary_unary(req)
1134
1178
  for open_order in res.open_orders:
1135
1179
  if open_order.id == order_id:
1136
1180
  return open_order
1137
1181
 
1138
- res = await self.graphql_client.get_historical_orders_query(
1139
- order_ids=[order_id]
1182
+ req = HistoricalOrdersRequest.new(
1183
+ order_ids=[order_id],
1140
1184
  )
1141
- if res.historical_orders and len(res.historical_orders) > 0:
1142
- return res.historical_orders[0]
1185
+ res = await grpc_client.unary_unary(req)
1186
+ if res.orders and len(res.orders) == 1:
1187
+ return res.orders[0]
1143
1188
 
1144
- async def get_orders(self, order_ids: list[str]) -> list[Optional[OrderFields]]:
1189
+ async def get_orders(self, order_ids: list[OrderId]) -> list[Optional[Order]]:
1145
1190
  """
1146
1191
  Returns the specified orders. Useful for looking at past sent orders.
1147
1192
  Plural form of get_order.
@@ -1149,24 +1194,27 @@ class AsyncClient:
1149
1194
  Args:
1150
1195
  order_ids: a list of order ids to get
1151
1196
  """
1152
- orders_dict: dict[str, Optional[OrderFields]] = {
1197
+ grpc_client = await self.core()
1198
+ orders_dict: dict[OrderId, Optional[Order]] = {
1153
1199
  order_id: None for order_id in order_ids
1154
1200
  }
1201
+ req = OpenOrdersRequest.new(
1202
+ order_ids=order_ids,
1203
+ )
1155
1204
 
1156
- res = await self.graphql_client.get_open_orders_query(order_ids=order_ids)
1157
- open_orders = res.open_orders
1158
- for open_order in open_orders:
1205
+ res = await grpc_client.unary_unary(req)
1206
+ for open_order in res.open_orders:
1159
1207
  orders_dict[open_order.id] = open_order
1160
1208
 
1161
1209
  not_open_order_ids = [
1162
1210
  order_id for order_id in order_ids if orders_dict[order_id] is None
1163
1211
  ]
1164
1212
 
1165
- res = await self.graphql_client.get_historical_orders_query(
1166
- order_ids=not_open_order_ids
1213
+ req = HistoricalOrdersRequest.new(
1214
+ order_ids=not_open_order_ids,
1167
1215
  )
1168
- historical_orders = res.historical_orders
1169
- for historical_order in historical_orders:
1216
+ res = await grpc_client.unary_unary(req)
1217
+ for historical_order in res.orders:
1170
1218
  orders_dict[historical_order.id] = historical_order
1171
1219
 
1172
1220
  return [orders_dict[order_id] for order_id in order_ids]
@@ -1177,8 +1225,9 @@ class AsyncClient:
1177
1225
  to_exclusive: Optional[datetime] = None,
1178
1226
  venue: Optional[str] = None,
1179
1227
  account: Optional[str] = None,
1180
- order_id: Optional[str] = None,
1181
- ) -> GetFillsQueryFolioHistoricalFills:
1228
+ order_id: Optional[OrderId] = None,
1229
+ limit: Optional[int] = None,
1230
+ ) -> HistoricalFillsResponse:
1182
1231
  """
1183
1232
  Returns all fills matching the given filters.
1184
1233
 
@@ -1189,15 +1238,35 @@ class AsyncClient:
1189
1238
  account: account uuid or name
1190
1239
  order_id: the order id to get fills for
1191
1240
  """
1192
- res = await self.graphql_client.get_fills_query(
1193
- venue, account, order_id, from_inclusive, to_exclusive
1241
+ grpc_client = await self.core()
1242
+ if from_inclusive is not None:
1243
+ assert from_inclusive.tzinfo is timezone.utc, (
1244
+ "from_inclusive must be a utc datetime:\n"
1245
+ "for example datetime.now(timezone.utc) or \n"
1246
+ "dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
1247
+ )
1248
+
1249
+ if to_exclusive is not None:
1250
+ assert to_exclusive.tzinfo is timezone.utc, (
1251
+ "to_exclusive must be a utc datetime:\n"
1252
+ "for example datetime.now(timezone.utc) or \n"
1253
+ "dt = datetime(2025, 4, 15, 12, 0, 0, tzinfo=timezone.utc)"
1254
+ )
1255
+ req = HistoricalFillsRequest(
1256
+ account=account,
1257
+ from_inclusive=from_inclusive,
1258
+ limit=limit,
1259
+ order_id=order_id,
1260
+ to_exclusive=to_exclusive,
1261
+ venue=venue,
1194
1262
  )
1195
- return res.historical_fills
1263
+ res = await grpc_client.unary_unary(req)
1264
+ return res
1196
1265
 
1197
1266
  async def orderflow(
1198
1267
  self,
1199
1268
  request_iterator: AsyncIterator[OrderflowRequest],
1200
- ) -> AsyncIterator[Orderflow]:
1269
+ ) -> AsyncGenerator[Orderflow, None]:
1201
1270
  """
1202
1271
  A two-way channel for both order entry and listening to order updates (fills, acks, outs, etc.).
1203
1272
 
@@ -1210,12 +1279,12 @@ class AsyncClient:
1210
1279
  """
1211
1280
  grpc_client = await self.core()
1212
1281
  decoder = grpc_client.get_decoder(OrderflowRequestUnannotatedResponseType)
1213
- stub = grpc_client.channel.stream_stream(
1282
+ stub: grpc.aio.StreamStreamMultiCallable = grpc_client.channel.stream_stream(
1214
1283
  OrderflowRequest_route,
1215
1284
  request_serializer=grpc_client.encoder().encode,
1216
1285
  response_deserializer=decoder.decode,
1217
1286
  )
1218
- call = stub(
1287
+ call: grpc.aio._base_call.StreamStreamCall[OrderflowRequest, Orderflow] = stub(
1219
1288
  request_iterator, metadata=(("authorization", f"Bearer {grpc_client.jwt}"),)
1220
1289
  )
1221
1290
  async for update in call:
@@ -1223,10 +1292,10 @@ class AsyncClient:
1223
1292
 
1224
1293
  async def stream_orderflow(
1225
1294
  self,
1226
- account: Optional[grpc_definitions.AccountIdOrName] = None,
1295
+ account: Optional[AccountIdOrName] = None,
1227
1296
  execution_venue: Optional[str] = None,
1228
- trader: Optional[grpc_definitions.TraderIdOrEmail] = None,
1229
- ) -> AsyncIterator[Orderflow]:
1297
+ trader: Optional[TraderIdOrEmail] = None,
1298
+ ) -> AsyncGenerator[Orderflow, None]:
1230
1299
  """
1231
1300
  A stream for listening to order updates (fills, acks, outs, etc.).
1232
1301
 
@@ -1245,12 +1314,16 @@ class AsyncClient:
1245
1314
  )
1246
1315
  grpc_client = await self.core()
1247
1316
  decoder = grpc_client.get_decoder(SubscribeOrderflowRequest)
1248
- stub = grpc_client.channel.unary_stream(
1317
+ stub: grpc.aio.UnaryStreamMultiCallable[
1318
+ SubscribeOrderflowRequest, Orderflow
1319
+ ] = grpc_client.channel.unary_stream(
1249
1320
  SubscribeOrderflowRequest.get_route(),
1250
1321
  request_serializer=grpc_client.encoder().encode,
1251
1322
  response_deserializer=decoder.decode,
1252
1323
  )
1253
- call = stub(request, metadata=(("authorization", f"Bearer {grpc_client.jwt}"),))
1324
+ call: grpc.aio._base_call.UnaryStreamCall[
1325
+ SubscribeOrderflowRequest, Orderflow
1326
+ ] = stub(request, metadata=(("authorization", f"Bearer {grpc_client.jwt}"),))
1254
1327
  async for update in call:
1255
1328
  yield update
1256
1329
 
@@ -1258,39 +1331,49 @@ class AsyncClient:
1258
1331
  # Order entry
1259
1332
  # ------------------------------------------------------------
1260
1333
 
1334
+ async def send_limit_order(
1335
+ self,
1336
+ *args,
1337
+ **kwargs,
1338
+ ) -> Order:
1339
+ """
1340
+ @deprecated(reason="Use place_limit_order instead")
1341
+ """
1342
+ return await self.place_limit_order(*args, **kwargs)
1343
+
1261
1344
  async def place_limit_order(
1262
1345
  self,
1263
1346
  *,
1264
- id: Optional[str] = None,
1347
+ id: Optional[OrderId] = None,
1265
1348
  symbol: TradableProduct | str,
1266
- execution_venue: Optional[str],
1267
- odir: OrderDir,
1349
+ execution_venue: Optional[str] = None,
1350
+ dir: Optional[OrderDir] = None,
1268
1351
  quantity: Decimal,
1269
1352
  limit_price: Decimal,
1270
1353
  order_type: OrderType = OrderType.LIMIT,
1271
- time_in_force: TimeInForce = TimeInForce.DAY,
1272
- good_til_date: Optional[datetime] = None,
1354
+ time_in_force: TimeInForce = TimeInForceEnum.DAY,
1273
1355
  price_round_method: Optional[TickRoundMethod] = None,
1274
1356
  account: Optional[str] = None,
1275
1357
  trader: Optional[str] = None,
1276
1358
  post_only: bool = False,
1277
1359
  trigger_price: Optional[Decimal] = None,
1278
- ) -> OrderFields:
1360
+ **kwargs: Any,
1361
+ ) -> Order:
1279
1362
  """
1280
1363
  Sends a regular limit order.
1281
1364
 
1282
1365
  Args:
1366
+ id: in case user wants to generate their own order id, otherwise it will be generated automatically
1283
1367
  symbol: the symbol to send the order for
1284
1368
  execution_venue: the execution venue to send the order to,
1285
1369
  if execution_venue is set to None, the OMS will send the order to the primary_exchange
1286
1370
  the primary_exchange can be deduced from `get_product_info`
1287
- odir: the direction of the order
1371
+ dir: the direction of the order, BUY or SELL
1288
1372
  quantity: the quantity of the order
1289
1373
  limit_price: the limit price of the order
1290
1374
  It is highly recommended to make this a Decimal object from the decimal module to avoid floating point errors
1291
1375
  order_type: the type of the order
1292
1376
  time_in_force: the time in force of the order
1293
- good_til_date: the date the order is good until, only relevant for time_in_force = "GTD"
1294
1377
  price_round_method: the method to round the price to the nearest tick, will not round if None
1295
1378
  account: the account to send the order for
1296
1379
  While technically optional, for most order types, the account is required
@@ -1299,13 +1382,21 @@ class AsyncClient:
1299
1382
  post_only: whether the order should be post only, not supported by all exchanges
1300
1383
  trigger_price: the trigger price for the order, only relevant for stop / take_profit orders
1301
1384
  Returns:
1302
- the OrderFields object for the order
1385
+ the Order object for the order
1303
1386
  The order.status should be "PENDING" until the order is "OPEN" / "REJECTED" / "OUT" / "CANCELED" / "STALE"
1304
1387
 
1305
1388
  If the order is rejected, the order.reject_reason and order.reject_message will be set
1306
1389
  """
1390
+ grpc_client = await self.core()
1307
1391
  assert quantity > 0, "quantity must be positive"
1308
1392
 
1393
+ if dir is None:
1394
+ if "odir" in kwargs and isinstance(kwargs["odir"], OrderDir):
1395
+ logging.warning("odir is deprecated, use dir instead")
1396
+ dir = kwargs["odir"]
1397
+ else:
1398
+ raise ValueError("dir is required")
1399
+
1309
1400
  if price_round_method is not None:
1310
1401
  if execution_venue is None:
1311
1402
  product_info = await self.get_product_info(symbol)
@@ -1331,44 +1422,43 @@ class AsyncClient:
1331
1422
  else:
1332
1423
  raise ValueError(f"Could not find market information for {symbol}")
1333
1424
 
1334
- if not isinstance(trigger_price, Decimal) and trigger_price is not None:
1335
- trigger_price = Decimal(trigger_price)
1336
-
1337
- order: PlaceOrderMutationOms = await self.graphql_client.place_order_mutation(
1338
- symbol,
1339
- odir,
1340
- quantity,
1341
- order_type,
1342
- time_in_force,
1343
- id,
1344
- trader,
1345
- account,
1346
- limit_price,
1347
- post_only,
1348
- trigger_price,
1349
- good_til_date,
1350
- execution_venue,
1425
+ req: PlaceOrderRequest = PlaceOrderRequest.new(
1426
+ dir=dir,
1427
+ quantity=quantity,
1428
+ symbol=symbol,
1429
+ time_in_force=time_in_force,
1430
+ limit_price=limit_price,
1431
+ order_type=order_type,
1432
+ account=account,
1433
+ id=id,
1434
+ parent_id=None,
1435
+ source=OrderSource.API,
1436
+ trader=trader,
1437
+ execution_venue=execution_venue,
1438
+ post_only=post_only,
1439
+ trigger_price=trigger_price,
1351
1440
  )
1352
-
1353
- return order.place_order
1441
+ res = await grpc_client.unary_unary(req)
1442
+ return res
1354
1443
 
1355
1444
  async def send_market_pro_order(
1356
1445
  self,
1357
1446
  *,
1358
- id: Optional[str] = None,
1447
+ id: Optional[OrderId] = None,
1359
1448
  symbol: TradableProduct | str,
1360
1449
  execution_venue: str,
1361
1450
  odir: OrderDir,
1362
1451
  quantity: Decimal,
1363
- time_in_force: TimeInForce = TimeInForce.DAY,
1452
+ time_in_force: TimeInForce = TimeInForceEnum.DAY,
1364
1453
  account: Optional[str] = None,
1365
1454
  fraction_through_market: Decimal = Decimal("0.001"),
1366
- ) -> OrderFields:
1455
+ ) -> Order:
1367
1456
  """
1368
1457
  Sends a market-order like limit price based on the BBO.
1369
1458
  Meant to behave as a market order but with more protections.
1370
1459
 
1371
1460
  Args:
1461
+ id: in case user wants to generate their own order id, otherwise it will be generated automatically
1372
1462
  symbol: the symbol to send the order for
1373
1463
  execution_venue: the execution venue to send the order to
1374
1464
  odir: the direction of the order
@@ -1440,7 +1530,7 @@ class AsyncClient:
1440
1530
  time_in_force=time_in_force,
1441
1531
  )
1442
1532
 
1443
- async def cancel_order(self, order_id: str) -> CancelFields:
1533
+ async def cancel_order(self, order_id: OrderId) -> Cancel:
1444
1534
  """
1445
1535
  Cancels an order by order id.
1446
1536
 
@@ -1449,10 +1539,17 @@ class AsyncClient:
1449
1539
  Returns:
1450
1540
  the CancelFields object
1451
1541
  """
1452
- cancel = await self.graphql_client.cancel_order_mutation(order_id)
1453
- return cancel.cancel_order
1542
+ grpc_client = await self.core()
1543
+ req = CancelOrderRequest(id=order_id)
1544
+ res = await grpc_client.unary_unary(req)
1545
+ return res
1454
1546
 
1455
- async def cancel_all_orders(self) -> bool:
1547
+ async def cancel_all_orders(
1548
+ self,
1549
+ account: Optional[AccountIdOrName] = None,
1550
+ execution_venue: Optional[str] = None,
1551
+ trader: Optional[TraderIdOrEmail] = None,
1552
+ ) -> bool:
1456
1553
  """
1457
1554
  Cancels all open orders.
1458
1555
 
@@ -1460,5 +1557,25 @@ class AsyncClient:
1460
1557
  True if all orders were cancelled successfully
1461
1558
  False if there was an error
1462
1559
  """
1463
- b = await self.graphql_client.cancel_all_orders_mutation()
1464
- return b.cancel_all_orders
1560
+
1561
+ open_orders = await self.get_open_orders(
1562
+ account=account,
1563
+ venue=execution_venue,
1564
+ trader=trader,
1565
+ )
1566
+ outputs = await asyncio.gather(
1567
+ *(self.cancel_order(order.id) for order in open_orders)
1568
+ )
1569
+
1570
+ for cancel in outputs:
1571
+ if cancel.reject_reason is not None:
1572
+ return False
1573
+ return True
1574
+ grpc_client = await self.core()
1575
+ req = CancelAllOrdersRequest(
1576
+ account=account,
1577
+ execution_venue=execution_venue,
1578
+ trader=trader,
1579
+ )
1580
+ res = await grpc_client.unary_unary(req)
1581
+ return res